Skip to content

Instantly share code, notes, and snippets.

@yamaimo
Created June 2, 2015 16:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yamaimo/6da266d856a483a9b2b6 to your computer and use it in GitHub Desktop.
Save yamaimo/6da266d856a483a9b2b6 to your computer and use it in GitHub Desktop.
YWF
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
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
#!/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