いものやま。

雑多な知識の寄せ集め

変種オセロを考えてみた。(その4)

ボードの実装が出来たので、今日は人同士が実際にこのゲームを遊べるようにしてみる。
CUIだけどw

ボードの表示

まずは、ボードを表示するためのモジュールを実装。

module YWF
  module BoardViewer
    COL_INDEX = "     1   2   3   4   5   6   7   8   9"
    LINE      = "   +---+---+---+---+---+---+---+---+---+"

    def view(board)
      puts "[#{sprintf '%03d', board.move}] turn: #{mark(board.turn)}, token: #{mark(board.token)}"
      puts COL_INDEX
      puts LINE
      (Board::ROW_MIN..Board::ROW_MAX).each do |row|
        print " #{row} "
        (Board::COL_MIN..Board::COL_MAX).each do |col|
          color = board.color(row, col)
          print "| #{mark(color)} "
        end
        puts "|"
        puts LINE
      end
    end

    def mark(color)
      case color
      when Board::EMPTY
        " "
      when Board::BLACK
        "O"
      when Board::GRAY
        "-"
      when Board::WHITE
        "X"
      else
        raise "invalid color. [color: #{color}]"
      end
    end

    module_function :view, :mark
  end
end

ここでは、黒をO、白をX、そして灰色を-で出力するようにしてる。

ゲーム進行の管理

次に、ゲーム進行を管理するクラスを実装。

module YWF
  class Game
    def initialize(black_player, white_player)
      @black_player = black_player
      @white_player = white_player
    end

    def start
      board = Board.new
      done = loop do
        BoardViewer.view(board)

        if board.game_end?
          break true
        end

        if board.must_pass?
          puts "pass!"
          board = board.pass
        else
          if board.turn == Board::BLACK
            command, row, col = @black_player.select(board)
          else
            command, row, col = @white_player.select(board)
          end

          case command
          when :play
            board = board.play(row, col)
          when :change
            board = board.change(row, col)
          when :exit
            break false
          end
        end
      end

      puts "----------"
      if done
        puts "black: #{board.count(Board::BLACK)}, white: #{board.count(Board::WHITE)}"
        if board.win?(Board::BLACK)
          puts "#{BoardViewer.mark(Board::BLACK)} win."
        elsif board.win?(Board::WHITE)
          puts "#{BoardViewer.mark(Board::WHITE)} win."
        else
          puts "draw."
        end
      else
        puts "exit."
      end
    end
  end
end

このクラスは、ゲームを進行させながら、手番のプレイヤーに手の選択を促すようにしていく。
そのインタフェースとなるのが、selectというメソッド。
このメソッドは、現在のボードを引数とし、実行するアクションとその位置を戻すことを要求する。
プレイヤーが人ならここで手の選択を促すし、AIなら思考ルーチンを走らせて手の選択をすることになる。

なお、途中でゲームをやめたくなる場合もあるので、実行するアクションとして:exitが返されたら、ゲームを終了するようにしてある。(Ctrl+Cでもいいんだけどね)

プレイヤー

残すは、プレイヤー。

#!/usr/bin/env ruby

require_relative "board"
require_relative "game"

module YWF
  class Human
    def select(board)
      puts "playable  : #{board.playable_places}"
      puts "changeable: #{board.changeable_places}"
      loop do
        $stdout.print "> "
        $stdout.flush
        command, row_s, col_s = $stdin.gets.split(/\s*[\s,]\s*/)
        row = row_s.to_i
        col = col_s.to_i
        begin
          case command[0]
          when 'p'
            if board.playable?(row, col)
              break [:play, row, col]
            else
              raise "cannot playable. [row: #{row}, col: #{col}]"
            end
          when 'c'
            if board.changeable?(row, col)
              break [:change, row, col]
            else
              raise "cannot changeable. [row: #{row}, col: #{col}]"
            end
          when 'e'
            break [:exit, 0, 0]
          else
            raise "invalid command. [command: #{command}]"
          end
        rescue => e
          puts e.message
          puts "input 'play row, col', 'change row, col', or 'exit'."
        end
      end
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  include YWF

  game = Game.new(Human.new, Human.new)
  game.start
end

YWF::Human#selectで行っているのは、置くことが可能な位置、灰色の石を自分の色に変えることが出来る位置をリストアップし、手の選択を促すこと。
ただし、入力のフォーマットが間違っていたり、合法手が指定されない可能性もあるので、例外が起きた場合はエラーメッセージを表示し、再度入力を促すようにしてある。

あとは、実際に遊べるように、ゲームを作って実行させるコードをちょろっと書いてある。

実行例

さて、これを実行すると、例えばこんな感じ。

[001] turn: O, token: -
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | O | X |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | X | - | O |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | X | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 4], [3, 6], [3, 7], [4, 3], [4, 7], [5, 3], [6, 3], [6, 7], [7, 4], [7, 5], [7, 6]]
changeable: [[4, 4], [6, 6]]
> play 3, 7
[002] turn: X, token: -
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   |   |   | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | O | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | X | O | O |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | X | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 4], [3, 5], [3, 6], [4, 7], [5, 7], [6, 3], [6, 7], [7, 4]]
changeable: []
> play 5, 7
[003] turn: O, token: -
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   |   |   | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | O | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | X | - | - | X |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | X | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 4], [4, 3], [4, 7], [6, 3], [6, 7], [7, 5]]
changeable: [[4, 4], [4, 6], [5, 5], [6, 6]]
> play 6, 7
[004] turn: X, token: -
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   |   |   | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | O | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | X | - | O | X |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | - | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 4], [3, 5], [3, 6], [7, 4], [7, 5], [7, 6], [7, 7]]
changeable: [[5, 5]]
> change 5, 5
[005] turn: O, token: O
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   |   |   | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | O | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | X | X | - | X |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | - | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 3], [3, 4], [3, 6], [4, 3], [4, 7], [4, 8], [6, 3], [7, 5]]
changeable: [[4, 4], [4, 6], [6, 5]]
> play 3, 6
[006] turn: X, token: O
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   |   | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | O | O |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | X | X | O | X |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | - | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[2, 7], [2, 8], [3, 3], [3, 4], [3, 5], [7, 3], [7, 4], [7, 5], [7, 6], [7, 7]]
changeable: []
> play 3, 5
[007] turn: O, token: O
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   | X | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | - | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | X | X | O | X |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | - | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 3], [3, 4], [4, 7], [4, 8], [5, 3], [5, 8], [6, 3], [7, 4]]
changeable: [[4, 4], [4, 6]]
> change 4, 4
[008] turn: X, token: -
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   | X | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | O | - | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   |   | - | - | O | X |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | - | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 8], [5, 3], [7, 5], [7, 7]]
changeable: [[5, 4], [5, 5], [6, 5]]
> play 5, 3
[009] turn: O, token: -
     1   2   3   4   5   6   7   8   9
   +---+---+---+---+---+---+---+---+---+
 1 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 2 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 3 |   |   |   |   | X | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 4 |   |   |   | - | - | - |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 5 |   |   | X | X | X | - | X |   |   |
   +---+---+---+---+---+---+---+---+---+
 6 |   |   |   | O | - | O | O |   |   |
   +---+---+---+---+---+---+---+---+---+
 7 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 8 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
 9 |   |   |   |   |   |   |   |   |   |
   +---+---+---+---+---+---+---+---+---+
playable  : [[3, 3], [3, 4], [4, 2], [4, 7], [4, 8], [6, 3]]
changeable: [[4, 4], [4, 5], [4, 6], [5, 6]]
> exit
----------
exit.

うん、いい感じ。
プレイ感は、割とオセロと変わらないけどw

今日はここまで!