【Ruby】確認問題5-☆3:マルバツゲーム(Tic-Tac-Toe)を作ろう

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

この記事の練習問題で使用する知識:
基礎文法、制御構造、メソッド(レッスン1~3)配列の基本eachメソッドクラスの定義と使用イニシャライザアクセスメソッド正規表現モジュールミックスイン

Rubyのゲームコード一覧はこちら

<<前のページ Ruby記事一覧 次のページ>>

確認問題:マルバツゲーム(Tic-Tac-Toe)を作ろう

3×3のマルバツゲーム(Tic Tac Toe)を作成してください。

このプログラムではゲームボードを表示し、プレイヤーが交互に〇または×を入力します。

勝者が決定するかすべてのマスが埋まるとゲームが終了します。

この問題の要件

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

  1. モジュール BoardDisplay を作成し、ゲームボードを表示する機能を定義すること。
    • display_board メソッドを実装し、ボードを「|」で区切り、行の間に「ー+ー+ー」の線を表示すること。
  2. クラス TicTacToe を作成し、ゲーム全体の管理を行うこと。
    • 初期化メソッド(initialize)で以下を設定する:
      • ボードを3×3の全角数字(1~9)で初期化すること。
      • 現在のプレイヤーを「〇」に設定すること。
    • switch_player メソッドを作成し、プレイヤーを交代すること。
    • player_move メソッドを作成し、プレイヤーが選択した位置に〇または×を記入すること。
      • 入力は全角数字(1~9)で受け取ること。
      • 入力された位置が無効またはすでに使用済みの場合、再入力を促すこと。
    • find_position メソッドを作成し、入力された全角数字に対応するボード上の位置(行・列)を特定すること。
    • valid_move? メソッドを作成し、入力された位置が有効かどうかを判定すること。
    • winner? メソッドを作成し、勝利条件を判定すること。
      • 行、列、または斜めがすべて同じ記号で埋まった場合に勝利とする。
    • draw? メソッドを作成し、すべてのマスが埋まり、勝者がいない場合に引き分けを判定すること。
    • play メソッドを作成し、ゲームのメインループを実装すること。
      • ボードを表示し、プレイヤーの入力を受け付け、勝者や引き分けを判定すること。
      • ゲームが終了するまでプレイヤーを交互に切り替えること。

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

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

1|2|3
ー+ー+ー
4|5|6
ー+ー+ー
7|8|9
〇のターンです。
選択するマスの番号を入力してください(例: 1 ~ 9):
1
〇|2|3
ー+ー+ー
4|5|6
ー+ー+ー
7|8|9
×のターンです。
選択するマスの番号を入力してください(例: 1 ~ 9):
5
〇|2|3
ー+ー+ー
4|×|6
ー+ー+ー
7|8|9
...

この問題を解くヒント

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

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

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

1:BoardDisplayモジュールの定義
  □ display_boardメソッドの定義
  □ □ ボードをループで走査し、各行を結合して表示
  □ □ 行の区切り線を特定の条件で表示
2:TicTacToeクラスの定義
  □ BoardDisplayモジュールをミックスイン
  □ initializeメソッドの定義
  □ □ 3×3のボードを全角数字で初期化
  □ □ 初期プレイヤーを「〇」に設定
  □ switch_playerメソッドの定義
  □ □ プレイヤーを「〇」または「×」に交代
  □ player_moveメソッドの定義
  □ □ 入力が有効になるまでループ
  □ □ □ 現在のプレイヤーを表示
  □ □ □ マス番号の入力を求める
  □ □ □ find_positionメソッドを呼び出して位置を特定
  □ □ □ 入力が有効であれば、ボードを更新してループを終了
  □ □ □ 入力が無効ならエラーメッセージを表示
  □ find_positionメソッドの定義
  □ □ 指定された番号が存在する行と列を返す
  □ □ 該当する位置がなければnilを返す
  □ valid_move?メソッドの定義
  □ □ ボード上で指定された位置が未使用かを確認
  □ winner?メソッドの定義
  □ □ 勝利条件を満たすラインが存在するか確認
  □ draw?メソッドの定義
  □ □ ボードがすべて埋まっているかを確認
  □ linesメソッドの定義
  □ □ 横・縦・斜めのすべてのラインを取得して返す
  □ playメソッドの定義
  □ □ ゲームループを開始
  □ □ □ ボードを表示
  □ □ □ player_moveメソッドを呼び出す
  □ □ □ 勝者がいる場合、勝利メッセージを表示して終了
  □ □ □ 引き分けの場合、引き分けメッセージを表示して終了
  □ □ □ 勝敗が決まらない場合、プレイヤーを交代
3:TicTacToeクラスのインスタンスを生成してplayメソッドを呼び出す

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

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

# ボード表示用のモジュール
module BoardDisplay
  # ボードを画面に表示するメソッド
  def display_board
=begin
【穴埋め問題1】
ここにボードの各行を「|」で区切り、画面に表示するコードを書いてください。
また、行の間に区切り線を挿入するコードも含めてください。
=end
  end
end

# マルバツゲームのメインクラス
class TicTacToe
  include BoardDisplay # モジュールを取り込む(ミックスイン)

  # 初期化メソッド(ゲームの初期状態を設定)
  def initialize
=begin
【穴埋め問題2】
ここに3x3のボードを全角の数字で初期化し、最初のプレイヤーを設定するコードを書いてください。
=end
  end

  # プレイヤーを交代するメソッド
  def switch_player
=begin
【穴埋め問題3】
ここにプレイヤーを交代するコードを書いてください。
現在のプレイヤーが「〇」の場合は「×」に、それ以外は「〇」に切り替えるロジックを実装してください。
=end
  end

  # プレイヤーのターンを処理するメソッド
  def player_move
=begin
【穴埋め問題4】
ここに現在のプレイヤーにターンを促し、ボードを更新するコードを書いてください。
入力が無効な場合には、再入力を求めるロジックを含めてください。
=end
  end

  # 勝利条件をチェックするメソッド
  def winner?
=begin
【穴埋め問題5】
ここに勝利条件を満たすラインがあるかを確認するコードを書いてください。
=end
  end

  # ゲームのメインループ
  def play
=begin
【穴埋め問題6】
ここにゲームを進行するメインループを記述してください。
勝者が決まるか、引き分けになるまでプレイヤーの操作を繰り返すコードを書いてください。
=end
  end
end

# ゲームを開始
TicTacToe.new.play

この問題の穴埋めコードは以上です。

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

マルバツゲームの正解コードと解説

この問題の一つの正解例とそのコードの解説を以下に示します。

正解コードの例

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

# ボード表示用のモジュール
module BoardDisplay
  # ボードを画面に表示するメソッド
  def display_board
    @board.each_with_index do |row, i|
      # ボードを「|」で区切り表示(全角「+」を使用)
      puts row.join('|')
      puts "ー+ー+ー" if i < 2 # 行の間に全角の区切り線を挿入
    end
  end
end

# マルバツゲームのメインクラス
class TicTacToe
  include BoardDisplay # モジュールを取り込む(ミックスイン)

  # 初期化メソッド(ゲームの初期状態を設定)
  def initialize
    # 3x3のボードを全角の数字で初期化
    @board = [
      ["1", "2", "3"],
      ["4", "5", "6"],
      ["7", "8", "9"]
    ]
    # 最初のプレイヤーを「〇」に設定
    @current_player = "〇"
  end

  # プレイヤーを交代するメソッド
  def switch_player
    @current_player = @current_player == "〇" ? "×" : "〇"
  end

  # プレイヤーのターンを処理するメソッド
  def player_move
    valid_move = false
    while !valid_move
      puts "#{@current_player}のターンです。" # 現在のプレイヤーを表示
      puts "選択するマスの番号を入力してください(例: 1 ~ 9):"
      move = gets.chomp # 入力を取得
      row, col = find_position(move) # 入力に対応する行と列を取得

      if row && col && valid_move?(row, col)
        @board[row][col] = @current_player # 選択したマスに記号を記入
        valid_move = true # 有効な入力があればループを終了
      else
        puts "その位置には置けません。別の番号を選んでください。"
      end
    end
  end

  # マス番号をボード上の行と列に変換するメソッド
  def find_position(move)
    @board.each_with_index do |row, i|
      col = row.index(move) # 行内で指定された番号を探す
      return [i, col] if col # 見つかった場合はその位置を返す
    end
    nil # 見つからない場合は nil を返す
  end

  # 入力が有効かどうかを判定するメソッド
  def valid_move?(row, col)
    # 指定された位置が現在のボードで数字のままかを確認
    @board[row][col] =~ /[1-9]/
  end

  # 勝利条件をチェックするメソッド
  def winner?
    lines.any? { |line| line.all? { |cell| cell == @current_player } }
  end

  # 引き分け(すべてのマスが埋まった場合)をチェックするメソッド
  def draw?
    @board.flatten.none? { |cell| cell =~ /[1-9]/ } # 数字が残っているか確認
  end

  # 勝利条件を構成するラインを取得するメソッド
  def lines
    @board +                       # 行(横方向)
    @board.transpose +             # 列(縦方向)
    [[@board[ 0][ 0], @board[ 1][ 1], @board[ 2][ 2]], # 左斜め
     [@board[ 0][ 2], @board[ 1][ 1], @board[ 2][ 0]]] # 右斜め
  end

  # ゲームのメインループ
  def play
    game_over = false
    while !game_over
      display_board
      player_move
      if winner?
        display_board
        puts "#{@current_player}の勝ちです!"
        game_over = true
      elsif draw?
        display_board
        puts "引き分けです!"
        game_over = true
      else
        switch_player
      end
    end
  end
end

# ゲームを開始
TicTacToe.new.play

正解のコードの解説

今回のコードはマルバツゲーム(Tic Tac Toe)を実行するプログラムです。

このコードをブロックごとに分けて解説します。

モジュールの定義

module BoardDisplay
  def display_board
    @board.each_with_index do |row, i|
      puts row.join('|')
      puts "ー+ー+ー" if i < 2
    end
  end
end
  1. module:
    • Rubyのモジュールを定義しています。モジュールは、関連するメソッドや定数をまとめて定義する仕組みです。
    • このモジュールはボードを表示するための機能を提供します。
  2. display_board:
    • 現在のボードの状態を画面に表示します。
    • 各セルの間に「|」を挿入し、行の間に「ー+ー+ー」を入れて見やすくしています。

クラスの定義と初期化

class TicTacToe
  include BoardDisplay

  def initialize
    @board = [
      ["1", "2", "3"],
      ["4", "5", "6"],
      ["7", "8", "9"]
    ]
    @current_player = "〇"
  end
  • class:マルバツゲーム全体を管理するクラスを定義しています。
  • include BoardDisplay:先ほど定義したモジュールをクラスに取り込み、display_board メソッドを利用可能にしています。
  • initialize:クラスの初期化メソッドで、3×3のボードを全角数字で初期化します。また最初のプレイヤーを「〇」に設定します。

プレイヤーを交代するメソッド

def switch_player
  @current_player = @current_player == "〇" ? "×" : "〇"
end
  • 現在のプレイヤーを管理するインスタンス変数。
  • このメソッドは現在のプレイヤーが「〇」なら「×」に、それ以外なら「〇」に交代します。

プレイヤーのターンを処理するメソッド

def player_move
  valid_move = false
  while !valid_move
    puts "#{@current_player}のターンです。"
    puts "選択するマスの番号を入力してください(例: 1 ~ 9):"
    move = gets.chomp
    row, col = find_position(move)

    if row && col && valid_move?(row, col)
      @board[row][col] = @current_player
      valid_move = true
    else
      puts "その位置には置けません。別の番号を選んでください。"
    end
  end
end
  • gets.chomp:プレイヤーが選択した番号を入力として受け取ります。
  • find_position:入力された番号をボード上の行と列に変換します。
  • valid_move?:指定されたセルがまだ未使用かどうかをチェックします。

行と列を取得するメソッド

def find_position(move)
  @board.each_with_index do |row, i|
    col = row.index(move)
    return [i, col] if col
  end
  nil
end
  • each_with_index:ボードを1行ずつ確認し、入力に該当するセルがあるかを探します。
  • index:行内で該当するセルの位置を探します。

勝利条件や引き分けをチェックするメソッド

def winner?
  lines.any? { |line| line.all? { |cell| cell == @current_player } }
end

def draw?
  @board.flatten.none? { |cell| cell =~ /[1-9]/ }
end
  • winner?:勝利条件を満たす行や列、斜めがあるかをチェックします。
  • draw?:ボード上に数字が残っていない場合、引き分けと判定します。

ゲームのメインループ

def play
  game_over = false
  while !game_over
    display_board
    player_move
    if winner?
      display_board
      puts "#{@current_player}の勝ちです!"
      game_over = true
    elsif draw?
      display_board
      puts "引き分けです!"
      game_over = true
    else
      switch_player
    end
  end
end
  • while:ゲームが終了するまでループを続けます。
  • game_over:勝者が決定するか、引き分けになるとループを終了します。

まとめ

このコードではRubyの基礎的な文法(クラス、メソッド、配列、ループなど)を使って、シンプルなマルバツゲームを実現しています。

特にモジュールを活用してコードを分割する点や、条件分岐を駆使してゲームの進行を管理する点が学びどころです。

この記事を通じてRubyの基本文法やオブジェクト指向の基礎を理解し、さらなるプログラミングスキルの向上を目指してください!

Rubyのゲームコード一覧はこちら

<<前のページ Ruby記事一覧 次のページ>>

この記事への質問・コメント

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

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






    Rubyテキスト&問題集へ戻る
    トップページへ戻る