OSDN Git Service

Implemented a new command: %%FORK copy
authorDaigo Moriwaki <beatles@users.sourceforge.jp>
Sun, 24 Feb 2013 07:32:57 +0000 (16:32 +0900)
committerDaigo Moriwaki <beatles@users.sourceforge.jp>
Sun, 24 Feb 2013 08:42:43 +0000 (17:42 +0900)
changelog
shogi_server/buoy.rb
shogi_server/command.rb
shogi_server/game.rb
test/TC_ALL.rb
test/TC_buoy.rb
test/TC_command.rb
test/TC_fork.rb [new file with mode: 0644]

index e2b3ab5..75d31f0 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,3 +1,22 @@
+2013-02-23  Daigo Moriwaki <daigo at debian dot org>
+
+       * [shogi-server]
+         - New command: %%FORK <source_game> <new_buoy_game> [<nth-move>]
+           Fork a new game from the posistion where the n-th (starting from
+           one) move of a source game is played. The new game should be a
+           valid buoy game name. The default value of n is the position
+           where the previous position of the last one.
+           - The objective of this command: The shogi-server may be used as
+           the back end server of computer-human match where a human player
+           plays with a real board and someone, or a proxy, inputs moves to
+           the shogi-server. If the proxy happens to enter a wrong move,
+           with this command you can restart a new buoy game from the
+           previous stable position.
+           ex. %%FORK server-denou-14400-60+p1+p2+20130223185013 buoy_denou-14400-60
+           - Note that this feature does not provice accurate thinking
+           time estimate. The thinking time of the last posision is carried
+           over to the new game regardless nth-move.
+
 2012-12-30  Daigo Moriwaki <daigo at debian dot org>
 
        * [shogi-server]
index c46c575..f189402 100644 (file)
@@ -10,10 +10,12 @@ module ShogiServer
     attr_reader :moves
     attr_reader :owner
     attr_reader :count
+    attr_reader :sente_time
+    attr_reader :gote_time
 
-    def initialize(game_name, moves, owner, count)
+    def initialize(game_name, moves, owner, count, sente_time, gote_time)
       raise "owner name is required" if owner && !owner.instance_of?(String)
-      @game_name, @moves, @owner, @count = game_name, moves, owner, count
+      @game_name, @moves, @owner, @count, @sente_time, @gote_time = game_name, moves, owner, count, sente_time, gote_time
     end
 
     def decrement_count
@@ -21,16 +23,18 @@ module ShogiServer
     end
 
     def ==(rhs)
-      return (@game_name == rhs.game_name && 
-              @moves == rhs.moves &&
-              @owner == rhs.owner &&
-              @count == rhs.count)
+      return (@game_name  == rhs.game_name &&
+              @moves      == rhs.moves &&
+              @owner      == rhs.owner &&
+              @count      == rhs.count &&
+              @sente_time == rhs.sente_time &&
+              @gote_time  == rhs.gote_time)
     end
   end
 
   class NilBuoyGame < BuoyGame
     def initialize
-      super(nil, nil, nil, 0)
+      super(nil, nil, nil, 0, nil, nil)
     end
   end
 
@@ -62,9 +66,11 @@ module ShogiServer
         if @db.root?(buoy_game.game_name)
           # error
         else
-          hash = {'moves' => buoy_game.moves,
-                  'owner' => buoy_game.owner,
-                  'count' => buoy_game.count}
+          hash = {'moves'      => buoy_game.moves,
+                  'owner'      => buoy_game.owner,
+                  'count'      => buoy_game.count,
+                  'sente_time' => buoy_game.sente_time,
+                  'gote_time'  => buoy_game.gote_time}
           @db[buoy_game.game_name] = hash
         end
       end
@@ -73,9 +79,11 @@ module ShogiServer
     def update_game(buoy_game)
       @db.transaction do
         if @db.root?(buoy_game.game_name)
-          hash = {'moves' => buoy_game.moves,
-                  'owner' => buoy_game.owner,
-                  'count' => buoy_game.count}
+          hash = {'moves'     => buoy_game.moves,
+                  'owner'     => buoy_game.owner,
+                  'count'     => buoy_game.count,
+                  'sene_time' => buoy_game.sente_time,
+                  'gote_time' => buoy_game.gote_time}
           @db[buoy_game.game_name] = hash
         else
           # error
@@ -93,10 +101,12 @@ module ShogiServer
       @db.transaction(true) do
         hash = @db[game_name]
         if hash
-          moves = hash['moves']
-          owner = hash['owner']
-          count = hash['count'].to_i
-          return BuoyGame.new(game_name, moves, owner, count)
+          moves      = hash['moves']
+          owner      = hash['owner']
+          count      = hash['count'].to_i
+          sente_time = hash['sente_time'] ? hash['sente_time'].to_i : nil
+          gote_time  = hash['gote_time']  ? hash['gote_time'].to_i  : nil
+          return BuoyGame.new(game_name, moves, owner, count, sente_time, gote_time)
         else
           return NilBuoyGame.new
         end
index a3b48a7..b8ffb9b 100644 (file)
@@ -94,6 +94,14 @@ module ShogiServer
       when /^%%GETBUOYCOUNT\s+(\S+)/
         game_name = $1
         cmd = GetBuoyCountCommand.new(str, player, game_name)
+      when /^%%FORK\s+(\S+)\s+(\S+)(.*)/
+        source_game   = $1
+        new_buoy_game = $2
+        nth_move      = nil
+        if $3 && /^\s+(\d+)/ =~ $3
+          nth_move = $3.to_i
+        end
+        cmd = ForkCommand.new(str, player, source_game, new_buoy_game, nth_move)
       when /^\s*$/
         cmd = SpaceCommand.new(str, player)
       when /^%%%[^%]/
@@ -532,7 +540,9 @@ module ShogiServer
               return :continue
             end
             buoy.decrement_count(buoy_game)
-            Game::new(@player.game_name, @player, rival, board)
+            options = {:sente_time => buoy_game.sente_time,
+                       :gote_time  => buoy_game.gote_time}
+            Game::new(@player.game_name, @player, rival, board, options)
           end
         else
           klass = Login.handicapped_game_name?(@game_name) || Board
@@ -692,6 +702,16 @@ module ShogiServer
       @game_name = game_name
       @moves     = moves
       @count     = count
+      @sente_time = nil
+      @gote_time  = nil
+    end
+
+    # ForkCommand may call this method to set remaining time of both
+    # players.
+    #
+    def set_initial_time(sente_time, gote_time)
+      @sente_time = sente_time
+      @gote_time  = gote_time
     end
 
     def call
@@ -721,7 +741,7 @@ module ShogiServer
         raise WrongMoves
       end
 
-      buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count)
+      buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count, @sente_time, @gote_time)
       buoy.add_game(buoy_game)
       @player.write_safe(sprintf("##[SETBUOY] +OK\n"))
       log_info("A buoy game was created: %s by %s" % [@game_name, @player.name])
@@ -749,7 +769,11 @@ module ShogiServer
       # found two players: p1 and p2
       log_info("Starting a buoy game: %s with %s and %s" % [@game_name, p1.name, p2.name])
       buoy.decrement_count(buoy_game)
-      game = Game::new(@game_name, p1, p2, board)
+
+      # remaining time for ForkCommand
+      options = {:sente_time => buoy_game.sente_time,
+                 :gote_time  => buoy_game.gote_time}
+      game = Game::new(@game_name, p1, p2, board, options)
       return :continue
 
     rescue WrongMoves => e
@@ -809,4 +833,41 @@ module ShogiServer
     end
   end
 
+  # %%FORK <source_game> <new_buoy_game> [<nth-move>]
+  # Fork a new game from the posistion where the n-th (starting from 1) move
+  # of a source game is played. The new game should be a valid buoy game
+  # name. The default value of n is the position where the previous position
+  # of the last one.
+  #
+  class ForkCommand < Command
+    def initialize(str, player, source_game, new_buoy_game, nth_move)
+      super(str, player)
+      @source_game   = source_game
+      @new_buoy_game = new_buoy_game
+      @nth_move      = nth_move # may be nil
+    end
+
+    def call
+      game = $league.games[@source_game]
+      unless game
+        @player.write_safe(sprintf("##[ERROR] wrong source game name: %s\n", @source_game))
+        log_error "Received a wrong source game name: %s from %s." % [@source_game, @player.name]
+        return :continue
+      end
+
+      moves = game.read_moves
+      @nth_move = moves.size - 1 unless @nth_move
+      if @nth_move > moves.size or @nth_move < 1
+        @player.write_safe(sprintf("##[ERROR] number of moves to fork is out of range: %s.\n", moves.size))
+        log_error "Number of moves to fork is out of range: %s [%s]" % [@nth_move, @player.name]
+        return :continue
+      end
+      new_moves = moves[0...@nth_move]
+
+      buoy_cmd = SetBuoyCommand.new(@str, @player, @new_buoy_game, new_moves.join(""), 1)
+      buoy_cmd.set_initial_time(game.sente.mytime, game.gote.mytime)
+      return buoy_cmd.call
+    end
+  end
+
 end # module ShogiServer
index ee90d52..4d246b9 100644 (file)
@@ -63,7 +63,10 @@ class Game
   end
 
 
-  def initialize(game_name, player0, player1, board)
+  # options may or may not have follwing keys: :sente_time, :gote_time; they
+  # are used for %%FORK command to set remaining times at the restart.
+  #
+  def initialize(game_name, player0, player1, board, options={})
     @monitors = Array::new # array of MonitorHandler*
     @game_name = game_name
     if (@game_name =~ /-(\d+)-(\d+)$/)
@@ -116,6 +119,8 @@ class Game
     @fh.sync = true
     @result = nil
 
+    @options = options.dup
+
     propose
   end
   attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors
@@ -307,8 +312,8 @@ class 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
-    @gote.mytime = @total_time
+    @sente.mytime = @options[:sente_time] || @total_time
+    @gote.mytime  = @options[:gote_time]  || @total_time
     @start_time = Time.now
   end
 
@@ -372,6 +377,13 @@ EOM
   end
 
   def propose_message(sg_flag)
+    time = @total_time
+    if @options[:sente_time] && sg_flag == "+"
+      time = @options[:sente_time]
+    elsif @options[:gote_time] && sg_flag == "-"
+      time = @options[:gote_time]
+    end
+
     str = <<EOM
 BEGIN Game_Summary
 Protocol_Version:1.1
@@ -386,7 +398,7 @@ Rematch_On_Draw:NO
 To_Move:#{@board.teban ? "+" : "-"}
 BEGIN Time
 Time_Unit:1sec
-Total_Time:#{@total_time}
+Total_Time:#{time}
 Byoyomi:#{@byoyomi}
 Least_Time_Per_Move:#{Least_Time_Per_Move}
 END Time
@@ -408,6 +420,19 @@ EOM
 
     return false
   end
+
+  # Read the .csa file and returns an array of moves.
+  # ex. ["+7776FU", "-3334FU"]
+  #
+  def read_moves
+    ret = []
+    IO.foreach(@logfile) do |line|
+      if /^[\+\-]\d{4}[A-Z]{2}/ =~ line
+        ret << line.chomp
+      end
+    end
+    return ret
+  end
   
   private
   
index 1c28109..f93033f 100644 (file)
@@ -9,6 +9,7 @@ require 'TC_floodgate'
 require 'TC_floodgate_history'
 require 'TC_floodgate_next_time_generator'
 require 'TC_floodgate_thread.rb'
+require 'TC_fork'
 require 'TC_functional'
 require 'TC_game'
 require 'TC_game_result'
index 1c15ca0..8f6237f 100644 (file)
@@ -9,14 +9,32 @@ require 'mock_log_message'
 
 class TestBuoyGame < Test::Unit::TestCase
   def test_equal
-    g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
-    g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
+    g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+    g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+    assert_equal g1, g2
+  end
+
+  def test_equal2
+    g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
+    g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
     assert_equal g1, g2
   end
 
   def test_not_equal
-    g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
-    g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2)
+    g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+    g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2, nil, nil)
+    assert_not_equal g1, g2
+  end
+
+  def test_not_equal2
+    g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
+    g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 200)
+    assert_not_equal g1, g2
+  end
+
+  def test_not_equal3
+    g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, nil)
+    g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 200)
     assert_not_equal g1, g2
   end
 end
@@ -49,7 +67,18 @@ class TestBuoy < Test::Unit::TestCase
   end
 
   def test_add_game
-    game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
+    game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+    @buoy.add_game(game)
+    assert !@buoy.is_new_game?("buoy_1234-900-0")
+    game2 = @buoy.get_game(game.game_name)
+    assert_equal game, game2
+
+    @buoy.delete_game game
+    assert @buoy.is_new_game?("buoy_1234-900-0")
+  end
+
+  def test_add_game2
+    game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
     @buoy.add_game(game)
     assert !@buoy.is_new_game?("buoy_1234-900-0")
     game2 = @buoy.get_game(game.game_name)
@@ -60,9 +89,9 @@ class TestBuoy < Test::Unit::TestCase
   end
 
   def test_update_game
-    game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2)
+    game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2, nil, nil)
     @buoy.add_game(game)
-    g2 = ShogiServer::BuoyGame.new(game.game_name, game.moves, game.owner, game.count-1)
+    g2 = ShogiServer::BuoyGame.new(game.game_name, game.moves, game.owner, game.count-1, nil, nil)
     @buoy.update_game(g2)
     
     get = @buoy.get_game(g2.game_name)
index f23062b..dc1c0f5 100644 (file)
@@ -228,6 +228,11 @@ class TestFactoryMethod < Test::Unit::TestCase
     assert_instance_of(ShogiServer::GetBuoyCountCommand, cmd)
   end
 
+  def test_fork_command
+    cmd = ShogiServer::Command.factory("%%FORK server-denou-14400-60+p1+p2+20130223185013 buoy_denou-14400-60", @p)
+    assert_instance_of(ShogiServer::ForkCommand, cmd)
+  end
+
   def test_void_command
     cmd = ShogiServer::Command.factory("%%%HOGE", @p)
     assert_instance_of(ShogiServer::VoidCommand, cmd)
@@ -837,7 +842,7 @@ class TestSetBuoyCommand < BaseTestBuoyCommand
     assert !$p1.out.empty?
     assert !$p2.out.empty?
     buoy_game2 = @buoy.get_game("buoy_hoge-1500-0")
-    assert_equal ShogiServer::BuoyGame.new("buoy_hoge-1500-0", "+7776FU", @p.name, 1), buoy_game2
+    assert_equal ShogiServer::BuoyGame.new("buoy_hoge-1500-0", "+7776FU", @p.name, 1, nil, nil), buoy_game2
   end
 
   def test_call_1
@@ -862,7 +867,7 @@ class TestSetBuoyCommand < BaseTestBuoyCommand
 
   def test_call_error_duplicated_game_name
     assert @buoy.is_new_game?("buoy_duplicated-1500-0")
-    bg = ShogiServer::BuoyGame.new("buoy_duplicated-1500-0", ["+7776FU"], @p.name, 1)
+    bg = ShogiServer::BuoyGame.new("buoy_duplicated-1500-0", ["+7776FU"], @p.name, 1, nil, nil)
     @buoy.add_game bg
     assert !@buoy.is_new_game?("buoy_duplicated-1500-0")
     
@@ -900,7 +905,7 @@ end
 #
 class TestDeleteBuoyCommand < BaseTestBuoyCommand
   def test_call
-    buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1)
+    buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1, nil, nil)
     assert @buoy.is_new_game?(buoy_game.game_name)
     @buoy.add_game buoy_game
     assert !@buoy.is_new_game?(buoy_game.game_name)
@@ -913,7 +918,7 @@ class TestDeleteBuoyCommand < BaseTestBuoyCommand
   end
 
   def test_call_not_exist
-    buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1)
+    buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1, nil, nil)
     assert @buoy.is_new_game?(buoy_game.game_name)
     cmd = ShogiServer::DeleteBuoyCommand.new "%%DELETEBUOY", @p, buoy_game.game_name
     rt = cmd.call
@@ -924,7 +929,7 @@ class TestDeleteBuoyCommand < BaseTestBuoyCommand
   end
 
   def test_call_another_player
-    buoy_game = ShogiServer::BuoyGame.new("buoy_anotherplayer-1500-0", "+7776FU", "another_player", 1)
+    buoy_game = ShogiServer::BuoyGame.new("buoy_anotherplayer-1500-0", "+7776FU", "another_player", 1, nil, nil)
     assert @buoy.is_new_game?(buoy_game.game_name)
     @buoy.add_game(buoy_game)
     assert !@buoy.is_new_game?(buoy_game.game_name)
@@ -941,7 +946,7 @@ end
 #
 class TestGetBuoyCountCommand < BaseTestBuoyCommand
   def test_call
-    buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1)
+    buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1, nil, nil)
     assert @buoy.is_new_game?(buoy_game.game_name)
     @buoy.add_game buoy_game
     assert !@buoy.is_new_game?(buoy_game.game_name)
@@ -952,7 +957,7 @@ class TestGetBuoyCountCommand < BaseTestBuoyCommand
   end
 
   def test_call_not_exist
-    buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1)
+    buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1, nil, nil)
     assert @buoy.is_new_game?(buoy_game.game_name)
     cmd = ShogiServer::GetBuoyCountCommand.new "%%GETBUOYCOUNT", @p, buoy_game.game_name
     rt = cmd.call
diff --git a/test/TC_fork.rb b/test/TC_fork.rb
new file mode 100644 (file)
index 0000000..4cd5e58
--- /dev/null
@@ -0,0 +1,88 @@
+$:.unshift File.join(File.dirname(__FILE__), "..")
+$topdir = File.expand_path File.dirname(__FILE__)
+require "baseclient"
+require "shogi_server/buoy.rb"
+
+class TestFork < BaseClient
+  def parse_game_name(player)
+    player.puts "%%LIST"
+    sleep 1
+    if /##\[LIST\] (.*)/ =~ player.message
+      return $1
+    end
+  end
+
+  def test_wrong_game
+    @admin = SocketPlayer.new "dummy", "admin", false
+    @admin.connect
+    @admin.reader
+    @admin.login
+
+    result, result2 = handshake do
+      @admin.puts "%%FORK wronggame-900-0 buoy_WrongGame-900-0"
+      sleep 1
+    end
+
+    assert /##\[ERROR\] wrong source game name/ =~ @admin.message
+    @admin.logout
+  end
+
+  def test_too_short_fork
+    @admin = SocketPlayer.new "dummy", "admin", false
+    @admin.connect
+    @admin.reader
+    @admin.login
+
+    result, result2 = handshake do
+      source_game = parse_game_name(@admin)
+      @admin.puts "%%FORK #{source_game} buoy_TooShortFork-900-0 0"
+      sleep 1
+    end
+
+    assert /##\[ERROR\] number of moves to fork is out of range/ =~ @admin.message
+    @admin.logout
+  end
+
+  def test_fork
+    buoy = ShogiServer::Buoy.new
+    
+    @admin = SocketPlayer.new "dummy", "admin", "*"
+    @admin.connect
+    @admin.reader
+    @admin.login
+    assert buoy.is_new_game?("buoy_Fork-1500-0")
+
+    result, result2 = handshake do
+      source_game = parse_game_name(@admin)
+      @admin.puts "%%FORK #{source_game} buoy_Fork-1500-0"
+      sleep 1
+    end
+
+    assert buoy.is_new_game?("buoy_Fork-1500-0")
+    @p1 = SocketPlayer.new "buoy_Fork", "p1", true
+    @p2 = SocketPlayer.new "buoy_Fork", "p2", false
+    @p1.connect
+    @p2.connect
+    @p1.reader
+    @p2.reader
+    @p1.login
+    @p2.login
+    sleep 1
+    @p1.game
+    @p2.game
+    sleep 1
+    @p1.agree
+    @p2.agree
+    sleep 1
+    assert /^Total_Time:1499/ =~ @p1.message
+    assert /^Total_Time:1499/ =~ @p2.message
+    @p2.move("-3334FU")
+    sleep 1
+    @p1.toryo
+    sleep 1
+    @p2.logout
+    @p1.logout
+
+    @admin.logout
+  end
+end