module ShogiServer # for a namespace
+class WrongMoves < ArgumentError; end
+
class Board
- def initialize
+
+ # Split a moves line into an array of a move string.
+ # If it fails to parse the moves, it raises WrongMoves.
+ # @param moves a moves line. Ex. "+776FU-3334Fu"
+ # @return an array of a move string. Ex. ["+7776FU", "-3334FU"]
+ #
+ def Board.split_moves(moves)
+ ret = []
+
+ rs = moves.gsub %r{[\+\-]\d{4}\w{2}} do |s|
+ ret << s
+ ""
+ end
+ raise WrongMoves, rs unless rs.empty?
+
+ return ret
+ end
+
+ def initialize(move_count=0)
@sente_hands = Array::new
@gote_hands = Array::new
@history = Hash::new(0)
@sente_history = Hash::new(0)
@gote_history = Hash::new(0)
@array = [[], [], [], [], [], [], [], [], [], []]
- @move_count = 0
+ @move_count = move_count
@teban = nil # black => true, white => false
+ @initial_moves = []
end
- attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
+ attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history, :teban
attr_reader :move_count
+
+ # Initial moves for a Buoy game. If it is an empty array, the game is
+ # normal with the initial setting; otherwise, the game is started after the
+ # moves.
+ attr_reader :initial_moves
+
+ def deep_copy
+ return Marshal.load(Marshal.dump(self))
+ end
def initial
PieceKY::new(self, 1, 1, false)
@teban = true
end
+ # Set up a board with the strs.
+ # Failing to parse the moves raises an StandardError.
+ # @param strs a board text
+ #
+ def set_from_str(strs)
+ strs.each_line do |str|
+ case str
+ when /^P\d/
+ str.sub!(/^P(.)/, '')
+ y = $1.to_i
+ x = 9
+ while (str.length > 2)
+ str.sub!(/^(...?)/, '')
+ one = $1
+ if (one =~ /^([\+\-])(..)/)
+ sg = $1
+ name = $2
+ if (sg == "+")
+ sente = true
+ else
+ sente = false
+ end
+ if ((x < 1) || (9 < x) || (y < 1) || (9 < y))
+ raise "bad position #{x} #{y}"
+ end
+ case (name)
+ when "FU"
+ PieceFU::new(self, x, y, sente)
+ when "KY"
+ PieceKY::new(self, x, y, sente)
+ when "KE"
+ PieceKE::new(self, x, y, sente)
+ when "GI"
+ PieceGI::new(self, x, y, sente)
+ when "KI"
+ PieceKI::new(self, x, y, sente)
+ when "OU"
+ PieceOU::new(self, x, y, sente)
+ when "KA"
+ PieceKA::new(self, x, y, sente)
+ when "HI"
+ PieceHI::new(self, x, y, sente)
+ when "TO"
+ PieceFU::new(self, x, y, sente, true)
+ when "NY"
+ PieceKY::new(self, x, y, sente, true)
+ when "NK"
+ PieceKE::new(self, x, y, sente, true)
+ when "NG"
+ PieceGI::new(self, x, y, sente, true)
+ when "UM"
+ PieceKA::new(self, x, y, sente, true)
+ when "RY"
+ PieceHI::new(self, x, y, sente, true)
+ else
+ raise "unkown piece #{name}"
+ end
+ end
+ x = x - 1
+ end
+ when /^P([\+\-])/
+ sg = $1
+ if (sg == "+")
+ sente = true
+ else
+ sente = false
+ end
+ str.sub!(/^../, '')
+ while (str.length > 3)
+ str.sub!(/^..(..)/, '')
+ name = $1
+ case (name)
+ when "FU"
+ PieceFU::new(self, 0, 0, sente)
+ when "KY"
+ PieceKY::new(self, 0, 0, sente)
+ when "KE"
+ PieceKE::new(self, 0, 0, sente)
+ when "GI"
+ PieceGI::new(self, 0, 0, sente)
+ when "KI"
+ PieceKI::new(self, 0, 0, sente)
+ when "KA"
+ PieceKA::new(self, 0, 0, sente)
+ when "HI"
+ PieceHI::new(self, 0, 0, sente)
+ else
+ raise "unkown piece #{name}"
+ end
+ end # while
+ when /^\+$/
+ @teban = true
+ when /^\-$/
+ @teban = false
+ else
+ raise "bad line: #{str}"
+ end # case
+ end # do
+ end
+
+ # Set up a board starting with a position after the moves.
+ # Failing to parse the moves raises an ArgumentError.
+ # @param moves an array of moves. ex. ["+7776FU", "-3334FU"]
+ #
+ def set_from_moves(moves)
+ initial()
+ return :normal if moves.empty?
+ rt = nil
+ moves.each do |move|
+ rt = handle_one_move(move, @teban)
+ raise ArgumentError, "bad moves: #{moves}" unless rt == :normal
+ end
+ @initial_moves = moves.dup
+ end
+
def have_piece?(hands, name)
piece = hands.find { |i|
i.name == name
if ((x0 == 0) || (y0 == 0))
piece = have_piece?(hands, name)
- return :illegal if (! piece.move_to?(x1, y1, name)) # TODO null check for the piece?
+ return :illegal if (piece == nil || ! piece.move_to?(x1, y1, name))
piece.move_to(x1, y1)
else
- return :illegal if (! @array[x0][y0].move_to?(x1, y1, name)) # TODO null check?
+ if (@array[x0][y0] == nil || !@array[x0][y0].move_to?(x1, y1, name))
+ return :illegal
+ end
if (@array[x0][y0].name != name) # promoted ?
@array[x0][y0].promoted = true
end
raise "can't find ou"
end
- # note checkmate, but check. sente is checked.
+ # not checkmate, but check. sente is checked.
def checkmated?(sente) # sente is loosing
ou = look_for_ou(sente)
x = 1
## case: rival_ou is moving
rival_ou.movable_grids.each do |(cand_x, cand_y)|
- tmp_board = Marshal.load(Marshal.dump(self))
+ tmp_board = deep_copy
s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
raise "internal error" if (s != true)
if (! tmp_board.checkmated?(! sente)) # good move
end
end
names.map! do |name|
- tmp_board = Marshal.load(Marshal.dump(self))
+ tmp_board = deep_copy
s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
if s == :illegal
s # result
end
def oute_sennichite?(player)
- if (@sente_history[to_s] >= 4)
- return :oute_sennichite_sente_lose
- elsif (@gote_history[to_s] >= 4)
- return :oute_sennichite_gote_lose
+ return nil unless sennichite?
+
+ if player
+ # sente's turn
+ if (@sente_history[to_s] >= 4) # sente is checking gote
+ return :oute_sennichite_sente_lose
+ elsif (@gote_history[to_s] >= 3) # sente is escaping
+ return :oute_sennichite_gote_lose
+ else
+ return nil # Not oute_sennichite, but sennichite
+ end
else
- return nil
+ # gote's turn
+ if (@gote_history[to_s] >= 4) # gote is checking sente
+ return :oute_sennichite_gote_lose
+ elsif (@sente_history[to_s] >= 3) # gote is escaping
+ return :oute_sennichite_sente_lose
+ else
+ return nil # Not oute_sennichite, but sennichite
+ end
end
end
- def sennichite?(sente)
+ def sennichite?
if (@history[to_s] >= 4) # already 3 times
return true
end
return :illegal # can't put on existing piece
end
- tmp_board = Marshal.load(Marshal.dump(self))
+ tmp_board = deep_copy
return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
return :oute_kaihimore if (tmp_board.checkmated?(sente))
tmp_board.update_sennichite(sente)
os_result = tmp_board.oute_sennichite?(sente)
return os_result if os_result # :oute_sennichite_sente_lose or :oute_sennichite_gote_lose
- return :sennichite if tmp_board.sennichite?(sente)
+ return :sennichite if tmp_board.sennichite?
if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
return :uchifuzume