reversi
This commit is contained in:
parent
37e91b7c23
commit
6645a15a7b
5 changed files with 383 additions and 1 deletions
66
position.rb
Normal file
66
position.rb
Normal file
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Position
|
||||
# マスを'f3','d6'などの表記で表現する。変数名cell_refとして取り扱う。
|
||||
COL = %w[a b c d e f g h].freeze
|
||||
ROW = %w[1 2 3 4 5 6 7 8].freeze
|
||||
|
||||
DIRECTIONS = [
|
||||
TOP_LEFT = :top_left,
|
||||
TOP = :top,
|
||||
TOP_RIGHT = :top_right,
|
||||
LEFT = :left,
|
||||
RIGHT = :right,
|
||||
BOTTOM_LEFT = :bottom_left,
|
||||
BOTTOM = :bottom,
|
||||
BOTTOM_RIGHT = :bottom_right
|
||||
].freeze
|
||||
|
||||
attr_accessor :row, :col
|
||||
|
||||
def initialize(row_or_cell_ref, col = nil)
|
||||
if col
|
||||
# Position.new(1, 5) のような呼び出し
|
||||
@row = row_or_cell_ref
|
||||
@col = col
|
||||
else
|
||||
# Position.new('f7')のような呼び出し
|
||||
@row = ROW.index(row_or_cell_ref[1])
|
||||
@col = COL.index(row_or_cell_ref[0])
|
||||
end
|
||||
end
|
||||
|
||||
def invalid?
|
||||
row.nil? || col.nil?
|
||||
end
|
||||
|
||||
def out_of_board?
|
||||
!((0..7).cover?(row) && (0..7).cover?(col))
|
||||
end
|
||||
|
||||
def stone_color(board)
|
||||
return nil if out_of_board?
|
||||
|
||||
board[row][col]
|
||||
end
|
||||
|
||||
def to_cell_ref
|
||||
return '盤面外' if out_of_board?
|
||||
|
||||
"#{COL[col]}#{ROW[row]}"
|
||||
end
|
||||
|
||||
def next_position(direction)
|
||||
case direction
|
||||
when TOP_LEFT then Position.new(row - 1, col - 1)
|
||||
when TOP then Position.new(row - 1, col)
|
||||
when TOP_RIGHT then Position.new(row - 1, col + 1)
|
||||
when LEFT then Position.new(row, col - 1)
|
||||
when RIGHT then Position.new(row, col + 1)
|
||||
when BOTTOM_LEFT then Position.new(row + 1, col - 1)
|
||||
when BOTTOM then Position.new(row + 1, col)
|
||||
when BOTTOM_RIGHT then Position.new(row + 1, col + 1)
|
||||
else raise 'Unknown direction'
|
||||
end
|
||||
end
|
||||
end
|
1
reversi
1
reversi
|
@ -1 +0,0 @@
|
|||
Subproject commit 165d5aafaeaeba50c855ea1f6998e0170bca8474
|
58
reversi.rb
Normal file
58
reversi.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative './reversi_methods'
|
||||
|
||||
class Reversi
|
||||
include ReversiMethods
|
||||
|
||||
QUIT_COMMANDS = %w[quit exit q].freeze
|
||||
|
||||
def initialize
|
||||
@board = build_initial_board
|
||||
@current_stone = BLACK_STONE
|
||||
end
|
||||
|
||||
def run
|
||||
loop do
|
||||
output(@board)
|
||||
|
||||
if finished?(@board)
|
||||
puts '試合終了'
|
||||
puts "白○:#{count_stone(@board, WHITE_STONE)}"
|
||||
puts "黒●:#{count_stone(@board, BLACK_STONE)}"
|
||||
break
|
||||
end
|
||||
|
||||
unless placeable?(@board, @current_stone)
|
||||
puts '詰みのためターンを切り替えます'
|
||||
toggle_stone
|
||||
next
|
||||
end
|
||||
|
||||
print "command? (#{@current_stone == WHITE_STONE ? '白○' : '黒●'}) > "
|
||||
command = gets.chomp
|
||||
break if QUIT_COMMANDS.include?(command)
|
||||
|
||||
begin
|
||||
if put_stone(@board, command, @current_stone)
|
||||
puts '配置成功、次のターン'
|
||||
toggle_stone
|
||||
else
|
||||
puts '配置失敗、ターン据え置き'
|
||||
end
|
||||
rescue StandardError => e
|
||||
puts "ERROR: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
puts 'finished!'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def toggle_stone
|
||||
@current_stone = @current_stone == WHITE_STONE ? BLACK_STONE : WHITE_STONE
|
||||
end
|
||||
end
|
||||
|
||||
Reversi.new.run if __FILE__ == $PROGRAM_NAME
|
95
reversi_methods.rb
Normal file
95
reversi_methods.rb
Normal file
|
@ -0,0 +1,95 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative './position'
|
||||
|
||||
module ReversiMethods
|
||||
WHITE_STONE = 'W'
|
||||
BLACK_STONE = 'B'
|
||||
BLANK_CELL = '-'
|
||||
|
||||
def build_initial_board
|
||||
# boardは盤面を示す二次元配列
|
||||
board = Array.new(8) { Array.new(8, BLANK_CELL) }
|
||||
board[3][3] = WHITE_STONE # d4
|
||||
board[4][4] = WHITE_STONE # e5
|
||||
board[3][4] = BLACK_STONE # d5
|
||||
board[4][3] = BLACK_STONE # e4
|
||||
board
|
||||
end
|
||||
|
||||
def output(board)
|
||||
puts " #{Position::COL.join(' ')}"
|
||||
board.each_with_index do |row, i|
|
||||
print Position::ROW[i]
|
||||
row.each do |cell|
|
||||
case cell
|
||||
when WHITE_STONE then print ' ○'
|
||||
when BLACK_STONE then print ' ●'
|
||||
else print ' -'
|
||||
end
|
||||
end
|
||||
print "\n"
|
||||
end
|
||||
end
|
||||
|
||||
def copy_board(to_board, from_board)
|
||||
from_board.each_with_index do |cols, row|
|
||||
cols.each_with_index do |cell, col|
|
||||
to_board[row][col] = cell
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def put_stone(board, cell_ref, stone_color, dry_run: false)
|
||||
pos = Position.new(cell_ref)
|
||||
raise '無効なポジションです' if pos.invalid?
|
||||
raise 'すでに石が置かれています' unless pos.stone_color(board) == BLANK_CELL
|
||||
|
||||
# コピーした盤面にて石の配置を試みて、成功すれば反映する
|
||||
copied_board = Marshal.load(Marshal.dump(board))
|
||||
copied_board[pos.row][pos.col] = stone_color
|
||||
|
||||
turn_succeed = false
|
||||
Position::DIRECTIONS.each do |direction|
|
||||
next_pos = pos.next_position(direction)
|
||||
turn_succeed = true if turn(copied_board, next_pos, stone_color, direction)
|
||||
end
|
||||
|
||||
copy_board(board, copied_board) if !dry_run && turn_succeed
|
||||
|
||||
turn_succeed
|
||||
end
|
||||
|
||||
def turn(board, target_pos, attack_stone_color, direction)
|
||||
return false if target_pos.out_of_board?
|
||||
return false if target_pos.stone_color(board) == attack_stone_color
|
||||
return false if target_pos.stone_color(board) == BLANK_CELL
|
||||
|
||||
next_pos = target_pos.next_position(direction)
|
||||
if (next_pos.stone_color(board) == attack_stone_color) || turn(board, next_pos, attack_stone_color, direction)
|
||||
board[target_pos.row][target_pos.col] = attack_stone_color
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def finished?(board)
|
||||
!placeable?(board, WHITE_STONE) && !placeable?(board, BLACK_STONE)
|
||||
end
|
||||
|
||||
def placeable?(board, attack_stone_color)
|
||||
board.each_with_index do |cols, row|
|
||||
cols.each_with_index do |cell, col|
|
||||
next unless cell == BLANK_CELL
|
||||
position = Position.new(row, col)
|
||||
return true if put_stone(board, position.to_cell_ref, attack_stone_color, dry_run: true)
|
||||
end
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def count_stone(board, stone_color)
|
||||
board.flatten.count { |cell| cell == stone_color }
|
||||
end
|
||||
end
|
164
reversi_methods_test.rb
Normal file
164
reversi_methods_test.rb
Normal file
|
@ -0,0 +1,164 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'minitest/autorun'
|
||||
require_relative './reversi_methods'
|
||||
|
||||
class ReversiMethodsTest < Minitest::Test
|
||||
include ReversiMethods
|
||||
|
||||
def build_board(board_text)
|
||||
board = build_initial_board
|
||||
board_text.split("\n").each_with_index do |row, i|
|
||||
row.each_char.with_index do |cell, j|
|
||||
board[i][j] = cell
|
||||
end
|
||||
end
|
||||
board
|
||||
end
|
||||
|
||||
def test_invalid_position
|
||||
board = build_initial_board
|
||||
e = assert_raises RuntimeError do
|
||||
put_stone(board, 'x0', BLACK_STONE)
|
||||
end
|
||||
assert_equal '無効なポジションです', e.message
|
||||
end
|
||||
|
||||
def test_already_have_a_stone
|
||||
board = build_initial_board
|
||||
e = assert_raises RuntimeError do
|
||||
put_stone(board, 'd5', BLACK_STONE)
|
||||
end
|
||||
assert_equal 'すでに石が置かれています', e.message
|
||||
end
|
||||
|
||||
def test_put_stone
|
||||
board = build_initial_board
|
||||
assert put_stone(board, 'e6', BLACK_STONE)
|
||||
assert_equal build_board(<<~BOARD), board
|
||||
--------
|
||||
--------
|
||||
--------
|
||||
---WB---
|
||||
---BB---
|
||||
----B---
|
||||
--------
|
||||
--------
|
||||
BOARD
|
||||
assert put_stone(board, 'f4', WHITE_STONE)
|
||||
assert_equal build_board(<<~BOARD), board
|
||||
--------
|
||||
--------
|
||||
--------
|
||||
---WWW--
|
||||
---BB---
|
||||
----B---
|
||||
--------
|
||||
--------
|
||||
BOARD
|
||||
end
|
||||
|
||||
def test_cannot_put_stone
|
||||
initial_data = <<~BOARD
|
||||
W-WWWW--
|
||||
W-BWWW--
|
||||
WBWWWWW-
|
||||
WWBWWW--
|
||||
WBBBBB--
|
||||
--B-----
|
||||
--B-----
|
||||
--B-----
|
||||
BOARD
|
||||
board = build_board(initial_data)
|
||||
refute put_stone(board, 'b1', BLACK_STONE)
|
||||
assert_equal build_board(initial_data), board
|
||||
end
|
||||
|
||||
def test_turn
|
||||
board = build_board(<<~BOARD)
|
||||
--------
|
||||
---B----
|
||||
--WB----
|
||||
--WBB---
|
||||
--WWW---
|
||||
--------
|
||||
--------
|
||||
--------
|
||||
BOARD
|
||||
assert put_stone(board, 'b4', BLACK_STONE)
|
||||
assert_equal build_board(<<~BOARD), board
|
||||
--------
|
||||
---B----
|
||||
--BB----
|
||||
-BBBB---
|
||||
--WWW---
|
||||
--------
|
||||
--------
|
||||
--------
|
||||
BOARD
|
||||
end
|
||||
|
||||
def test_finished_of_initial_board
|
||||
board = build_initial_board
|
||||
refute finished?(board) # 初期盤面
|
||||
end
|
||||
|
||||
def test_finished_of_full_board
|
||||
assert finished?(build_board(<<~BOARD)) # 全て埋まった盤面
|
||||
WWWWWWWW
|
||||
WBBWWBWB
|
||||
WBBBBWBB
|
||||
WBWBBBBB
|
||||
WBWWBBBB
|
||||
WBWWWBBB
|
||||
WWWWWWBB
|
||||
WBBBBBBB
|
||||
BOARD
|
||||
end
|
||||
|
||||
def test_finished_of_quickest_win_board
|
||||
assert finished?(build_board(<<~BOARD)) # 白最短勝利
|
||||
--------
|
||||
---W----
|
||||
---WW---
|
||||
-WWWWW--
|
||||
---WWW--
|
||||
---WWW--
|
||||
--------
|
||||
--------
|
||||
BOARD
|
||||
assert finished?(build_board(<<~BOARD)) # 黒最短勝利
|
||||
--------
|
||||
--------
|
||||
----B---
|
||||
---BBB--
|
||||
--BBBBB-
|
||||
---BBB--
|
||||
----B---
|
||||
--------
|
||||
BOARD
|
||||
end
|
||||
|
||||
def test_finished_of_player_skip_board
|
||||
refute finished?(build_board(<<~BOARD)) # 白配置可・黒配置不可
|
||||
WWWWWWWB
|
||||
WBBWWBWB
|
||||
WBBBBWBB
|
||||
WBWBBBB-
|
||||
WBWWBBBB
|
||||
WBWWWBBB
|
||||
WWWWBWBB
|
||||
WBBBBBBB
|
||||
BOARD
|
||||
refute finished?(build_board(<<~BOARD)) # 白配置不可・黒配置可
|
||||
WWWWWWWW
|
||||
WBBWWBWB
|
||||
WBBBBWBB
|
||||
WBWBBBBB
|
||||
WBWWBBBB
|
||||
WBWWWBBB
|
||||
BBBB-WBB
|
||||
WBBBBBBB
|
||||
BOARD
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue