Created
June 2, 2015 16:26
-
-
Save yamaimo/6da266d856a483a9b2b6 to your computer and use it in GitHub Desktop.
YWF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module YWF | |
class Board | |
ROW_MIN = 1 | |
ROW_MAX = 9 | |
COL_MIN = 1 | |
COL_MAX = 9 | |
WALL = -1 | |
EMPTY = 0 | |
BLACK = 1 | |
GRAY = 2 | |
WHITE = 3 | |
Direction = [ | |
[-1, -1], [-1, 0], [-1, 1], | |
[ 0, -1], [ 0, 1], | |
[ 1, -1], [ 1, 0], [ 1, 1], | |
] | |
def initialize(other=nil) | |
@board = [ | |
[WALL, WALL , WALL , WALL , WALL , WALL , WALL , WALL , WALL , WALL , WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, GRAY , BLACK, WHITE, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, WHITE, GRAY , BLACK, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, BLACK, WHITE, GRAY , EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, WALL], | |
[WALL, WALL , WALL , WALL , WALL , WALL , WALL , WALL , WALL , WALL , WALL], | |
] | |
@move = 1 | |
@turn = BLACK | |
@token = GRAY | |
if other | |
copy(other) | |
end | |
end | |
attr_reader :move, :turn, :token | |
def opponent | |
(@turn + 2) % 4 | |
end | |
def color(row, col) | |
if (row < ROW_MIN) || (ROW_MAX < row) | |
raise "invalid row. [row: #{row}]" | |
end | |
if (col < COL_MIN) || (COL_MAX < col) | |
raise "invalid col. [col: #{col}]" | |
end | |
@board[row][col] | |
end | |
def count(color) | |
if (color != EMPTY) && (color != BLACK) && (color != GRAY) && (color != WHITE) | |
raise "invalid color. [color: #{color}]" | |
end | |
count = 0 | |
(ROW_MIN..ROW_MAX).each do |row| | |
(COL_MIN..COL_MAX).each do |col| | |
if self.color(row, col) == color | |
count += 1 | |
end | |
end | |
end | |
count | |
end | |
def playable?(row, col) | |
if self.color(row, col) != EMPTY | |
return false | |
end | |
has_color_from?(row, col) | |
end | |
def changeable?(row, col) | |
if self.color(row, col) != GRAY | |
return false | |
end | |
if (@token != GRAY) && (@token != @turn) | |
return false | |
end | |
has_color_from?(row, col) | |
end | |
def must_pass? | |
(self.playable_places.size == 0) && (self.changeable_places.size == 0) | |
end | |
def playable_places | |
places = [] | |
(ROW_MIN..ROW_MAX).each do |row| | |
(COL_MIN..COL_MAX).each do |col| | |
if self.playable?(row, col) | |
places.push [row, col] | |
end | |
end | |
end | |
places | |
end | |
def changeable_places | |
places = [] | |
if @token != opponent | |
(ROW_MIN..ROW_MAX).each do |row| | |
(COL_MIN..COL_MAX).each do |col| | |
if self.changeable?(row, col) | |
places.push [row, col] | |
end | |
end | |
end | |
end | |
places | |
end | |
def legal_actions | |
actions = Array.new | |
if self.must_pass? | |
actions.push [:pass, nil] | |
else | |
self.playable_places.each do |place| | |
actions.push [:play, place] | |
end | |
self.changeable_places.each do |place| | |
actions.push [:change, place] | |
end | |
end | |
actions | |
end | |
def play(row, col) | |
unless self.playable?(row, col) | |
raise "not playable. [row: #{row}, col: #{col}]" | |
end | |
new_board = self.class.new(self) | |
new_board.put_piece(row, col) | |
new_board.change_turn | |
new_board.add_move | |
new_board | |
end | |
def change(row, col) | |
unless self.changeable?(row, col) | |
raise "not changeable. [row: #{row}, col: #{col}]" | |
end | |
new_board = self.class.new(self) | |
new_board.put_piece(row, col) | |
new_board.change_token | |
new_board.change_turn | |
new_board.add_move | |
new_board | |
end | |
def pass | |
unless self.must_pass? | |
raise "cannot pass." | |
end | |
new_board = self.class.new(self) | |
new_board.change_turn | |
new_board.add_move | |
new_board | |
end | |
def game_end? | |
if self.count(EMPTY) == 0 | |
return true | |
end | |
if self.must_pass? | |
passed = self.pass | |
if passed.must_pass? | |
return true | |
end | |
end | |
false | |
end | |
def win?(color) | |
if (color != BLACK) && (color != WHITE) | |
raise "invalid color. [color: #{color}]" | |
end | |
unless self.game_end? | |
return false | |
end | |
if color == BLACK | |
(self.count(BLACK) > self.count(WHITE)) || | |
((self.count(BLACK) == self.count(WHITE)) && (@token == BLACK)) | |
else | |
(self.count(BLACK) < self.count(WHITE)) || | |
((self.count(BLACK) == self.count(WHITE)) && (@token == WHITE)) | |
end | |
end | |
protected | |
def put_piece(row, col) | |
@board[row][col] = @turn | |
color_diff = (@turn == BLACK) ? -1 : +1 | |
Direction.each do |direction| | |
if has_color_from_to?(row, col, direction) | |
traverse_to(row, col, direction) do |step_count, traverse_row, traverse_col, traverse_color| | |
if traverse_color == @turn | |
break | |
else | |
@board[traverse_row][traverse_col] += color_diff | |
end | |
end | |
end | |
end | |
end | |
def change_turn | |
@turn = self.opponent | |
end | |
def change_token | |
@token += (@turn == BLACK) ? +1 : -1 | |
end | |
def add_move | |
@move += 1 | |
end | |
private | |
def copy(other) | |
(ROW_MIN..ROW_MAX).each do |row| | |
(COL_MIN..COL_MAX).each do |col| | |
@board[row][col] = other.color(row, col) | |
end | |
end | |
@move = other.move | |
@turn = other.turn | |
@token = other.token | |
end | |
def has_color_from?(row, col) | |
Direction.each do |direction| | |
if has_color_from_to?(row, col, direction) | |
return true | |
end | |
end | |
return false | |
end | |
def has_color_from_to?(row, col, direction) | |
traverse_to(row, col, direction) do |step_count, traverse_row, traverse_col, traverse_color| | |
if traverse_color == @turn | |
if step_count == 1 | |
return false | |
else | |
return true | |
end | |
end | |
end | |
return false | |
end | |
def traverse_to(row, col, direction, &block) | |
# NOTE: | |
# 'traverse_to' accesses to 'WALL', | |
# so 'color' cannot be used. | |
traverse_row = row + direction[0] | |
traverse_col = col + direction[1] | |
traverse_color = @board[traverse_row][traverse_col] | |
step_count = 1 | |
while (traverse_color != WALL) && (traverse_color != EMPTY) | |
block.call(step_count, traverse_row, traverse_col, traverse_color) | |
traverse_row += direction[0] | |
traverse_col += direction[1] | |
traverse_color = @board[traverse_row][traverse_col] | |
step_count += 1 | |
end | |
end | |
end | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment