OSDN Git Service

* [shogi-server]
authorbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Wed, 29 Jul 2009 13:48:30 +0000 (13:48 +0000)
committerbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Wed, 29 Jul 2009 13:48:30 +0000 (13:48 +0000)
  - Added shogi_server/command.rb: refactored commands out of player.rb, and assign a dedicated class for each command

changelog
shogi_server/command.rb [new file with mode: 0644]
shogi_server/game.rb
shogi_server/player.rb
test/TC_ALL.rb
test/TC_command.rb [new file with mode: 0644]

index 9e917dd..7c5a250 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,3 +1,8 @@
+2009-07-11 Daigo Moriwaki <daigo at debian dot org>
+
+       * [shogi-server]
+         - shogi_server/command.rb: refactored commands out of player.rb.
+
 2009-06-18 Daigo Moriwaki <daigo at debian dot org>
 
        * [shogi-server]
diff --git a/shogi_server/command.rb b/shogi_server/command.rb
new file mode 100644 (file)
index 0000000..6334c28
--- /dev/null
@@ -0,0 +1,542 @@
+require 'kconv'
+require 'shogi_server'
+
+module ShogiServer
+
+  class Command
+    # Factory method
+    #
+    def Command.factory(str, player)
+      cmd = nil
+      case str 
+      when "" 
+        cmd = KeepAliveCommand.new(str, player)
+      when /^[\+\-][^%]/
+        cmd = MoveCommand.new(str, player)
+      when /^%[^%]/, :timeout
+        cmd = SpecialCommand.new(str, player)
+      when :exception
+        cmd = ExceptionCommand.new(str, player)
+      when /^REJECT/
+        cmd = RejectCommand.new(str, player)
+      when /^AGREE/
+        cmd = AgreeCommand.new(str, player)
+      when /^%%SHOW\s+(\S+)/
+        game_id = $1
+        cmd = ShowCommand.new(str, player, $league.games[game_id])
+      when /^%%MONITORON\s+(\S+)/
+        game_id = $1
+        cmd = MonitorOnCommand.new(str, player, $league.games[game_id])
+      when /^%%MONITOROFF\s+(\S+)/
+        game_id = $1
+        cmd = MonitorOffCommand.new(str, player, $league.games[game_id])
+      when /^%%HELP/
+        cmd = HelpCommand.new(str, player)
+      when /^%%RATING/
+        cmd = RatingCommand.new(str, player, $league.rated_players)
+      when /^%%VERSION/
+        cmd = VersionCommand.new(str, player)
+      when /^%%GAME\s*$/
+        cmd = GameCommand.new(str, player)
+      when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
+        command_name = $1
+        game_name = $2
+        my_sente_str = $3
+        cmd = GameChallengeCommand.new(str, player, 
+                                       command_name, game_name, my_sente_str)
+      when /^%%CHAT\s+(.+)/
+        message = $1
+        cmd = ChatCommand.new(str, player, message, $league.players)
+      when /^%%LIST/
+        cmd = ListCommand.new(str, player, $league.games)
+      when /^%%WHO/
+        cmd = WhoCommand.new(str, player, $league.players)
+      when /^LOGOUT/
+        cmd = LogoutCommand.new(str, player)
+      when /^CHALLENGE/
+        cmd = ChallengeCommand.new(str, player)
+      when /^\s*$/
+        cmd = SpaceCommand.new(str, player)
+      else
+        cmd = ErrorCommand.new(str, player)
+      end
+
+      return cmd
+    end
+
+    def initialize(str, player)
+      @str    = str
+      @player = player
+    end
+  end
+
+  # Application-level protocol for Keep-Alive.
+  # If the server receives an LF, it sends back an LF.  Note that the 30 sec
+  # rule (client may not send LF again within 30 sec) is not implemented
+  # yet.
+  #
+  class KeepAliveCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      @player.write_safe("\n")
+      return :continue
+    end
+  end
+
+  # Command of moving a piece.
+  #
+  class MoveCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      if (@player.status == "game")
+        array_str = @str.split(",")
+        move = array_str.shift
+        additional = array_str.shift
+        comment = nil
+        if /^'(.*)/ =~ additional
+          comment = array_str.unshift("'*#{$1.toeuc}")
+        end
+        s = @player.game.handle_one_move(move, @player)
+        @player.game.log_game(Kconv.toeuc(comment.first)) if (comment && comment.first && !s)
+        return :return if (s && @player.protocol == LoginCSA::PROTOCOL)
+      end
+      return :continue
+    end
+  end
+
+  # Command like "%TORYO" or :timeout
+  #
+  class SpecialCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      rc = :continue
+      if (@player.status == "game")
+        rc = in_game_status()
+      elsif ["agree_waiting", "start_waiting"].include?(@player.status) 
+        rc = in_waiting_status()
+      else
+        log_error("Received a command [#{@str}] from #{@player.name} in an inappropriate status [#{@player.status}].") unless @str == :timeout
+      end
+      return rc
+    end
+
+    def in_game_status
+      rc = :continue
+
+      s = @player.game.handle_one_move(@str, @player)
+      rc = :return if (s && @player.protocol == LoginCSA::PROTOCOL)
+
+      return rc
+    end
+
+    def in_waiting_status
+      rc = :continue
+
+      if @player.game.prepared_expire?
+        log_warning("#{@player.status} lasted too long. This play has been expired.")
+        @player.game.reject("the Server (timed out)")
+        rc = :return if (@player.protocol == LoginCSA::PROTOCOL)
+      end
+
+      return rc
+    end
+  end
+
+  # Command of :exception
+  #
+  class ExceptionCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      log_error("Failed to receive a message from #{@player.name}.")
+      return :return
+    end
+  end
+
+  # Command of REJECT
+  #
+  class RejectCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      if (@player.status == "agree_waiting")
+        @player.game.reject(@player.name)
+        return :return if (@player.protocol == LoginCSA::PROTOCOL)
+      else
+        log_error("Received a command [#{@str}] from #{@player.name} in an inappropriate status [#{@player.status}].")
+        @player.write_safe(sprintf("##[ERROR] you are in %s status. REJECT is valid in agree_waiting status\n", @player.status))
+      end
+      return :continue
+    end
+  end
+
+  # Command of AGREE
+  #
+  class AgreeCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      if (@player.status == "agree_waiting")
+        @player.status = "start_waiting"
+        if (@player.game.is_startable_status?)
+          @player.game.start
+        end
+      else
+        log_error("Received a command [#{@str}] from #{@player.name} in an inappropriate status [#{@player.status}].")
+        @player.write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @player.status))
+      end
+      return :continue
+    end
+  end
+
+  # Base Command calss requiring a game instance
+  #
+  class BaseCommandForGame < Command
+    def initialize(str, player, game)
+      super(str, player)
+      @game    = game
+      @game_id = game ? game.game_id : nil
+    end
+  end
+
+  # Command of SHOW
+  #
+  class ShowCommand < BaseCommandForGame
+    def initialize(str, player, game)
+      super
+    end
+
+    def call
+      if (@game)
+        @player.write_safe(@game.show.gsub(/^/, '##[SHOW] '))
+      end
+      @player.write_safe("##[SHOW] +OK\n")
+      return :continue
+    end
+  end
+
+  # Command of MONITORON
+  #
+  class MonitorOnCommand < BaseCommandForGame
+    def initialize(str, player, game)
+      super
+    end
+
+    def call
+      if (@game)
+        @game.monitoron(@player)
+        @player.write_safe(@game.show.gsub(/^/, "##[MONITOR][#{@game_id}] "))
+        @player.write_safe("##[MONITOR][#{@game_id}] +OK\n")
+      end
+      return :continue
+    end
+  end
+
+  # Command of MONITOROFF
+  #
+  class MonitorOffCommand < BaseCommandForGame
+    def initialize(str, player, game)
+      super
+    end
+
+    def call
+      if (@game)
+        @game.monitoroff(@player)
+      end
+      return :continue
+    end
+  end
+
+  # Command of HELP
+  #
+  class HelpCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      @player.write_safe(
+        %!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!)
+      return :continue
+    end
+  end
+
+  # Command of RATING
+  #
+  class RatingCommand < Command
+    def initialize(str, player, rated_players)
+      super(str, player)
+      @rated_players = rated_players
+    end
+
+    def call
+      @rated_players.sort {|a,b| b.rate <=> a.rate}.each do |p|
+        @player.write_safe("##[RATING] %s \t %4d @%s\n" % 
+                   [p.simple_player_id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
+      end
+      @player.write_safe("##[RATING] +OK\n")
+      return :continue
+    end
+  end
+
+  # Command of VERSION
+  #
+  class VersionCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      @player.write_safe "##[VERSION] Shogi Server revision #{ShogiServer::Revision}\n"
+      @player.write_safe("##[VERSION] +OK\n")
+      return :continue
+    end
+  end
+
+  # Command of GAME
+  #
+  class GameCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      if ((@player.status == "connected") || (@player.status == "game_waiting"))
+        @player.status = "connected"
+        @player.game_name = ""
+      else
+        @player.write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @player.status))
+      end
+      return :continue
+    end
+  end
+
+  # Commando of game challenge
+  # TODO make a test case
+  #
+  class GameChallengeCommand < Command
+    def initialize(str, player, command_name, game_name, my_sente_str)
+      super(str, player)
+      @command_name = command_name
+      @game_name    = game_name
+      @my_sente_str = my_sente_str
+    end
+
+    def call
+      if (! Login::good_game_name?(@game_name))
+        @player.write_safe(sprintf("##[ERROR] bad game name\n"))
+        return :continue
+      elsif ((@player.status == "connected") || (@player.status == "game_waiting"))
+        ## continue
+      else
+        @player.write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @player.status))
+        return :continue
+      end
+
+      rival = nil
+      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))
+          return :continue
+        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
+        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::new(@player.game_name, @player, rival)
+      else # rival not found
+        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
+        else                # challenge
+          @player.write_safe(sprintf("##[ERROR] can't find rival for %s\n", @game_name))
+          @player.status = "connected"
+          @player.game_name = ""
+          @player.sente = nil
+        end
+      end
+      return :continue
+    end
+  end
+
+  # Command of CHAT
+  #
+  class ChatCommand < Command
+
+    # players array of [name, player]
+    #
+    def initialize(str, player, message, players)
+      super(str, player)
+      @message = message
+      @players = players
+    end
+
+    def call
+      @players.each do |name, p| # TODO player change name
+        if (p.protocol != LoginCSA::PROTOCOL)
+          p.write_safe(sprintf("##[CHAT][%s] %s\n", @player.name, @message)) 
+        end
+      end
+      return :continue
+    end
+  end
+
+  # Command of LIST
+  #
+  class ListCommand < Command
+
+    # games array of [game_id, game]
+    #
+    def initialize(str, player, games)
+      super(str, player)
+      @games = games
+    end
+
+    def call
+      buf = Array::new
+      @games.each do |id, game|
+        buf.push(sprintf("##[LIST] %s\n", id))
+      end
+      buf.push("##[LIST] +OK\n")
+      @player.write_safe(buf.join)
+      return :continue
+    end
+  end
+
+  # Command of WHO
+  #
+  class WhoCommand < Command
+
+    # players array of [[name, player]]
+    #
+    def initialize(str, player, players)
+      super(str, player)
+      @players = players
+    end
+
+    def call
+      buf = Array::new
+      @players.each do |name, p|
+        buf.push(sprintf("##[WHO] %s\n", p.to_s))
+      end
+      buf.push("##[WHO] +OK\n")
+      @player.write_safe(buf.join)
+      return :continue
+    end
+  end
+
+  # Command of LOGOUT
+  #
+  class LogoutCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      @player.status = "connected"
+      @player.write_safe("LOGOUT:completed\n")
+      return :return
+    end
+  end
+
+  # Command of CHALLENGE
+  #
+  class ChallengeCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      # This command is only available for CSA's official testing server.
+      # So, this means nothing for this program.
+      @player.write_safe("CHALLENGE ACCEPTED\n")
+      return :continue
+    end
+  end
+
+  # Command for a space
+  #
+  class SpaceCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      ## ignore null string
+      return :continue
+    end
+  end
+
+  # Command for an error
+  #
+  class ErrorCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      msg = "##[ERROR] unknown command %s\n" % [@str]
+      @player.write_safe(msg)
+      log_error(msg)
+      return :continue
+    end
+  end
+
+
+end # module ShogiServer
index 0134ddd..02853e1 100644 (file)
@@ -483,8 +483,16 @@ class Game
     return finish_flag
   end
 
+  def is_startable_status?
+    return (@sente && @gote &&
+            (@sente.status == "start_waiting") &&
+            (@gote.status  == "start_waiting"))
+  end
+
   def start
     log_message(sprintf("game started %s", @game_id))
+    @sente.status = "game"
+    @gote.status  = "game"
     @sente.write_safe(sprintf("START:%s\n", @game_id))
     @gote.write_safe(sprintf("START:%s\n", @game_id))
     @sente.mytime = @total_time
index 9257874..f893c6f 100644 (file)
@@ -17,6 +17,8 @@
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+require 'shogi_server/command'
+
 module ShogiServer # for a namespace
 
 class BasicPlayer
@@ -265,209 +267,17 @@ class Player < BasicPlayer
           return
         end
         str.chomp! if (str.class == String) # may be strip! ?
-        case str 
-        when "" 
-          # Application-level protocol for Keep-Alive
-          # If the server gets LF, it sends back LF.
-          # 30 sec rule (client may not send LF again within 30 sec) is not implemented yet.
-          write_safe("\n")
-        when /^[\+\-][^%]/
-          if (@status == "game")
-            array_str = str.split(",")
-            move = array_str.shift
-            additional = array_str.shift
-            comment = nil
-            if /^'(.*)/ =~ additional
-              comment = array_str.unshift("'*#{$1.toeuc}")
-            end
-            s = @game.handle_one_move(move, self)
-            @game.fh.print("#{Kconv.toeuc(comment.first)}\n") if (comment && comment.first && !s)
-            return if (s && @protocol == LoginCSA::PROTOCOL)
-          end
-        when /^%[^%]/, :timeout
-          if (@status == "game")
-            s = @game.handle_one_move(str, self)
-            return if (s && @protocol == LoginCSA::PROTOCOL)
-          elsif ["agree_waiting", "start_waiting"].include?(@status) 
-            if @game.prepared_expire?
-              log_warning("#{@status} lasted too long. This play has been expired.")
-              @game.reject("the Server (timed out)")
-              return if (@protocol == LoginCSA::PROTOCOL)
-            end
-          end
-        when :exception
-          log_error("Failed to receive a message from #{@name}.")
-          return
-        when /^REJECT/
-          if (@status == "agree_waiting")
-            @game.reject(@name)
-            return if (@protocol == LoginCSA::PROTOCOL)
-          else
-            write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
-          end
-        when /^AGREE/
-          if (@status == "agree_waiting")
-            @status = "start_waiting"
-            if ((@game.sente.status == "start_waiting") &&
-                (@game.gote.status == "start_waiting"))
-              @game.start
-              @game.sente.status = "game"
-              @game.gote.status = "game"
-            end
-          else
-            write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
-          end
-        when /^%%SHOW\s+(\S+)/
-          game_id = $1
-          if ($league.games[game_id])
-            write_safe($league.games[game_id].show.gsub(/^/, '##[SHOW] '))
-          end
-          write_safe("##[SHOW] +OK\n")
-        when /^%%MONITORON\s+(\S+)/
-          game_id = $1
-          if ($league.games[game_id])
-            $league.games[game_id].monitoron(self)
-            write_safe($league.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
-            write_safe("##[MONITOR][#{game_id}] +OK\n")
-          end
-        when /^%%MONITOROFF\s+(\S+)/
-          game_id = $1
-          if ($league.games[game_id])
-            $league.games[game_id].monitoroff(self)
-          end
-        when /^%%HELP/
-          write_safe(
-            %!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!)
-        when /^%%RATING/
-          players = $league.rated_players
-          players.sort {|a,b| b.rate <=> a.rate}.each do |p|
-            write_safe("##[RATING] %s \t %4d @%s\n" % 
-                       [p.simple_player_id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
-          end
-          write_safe("##[RATING] +OK\n")
-        when /^%%VERSION/
-          write_safe "##[VERSION] Shogi Server revision #{Revision}\n"
-          write_safe("##[VERSION] +OK\n")
-        when /^%%GAME\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
-          if (! Login::good_game_name?(game_name))
-            write_safe(sprintf("##[ERROR] bad game name\n"))
-            next
-          elsif ((@status == "connected") || (@status == "game_waiting"))
-            ## continue
-          else
-            write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
-            next
-          end
 
-          rival = nil
-          if (League::Floodgate.game_name?(game_name))
-            if (my_sente_str != "*")
-              write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", my_sente_str, game_name))
-              next
-            end
-            @sente = nil
-          else
-            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
-            elsif (my_sente_str == "-")
-              rival = $league.get_player("game_waiting", game_name, true, self) # rival must be sente
-            else
-              ## never reached
-              write_safe(sprintf("##[ERROR] bad game option\n"))
-              next
-            end
-          end
-
-          if (rival)
-            @game_name = game_name
-            if ((my_sente_str == "*") && (rival.sente == nil))
-              if (rand(2) == 0)
-                @sente = true
-                rival.sente = false
-              else
-                @sente = false
-                rival.sente = true
-              end
-            elsif (rival.sente == true) # rival has higher priority
-              @sente = false
-            elsif (rival.sente == false)
-              @sente = true
-            elsif (my_sente_str == "+")
-              @sente = true
-              rival.sente = false
-            elsif (my_sente_str == "-")
-              @sente = false
-              rival.sente = true
-            else
-              ## never reached
-            end
-            Game::new(@game_name, self, rival)
-          else # rival not found
-            if (command_name == "GAME")
-              @status = "game_waiting"
-              @game_name = game_name
-              if (my_sente_str == "+")
-                @sente = true
-              elsif (my_sente_str == "-")
-                @sente = false
-              else
-                @sente = nil
-              end
-            else                # challenge
-              write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
-              @status = "connected"
-              @game_name = ""
-              @sente = nil
-            end
-          end
-        when /^%%CHAT\s+(.+)/
-          message = $1
-          $league.players.each do |name, player|
-            if (player.protocol != LoginCSA::PROTOCOL)
-              player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
-            end
-          end
-        when /^%%LIST/
-          buf = Array::new
-          $league.games.each do |id, game|
-            buf.push(sprintf("##[LIST] %s\n", id))
-          end
-          buf.push("##[LIST] +OK\n")
-          write_safe(buf.join)
-        when /^%%WHO/
-          buf = Array::new
-          $league.players.each do |name, player|
-            buf.push(sprintf("##[WHO] %s\n", player.to_s))
-          end
-          buf.push("##[WHO] +OK\n")
-          write_safe(buf.join)
-        when /^LOGOUT/
-          @status = "connected"
-          write_safe("LOGOUT:completed\n")
+        cmd = ShogiServer::Command.factory(str, self)
+        case cmd.call
+        when :return
           return
-        when /^CHALLENGE/
-          # This command is only available for CSA's official testing server.
-          # So, this means nothing for this program.
-          write_safe("CHALLENGE ACCEPTED\n")
-        when /^\s*$/
-          ## ignore null string
+        when :continue
+          # do nothing
         else
-          msg = "##[ERROR] unknown command %s\n" % [str]
-          write_safe(msg)
-          log_error(msg)
+          # TODO never reach
         end
+
       ensure
         $mutex.unlock
       end
index d0b88f9..245c3a3 100644 (file)
@@ -2,6 +2,7 @@ $:.unshift File.dirname(__FILE__)
 
 require 'TC_board'
 require 'TC_before_agree'
+require 'TC_command'
 require 'TC_floodgate'
 require 'TC_floodgate_history'
 require 'TC_functional'
diff --git a/test/TC_command.rb b/test/TC_command.rb
new file mode 100644 (file)
index 0000000..7c23e1b
--- /dev/null
@@ -0,0 +1,709 @@
+$:.unshift File.join(File.dirname(__FILE__), "..")
+require 'test/unit'
+require 'shogi_server/login'
+require 'shogi_server/player'
+require 'shogi_server/command'
+
+def log_warning(str)
+  $stderr.puts str
+end
+
+def log_error(str)
+  $stderr.puts str
+end
+
+class MockPlayer < ShogiServer::BasicPlayer
+  attr_reader :out
+  attr_accessor :game, :status, :protocol
+  attr_accessor :game_name
+
+  def initialize
+    @out      = []
+    @game     = nil
+    @status   = nil
+    @protocol = nil
+    @game_name = "dummy_game_name"
+  end
+
+  def write_safe(str)
+    @out << str
+  end
+end
+
+class MockGame
+  attr_accessor :finish_flag
+  attr_reader :log
+  attr_accessor :prepared_expire
+  attr_accessor :rejected
+  attr_accessor :is_startable_status
+  attr_accessor :started
+  attr_accessor :game_id
+
+  def initialize
+    @finish_flag     = false
+    @log             = []
+    @prepared_expire = false
+    @rejected        = false
+    @is_startable_status = false
+    @started             = false
+    @game_id         = "dummy_game_id"
+    @monitoron_called = false
+    @monitoroff_called = false
+  end
+
+  def handle_one_move(move, player)
+    return @finish_flag
+  end
+
+  def log_game(str)
+    @log << str
+  end
+
+  def prepared_expire?
+    return @prepared_expire
+  end
+
+  def reject(str)
+    @rejected = true
+  end
+
+  def is_startable_status?
+    return @is_startable_status
+  end
+
+  def start
+    @started = true
+  end
+
+  def show
+    return "dummy_game_show"
+  end
+
+  def monitoron(player)
+    @monitoron_called = true
+  end
+
+  def monitoroff(player)
+    @monitoroff_called = true
+  end
+end
+
+class MockLeague
+  def initialize
+    @games = {}
+    @games["dummy_game_id"] = MockGame.new
+  end
+
+  def games
+    return @games
+  end
+
+  def rated_players
+    return []
+  end
+
+  def players
+    return [MockPlayer.new]
+  end
+end
+
+
+class TestFactoryMethod < Test::Unit::TestCase 
+
+  def setup
+    @p = MockPlayer.new
+    $league = MockLeague.new
+  end
+
+  def test_keep_alive_command
+    cmd = ShogiServer::Command.factory("", @p)
+    assert_instance_of(ShogiServer::KeepAliveCommand, cmd)
+  end
+
+  def test_move_command
+    cmd = ShogiServer::Command.factory("+7776FU", @p)
+    assert_instance_of(ShogiServer::MoveCommand, cmd)
+  end
+
+  def test_special_command
+    cmd = ShogiServer::Command.factory("%TORYO", @p)
+    assert_instance_of(ShogiServer::SpecialCommand, cmd)
+  end
+
+  def test_special_command_timeout
+    cmd = ShogiServer::Command.factory(:timeout, @p)
+    assert_instance_of(ShogiServer::SpecialCommand, cmd)
+  end
+
+  def test_execption_command
+    cmd = ShogiServer::Command.factory(:exception, @p)
+    assert_instance_of(ShogiServer::ExceptionCommand, cmd)
+  end
+
+  def test_reject_command
+    cmd = ShogiServer::Command.factory("REJECT", @p)
+    assert_instance_of(ShogiServer::RejectCommand, cmd)
+  end
+
+  def test_agree_command
+    cmd = ShogiServer::Command.factory("AGREE", @p)
+    assert_instance_of(ShogiServer::AgreeCommand, cmd)
+  end
+
+  def test_show_command
+    cmd = ShogiServer::Command.factory("%%SHOW game_id", @p)
+    assert_instance_of(ShogiServer::ShowCommand, cmd)
+  end
+
+  def test_monitoron_command
+    cmd = ShogiServer::Command.factory("%%MONITORON game_id", @p)
+    assert_instance_of(ShogiServer::MonitorOnCommand, cmd)
+  end
+
+  def test_monitoroff_command
+    cmd = ShogiServer::Command.factory("%%MONITOROFF game_id", @p)
+    assert_instance_of(ShogiServer::MonitorOffCommand, cmd)
+  end
+
+  def test_help_command
+    cmd = ShogiServer::Command.factory("%%HELP", @p)
+    assert_instance_of(ShogiServer::HelpCommand, cmd)
+  end
+
+  def test_rating_command
+    cmd = ShogiServer::Command.factory("%%RATING", @p)
+    assert_instance_of(ShogiServer::RatingCommand, cmd)
+  end
+
+  def test_version_command
+    cmd = ShogiServer::Command.factory("%%VERSION", @p)
+    assert_instance_of(ShogiServer::VersionCommand, cmd)
+  end
+
+  def test_game_command
+    cmd = ShogiServer::Command.factory("%%GAME", @p)
+    assert_instance_of(ShogiServer::GameCommand, cmd)
+  end
+
+  def test_game_challenge_command_game
+    cmd = ShogiServer::Command.factory("%%GAME default-1500-0 +", @p)
+    assert_instance_of(ShogiServer::GameChallengeCommand, cmd)
+  end
+
+  def test_game_challenge_command_challenge
+    cmd = ShogiServer::Command.factory("%%CHALLENGE default-1500-0 -", @p)
+    assert_instance_of(ShogiServer::GameChallengeCommand, cmd)
+  end
+
+  def test_chat_command
+    cmd = ShogiServer::Command.factory("%%CHAT hello", @p)
+    assert_instance_of(ShogiServer::ChatCommand, cmd)
+  end
+
+  def test_list_command
+    cmd = ShogiServer::Command.factory("%%LIST", @p)
+    assert_instance_of(ShogiServer::ListCommand, cmd)
+  end
+
+  def test_who_command
+    cmd = ShogiServer::Command.factory("%%WHO", @p)
+    assert_instance_of(ShogiServer::WhoCommand, cmd)
+  end
+
+  def test_logout_command
+    cmd = ShogiServer::Command.factory("LOGOUT", @p)
+    assert_instance_of(ShogiServer::LogoutCommand, cmd)
+  end
+
+  def test_challenge_command
+    cmd = ShogiServer::Command.factory("CHALLENGE", @p)
+    assert_instance_of(ShogiServer::ChallengeCommand, cmd)
+  end
+
+  def test_space_command
+    cmd = ShogiServer::Command.factory(" ", @p)
+    assert_instance_of(ShogiServer::SpaceCommand, cmd)
+  end
+
+  def test_error
+    cmd = ShogiServer::Command.factory("should_be_error", @p)
+    assert_instance_of(ShogiServer::ErrorCommand, cmd)
+  end
+end
+
+#
+#
+class TestKeepAliveCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+  end
+
+  def test_call
+    cmd = ShogiServer::KeepAliveCommand.new("", @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestMoveCommand < Test::Unit::TestCase
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+    @p.status = "game"
+  end
+
+  def test_call
+    cmd = ShogiServer::MoveCommand.new("+7776FU", @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+  end
+
+  def test_comment
+    cmd = ShogiServer::MoveCommand.new("+7776FU,'comment", @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+    assert_equal("'*comment", @game.log.first)
+  end
+
+  def test_x1_return
+    @game.finish_flag = true
+    @p.protocol = ShogiServer::LoginCSA::PROTOCOL
+    cmd = ShogiServer::MoveCommand.new("+7776FU", @p)
+    rc = cmd.call
+    assert_equal(:return, rc)
+  end
+end
+
+#
+#
+class TestSpecialComand < Test::Unit::TestCase
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+    @p.status = "game"
+  end
+
+  def test_toryo
+    @game.finish_flag = true
+    cmd = ShogiServer::SpecialCommand.new("%TORYO", @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+  end
+
+  def test_toryo_csa_protocol
+    @game.finish_flag = true
+    @p.protocol = ShogiServer::LoginCSA::PROTOCOL
+    cmd = ShogiServer::SpecialCommand.new("%TORYO", @p)
+    rc = cmd.call
+    assert_equal(:return, rc)
+  end
+
+  def test_timeout
+    cmd = ShogiServer::SpecialCommand.new(:timeout, @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+  end
+
+  def test_expired_game
+    @p.status = "agree_waiting"
+    @game.prepared_expire = true
+    assert(!@game.rejected)
+    cmd = ShogiServer::SpecialCommand.new(:timeout, @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+    assert(@game.rejected)
+  end
+
+  def test_expired_game_csa_protocol
+    @p.protocol = ShogiServer::LoginCSA::PROTOCOL
+    @p.status = "agree_waiting"
+    @game.prepared_expire = true
+    assert(!@game.rejected)
+    cmd = ShogiServer::SpecialCommand.new(:timeout, @p)
+    rc = cmd.call
+    assert_equal(:return, rc)
+    assert(@game.rejected)
+  end
+
+  def test_error
+    @p.status = "should_be_ignored"
+    cmd = ShogiServer::SpecialCommand.new(:timeout, @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestExceptionCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+  end
+
+  def test_call
+    cmd = ShogiServer::ExceptionCommand.new(:exception, @p)
+    rc = cmd.call
+    assert_equal(:return, rc)
+  end
+end
+
+#
+#
+class TestRejectCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+    @p.status = "game"
+  end
+
+  def test_call
+    @p.status = "agree_waiting"
+    assert(!@game.rejected)
+    cmd = ShogiServer::RejectCommand.new("REJECT", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+    assert(@game.rejected)
+  end
+
+  def test_call_csa_protocol
+    @p.protocol = ShogiServer::LoginCSA::PROTOCOL
+    @p.status = "agree_waiting"
+    assert(!@game.rejected)
+    cmd = ShogiServer::RejectCommand.new("REJECT", @p)
+    rc = cmd.call
+
+    assert_equal(:return, rc)
+    assert(@game.rejected)
+  end
+
+  def test_error
+    @p.status = "should_be_ignored"
+    cmd = ShogiServer::RejectCommand.new("REJECT", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+    assert(!@game.rejected)
+  end
+end
+
+#
+#
+class TestAgreeCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+    @p.status = "agree_waiting"
+  end
+
+  def test_not_start_yet
+    cmd = ShogiServer::AgreeCommand.new("AGREE", @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+    assert(!@game.started)
+  end
+
+  def test_start
+    @game.is_startable_status = true
+    cmd = ShogiServer::AgreeCommand.new("AGREE", @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+    assert(@game.started)
+  end
+
+  def test_error
+    @p.status = "should_be_ignored"
+    cmd = ShogiServer::AgreeCommand.new("AGREE", @p)
+    rc = cmd.call
+    assert_equal(:continue, rc)
+    assert(!@game.started)
+  end
+end
+
+#
+#
+class TestShowCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    cmd = ShogiServer::ShowCommand.new("%%SHOW hoge", @p, @game)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+
+  def test_call_nil_game
+    cmd = ShogiServer::ShowCommand.new("%%SHOW hoge", @p, nil)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestMonitorOnCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    cmd = ShogiServer::MonitorOnCommand.new("%%MONITORON hoge", @p, nil)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestMonitorOffCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    cmd = ShogiServer::MonitorOffCommand.new("%%MONITOROFF hoge", @p, nil)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestHelpCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    cmd = ShogiServer::HelpCommand.new("%%HELP", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestRatingCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    players = [MockPlayer.new]
+    cmd = ShogiServer::RatingCommand.new("%%RATING", @p, players)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestVersionCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    cmd = ShogiServer::VersionCommand.new("%%VERSION", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestGameCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call_connected
+    @p.status = "connected"
+    cmd = ShogiServer::GameCommand.new("%%GAME", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+    assert_equal("connected", @p.status)
+  end
+
+  def test_call_game_waiting
+    @p.status = "game_waiting"
+    cmd = ShogiServer::GameCommand.new("%%GAME", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+    assert_equal("connected", @p.status)
+  end
+
+  def test_call_agree_waiting
+    @p.status = "agree_waiting"
+    cmd = ShogiServer::GameCommand.new("%%GAME", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+    assert_equal("agree_waiting", @p.status)
+  end
+end
+
+#
+#
+class TestChatCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    players = [["dummy_name", MockPlayer.new]]
+    cmd = ShogiServer::ChatCommand.new("%%CHAT hoge", @p, "dummy message", players)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+
+  def test_call_csa_protocol
+    players = [["dummy_name", MockPlayer.new]]
+    players.each do |name, p|
+      p.protocol = ShogiServer::LoginCSA::PROTOCOL
+    end
+    cmd = ShogiServer::ChatCommand.new("%%CHAT hoge", @p, "dummy message", players)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestListCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    games = [["dummy_game_id", MockGame.new]]
+    cmd = ShogiServer::ListCommand.new("%%LIST", @p, games)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+
+end
+
+#
+#
+class TestWhoCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    players = [["dummy_name", MockPlayer.new]]
+    cmd = ShogiServer::WhoCommand.new("%%LIST", @p, players)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+
+end
+
+#
+#
+class TestLogoutCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+    @p.game = @game
+  end
+
+  def test_call
+    cmd = ShogiServer::LogoutCommand.new("LOGOUT", @p)
+    rc = cmd.call
+
+    assert_equal(:return, rc)
+  end
+
+end
+
+#
+#
+class TestChallengeCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+  end
+
+  def test_call
+    cmd = ShogiServer::ChallengeCommand.new("CHALLENGE", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestSpaceCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+  end
+
+  def test_call
+    cmd = ShogiServer::SpaceCommand.new("", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+#
+#
+class TestErrorCommand < Test::Unit::TestCase 
+  def setup
+    @p = MockPlayer.new
+    @game = MockGame.new
+  end
+
+  def test_call
+    cmd = ShogiServer::ErrorCommand.new("", @p)
+    rc = cmd.call
+
+    assert_equal(:continue, rc)
+  end
+end
+
+