OSDN Git Service

Refactored Player#set_sente_from_str().
[shogi-server/shogi-server.git] / shogi_server / command.rb
index b677c5e..fe0ddf4 100644 (file)
@@ -25,7 +25,7 @@ module ShogiServer
   class Command
     # Factory method
     #
   class Command
     # Factory method
     #
-    def Command.factory(str, player)
+    def Command.factory(str, player, time=Time.now)
       cmd = nil
       case str 
       when "" 
       cmd = nil
       case str 
       when "" 
@@ -49,6 +49,12 @@ module ShogiServer
       when /^%%MONITOROFF\s+(\S+)/
         game_id = $1
         cmd = MonitorOffCommand.new(str, player, $league.games[game_id])
       when /^%%MONITOROFF\s+(\S+)/
         game_id = $1
         cmd = MonitorOffCommand.new(str, player, $league.games[game_id])
+      when /^%%MONITOR2ON\s+(\S+)/
+        game_id = $1
+        cmd = Monitor2OnCommand.new(str, player, $league.games[game_id])
+      when /^%%MONITOR2OFF\s+(\S+)/
+        game_id = $1
+        cmd = Monitor2OffCommand.new(str, player, $league.games[game_id])
       when /^%%HELP/
         cmd = HelpCommand.new(str, player)
       when /^%%RATING/
       when /^%%HELP/
         cmd = HelpCommand.new(str, player)
       when /^%%RATING/
@@ -90,16 +96,35 @@ module ShogiServer
         cmd = GetBuoyCountCommand.new(str, player, game_name)
       when /^\s*$/
         cmd = SpaceCommand.new(str, player)
         cmd = GetBuoyCountCommand.new(str, player, game_name)
       when /^\s*$/
         cmd = SpaceCommand.new(str, player)
+      when /^%%%[^%]/
+        # TODO: just ignore commands specific to 81Dojo.
+        # Need to discuss with 81Dojo people.
+        cmd = VoidCommand.new(str, player)
       else
         cmd = ErrorCommand.new(str, player)
       end
 
       else
         cmd = ErrorCommand.new(str, player)
       end
 
+      cmd.time = time
       return cmd
     end
 
     def initialize(str, player)
       @str    = str
       @player = player
       return cmd
     end
 
     def initialize(str, player)
       @str    = str
       @player = player
+      @time   = Time.now # this should be replaced later with a real time
+    end
+    attr_accessor :time
+  end
+
+  # Dummy command which does nothing.
+  #
+  class VoidCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      return :continue
     end
   end
 
     end
   end
 
@@ -130,12 +155,17 @@ module ShogiServer
       if (@player.status == "game")
         array_str = @str.split(",")
         move = array_str.shift
       if (@player.status == "game")
         array_str = @str.split(",")
         move = array_str.shift
+        if @player.game.last_move &&
+           @player.game.last_move.split(",").first == move
+          log_warning("Received two sequencial identical moves [#{move}] from #{@player.name}. The last one was ignored.")
+          return :continue
+        end
         additional = array_str.shift
         comment = nil
         if /^'(.*)/ =~ additional
           comment = array_str.unshift("'*#{$1.toeuc}")
         end
         additional = array_str.shift
         comment = nil
         if /^'(.*)/ =~ additional
           comment = array_str.unshift("'*#{$1.toeuc}")
         end
-        s = @player.game.handle_one_move(move, @player)
+        s = @player.game.handle_one_move(move, @player, @time)
         @player.game.log_game(Kconv.toeuc(comment.first)) if (comment && comment.first && !s)
         return :return if (s && @player.protocol == LoginCSA::PROTOCOL)
       end
         @player.game.log_game(Kconv.toeuc(comment.first)) if (comment && comment.first && !s)
         return :return if (s && @player.protocol == LoginCSA::PROTOCOL)
       end
@@ -165,7 +195,7 @@ module ShogiServer
     def in_game_status
       rc = :continue
 
     def in_game_status
       rc = :continue
 
-      s = @player.game.handle_one_move(@str, @player)
+      s = @player.game.handle_one_move(@str, @player, @time)
       rc = :return if (s && @player.protocol == LoginCSA::PROTOCOL)
 
       return rc
       rc = :return if (s && @player.protocol == LoginCSA::PROTOCOL)
 
       return rc
@@ -263,6 +293,53 @@ module ShogiServer
     end
   end
 
     end
   end
 
+  class MonitorHandler
+    def initialize(player)
+      @player = player
+      @type = nil
+      @header = nil
+    end
+    attr_reader :player, :type, :header
+
+    def ==(rhs)
+      return rhs != nil &&
+             rhs.is_a?(MonitorHandler) &&
+             @player == rhs.player &&
+             @type   == rhs.type
+    end
+
+    def write_safe(game_id, str)
+      str.chomp.split("\n").each do |line|
+        @player.write_safe("##[%s][%s] %s\n" % [@header, game_id, line.chomp])
+      end
+      @player.write_safe("##[%s][%s] %s\n" % [@header, game_id, "+OK"])
+    end
+  end
+
+  class MonitorHandler1 < MonitorHandler
+    def initialize(player)
+      super
+      @type = 1
+      @header = "MONITOR"
+    end
+
+    def write_one_move(game_id, game)
+      write_safe(game_id, game.show.chomp)
+    end
+  end
+
+  class MonitorHandler2 < MonitorHandler
+    def initialize(player)
+      super
+      @type = 2
+      @header = "MONITOR2"
+    end
+
+    def write_one_move(game_id, game)
+      write_safe(game_id, game.last_move.gsub(",", "\n"))
+    end
+  end
+
   # Command of MONITORON
   #
   class MonitorOnCommand < BaseCommandForGame
   # Command of MONITORON
   #
   class MonitorOnCommand < BaseCommandForGame
@@ -272,9 +349,9 @@ module ShogiServer
 
     def call
       if (@game)
 
     def call
       if (@game)
-        @game.monitoron(@player)
-        @player.write_safe(@game.show.gsub(/^/, "##[MONITOR][#{@game_id}] "))
-        @player.write_safe("##[MONITOR][#{@game_id}] +OK\n")
+        monitor_handler = MonitorHandler1.new(@player)
+        @game.monitoron(monitor_handler)
+        monitor_handler.write_safe(@game_id, @game.show)
       end
       return :continue
     end
       end
       return :continue
     end
@@ -289,7 +366,38 @@ module ShogiServer
 
     def call
       if (@game)
 
     def call
       if (@game)
-        @game.monitoroff(@player)
+        @game.monitoroff(MonitorHandler1.new(@player))
+      end
+      return :continue
+    end
+  end
+
+  # Command of MONITOR2ON
+  #
+  class Monitor2OnCommand < BaseCommandForGame
+    def initialize(str, player, game)
+      super
+    end
+
+    def call
+      if (@game)
+        monitor_handler = MonitorHandler2.new(@player)
+        @game.monitoron(monitor_handler)
+        lines = IO::readlines(@game.logfile).join("")
+        monitor_handler.write_safe(@game_id, lines)
+      end
+      return :continue
+    end
+  end
+
+  class Monitor2OffCommand < MonitorOffCommand
+    def initialize(str, player, game)
+      super
+    end
+
+    def call
+      if (@game)
+        @game.monitoroff(MonitorHandler2.new(@player))
       end
       return :continue
     end
       end
       return :continue
     end
@@ -382,14 +490,6 @@ module ShogiServer
       end
 
       rival = nil
       end
 
       rival = nil
-      if (Buoy.game_name?(@game_name))
-        if (@my_sente_str != "*")
-          @player.write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", @my_sente_str, @game_name))
-          return :continue
-        end
-        @player.sente = nil
-      end # really end
-
       if (League::Floodgate.game_name?(@game_name))
         if (@my_sente_str != "*")
           @player.write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", @my_sente_str, @game_name))
       if (League::Floodgate.game_name?(@game_name))
         if (@my_sente_str != "*")
           @player.write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", @my_sente_str, @game_name))
@@ -397,56 +497,34 @@ module ShogiServer
         end
         @player.sente = nil
       else
         end
         @player.sente = nil
       else
-        if (@my_sente_str == "*")
-          rival = $league.get_player("game_waiting", @game_name, nil, @player) # no preference
-        elsif (@my_sente_str == "+")
-          rival = $league.get_player("game_waiting", @game_name, false, @player) # rival must be gote
-        elsif (@my_sente_str == "-")
-          rival = $league.get_player("game_waiting", @game_name, true, @player) # rival must be sente
-        else
-          ## never reached
-          @player.write_safe(sprintf("##[ERROR] bad game option\n"))
-          return :continue
+        rival = $league.find_rival(@player, @my_sente_str, @game_name)
+        if rival.instance_of?(Symbol)  
+          # An error happened. rival is not a player instance, but an error
+          # symobl that must be returned to the main routine immediately.
+          return rival
         end
       end
 
       if (rival)
         @player.game_name = @game_name
         end
       end
 
       if (rival)
         @player.game_name = @game_name
-        
-        if ((@my_sente_str == "*") && (rival.sente == nil))
-          if (rand(2) == 0)
-            @player.sente = true
-            rival.sente = false
-          else
-            @player.sente = false
-            rival.sente = true
-          end
-        elsif (rival.sente == true) # rival has higher priority
-          @player.sente = false
-        elsif (rival.sente == false)
-          @player.sente = true
-        elsif (@my_sente_str == "+")
-          @player.sente = true
-          rival.sente = false
-        elsif (@my_sente_str == "-")
-          @player.sente = false
-          rival.sente = true
-        else
-          ## never reached
-        end
+        Game::decide_turns(@player, @my_sente_str, rival)
+
         if (Buoy.game_name?(@game_name))
           buoy = Buoy.new # TODO config
           if buoy.is_new_game?(@game_name)
             # The buoy game is not ready yet.
             # When the game is set, it will be started.
         if (Buoy.game_name?(@game_name))
           buoy = Buoy.new # TODO config
           if buoy.is_new_game?(@game_name)
             # The buoy game is not ready yet.
             # When the game is set, it will be started.
+            @player.status = "game_waiting"
           else
             buoy_game = buoy.get_game(@game_name)
             if buoy_game.instance_of? NilBuoyGame
               # error. never reach
             end
           else
             buoy_game = buoy.get_game(@game_name)
             if buoy_game.instance_of? NilBuoyGame
               # error. never reach
             end
+
+            moves_array = Board::split_moves(buoy_game.moves)
             board = Board.new
             begin
             board = Board.new
             begin
-              board.set_from_moves(buoy_game.moves)
+              board.set_from_moves(moves_array)
             rescue => err
               # it will never happen since moves have already been checked
               log_error "Failed to set up a buoy game: #{moves}"
             rescue => err
               # it will never happen since moves have already been checked
               log_error "Failed to set up a buoy game: #{moves}"
@@ -456,7 +534,8 @@ module ShogiServer
             Game::new(@player.game_name, @player, rival, board)
           end
         else
             Game::new(@player.game_name, @player, rival, board)
           end
         else
-          board = Board.new
+          klass = Login.handicapped_game_name?(@game_name) || Board
+          board = klass.new
           board.initial
           Game::new(@player.game_name, @player, rival, board)
         end
           board.initial
           Game::new(@player.game_name, @player, rival, board)
         end
@@ -464,13 +543,7 @@ module ShogiServer
         if (@command_name == "GAME")
           @player.status = "game_waiting"
           @player.game_name = @game_name
         if (@command_name == "GAME")
           @player.status = "game_waiting"
           @player.game_name = @game_name
-          if (@my_sente_str == "+")
-            @player.sente = true
-          elsif (@my_sente_str == "-")
-            @player.sente = false
-          else
-            @player.sente = nil
-          end
+          @player.set_sente_from_str(@my_sente_str)
         else                # challenge
           @player.write_safe(sprintf("##[ERROR] can't find rival for %s\n", @game_name))
           @player.status = "connected"
         else                # challenge
           @player.write_safe(sprintf("##[ERROR] can't find rival for %s\n", @game_name))
           @player.status = "connected"
@@ -595,12 +668,17 @@ module ShogiServer
   class ErrorCommand < Command
     def initialize(str, player)
       super
   class ErrorCommand < Command
     def initialize(str, player)
       super
+      @msg = nil
     end
     end
+    attr_reader :msg
 
     def call
 
     def call
-      msg = "##[ERROR] unknown command %s\n" % [@str]
-      @player.write_safe(msg)
-      log_error(msg)
+      cmd = @str.chomp
+      # Aim to hide a possible password
+      cmd.gsub!(/LOGIN\s*(\w+)\s+.*/i, 'LOGIN \1...')
+      @msg = "##[ERROR] unknown command %s\n" % [cmd]
+      @player.write_safe(@msg)
+      log_error(@msg)
       return :continue
     end
   end
       return :continue
     end
   end
@@ -608,7 +686,6 @@ module ShogiServer
   #
   #
   class SetBuoyCommand < Command
   #
   #
   class SetBuoyCommand < Command
-    class WrongMoves < ArgumentError; end
 
     def initialize(str, player, game_name, moves, count)
       super(str, player)
 
     def initialize(str, player, game_name, moves, count)
       super(str, player)
@@ -636,8 +713,7 @@ module ShogiServer
       end
 
       # check moves
       end
 
       # check moves
-      moves_array = split_moves @moves
-
+      moves_array = Board::split_moves(@moves)
       board = Board.new
       begin
         board.set_from_moves(moves_array)
       board = Board.new
       begin
         board.set_from_moves(moves_array)
@@ -647,6 +723,8 @@ module ShogiServer
 
       buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count)
       buoy.add_game(buoy_game)
 
       buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count)
       buoy.add_game(buoy_game)
+      @player.write_safe(sprintf("##[SETBUOY] +OK\n"))
+      log_info("A buoy game was created: %s by %s" % [@game_name, @player.name])
 
       # if two players, who are not @player, are waiting for a new game, start it
       p1 = $league.get_player("game_waiting", @game_name, true, @player)
 
       # if two players, who are not @player, are waiting for a new game, start it
       p1 = $league.get_player("game_waiting", @game_name, true, @player)
@@ -662,25 +740,6 @@ module ShogiServer
       log_error "Received wrong moves: %s from %s. [%s]" % [@moves, @player.name, e.message]
       return :continue
     end
       log_error "Received wrong moves: %s from %s. [%s]" % [@moves, @player.name, e.message]
       return :continue
     end
-
-    private
-    
-    # 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 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
   end
 
   #
   end
 
   #
@@ -707,6 +766,7 @@ module ShogiServer
       end
 
       buoy.delete_game(buoy_game)
       end
 
       buoy.delete_game(buoy_game)
+      @player.write_safe(sprintf("##[DELETEBUOY] +OK\n"))
       log_info("A buoy game was deleted: %s" % [@game_name])
       return :continue
     end
       log_info("A buoy game was deleted: %s" % [@game_name])
       return :continue
     end