9 def Command.factory(str, player)
13 cmd = KeepAliveCommand.new(str, player)
15 cmd = MoveCommand.new(str, player)
16 when /^%[^%]/, :timeout
17 cmd = SpecialCommand.new(str, player)
19 cmd = ExceptionCommand.new(str, player)
21 cmd = RejectCommand.new(str, player)
23 cmd = AgreeCommand.new(str, player)
24 when /^%%SHOW\s+(\S+)/
26 cmd = ShowCommand.new(str, player, $league.games[game_id])
27 when /^%%MONITORON\s+(\S+)/
29 cmd = MonitorOnCommand.new(str, player, $league.games[game_id])
30 when /^%%MONITOROFF\s+(\S+)/
32 cmd = MonitorOffCommand.new(str, player, $league.games[game_id])
34 cmd = HelpCommand.new(str, player)
36 cmd = RatingCommand.new(str, player, $league.rated_players)
38 cmd = VersionCommand.new(str, player)
40 cmd = GameCommand.new(str, player)
41 when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
45 cmd = GameChallengeCommand.new(str, player,
46 command_name, game_name, my_sente_str)
49 cmd = ChatCommand.new(str, player, message, $league.players)
51 cmd = ListCommand.new(str, player, $league.games)
53 cmd = WhoCommand.new(str, player, $league.players)
55 cmd = LogoutCommand.new(str, player)
57 cmd = ChallengeCommand.new(str, player)
59 cmd = SpaceCommand.new(str, player)
61 cmd = ErrorCommand.new(str, player)
67 def initialize(str, player)
73 # Application-level protocol for Keep-Alive.
74 # If the server receives an LF, it sends back an LF. Note that the 30 sec
75 # rule (client may not send LF again within 30 sec) is not implemented
78 class KeepAliveCommand < Command
79 def initialize(str, player)
84 @player.write_safe("\n")
89 # Command of moving a piece.
91 class MoveCommand < Command
92 def initialize(str, player)
97 if (@player.status == "game")
98 array_str = @str.split(",")
99 move = array_str.shift
100 additional = array_str.shift
102 if /^'(.*)/ =~ additional
103 comment = array_str.unshift("'*#{$1.toeuc}")
105 s = @player.game.handle_one_move(move, @player)
106 @player.game.log_game(Kconv.toeuc(comment.first)) if (comment && comment.first && !s)
107 return :return if (s && @player.protocol == LoginCSA::PROTOCOL)
113 # Command like "%TORYO" or :timeout
115 class SpecialCommand < Command
116 def initialize(str, player)
122 if (@player.status == "game")
123 rc = in_game_status()
124 elsif ["agree_waiting", "start_waiting"].include?(@player.status)
125 rc = in_waiting_status()
127 log_error("Received a command [#{@str}] from #{@player.name} in an inappropriate status [#{@player.status}].") unless @str == :timeout
135 s = @player.game.handle_one_move(@str, @player)
136 rc = :return if (s && @player.protocol == LoginCSA::PROTOCOL)
141 def in_waiting_status
144 if @player.game.prepared_expire?
145 log_warning("#{@player.status} lasted too long. This play has been expired.")
146 @player.game.reject("the Server (timed out)")
147 rc = :return if (@player.protocol == LoginCSA::PROTOCOL)
154 # Command of :exception
156 class ExceptionCommand < Command
157 def initialize(str, player)
162 log_error("Failed to receive a message from #{@player.name}.")
169 class RejectCommand < Command
170 def initialize(str, player)
175 if (@player.status == "agree_waiting")
176 @player.game.reject(@player.name)
177 return :return if (@player.protocol == LoginCSA::PROTOCOL)
179 log_error("Received a command [#{@str}] from #{@player.name} in an inappropriate status [#{@player.status}].")
180 @player.write_safe(sprintf("##[ERROR] you are in %s status. REJECT is valid in agree_waiting status\n", @player.status))
188 class AgreeCommand < Command
189 def initialize(str, player)
194 if (@player.status == "agree_waiting")
195 @player.status = "start_waiting"
196 if (@player.game.is_startable_status?)
200 log_error("Received a command [#{@str}] from #{@player.name} in an inappropriate status [#{@player.status}].")
201 @player.write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @player.status))
207 # Base Command calss requiring a game instance
209 class BaseCommandForGame < Command
210 def initialize(str, player, game)
213 @game_id = game ? game.game_id : nil
219 class ShowCommand < BaseCommandForGame
220 def initialize(str, player, game)
226 @player.write_safe(@game.show.gsub(/^/, '##[SHOW] '))
228 @player.write_safe("##[SHOW] +OK\n")
233 # Command of MONITORON
235 class MonitorOnCommand < BaseCommandForGame
236 def initialize(str, player, game)
242 @game.monitoron(@player)
243 @player.write_safe(@game.show.gsub(/^/, "##[MONITOR][#{@game_id}] "))
244 @player.write_safe("##[MONITOR][#{@game_id}] +OK\n")
250 # Command of MONITOROFF
252 class MonitorOffCommand < BaseCommandForGame
253 def initialize(str, player, game)
259 @game.monitoroff(@player)
267 class HelpCommand < Command
268 def initialize(str, player)
274 %!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!)
281 class RatingCommand < Command
282 def initialize(str, player, rated_players)
284 @rated_players = rated_players
288 @rated_players.sort {|a,b| b.rate <=> a.rate}.each do |p|
289 @player.write_safe("##[RATING] %s \t %4d @%s\n" %
290 [p.simple_player_id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
292 @player.write_safe("##[RATING] +OK\n")
299 class VersionCommand < Command
300 def initialize(str, player)
305 @player.write_safe "##[VERSION] Shogi Server revision #{ShogiServer::Revision}\n"
306 @player.write_safe("##[VERSION] +OK\n")
313 class GameCommand < Command
314 def initialize(str, player)
319 if ((@player.status == "connected") || (@player.status == "game_waiting"))
320 @player.status = "connected"
321 @player.game_name = ""
323 @player.write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @player.status))
329 # Commando of game challenge
330 # TODO make a test case
332 class GameChallengeCommand < Command
333 def initialize(str, player, command_name, game_name, my_sente_str)
335 @command_name = command_name
336 @game_name = game_name
337 @my_sente_str = my_sente_str
341 if (! Login::good_game_name?(@game_name))
342 @player.write_safe(sprintf("##[ERROR] bad game name\n"))
344 elsif ((@player.status == "connected") || (@player.status == "game_waiting"))
347 @player.write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @player.status))
352 if (League::Floodgate.game_name?(@game_name))
353 if (@my_sente_str != "*")
354 @player.write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", @my_sente_str, @game_name))
359 if (@my_sente_str == "*")
360 rival = $league.get_player("game_waiting", @game_name, nil, @player) # no preference
361 elsif (@my_sente_str == "+")
362 rival = $league.get_player("game_waiting", @game_name, false, @player) # rival must be gote
363 elsif (@my_sente_str == "-")
364 rival = $league.get_player("game_waiting", @game_name, true, @player) # rival must be sente
367 @player.write_safe(sprintf("##[ERROR] bad game option\n"))
373 @player.game_name = @game_name
374 if ((@my_sente_str == "*") && (rival.sente == nil))
379 @player.sente = false
382 elsif (rival.sente == true) # rival has higher priority
383 @player.sente = false
384 elsif (rival.sente == false)
386 elsif (@my_sente_str == "+")
389 elsif (@my_sente_str == "-")
390 @player.sente = false
395 Game::new(@player.game_name, @player, rival)
396 else # rival not found
397 if (@command_name == "GAME")
398 @player.status = "game_waiting"
399 @player.game_name = @game_name
400 if (@my_sente_str == "+")
402 elsif (@my_sente_str == "-")
403 @player.sente = false
408 @player.write_safe(sprintf("##[ERROR] can't find rival for %s\n", @game_name))
409 @player.status = "connected"
410 @player.game_name = ""
420 class ChatCommand < Command
422 # players array of [name, player]
424 def initialize(str, player, message, players)
431 @players.each do |name, p| # TODO player change name
432 if (p.protocol != LoginCSA::PROTOCOL)
433 p.write_safe(sprintf("##[CHAT][%s] %s\n", @player.name, @message))
442 class ListCommand < Command
444 # games array of [game_id, game]
446 def initialize(str, player, games)
453 @games.each do |id, game|
454 buf.push(sprintf("##[LIST] %s\n", id))
456 buf.push("##[LIST] +OK\n")
457 @player.write_safe(buf.join)
464 class WhoCommand < Command
466 # players array of [[name, player]]
468 def initialize(str, player, players)
475 @players.each do |name, p|
476 buf.push(sprintf("##[WHO] %s\n", p.to_s))
478 buf.push("##[WHO] +OK\n")
479 @player.write_safe(buf.join)
486 class LogoutCommand < Command
487 def initialize(str, player)
492 @player.status = "connected"
493 @player.write_safe("LOGOUT:completed\n")
498 # Command of CHALLENGE
500 class ChallengeCommand < Command
501 def initialize(str, player)
506 # This command is only available for CSA's official testing server.
507 # So, this means nothing for this program.
508 @player.write_safe("CHALLENGE ACCEPTED\n")
513 # Command for a space
515 class SpaceCommand < Command
516 def initialize(str, player)
521 ## ignore null string
526 # Command for an error
528 class ErrorCommand < Command
529 def initialize(str, player)
534 msg = "##[ERROR] unknown command %s\n" % [@str]
535 @player.write_safe(msg)
542 end # module ShogiServer