OSDN Git Service

Improved a way to handle diferred moves (2008-03-24's change),
[shogi-server/shogi-server.git] / shogi-server
index 232c8f5..5d33d6f 100755 (executable)
@@ -461,12 +461,15 @@ class Player < BasicPlayer
     @game_name = ""
     @mytime = 0                 # set in start method also
     @sente = nil
+    @socket_buffer = []
     @main_thread = Thread::current
+    @mutex_write_guard = Mutex.new
   end
 
   attr_accessor :socket, :status
   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
   attr_accessor :main_thread
+  attr_reader :socket_buffer
   
   def kill
     log_message(sprintf("user %s killed", @name))
@@ -490,12 +493,20 @@ class Player < BasicPlayer
   end
 
   def write_safe(str)
-    begin
-      @socket.write(str)
-    rescue Exception => ex
-      log_error("Failed to send a message to #{@name}.")
-      log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
-      # TODO close
+    @mutex_write_guard.synchronize do
+      begin
+        if @socket.closed?
+          log_warning("%s's socket has been closed." % [@name])
+          return
+        end
+        if r = select(nil, [@socket], nil, 20)
+          r[1].first.write(str)
+        else
+          log_error("Sending a message to #{@name} timed up.")
+        end
+      rescue Exception => ex
+        log_error("Failed to send a message to #{@name}. #{ex.class}: #{ex.message}\t#{ex.backtrace[0]}")
+      end
     end
   end
 
@@ -514,9 +525,15 @@ class Player < BasicPlayer
   end
 
   def run(csa_1st_str=nil)
-    while (csa_1st_str || (str = gets_safe(@socket, Default_Timeout)))
+    while ( csa_1st_str || 
+            str = gets_safe(@socket, (@socket_buffer.empty? ? Default_Timeout : 1)) )
+      $mutex.lock
       begin
-        $mutex.lock
+        if (@game && @game.turn?(self))
+          @socket_buffer << str
+          str = @socket_buffer.shift
+        end
+        log_message("%s (%s)" % [str, @socket_buffer.map {|a| String === a ? a.strip : a }.join(",")]) if $DEBUG
 
         if (csa_1st_str)
           str = csa_1st_str
@@ -527,7 +544,6 @@ class Player < BasicPlayer
           return
         end
         str.chomp! if (str.class == String) # may be strip! ?
-        log_message(str) if $DEBUG
         case str 
         when "" 
           # Application-level protocol for Keep-Alive
@@ -543,7 +559,7 @@ class Player < BasicPlayer
               comment = array_str.unshift("'*#{$1.toeuc}")
             end
             s = @game.handle_one_move(move, self)
-            @game.fh.print("#{comment}\n") if (comment && !s)
+            @game.fh.print("#{Kconv.toeuc(comment.first)}\n") if (comment && comment.first && !s)
             return if (s && @protocol == LoginCSA::PROTOCOL)
           end
         when /^%[^%]/, :timeout
@@ -1124,6 +1140,7 @@ class Board
     @gote_history  = Hash::new(0)
     @array = [[], [], [], [], [], [], [], [], [], []]
     @move_count = 0
+    @teban = nil # black => true, white => false
   end
   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
   attr_reader :move_count
@@ -1158,6 +1175,7 @@ class Board
     (1..9).each do |i|
       PieceFU::new(self, i, 7, true)
     end
+    @teban = true
   end
 
   def have_piece?(hands, name)
@@ -1196,6 +1214,7 @@ class Board
       @array[x0][y0].move_to(x1, y1)
     end
     @move_count += 1
+    @teban = @teban ? false : true
     return true
   end
 
@@ -1486,7 +1505,6 @@ class Board
     end
 
     move_to(x0, y0, x1, y1, name, sente)
-    str = to_s
 
     update_sennichite(sente)
     return :normal
@@ -1525,7 +1543,7 @@ class Board
       end
       a.push("\n")
     end
-    a.push("+\n")
+    a.push("%s\n" % [@teban ? "+" : "-"])
     return a.join
   end
 end
@@ -1587,6 +1605,8 @@ class Game
     else
       @sente, @gote = player1, player0
     end
+    @sente.socket_buffer.clear
+    @gote.socket_buffer.clear
     @current_player, @next_player = @sente, @gote
     @sente.game = self
     @gote.game  = self
@@ -1622,6 +1642,10 @@ class Game
     @sente.rated? && @gote.rated?
   end
 
+  def turn?(player)
+    return player.status == "game" && @current_player == player
+  end
+
   def monitoron(monitor)
     @monitors.delete(monitor)
     @monitors.push(monitor)
@@ -1670,8 +1694,17 @@ class Game
     LEAGUE.games.delete(@id)
   end
 
+  # class Game
   def handle_one_move(str, player)
-    return nil unless @current_player == player
+    unless turn?(player)
+      return false if str == :timeout
+
+      @fh.puts("'Deferred %s" % [str])
+      log_warning("Deferred a move [%s] scince it is not %s 's turn." %
+                  [str, player.name])
+      player.socket_buffer << str # always in the player's thread
+      return nil
+    end
 
     finish_flag = true
     @end_time = Time::new
@@ -1695,6 +1728,7 @@ class Game
         @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
       else
         if [:normal, :outori, :sennichite, :oute_sennichite_sente_lose, :oute_sennichite_gote_lose].include?(move_status)
+          # Thinking time includes network traffic
           @sente.write_safe(sprintf("%s,T%d\n", str, t))
           @gote.write_safe(sprintf("%s,T%d\n", str, t))
           @fh.printf("%s\nT%d\n", str, t)
@@ -2160,8 +2194,8 @@ def login_loop(client)
         LEAGUE.add(player)
         break
       else
-        client.write_safe("LOGIN:incorrect" + eol)
-        client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
+        client.write("LOGIN:incorrect" + eol)
+        client.write("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
       end
     ensure
       $mutex.unlock