Ruby練習問題4-☆1:マルバツゲーム(Tic-Tac-Toe)を作ろう

記事内に商品プロモーションを含む場合があります

Rubyの初心者向け問題4-☆1:マルバツゲーム(Tic-Tac-Toe)を作ろう

この問題を解くために必要な知識:
【レベル1~3の知識】
コメントの書き方、変数と定数、基本データ型、四則演算と演算子、入力と出力、文字列操作、分岐処理(if、if~else、elsif、case)、繰り返し処理(for、while、until、each)、範囲(Range)、例外処理(begin-rescue-end)、配列(Array)、ハッシュ(Hash)、コレクション操作(配列、ハッシュの操作)、Enumerableモジュール、メソッドの定義と呼び出し、メソッドの戻り値、クラスの定義と使用、コンストラクタ、クラスの継承、モジュールの定義と使用、ミックスイン

【レベル4の知識】
アクセスメソッド(ゲッター、セッター)、カプセル化、クラスメソッドとインスタンスメソッド、共通メソッド、ブロックとイテレータ、プロック(Proc)、ラムダ(Lambda)、ファイル操作(読み書き)、正規表現、メタプログラミングの基礎

<<前の問題 問題集Top 次の問題>>

Ruby練習問題4-☆1:マルバツゲーム(Tic-Tac-Toe)を作ろう

マルバツゲーム(〇×ゲーム、Tic-Tac-Toe)を作成しましょう。

このゲームは2人で交互に「〇」か「×」を3×3のグリッドに置き、先に一列(横、縦、斜め)を揃えた方が勝ちとなります。

ゲームは次の要件に従って実装してください。

この問題の要件

以下の要件に従ったプログラムを作成してください。

  • 3×3のグリッドを表す配列を使用すること。
  • プレイヤーは「〇」と「×」を交互に置くこと。
  • 勝利条件を判定するメソッドを作成すること。
  • 勝者が決まるか、全てのマスが埋まったらゲームを終了すること。
  • ゲームの進行を表示するためのメソッドを作成すること

ただし、以下のような実行結果となること。

----- ↓出力される結果の例↓ -----

   |   |  
---+---+---
   |   |  
---+---+---
   |   |  
〇のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。
0 0

 〇 |   |  
---+---+---
   |   |  
---+---+---
   |   |  
×のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。
1 1

 〇 |   |  
---+---+---
   | × |  
---+---+---
   |   |  
〇のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。
0 1

 〇 | 〇 |  
---+---+---
   | × |  
---+---+---
   |   |  
×のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。
1 0

 〇 | 〇 |  
---+---+---
× | × |  
---+---+---
   |   |  
〇のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。
0 2

 〇 | 〇 | 〇
---+---+---
× | × |  
---+---+---
   |   |  
〇の勝ちです!

----- ↑出力される結果の例↑ -----

この問題を解くヒント

1からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

ヒント1【コードの構成を見る】

正解のコードは上から順に以下のような構成となっています。

1.クラス定義
 1-1. TicTacToeクラスの定義

2.コンストラクタ
 2-1. 初期状態のボードを作成
 2-2. 初期プレイヤーを設定

3.メソッド定義
 3-1. display_boardメソッド
  3-1-1. 現在のボードの状態を表示
 3-2. switch_playerメソッド
  3-2-1. 現在のプレイヤーを交互に切り替え
 3-3. player_moveメソッド
  3-3-1. 現在のプレイヤーのターンを表示
  3-3-2. 行と列を入力として受け取る
  3-3-3. 入力位置が空であるかを確認
  3-3-4. ボードに現在のプレイヤーのマークを配置
  3-3-5. 入力位置が無効な場合、再度入力を求める
 3-4. winner?メソッド
  3-4-1. 行、列、斜めの勝利条件をチェック
 3-5. draw?メソッド
  3-5-1. 全てのマスが埋まっているかを確認
 3-6. playメソッド
  3-6-1. メインループを実行
  3-6-2. ボードを表示
  3-6-3. プレイヤーの入力を受け取る
  3-6-4. 勝利条件を判定
  3-6-5. 引き分け条件を判定
  3-6-6. プレイヤーを切り替え

4.ゲームの実行
 4-1. TicTacToeクラスのインスタンスを作成
 4-2. ゲームを開始

ヒント2【穴埋め問題にする】

以下のコードをコピーし、コメントに従ってコードを完成させて下さい。

# マルバツゲームクラス
class TicTacToe
# コンストラクタ:初期状態のボードとプレイヤーを設定
def initialize
# ***ここでボードを初期化し、初期プレイヤーを設定するコードを書いてください***
end

# ボードを表示するメソッド
def display_board
puts " #{@board[0][0]} | #{@board[0][ 1 ]} | #{@board[0][ 2 ]} "
puts "---+---+---"
puts " #{@board[ 1 ][0]} | #{@board[ 1 ][ 1 ]} | #{@board[ 1 ][ 2 ]} "
puts "---+---+---"
puts " #{@board[ 2 ][0]} | #{@board[ 2 ][ 1 ]} | #{@board[ 2 ][ 2 ]} "
end

# プレイヤーのターンを切り替えるメソッド
def switch_player
# ***ここで現在のプレイヤーを交互に切り替えるコードを書いてください***
end

# プレイヤーの入力を受け取るメソッド
def player_move
puts "#{@current_player}のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。"
# ***ここで入力を受け取り、その位置にマークを配置するコードを書いてください***
end

# 勝利条件を判定するメソッド
def winner?
# ***ここで行、列、斜めの勝利条件をチェックするコードを書いてください***
end

# 全てのマスが埋まったかどうかを判定するメソッド
def draw?
# ***ここでボードの全てのマスが埋まっているかチェックするコードを書いてください***
end

# ゲームを実行するメソッド
def play
loop do
display_board # ボードを表示
# ***ここでプレイヤーの入力を受け取るコードを書いてください***
if winner?
display_board
puts "#{@current_player}の勝ちです!"
break
elsif draw?
display_board
puts "引き分けです!"
break
else
# ***ここでプレイヤーを切り替えるコードを書いてください***
end
end
end
end

# ゲームの実行
# ***ここでTicTacToeクラスのインスタンスを作成し、ゲームを開始するコードを書いてください***

 

このヒントを見てもまだ回答を導き出すのが難しいと感じる場合は、先に正解のコードと解説を見て内容を理解するようにしましょう。

この問題の解答例

例えば以下のようなプログラムが考えられます。

********************

# マルバツゲームクラス
class TicTacToe
# コンストラクタ:初期状態のボードとプレイヤーを設定
def initialize
@board = Array.new(3) { Array.new(3, " ") } # 3x3の空のボードを作成
@current_player = "〇" # 初期プレイヤーは「〇」
end

# ボードを表示するメソッド
def display_board
puts " #{@board[0][0]} | #{@board[0][ 1 ]} | #{@board[0][ 2 ]} "
puts "---+---+---"
puts " #{@board[ 1 ][0]} | #{@board[ 1 ][ 1 ]} | #{@board[ 1 ][ 2 ]} "
puts "---+---+---"
puts " #{@board[ 2 ][0]} | #{@board[ 2 ][ 1 ]} | #{@board[ 2 ][ 2 ]} "
end

# プレイヤーのターンを切り替えるメソッド
def switch_player
@current_player = @current_player == "〇" ? "×" : "〇" # 「〇」と「×」を交互に切り替え
end

# プレイヤーの入力を受け取るメソッド
def player_move
puts "#{@current_player}のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。"
row, col = gets.split.map(&:to_i) # 入力を行と列に分割し整数に変換
if @board[row][col] == " "
@board[row][col] = @current_player # ボードに現在のプレイヤーのマークを配置
else
puts "その位置には置けません。別の場所を選んでください。"
player_move # 再度入力を求める
end
end

# 勝利条件を判定するメソッド
def winner?
# 行、列、斜めの勝利条件をチェック
(0..2).any? { |i| @board[i].all? { |cell| cell == @current_player } } || # 行のチェック
(0..2).any? { |i| @board.all? { |row| row[i] == @current_player } } || # 列のチェック
[@board[0][0], @board[ 1 ][ 1 ], @board[ 2 ][ 2 ]].all? { |cell| cell == @current_player } || # 左斜めのチェック
[@board[0][ 2 ], @board[ 1 ][ 1 ], @board[ 2 ][0]].all? { |cell| cell == @current_player } # 右斜めのチェック
end

# 全てのマスが埋まったかどうかを判定するメソッド
def draw?
@board.flatten.none? { |cell| cell == " " } # ボードの全てのマスが埋まっているかチェック
end

# ゲームを実行するメソッド
def play
loop do
display_board # ボードを表示
player_move # プレイヤーの入力を受け取る
if winner?
display_board
puts "#{@current_player}の勝ちです!"
break
elsif draw?
display_board
puts "引き分けです!"
break
else
switch_player # プレイヤーを切り替え
end
end
end
end

# ゲームの実行
game = TicTacToe.new # ゲームオブジェクトを作成
game.play # ゲームを開始

********************

正解のコードの解説

initializeメソッド:ゲームの初期設定

def initialize
  @board = Array.new(3) { Array.new(3, " ") } # 3x3の空のボードを作成
  @current_player = "〇" # 初期プレイヤーは「〇」
end

このメソッドは、ゲーム開始時に呼び出され、ボードと現在のプレイヤーを初期化します。

  • @board: 3×3の二次元配列を作成し、全てのセルを空白(” “)で埋めています。これがゲームボードになります。
  • @current_player: ゲームを開始する最初のプレイヤーは「〇」です。

display_boardメソッド:ボードの表示

def display_board
  puts " #{@board[ 0][ 0]} | #{@board[ 0][ 1]} | #{@board[ 0][ 2]} "
  puts "---+---+---"
  puts " #{@board[ 1][ 0]} | #{@board[ 1][ 1]} | #{@board[ 1][ 2]} "
  puts "---+---+---"
  puts " #{@board[ 2][ 0]} | #{@board[ 2][ 1]} | #{@board[ 2][ 2]} "
end

このメソッドは、現在のゲームボードを表示します。各行の間には区切り線が表示され、ボードの状態をわかりやすく視覚化しています。

switch_playerメソッド:プレイヤーの切り替え

def switch_player
  @current_player = @current_player == "〇" ? "×" : "〇"
end

このメソッドでは、現在のプレイヤーを切り替えます。@current_playerが「〇」なら「×」に変更し、逆も同様です。

player_moveメソッド:プレイヤーの入力

def player_move
  puts "#{@current_player}のターンです。行(0, 1, 2)と列(0, 1, 2)を入力してください。"
  row, col = gets.split.map(&:to_i)
  if @board[row][col] == " "
    @board[row][col] = @current_player
  else
    puts "その位置には置けません。別の場所を選んでください。"
    player_move
  end
end

プレイヤーに行(row)と列(col)を入力してもらい、ボード上にその位置が空いているか確認します。

空いていればプレイヤーの記号(〇または×)を配置し、埋まっている場合は再度入力を求めます。

winner?メソッド:勝利条件のチェック

def winner?
  (0..2).any? { |i| @board[i].all? { |cell| cell == @current_player } } ||
  (0..2).any? { |i| @board.all? { |row| row[i] == @current_player } } ||
  [@board[ 0][ 0], @board[ 1][ 1], @board[ 2][ 2]].all? { |cell| cell == @current_player } ||
  [@board[ 0][ 2], @board[ 1][ 1], @board[ 2][ 0]].all? { |cell| cell == @current_player }
end

このメソッドでは、プレイヤーが勝利条件を満たしたかを判定します。

  • 各行が同じプレイヤーの記号で埋まっているか
  • 各列が同じプレイヤーの記号で埋まっているか
  • 左斜めと右斜めのラインが同じ記号で埋まっているか

いずれかが満たされていれば勝利です。

draw?メソッド:引き分けの判定

def draw?
  @board.flatten.none? { |cell| cell == " " }
end

ボード上に空いているマスがなく、勝者がいない場合、ゲームは引き分けになります。このメソッドでは、全てのマスが埋まっているかをチェックします。

playメソッド:ゲームの実行

def play
  loop do
    display_board
    player_move
    if winner?
      display_board
      puts "#{@current_player}の勝ちです!"
      break
    elsif draw?
      display_board
      puts "引き分けです!"
      break
    else
      switch_player
    end
  end
end

このメソッドはゲームのメインループです。ボードを表示し、プレイヤーの入力を受け取り、勝者や引き分けのチェックを行います。

勝敗が決まるまでプレイヤーを交互に切り替えて続行します。

<<前の問題 問題集Top 次の問題>>

この問題への質問・コメント

この問題を作成するにあたりAIを活用しています。

問題ないことは確認していますが、もし間違いや表現の違和感などありましたら、ご指摘頂けると大変助かります。






    Ruby練習問題集へ戻る
    トップページへ戻る