OSDN Git Service

Proposal implementation of a new command called MONITOR2{ON,OFF}.
authorDaigo Moriwaki <daigo@debian.org>
Fri, 15 Jan 2010 10:25:39 +0000 (19:25 +0900)
committerDaigo Moriwaki <daigo@debian.org>
Fri, 15 Jan 2010 10:25:39 +0000 (19:25 +0900)
* [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).

changelog
shogi_server/board.rb
shogi_server/command.rb
shogi_server/game.rb
shogi_server/game_result.rb
test/TC_command.rb
test/mock_game.rb

index 1434e79..7a9c40c 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,3 +1,16 @@
+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.
index dce239b..3778bd7 100644 (file)
@@ -484,6 +484,19 @@ class Board
   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
index 897413c..55b56f1 100644 (file)
@@ -49,6 +49,12 @@ module ShogiServer
       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/
@@ -263,6 +269,53 @@ module ShogiServer
     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
@@ -272,9 +325,9 @@ module ShogiServer
 
     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
@@ -289,12 +342,36 @@ module ShogiServer
 
     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
index 532c552..9e8f71b 100644 (file)
@@ -31,7 +31,7 @@ class Game
   @@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
@@ -100,18 +100,18 @@ class Game
     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
 
@@ -207,20 +207,20 @@ class Game
       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
index 0dd90f1..291aa3d 100644 (file)
@@ -26,8 +26,8 @@ module ShogiServer # for a namespace
 #
 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
index 2f5342b..63d88e4 100644 (file)
@@ -1,6 +1,7 @@
 $:.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'
@@ -106,6 +107,11 @@ class TestFactoryMethod < Test::Unit::TestCase
     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)
@@ -431,6 +437,46 @@ class TestMonitorOnCommand < Test::Unit::TestCase
 
     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
 
 #
@@ -840,3 +886,55 @@ class TestGetBuoyCountCommand < BaseTestBuoyCommand
 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
+
index b18ab20..80263df 100644 (file)
@@ -46,7 +46,7 @@ class MockGame
   end
 
   def show
-    return "dummy_game_show"
+    return "dummy_game_show\nline1\nline2\n"
   end
 
   def monitoron(player)