【Ruby】レッスン5-☆3:マルバツゲームで学ぶクラスとメソッドの使い方

50110130

一つ前のページではモンスターとの戦闘ゲームを作成しました。

今回は マルバツゲーム を作成しましょう。

Lesson1:基礎文法編
Lesson2:制御構造編
Lesson3:メソッド編
Lesson4:コレクション編
Lesson5:オブジェクト指向編

 ・Lesson5-1:クラスの基本を理解しよう
 ・Lesson5-2:イニシャライザを理解しよう
 ・Lesson5-3:アクセスメソッドを理解しよう
 ・Lesson5-4:クラス変数とクラスメソッドを理解しよう
 ・Lesson5-5:privateメソッドを理解しよう
 ・Lesson5-6:正規表現を理解しよう
 ・Lesson5-7:クラスの継承を理解しよう
 ・Lesson5-8:ファイル操作を理解しよう
 ・Lesson5-9:オーバーライドを理解しよう
 ・Lesson5-10:モジュールを使ってみよう
 ・Lesson5-11:ミックスインを使ってみよう
 ・確認問題5-☆1:モンスター捕獲ゲームを作ろう
 ・確認問題5-☆2:モンスターとの戦闘ゲームを作ろう
 ・確認問題5-☆3:マルバツゲームを作ろう ◁今回はココ

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

<<前のページ

Rubyの記事一覧

次のページ>>

確認問題|Rubyでマルバツゲームを作りながらクラス設計を練習しよう

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からコードを組み立てることが難しい場合は、以下のヒントを開いて参考にしましょう。

Q
ヒント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メソッドを呼び出す

Q
ヒント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

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

この問題の解答例と解説

この問題の正解コードとその解説は以下の通りです。

クリックして開いて確認してください。

Q
正解コード
# ボード表示用のモジュール
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
Q
正解コードの解説

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

モジュールの定義

module BoardDisplay
  def display_board
    @board.each_with_index do |row, i|
      puts row.join('|')
      puts "ー+ー+ー" if i < 2
    end
  end
end
  • module:
    Rubyのモジュールを定義しています。モジュールは、関連するメソッドや定数をまとめて定義する仕組みです。
    このモジュールはボードを表示するための機能を提供します。
  • 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学習サイトにするため、ぜひ 問い合わせフォーム からご意見下さい。

<<前のページ

Rubyの記事一覧

次のページ>>

記事URLをコピーしました