- Added shogi_server/command.rb: refactored commands out of player.rb, and assign a dedicated class for each command
+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]
--- /dev/null
+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
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
## 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
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
require 'TC_board'
require 'TC_before_agree'
+require 'TC_command'
require 'TC_floodgate'
require 'TC_floodgate_history'
require 'TC_functional'
--- /dev/null
+$:.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
+
+