+2010-01-16 Daigo Moriwaki <daigo at debian dot org>
+
+ * [shogi-server]
+ - shogi_server/command.rb, test/TC_command.rb
+ Proposal implementation of a new command called MONITOR2{ON,OFF}.
+ When the MONITOR2ON command is issued by a player, the server
+ immediately sends the player the entire contents of a record
+ file of the game, i.e. history of moves and so on, at that time.
+ Then, the server will forward subsequent moves like they are
+ appended to the record file.
+ - Behavior changed: A player monitoring a game with MONITORON will
+ not receive Game#show again when a gaming player resigns (:toryo).
+
2010-01-10 Daigo Moriwaki <daigo at debian dot org>
* Converted the repository from Subversion to Git.
end
# sente is nil only if tests in test_board run
+ # @return
+ # - :normal
+ # - :toryo
+ # - :kachi_win
+ # - :kachi_lose
+ # - :sennichite
+ # - :oute_sennichite_sente_lose
+ # - :oute_sennichite_gote_lose
+ # - :illegal
+ # - :uchifuzume
+ # - :oute_kaihimore
+ # - (:outori will not be returned)
+ #
def handle_one_move(str, sente=nil)
if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
sg = $1
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/
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
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
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 # same
+ def initialize(str, player, game)
+ super
+ end
+ end
+
# Command of HELP
#
class HelpCommand < Command
@@mutex = Mutex.new
@@time = 0
def initialize(game_name, player0, player1, board)
- @monitors = Array::new
+ @monitors = Array::new # array of MonitorHandler*
@game_name = game_name
if (@game_name =~ /-(\d+)-(\d+)$/)
@total_time = $1.to_i
return player.status == "game" && @current_player == player
end
- def monitoron(monitor)
- @monitors.delete(monitor)
- @monitors.push(monitor)
+ def monitoron(monitor_handler)
+ monitoroff(monitor_handler)
+ @monitors.push(monitor_handler)
end
- def monitoroff(monitor)
- @monitors.delete(monitor)
+ def monitoroff(monitor_handler)
+ @monitors.delete_if {|mon| mon == monitor_handler}
end
def each_monitor
- @monitors.each do |monitor|
- yield monitor
+ @monitors.each do |monitor_handler|
+ yield monitor_handler
end
end
if [:illegal, :uchifuzume, :oute_kaihimore].include?(move_status)
@fh.printf("'ILLEGAL_MOVE(%s)\n", str)
else
- if [:normal, :outori, :sennichite, :oute_sennichite_sente_lose, :oute_sennichite_gote_lose].include?(move_status)
+ if :toryo != move_status
# Thinking time includes network traffic
@sente.write_safe(sprintf("%s,T%d\n", str, t))
@gote.write_safe(sprintf("%s,T%d\n", str, t))
@fh.printf("%s\nT%d\n", str, t)
@last_move = sprintf("%s,T%d", str, t)
@current_turn += 1
- end
- @monitors.each do |monitor|
- monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@game_id}] "))
- monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @game_id))
- end
- end
+ @monitors.each do |monitor_handler|
+ monitor_handler.write_one_move(@game_id, self)
+ end
+ end # if
+ # if move_status is :toryo then a GameResult message will be sent to monitors
+ end # if
end
@result = nil
#
class MonitorObserver
def update(game_result)
- game_result.game.each_monitor do |monitor|
- monitor.write_safe("##[MONITOR][%s] %s\n" % [game_result.game.game_id, game_result.result_type])
+ game_result.game.each_monitor do |monitor_handler|
+ monitor_handler.write_safe(game_result.game.game_id, game_result.result_type)
end
end
end
$:.unshift File.join(File.dirname(__FILE__), "..")
$topdir = File.expand_path File.dirname(__FILE__)
require 'test/unit'
+require 'tempfile'
require 'mock_game'
require 'mock_log_message'
require 'test/mock_player'
assert_instance_of(ShogiServer::MonitorOnCommand, cmd)
end
+ def test_monitor2on_command
+ cmd = ShogiServer::Command.factory("%%MONITOR2ON game_id", @p)
+ assert_instance_of(ShogiServer::Monitor2OnCommand, cmd)
+ end
+
def test_monitoroff_command
cmd = ShogiServer::Command.factory("%%MONITOROFF game_id", @p)
assert_instance_of(ShogiServer::MonitorOffCommand, cmd)
assert_equal(:continue, rc)
end
+
+ def test_call_read_logfile
+ game = MockGame.new
+ cmd = ShogiServer::MonitorOnCommand.new("%%MONITORON hoge", @p, game)
+ rc = cmd.call
+ assert_equal("##[MONITOR][dummy_game_id] dummy_game_show\n##[MONITOR][dummy_game_id] line1\n##[MONITOR][dummy_game_id] line2\n##[MONITOR][dummy_game_id] +OK\n", @p.out.join)
+ assert_equal(:continue, rc)
+ end
+end
+
+#
+#
+class TestMonitor2OnCommand < Test::Unit::TestCase
+ def setup
+ @p = MockPlayer.new
+ @game = MockGame.new
+ @p.game = @game
+ end
+
+ def test_call
+ cmd = ShogiServer::Monitor2OnCommand.new("%%MONITOR2ON hoge", @p, nil)
+ rc = cmd.call
+
+ assert_equal(:continue, rc)
+ end
+
+ def test_call_read_logfile
+ $tempfile = Tempfile.new("TC_command_test_call_read_logfile")
+ $tempfile.write "hoge\nfoo\n"
+ $tempfile.close
+ game = MockGame.new
+ def game.logfile
+ $tempfile.path
+ end
+ cmd = ShogiServer::Monitor2OnCommand.new("%%MONITOR2ON hoge", @p, game)
+ rc = cmd.call
+ assert_equal("##[MONITOR2][dummy_game_id] hoge\n##[MONITOR2][dummy_game_id] foo\n##[MONITOR2][dummy_game_id] +OK\n", @p.out.join)
+ assert_equal(:continue, rc)
+ $tempfile = nil
+ end
end
#
end
+#
+#
+class TestMonitorHandler1 < Test::Unit::TestCase
+ def setup
+ @player = MockPlayer.new
+ @handler = ShogiServer::MonitorHandler1.new @player
+ end
+
+ def test_type
+ assert_equal(1, @handler.type)
+ end
+
+ def test_header
+ assert_equal("MONITOR", @handler.header)
+ end
+
+ def test_write_safe
+ @handler.write_safe("game_id", "hoge")
+ assert_equal("##[MONITOR][game_id] hoge\n##[MONITOR][game_id] +OK\n",
+ @player.out.join)
+ end
+end
+
+#
+#
+class TestMonitorHandler2 < Test::Unit::TestCase
+ def setup
+ @player = MockPlayer.new
+ @handler = ShogiServer::MonitorHandler2.new @player
+ end
+
+ def test_type
+ assert_equal(2, @handler.type)
+ end
+
+ def test_header
+ assert_equal("MONITOR2", @handler.header)
+ end
+
+ def test_write_safe
+ @handler.write_safe("game_id", "hoge")
+ assert_equal("##[MONITOR2][game_id] hoge\n##[MONITOR2][game_id] +OK\n",
+ @player.out.join)
+ end
+
+ def test_write_safe2
+ @handler.write_safe("game_id", "hoge\nfoo")
+ assert_equal("##[MONITOR2][game_id] hoge\n##[MONITOR2][game_id] foo\n##[MONITOR2][game_id] +OK\n",
+ @player.out.join)
+ end
+end
+
end
def show
- return "dummy_game_show"
+ return "dummy_game_show\nline1\nline2\n"
end
def monitoron(player)