OSDN Git Service

Fix a jishogi bug.
[shogi-server/shogi-server.git] / shogi-server
index 8dbc179..d2b1251 100755 (executable)
@@ -40,6 +40,7 @@ require 'timeout'
 require 'socket'
 
 TCPSocket.do_not_reverse_lookup = true
+Thread.abort_on_exception = true
 
 
 class TCPSocket
@@ -101,7 +102,7 @@ class League
     @players.each do |name, player|
       if ((player.status == status) &&
           (player.game_name == game_name) &&
-          ((player.sente == nil) || (player.sente == sente)) &&
+          ((sente == nil) || (player.sente == nil) || (player.sente == sente)) &&
           ((searcher == nil) || (player != searcher)))
         return player
       end
@@ -174,7 +175,7 @@ class Player
       elsif (@sente == false)
         return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
       elsif (@sente == nil)
-        return sprintf("%s %s %s %s", @name, @protocol, @status, @game_name)
+        return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
       end
     else
       return sprintf("%s %s %s", @name, @protocol, @status)
@@ -208,7 +209,11 @@ class Player
       write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
     else
       log_message(sprintf("user %s run in CSA mode", @name))
-      csa_1st_str = "%%GAME #{Default_Game_Name}"
+      if (good_game_name?(@password))
+        csa_1st_str = "%%GAME #{@password} *"
+      else
+        csa_1st_str = "%%GAME #{Default_Game_Name} *"
+      end
     end
     
     while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
@@ -220,7 +225,7 @@ class Player
         end
         if (@write_queue.size > Max_Write_Queue_Size)
           log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
-         return
+               return
         end
 
         if (@status == "finished")
@@ -228,7 +233,19 @@ class Player
         end
         str.chomp! if (str.class == String)
         case str
-        when /^[\+\-%][^%]/, :timeout
+        when /^[\+\-][^%]/
+          if (@status == "game")
+            array_str = str.split(",")
+            move = array_str.shift
+            additional = array_str.shift
+            if /^'(.*)/ =~ additional
+              comment = array_str.unshift("'*#{$1}")
+            end
+            s = @game.handle_one_move(move, self)
+            @game.fh.print("#{comment}\n") if (comment && !s)
+            return if (s && @protocol == "CSA")
+          end
+        when /^%[^%]/, :timeout
           if (@status == "game")
             s = @game.handle_one_move(str, self)
             return if (s && @protocol == "CSA")
@@ -273,9 +290,13 @@ class Player
         when /^%%HELP/
           write_help
         when /^%%GAME\s*$/
-          @status = "connected"
-          @game_name = ""
-        when /^%%(GAME|CHALLENGE)\s+(\S+)\s*([\+\-]*)\s*$/
+          if ((@status == "connected") || (@status == "game_waiting"))
+            @status = "connected"
+            @game_name = ""
+          else
+            write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
+          end
+        when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
           command_name = $1
           game_name = $2
           my_sente_str = $3
@@ -288,7 +309,7 @@ class Player
             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
             next
           end
-          if ((my_sente_str == "") ||
+          if ((my_sente_str == "*") ||
               (my_sente_str == "+") ||
               (my_sente_str == "-"))
             ## ok
@@ -297,7 +318,7 @@ class Player
             next
           end
 
-          if (my_sente_str == "")
+          if (my_sente_str == "*")
             rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
           elsif (my_sente_str == "+")
             rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
@@ -308,7 +329,7 @@ class Player
           end
           if (rival)
             @game_name = game_name
-            if ((my_sente_str == "") && (rival.sente == nil))
+            if ((my_sente_str == "*") && (rival.sente == nil))
               if (rand(2) == 0)
                 @sente = true
                 rival.sente = false
@@ -354,8 +375,7 @@ class Player
           message = $1
           LEAGUE.players.each do |name, player|
             if (player.protocol != "CSA")
-              s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
-              player.status = "zombie" if (! s)
+              player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
             end
           end
         when /^%%LIST/
@@ -475,7 +495,7 @@ class Piece
       if (@sente)
         return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
       else
-        return false if ((6 >= @y) && (6 <= y) && (name != @name))
+        return false if ((6 >= @y) && (6 >= y) && (name != @name))
       end
     end
 
@@ -495,6 +515,7 @@ class Piece
       end
       @board.array[x][y] = self
     elsif ((x == 0) || (y == 0))
+      @promoted = false         # clear promoted flag before moving to hands
       if (@sente)
         @board.sente_hands.push(self)
       else
@@ -509,6 +530,10 @@ class Piece
     @y = y
   end
 
+  def point
+    @point
+  end
+
   def name
     @name
   end
@@ -534,6 +559,7 @@ end
 
 class PieceFU < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[0, +1]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "FU"
@@ -567,6 +593,7 @@ end
 
 class PieceKY  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = []
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "KY"
@@ -611,6 +638,7 @@ class PieceKY  < Piece
 end
 class PieceKE  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[+1, +2], [-1, +2]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "KE"
@@ -630,6 +658,7 @@ class PieceKE  < Piece
 end
 class PieceGI  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "GI"
@@ -639,6 +668,7 @@ class PieceGI  < Piece
 end
 class PieceKI  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @promoted_moves = []
     @name = "KI"
@@ -648,6 +678,7 @@ class PieceKI  < Piece
 end
 class PieceKA  < Piece
   def initialize(*arg)
+    @point = 5
     @normal_moves = []
     @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
     @name = "KA"
@@ -697,6 +728,7 @@ class PieceKA  < Piece
 end
 class PieceHI  < Piece
   def initialize(*arg)
+    @point = 5
     @normal_moves = []
     @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
     @name = "HI"
@@ -742,6 +774,7 @@ class PieceHI  < Piece
 end
 class PieceOU < Piece
   def initialize(*arg)
+    @point = 0
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
     @promoted_moves = []
     @name = "OU"
@@ -823,7 +856,6 @@ class Board
       piece = have_piece?(hands, name)
       return :illegal if (! piece.move_to?(x1, y1, name))
       piece.move_to(x1, y1)
-      piece.promoted = false
     else
       return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
       if (@array[x0][y0].name != name) # promoted ?
@@ -966,7 +998,69 @@ class Board
     return false
   end
 
-  def handle_one_move(str)
+  def good_kachi?(sente)
+    if (checkmated?(sente))
+      puts "'NG: Checkmating." if $DEBUG
+      return false 
+    end
+    
+    ou = look_for_ou(sente)
+    if (sente && (ou.y >= 4))
+      puts "'NG: Black's OU does not enter yet." if $DEBUG
+      return false     
+    end  
+    if (! sente && (ou.y <= 6))
+      puts "'NG: White's OU does not enter yet." if $DEBUG
+      return false 
+    end
+      
+    number = 0
+    point = 0
+
+    if (sente)
+      hands = @sente_hands
+      r = [1, 2, 3]
+    else
+      hands = @gote_hands
+      r = [7, 8, 9]
+    end
+    r.each do |y|
+      x = 1
+      while (x <= 9)
+        if (@array[x][y] &&
+            (@array[x][y].sente == sente) &&
+            (@array[x][y].point > 0))
+          point = point + @array[x][y].point
+          number = number + 1
+        end
+        x = x + 1
+      end
+    end
+    hands.each do |piece|
+      point = point + piece.point
+    end
+
+    if (number < 10)
+      puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
+      return false     
+    end  
+    if (sente)
+      if (point < 28)
+        puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end  
+    else
+      if (point < 27)
+        puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end
+    end
+
+    puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
+    return true
+  end
+
+  def handle_one_move(str, sente)
     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
       sg = $1
       x0 = $2.to_i
@@ -975,7 +1069,11 @@ class Board
       y1 = $5.to_i
       name = $6
     elsif (str =~ /^%KACHI/)
-      return :kachi
+      if (good_kachi?(sente))
+        return :kachi_win
+      else
+        return :kachi_lose
+      end
     elsif (str =~ /^%TORYO/)
       return :toryo
     else
@@ -1085,6 +1183,9 @@ class Board
 end
 
 class Game
+  @@mutex = Mutex.new
+  @@time  = 0
+
   def initialize(game_name, player0, player1)
     @monitors = Array::new
     @game_name = game_name
@@ -1111,16 +1212,15 @@ class Game
 
     @sente.status = "agree_waiting"
     @gote.status = "agree_waiting"
+    
     @id = sprintf("%s+%s+%s+%s+%s", 
-                  LEAGUE.event, @game_name, @sente.name, @gote.name,
-                  Time::new.strftime("%Y%m%d%H%M%S"))
+                  LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time)
+    @logfile = @id + ".csa"
 
     LEAGUE.games[@id] = self
 
-
     log_message(sprintf("game created %s", @id))
 
-    @logfile = @id + ".csa"
     @board = Board::new
     @board.initial
     @start_time = nil
@@ -1183,21 +1283,27 @@ class Game
     finish_flag = true
     if (@current_player == player)
       @end_time = Time::new
-      t = (@end_time - @start_time).ceil
+      t = (@end_time - @start_time).floor
       t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
       
       move_status = nil
-      if ((@current_player.mytime - t <= 0) && (@total_time > 0))
+      if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0)))
         status = :timeout
       elsif (str == :timeout)
         return false            # time isn't expired. players aren't swapped. continue game
       else
+        @current_player.mytime = @current_player.mytime - t
+        if (@current_player.mytime < 0)
+          @current_player.mytime = 0
+        end
+
 #        begin
-          move_status = @board.handle_one_move(str)
+          move_status = @board.handle_one_move(str, @sente == @current_player)
 #        rescue
 #          log_error("handle_one_move raise exception for #{str}")
 #          move_status = :illegal
 #        end
+
         if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
           @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
         else
@@ -1208,6 +1314,7 @@ class Game
             @last_move = sprintf("%s,T%d", str, t)
             @current_turn = @current_turn + 1
           end
+
           @monitors.each do |monitor|
             monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
             monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
@@ -1215,20 +1322,16 @@ class Game
         end
       end
 
-      if (@current_player.mytime - t < @byoyomi)
-        @current_player.mytime = @byoyomi
-      else
-        @current_player.mytime = @current_player.mytime - t
-      end
-
       if (@next_player.status != "game") # rival is logout or disconnected
         abnormal_win()
       elsif (status == :timeout)
         timeout_lose()
       elsif (move_status == :illegal)
         illegal_lose()
-      elsif (move_status == :kachi)
+      elsif (move_status == :kachi_win)
         kachi_win()
+      elsif (move_status == :kachi_lose)
+        kachi_lose()
       elsif (move_status == :toryo)
         toryo_lose()
       elsif (move_status == :outori)
@@ -1362,6 +1465,19 @@ class Game
     end
   end
 
+  def kachi_lose
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
+    @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
+    @fh.printf("%%KACHI\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
+    end
+  end
+
   def toryo_lose
     @current_player.status = "connected"
     @next_player.status = "connected"
@@ -1428,9 +1544,10 @@ EOM
   def show()
     str0 = <<EOM
 BEGIN Game_Summary
-Protocol_Version:1.0
+Protocol_Version:1.1
 Protocol_Mode:Server
 Format:Shogi 1.0
+Declaration:Jishogi 1.1
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
@@ -1447,7 +1564,6 @@ Last_Move:#{@last_move}
 Current_Turn:#{@current_turn}
 END Time
 BEGIN Position
-Jishogi_Declaration:1.1
 EOM
 
     str1 = <<EOM
@@ -1461,9 +1577,10 @@ EOM
   def propose_message(sg_flag)
     str = <<EOM
 BEGIN Game_Summary
-Protocol_Version:1.0
+Protocol_Version:1.1
 Protocol_Mode:Server
 Format:Shogi 1.0
+Declaration:Jishogi 1.1
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
@@ -1477,7 +1594,6 @@ Byoyomi:#{@byoyomi}
 Least_Time_Per_Move:#{Least_Time_Per_Move}
 END Time
 BEGIN Position
-Jishogi_Declaration:1.1
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
@@ -1495,6 +1611,18 @@ END Game_Summary
 EOM
     return str
   end
+  
+  private
+  
+  def issue_current_time
+    time = Time::new.strftime("%Y%m%d%H%M%S").to_i
+    @@mutex.synchronize do
+      while time <= @@time do
+        time += 1
+      end
+      @@time = time
+    end
+  end
 end
 
 def usage
@@ -1597,8 +1725,11 @@ def mutex_watchdog(mutex, sec)
   while true
     begin
       timeout(sec) do
-        mutex.lock
-        mutex.unlock
+        begin
+          mutex.lock
+        ensure
+          mutex.unlock
+        end
       end
       sleep(sec)
     rescue TimeoutError
@@ -1611,6 +1742,7 @@ end
 def main
   $mutex = Mutex::new
   Thread::start do
+    Thread.pass
     mutex_watchdog($mutex, 10)
   end
 
@@ -1626,13 +1758,13 @@ def main
   write_pid_file($options["pid-file"]) if ($options["pid-file"])
 
 
-  Thread.abort_on_exception = true
 
   server = TCPserver.open(port)
   log_message("server started")
 
   while true
     Thread::start(server.accept) do |client|
+      Thread.pass
       client.sync = true
       player = nil
       while (str = client.gets_timeout(Login_Time))
@@ -1652,6 +1784,7 @@ def main
                 client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
                 client.close
                 Thread::exit
+                return
               end
             end
             LEAGUE.add(player)
@@ -1667,6 +1800,7 @@ def main
       if (! player)
         client.close
         Thread::exit
+        return
       end
       log_message(sprintf("user %s login", player.name))
       player.run
@@ -1675,7 +1809,7 @@ def main
         if (player.game)
           player.game.kill(player)
         end
-        player.finish
+        player.finish # socket has been closed
         LEAGUE.delete(player)
         log_message(sprintf("user %s logout", player.name))
       ensure