OSDN Git Service

* Return [...] +OK after %%SETBUOY and %%DELETEBUOY commands
[shogi-server/shogi-server.git] / shogi_server / board.rb
index 7a1c91e..dce239b 100644 (file)
 
 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)
@@ -66,6 +96,121 @@ class Board
     @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
@@ -82,10 +227,12 @@ class Board
 
     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
@@ -123,7 +270,7 @@ class Board
     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
@@ -169,7 +316,7 @@ class Board
 
     ## 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
@@ -197,7 +344,7 @@ class Board
             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
@@ -244,16 +391,30 @@ class Board
   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
@@ -380,13 +541,13 @@ class Board
       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