OSDN Git Service

Fix #36230: Support Fischer Time Control
authorDaigo Moriwaki <daigo@debian.org>
Sun, 3 Apr 2016 09:53:05 +0000 (09:53 +0000)
committerDaigo Moriwaki <daigo@debian.org>
Sun, 3 Apr 2016 09:53:05 +0000 (09:53 +0000)
13 files changed:
changelog
shogi_server/buoy.rb
shogi_server/command.rb
shogi_server/game.rb
shogi_server/league/floodgate.rb
shogi_server/login.rb
shogi_server/time_clock.rb
test/TC_buoy.rb
test/TC_command.rb
test/TC_game.rb
test/TC_game_least_0.rb
test/TC_login.rb
test/TC_time_clock.rb

index e448a2a..c7c1c52 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,3 +1,26 @@
+2016-04-02  Daigo Moriwaki <daigo at debian dot org>
+
+       * [shogi-server] Support Fischer Time Control
+         (Closes #36230)
+         - Fischer time control means that:
+           Before a player has made their move, a specified time increment is
+           added to their clock. Time can be accumulated, so if the player
+           moves within the delay period, their remaining time actually
+           increases.
+           (https://en.wikipedia.org/wiki/Chess_clock)
+         - New syntax of game names:
+           An 'F' suffix denotes a Fisher time in seconds. For example,
+           "floodgate-600-10F" specifies
+             + A floodgate game
+             + Total allotted time is 600 seconds for each player
+             + 10-second increment before a player's move in a Fischer way
+         - The server now proposes game conditions upon a game start with
+           - "Protocol_Version 1.2"
+           - "Increment:<n>" for Fischer Time Control
+         - Kifu files can include the following comment for Fischer Time
+           Control:
+           - "'Increment:<n>"
+
 2015-12-13  Daigo Moriwaki <daigo at debian dot org>
 
        * [shogi-server] Enhance capability of Floodgate configuration file
index e2f88ee..ec6a1ef 100644 (file)
@@ -39,7 +39,7 @@ module ShogiServer
     # "buoy_hoge-900-0"
     #
     def Buoy.game_name?(str)
-      return /^buoy_.*\-\d+\-\d+$/.match(str) ? true : false
+      return /^buoy_.*\-\d+\-\d+F?$/.match(str) ? true : false
     end
 
     def initialize(conf = {})
index 5b25200..b3e5ca2 100644 (file)
@@ -499,7 +499,7 @@ module ShogiServer
     def call
       if (! Login::good_game_name?(@game_name))
         @player.write_safe(sprintf("##[ERROR] bad game name: %s.\n", @game_name))
-        if (/^(.+)-\d+-\d+$/ =~ @game_name)
+        if (/^(.+)-\d+-\d+F?$/ =~ @game_name)
           if Login::good_identifier?($1)
             # do nothing
           else
@@ -857,7 +857,7 @@ module ShogiServer
       byo_time   = nil
 
       if @source_game.split("+").size >= 2 &&
-         /^([^-]+)-(\d+)-(\d+)/ =~ @source_game.split("+")[1]
+         /^([^-]+)-(\d+)-(\d+F?)/ =~ @source_game.split("+")[1]
         name       = $1
         total_time = $2
         byo_time   = $3
index 2d0b0a5..9bcecfb 100644 (file)
@@ -63,16 +63,42 @@ class Game
     end
   end
 
+  TimeControlParams = Struct.new(:total_time, :byoyomi, :fischer, :stop_watch, :error)
+
+  # Parse time related parts of a game name.
+  #
+  # Return an array of a total allotted time, byoyomi seconds and fischer
+  # seconds; if it fails to parse, return nil.
+  def Game.parse_time(game_name)
+    ret = TimeControlParams.new(0, 0, 0, false, false)
+
+    if (game_name =~ /-(\d+)-(\d+F?)$/)
+      ret[:total_time] = $1.to_i
+      tmp = $2
+      if tmp =~ /^0\d/
+        ret[:stop_watch] = true
+      end
+      if tmp[-1] == "F"
+        ret[:fischer] = tmp.chop.to_i
+      else
+        ret[:byoyomi] = tmp.to_i
+      end
+      return ret
+    end
+
+    ret[:error] = true
+    return ret
+  end
+
 
   def initialize(game_name, player0, player1, board)
     @monitors = Array::new # array of MonitorHandler*
     @game_name = game_name
-    if (@game_name =~ /-(\d+)-(\d+)$/)
-      @total_time = $1.to_i
-      @byoyomi = $2.to_i
-
-      @time_clock = TimeClock::factory(board.least_time_per_move, @game_name)
-    end
+    time_map = Game.parse_time @game_name
+    @total_time = time_map[:total_time]
+    @byoyomi    = time_map[:byoyomi]
+    @fischer    = time_map[:fischer]
+    @time_clock = TimeClock::factory(board.least_time_per_move, @game_name)
 
     if (player0.sente)
       @sente, @gote = player0, player1
@@ -131,7 +157,7 @@ class Game
 
     propose
   end
-  attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors, :time_clock
+  attr_accessor :game_name, :total_time, :byoyomi, :fischer, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors, :time_clock
   attr_accessor :last_move, :current_turn
   attr_reader   :result, :prepared_time
 
@@ -327,6 +353,9 @@ class Game
     @fh.puts("N-#{@gote.name}")
     @fh.puts("'Max_Moves:#{@board.max_moves}")
     @fh.puts("'Least_Time_Per_Move:#{@board.least_time_per_move}")
+    if @fischer > 0
+      @fh.puts("'Increment:#{@fischer}")
+    end
     @fh.puts("$EVENT:#{@game_id}")
 
     @sente.write_safe(propose_message("+"))
@@ -364,7 +393,7 @@ class Game
   def show()
     str0 = <<EOM
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -378,6 +407,7 @@ BEGIN Time
 Time_Unit:#{@time_clock.time_unit}
 Total_Time:#{@total_time}
 Byoyomi:#{@byoyomi}
+Increment:#{@fischer}
 Least_Time_Per_Move:#{@board.least_time_per_move}
 Remaining_Time+:#{@sente.mytime}
 Remaining_Time-:#{@gote.mytime}
@@ -398,7 +428,7 @@ EOM
   def propose_message(sg_flag)
     str = <<EOM
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -413,6 +443,7 @@ BEGIN Time
 Time_Unit:#{@time_clock.time_unit}
 Total_Time:#{@total_time}
 Byoyomi:#{@byoyomi}
+Increment:#{@fischer}
 Least_Time_Per_Move:#{@board.least_time_per_move}
 END Time
 BEGIN Position
index e52c3c3..c184738 100644 (file)
@@ -12,7 +12,7 @@ class League
       # ex. "floodgate-900-0"
       #
       def game_name?(str)
-        return /^floodgate\-\d+\-\d+$/.match(str) ? true : false
+        return /^floodgate\-\d+\-\d+F?$/.match(str) ? true : false
       end
 
       def history_file_path(gamename)
index 8255a1e..54e8c25 100644 (file)
@@ -38,7 +38,7 @@ class Login
   end
 
   def Login.good_game_name?(str)
-    if ((str =~ /^(.+)-\d+-\d+$/) && (good_identifier?($1)))
+    if ((str =~ /^(.+)-\d+-\d+F?$/) && (good_identifier?($1)))
       return true
     else
       return false
@@ -149,7 +149,7 @@ class LoginCSA < Login
   # turn character "+" or "-"; false otherwise
   #
   def parse_gamename_turn(str)
-    if str =~ /^(.+)-\d+-\d+-(\w)$/
+    if str =~ /^(.+)-\d+-\d+F?-(\w)$/
       case $2
       when "b","B"
         return [str[0, str.length-2], "+"]
index 06ac229..6b0ee46 100644 (file)
@@ -24,30 +24,32 @@ module ShogiServer # for a namespace
 class TimeClock
 
   def TimeClock.factory(least_time_per_move, game_name)
-    total_time_str = nil
-    byoyomi_str = nil
-    if (game_name =~ /-(\d+)-(\d+)$/)
-      total_time_str = $1
-      byoyomi_str    = $2
-    end
-    total_time = total_time_str.to_i
-    byoyomi    = byoyomi_str.to_i
-    if (byoyomi_str == "060")
-      @time_clock = StopWatchClock.new(least_time_per_move, total_time, byoyomi)
+    time_map = Game.parse_time game_name
+
+    if time_map[:stop_watch]
+      @time_clock = StopWatchClock.new(least_time_per_move, time_map[:total_time], time_map[:byoyomi])
     else
       if least_time_per_move == 0
-        @time_clock = ChessClockWithLeastZero.new(least_time_per_move, total_time, byoyomi)
+        @time_clock = ChessClockWithLeastZero.new(least_time_per_move,
+                                                  time_map[:total_time],
+                                                  time_map[:byoyomi],
+                                                  time_map[:fischer])
       else
-        @time_clock = ChessClock.new(least_time_per_move, total_time, byoyomi)
+        @time_clock = ChessClock.new(least_time_per_move,
+                                     time_map[:total_time],
+                                     time_map[:byoyomi],
+                                     time_map[:fischer])
       end
     end
+
+    @time_clock
   end
 
-  def initialize(least_time_per_move, total_time, byoyomi)
+  def initialize(least_time_per_move, total_time, byoyomi, fischer=0)
     @least_time_per_move = least_time_per_move
     @total_time = total_time
-    @byoyomi     = byoyomi
+    @byoyomi    = byoyomi
+    @fischer    = fischer
   end
 
   # Returns thinking time duration
@@ -74,7 +76,8 @@ class TimeClock
   #
   def process_time(player, start_time, end_time)
     t = time_duration(player.mytime, start_time, end_time)
-    
+
+    player.mytime += @fischer
     player.mytime -= t
     if (player.mytime < 0)
       player.mytime = 0
@@ -87,7 +90,7 @@ end
 # Calculates thinking time with chess clock.
 #
 class ChessClock < TimeClock
-  def initialize(least_time_per_move, total_time, byoyomi)
+  def initialize(least_time_per_move, total_time, byoyomi, fischer=0)
     super
   end
 
@@ -98,8 +101,8 @@ class ChessClock < TimeClock
   def timeout?(player, start_time, end_time)
     t = time_duration(player.mytime, start_time, end_time)
 
-    if ((player.mytime - t <= -@byoyomi) && 
-        ((@total_time > 0) || (@byoyomi > 0)))
+    if ((player.mytime - t + @byoyomi + @fischer <= 0) &&
+        ((@total_time > 0) || (@byoyomi > 0) || (@fischer > 0)))
       return true
     else
       return false
@@ -107,7 +110,8 @@ class ChessClock < TimeClock
   end
 
   def to_s
-    return "ChessClock: LeastTimePerMove %d; TotalTime %d; Byoyomi %d" % [@least_time_per_move, @total_time, @byoyomi]
+    return "ChessClock: LeastTimePerMove %d; TotalTime %d; Byoyomi %d; Fischer" %
+      [@least_time_per_move, @total_time, @byoyomi, @fischer]
   end
 end
 
@@ -118,7 +122,7 @@ end
 # byoyomi should be more than 0.
 #
 class ChessClockWithLeastZero < ChessClock
-  def initialize(least_time_per_move, total_time, byoyomi)
+  def initialize(least_time_per_move, total_time, byoyomi, fischer=0)
     if least_time_per_move != 0
       raise ArgumentError, "least_time_per_move #{least_time_per_move} should be 0."
     end
@@ -126,13 +130,16 @@ class ChessClockWithLeastZero < ChessClock
   end
 
   def to_s
-    return "ChessClockWithLeastZero: LeastTimePerMove %d; TotalTime %d; Byoyomi %d" % [@least_time_per_move, @total_time, @byoyomi]
+    return "ChessClockWithLeastZero: LeastTimePerMove %d; TotalTime %d; Byoyomi %d; Fischer %d" %
+      [@least_time_per_move, @total_time, @byoyomi, @fischer]
   end
 end
 
+# StopWatchClock does not support Fischer time.
+#
 class StopWatchClock < TimeClock
   def initialize(least_time_per_move, total_time, byoyomi)
-    super
+    super least_time_per_move, total_time, byoyomi, 0
   end
 
   def time_unit
index 03a18d2..091e940 100644 (file)
@@ -40,6 +40,7 @@ class TestBuoy < Test::Unit::TestCase
     assert(ShogiServer::Buoy.game_name?("buoy_hoge-1500-0"))
     assert(ShogiServer::Buoy.game_name?("buoy_hoge-900-0"))
     assert(ShogiServer::Buoy.game_name?("buoy_hoge-0-30"))
+    assert(ShogiServer::Buoy.game_name?("buoy_hoge-600-10F"))
     assert(!ShogiServer::Buoy.game_name?("buoyhoge-1500-0"))
     assert(!ShogiServer::Buoy.game_name?("hoge-1500-0"))
   end 
index 5e231fa..3487b94 100644 (file)
@@ -173,11 +173,21 @@ class TestFactoryMethod < Test::Unit::TestCase
     assert_instance_of(ShogiServer::GameChallengeCommand, cmd)
   end
 
+  def test_game_challenge_command_game_fischer
+    cmd = ShogiServer::Command.factory("%%GAME default-600-10F +", @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_game_challenge_command_challenge_fischer
+    cmd = ShogiServer::Command.factory("%%CHALLENGE default-600-10F -", @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)
@@ -213,31 +223,61 @@ class TestFactoryMethod < Test::Unit::TestCase
     assert_instance_of(ShogiServer::SetBuoyCommand, cmd)
   end
 
+  def test_setbuoy_command_fischer
+    cmd = ShogiServer::Command.factory("%%SETBUOY buoy_test-600-10F +7776FU", @p)
+    assert_instance_of(ShogiServer::SetBuoyCommand, cmd)
+  end
+
   def test_setbuoy_command_with_counter
     cmd = ShogiServer::Command.factory("%%SETBUOY buoy_test-1500-0 +7776FU 3", @p)
     assert_instance_of(ShogiServer::SetBuoyCommand, cmd)
   end
 
+  def test_setbuoy_command_with_counter_fischer
+    cmd = ShogiServer::Command.factory("%%SETBUOY buoy_test-600-10F +7776FU 3", @p)
+    assert_instance_of(ShogiServer::SetBuoyCommand, cmd)
+  end
+
   def test_deletebuoy_command
     cmd = ShogiServer::Command.factory("%%DELETEBUOY buoy_test-1500-0", @p)
     assert_instance_of(ShogiServer::DeleteBuoyCommand, cmd)
   end
 
+  def test_deletebuoy_command_fischer
+    cmd = ShogiServer::Command.factory("%%DELETEBUOY buoy_test-600-10F", @p)
+    assert_instance_of(ShogiServer::DeleteBuoyCommand, cmd)
+  end
+
   def test_getbuoycount_command
     cmd = ShogiServer::Command.factory("%%GETBUOYCOUNT buoy_test-1500-0", @p)
     assert_instance_of(ShogiServer::GetBuoyCountCommand, cmd)
   end
 
+  def test_getbuoycount_command_fischer
+    cmd = ShogiServer::Command.factory("%%GETBUOYCOUNT buoy_test-600-10F", @p)
+    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_fork_command_fischer
+    cmd = ShogiServer::Command.factory("%%FORK server-denou-14400-60F+p1+p2+20130223185013 buoy_denou-14400-60F", @p)
+    assert_instance_of(ShogiServer::ForkCommand, cmd)
+  end
+
   def test_fork_command2
     cmd = ShogiServer::Command.factory("%%FORK server-denou-14400-60+p1+p2+20130223185013", @p)
     assert_instance_of(ShogiServer::ForkCommand, cmd)
   end
 
+  def test_fork_command2_fischer
+    cmd = ShogiServer::Command.factory("%%FORK server-denou-14400-60F+p1+p2+20130223185013", @p)
+    assert_instance_of(ShogiServer::ForkCommand, cmd)
+  end
+
   def test_void_command
     cmd = ShogiServer::Command.factory("%%%HOGE", @p)
     assert_instance_of(ShogiServer::VoidCommand, cmd)
index 52227e0..6fba0f0 100644 (file)
@@ -26,6 +26,14 @@ $league.event = "test"
 
 class TestGame < Test::Unit::TestCase
 
+  def test_parse_time
+    assert_equal ShogiServer::Game::TimeControlParams.new(1500,0,0,false,false),
+                 ShogiServer::Game.parse_time("hoge-1500-0")
+    assert_equal ShogiServer::Game::TimeControlParams.new(600, 0, 10, false, false),
+                 ShogiServer::Game.parse_time("hoge-600-10F")
+    assert_equal true, ShogiServer::Game.parse_time("hoge-600-10f").error
+  end
+
   def test_new
     game_name = "hoge-1500-0"
     board = ShogiServer::Board.new
@@ -42,7 +50,7 @@ class TestGame < Test::Unit::TestCase
 
     p1_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -57,6 +65,7 @@ BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
 Byoyomi:0
+Increment:0
 Least_Time_Per_Move:#{$options["least-time-per-move"]}
 END Time
 BEGIN Position
@@ -77,7 +86,7 @@ EOF
 
     p2_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -92,6 +101,115 @@ BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
 Byoyomi:0
+Increment:0
+Least_Time_Per_Move:#{$options["least-time-per-move"]}
+END Time
+BEGIN Position
+P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
+P2 * -HI *  *  *  *  * -KA * 
+P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
+P4 *  *  *  *  *  *  *  *  * 
+P5 *  *  *  *  *  *  *  *  * 
+P6 *  *  *  *  *  *  *  *  * 
+P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
+P8 * +KA *  *  *  *  * +HI * 
+P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
++
+END Position
+END Game_Summary
+EOF
+    assert_equal(p2_out, p2.out.first)
+
+    file = Pathname.new(game.logfile)
+    log = file.read
+    assert_equal(<<EOF, log.gsub(/^\$START_TIME.*?\n/,''))
+V2
+N+p1
+N-p2
+'Max_Moves:#{$options["max-moves"]}
+'Least_Time_Per_Move:#{$options["least-time-per-move"]}
+$EVENT:#{game.game_id}
+P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
+P2 * -HI *  *  *  *  * -KA * 
+P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
+P4 *  *  *  *  *  *  *  *  * 
+P5 *  *  *  *  *  *  *  *  * 
+P6 *  *  *  *  *  *  *  *  * 
+P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
+P8 * +KA *  *  *  *  * +HI * 
+P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
++
+EOF
+  end
+
+  def test_new_fischer
+    game_name = "hoge-600-10F"
+    board = ShogiServer::Board.new
+    board.initial
+    p1 = MockPlayer.new
+    p1.sente = true
+    p1.name  = "p1"
+    p2 = MockPlayer.new
+    p2.sente = false
+    p2.name  = "p2"
+
+    game = ShogiServer::Game.new game_name, p1, p2, board
+    assert_equal "", game.last_move
+
+    p1_out = <<EOF
+BEGIN Game_Summary
+Protocol_Version:1.2
+Protocol_Mode:Server
+Format:Shogi 1.0
+Declaration:Jishogi 1.1
+Game_ID:#{game.game_id}
+Name+:p1
+Name-:p2
+Your_Turn:+
+Rematch_On_Draw:NO
+To_Move:+
+Max_Moves:#{$options["max-moves"]}
+BEGIN Time
+Time_Unit:1sec
+Total_Time:600
+Byoyomi:0
+Increment:10
+Least_Time_Per_Move:#{$options["least-time-per-move"]}
+END Time
+BEGIN Position
+P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
+P2 * -HI *  *  *  *  * -KA * 
+P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
+P4 *  *  *  *  *  *  *  *  * 
+P5 *  *  *  *  *  *  *  *  * 
+P6 *  *  *  *  *  *  *  *  * 
+P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
+P8 * +KA *  *  *  *  * +HI * 
+P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
++
+END Position
+END Game_Summary
+EOF
+    assert_equal(p1_out, p1.out.first)
+
+    p2_out = <<EOF
+BEGIN Game_Summary
+Protocol_Version:1.2
+Protocol_Mode:Server
+Format:Shogi 1.0
+Declaration:Jishogi 1.1
+Game_ID:#{game.game_id}
+Name+:p1
+Name-:p2
+Your_Turn:-
+Rematch_On_Draw:NO
+To_Move:+
+Max_Moves:#{$options["max-moves"]}
+BEGIN Time
+Time_Unit:1sec
+Total_Time:600
+Byoyomi:0
+Increment:10
 Least_Time_Per_Move:#{$options["least-time-per-move"]}
 END Time
 BEGIN Position
@@ -118,6 +236,7 @@ N+p1
 N-p2
 'Max_Moves:#{$options["max-moves"]}
 'Least_Time_Per_Move:#{$options["least-time-per-move"]}
+'Increment:10
 $EVENT:#{game.game_id}
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
@@ -148,7 +267,7 @@ EOF
 
     p1_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -163,6 +282,7 @@ BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
 Byoyomi:0
+Increment:0
 Least_Time_Per_Move:#{$options["least-time-per-move"]}
 END Time
 BEGIN Position
@@ -184,7 +304,7 @@ EOF
 
     p2_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -199,6 +319,7 @@ BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
 Byoyomi:0
+Increment:0
 Least_Time_Per_Move:#{$options["least-time-per-move"]}
 END Time
 BEGIN Position
@@ -259,7 +380,7 @@ EOF
 
     p1_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -274,6 +395,7 @@ BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
 Byoyomi:0
+Increment:0
 Least_Time_Per_Move:#{$options["least-time-per-move"]}
 END Time
 BEGIN Position
@@ -296,7 +418,7 @@ EOF
 
     p2_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -311,6 +433,7 @@ BEGIN Time
 Time_Unit:1sec
 Total_Time:1500
 Byoyomi:0
+Increment:0
 Least_Time_Per_Move:#{$options["least-time-per-move"]}
 END Time
 BEGIN Position
index 127203c..eb9c18e 100644 (file)
@@ -42,7 +42,7 @@ class TestGameWithLeastZero < Test::Unit::TestCase
 
     p1_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -77,7 +77,7 @@ EOF
 
     p2_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -148,7 +148,7 @@ EOF
 
     p1_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -184,7 +184,7 @@ EOF
 
     p2_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -259,7 +259,7 @@ EOF
 
     p1_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
@@ -296,7 +296,7 @@ EOF
 
     p2_out = <<EOF
 BEGIN Game_Summary
-Protocol_Version:1.1
+Protocol_Version:1.2
 Protocol_Mode:Server
 Format:Shogi 1.0
 Declaration:Jishogi 1.1
index ad4cc93..2cbf060 100644 (file)
@@ -43,6 +43,16 @@ class TestLogin < Test::Unit::TestCase
     assert_equal("*", login.turn_preference)
   end
 
+  def test_login_factory_csa_fischer
+    player = ShogiServer::BasicPlayer.new
+    player.name = "hoge"
+    login = ShogiServer::Login::factory("LOGIN hoge floodagate-600-10F,xyz", player)
+    assert_instance_of(ShogiServer::LoginCSA, login)
+    assert_equal("xyz", player.password)
+    assert_equal(@p_csa.player_id, player.player_id)
+    assert_equal("*", login.turn_preference)
+  end
+
   def test_login_factory_csa_no_gamename
     player = ShogiServer::BasicPlayer.new
     player.name = "hoge"
@@ -65,6 +75,17 @@ class TestLogin < Test::Unit::TestCase
     assert_equal("floodgate-900-0", login.gamename)
   end
 
+  def test_login_factory_csa_with_black_fischer
+    player = ShogiServer::BasicPlayer.new
+    player.name = "hoge"
+    login = ShogiServer::Login::factory("LOGIN hoge floodgate-900-10F-B,xyz", player)
+    assert_instance_of(ShogiServer::LoginCSA, login)
+    assert_equal("xyz", player.password)
+    assert_equal(@p_csa.player_id, player.player_id)
+    assert_equal("+", login.turn_preference)
+    assert_equal("floodgate-900-10F", login.gamename)
+  end
+
   def test_login_factory_csa_with_white
     player = ShogiServer::BasicPlayer.new
     player.name = "hoge"
@@ -76,6 +97,17 @@ class TestLogin < Test::Unit::TestCase
     assert_equal("floodgate-900-0", login.gamename)
   end
 
+  def test_login_factory_csa_with_white
+    player = ShogiServer::BasicPlayer.new
+    player.name = "hoge"
+    login = ShogiServer::Login::factory("LOGIN hoge floodgate-900-10F-W,xyz", player)
+    assert_instance_of(ShogiServer::LoginCSA, login)
+    assert_equal("xyz", player.password)
+    assert_equal(@p_csa.player_id, player.player_id)
+    assert_equal("-", login.turn_preference)
+    assert_equal("floodgate-900-10F", login.gamename)
+  end
+
   def test_login_factory_csa_without_trip
     player = ShogiServer::BasicPlayer.new
     player.name = "hoge"
index 215fef4..e4d812e 100644 (file)
@@ -9,7 +9,7 @@ class DummyPlayer
   def initialize(mytime)
     @mytime = mytime
   end
-  attr_reader :mytime
+  attr_accessor :mytime
 end
 
 class TestTimeClockFactor < Test::Unit::TestCase
@@ -25,6 +25,11 @@ class TestTimeClockFactor < Test::Unit::TestCase
     c = ShogiServer::TimeClock::factory(1, "hoge-1500-060")
     assert_instance_of(ShogiServer::StopWatchClock, c)
   end
+
+  def test_check_clock_fischer
+    c = ShogiServer::TimeClock::factory(1, "hoge-600-10F")
+    assert_instance_of(ShogiServer::ChessClock, c)
+  end
 end
 
 class TestChessClock < Test::Unit::TestCase
@@ -113,6 +118,65 @@ class TestChessClockWithLeastZero < Test::Unit::TestCase
   end
 end
 
+class TestChessClockWithLeastZeroFischer < Test::Unit::TestCase
+  def test_time_duration_within_thinking_time
+    tc = ShogiServer::ChessClockWithLeastZero.new(0, 600, 0, 10)
+    assert_equal(0, tc.time_duration(100, 100.1, 100.9))  # 0.8
+    assert_equal(1, tc.time_duration(100, 100, 101))      # 1
+    assert_equal(1, tc.time_duration(100, 100.1, 101.9))  # 1.8
+    assert_equal(1, tc.time_duration(1,    100,   101))   # 1
+    assert_equal(2, tc.time_duration(100, 100.1, 102.9))  # 2.8
+    assert_equal(2, tc.time_duration(100, 100, 102))      # 2
+
+    assert_equal(0, tc.time_duration(100, 100, 99.9))     # -0.1
+  end
+
+  def test_time_duration_over_thinking_time
+    tc = ShogiServer::ChessClockWithLeastZero.new(0, 600, 0 ,10)
+    assert_equal(1, tc.time_duration(1,    100.1, 101.9))  # 1.8
+    assert_equal(2, tc.time_duration(2,    100.1, 102.9))  # 2.8
+  end
+
+  def test_with_fischer
+    tc = ShogiServer::ChessClockWithLeastZero.new(0, 600, 0, 10)
+
+    p = DummyPlayer.new 100
+    assert(!tc.timeout?(p, 100, 101))    # 1
+    assert(!tc.timeout?(p, 100, 209))    # 109
+    assert(!tc.timeout?(p, 100, 209.9))  # 109.9
+    assert(tc.timeout?(p, 100, 210))     # 110
+    assert(tc.timeout?(p, 100, 210.1))   # 110.1
+    assert(tc.timeout?(p, 100, 211))     # 111
+  end
+
+  def test_with_fischer2
+    tc = ShogiServer::ChessClockWithLeastZero.new(0, 0, 0, 10)
+
+    p = DummyPlayer.new 0
+    assert(!tc.timeout?(p, 100, 109))    # 9
+    assert(!tc.timeout?(p, 100, 109.9))  # 9.9
+    assert(tc.timeout?(p, 100, 110))     # 10
+    assert(tc.timeout?(p, 100, 110.1))   # 10.1
+    assert(tc.timeout?(p, 100, 110))     # 10.1
+  end
+
+  def test_process_time
+    tc = ShogiServer::ChessClockWithLeastZero.new(0, 600, 0, 10)
+
+    p = DummyPlayer.new 100
+    tc.process_time(p, 100, 101) # 1
+    assert_equal(109, p.mytime)
+
+    p = DummyPlayer.new 100
+    tc.process_time(p, 100, 111) # 11
+    assert_equal(99, p.mytime)
+
+    p = DummyPlayer.new 100
+    tc.process_time(p, 100, 209) # 109
+    assert_equal(1, p.mytime)
+  end
+end
+
 class TestStopWatchClock < Test::Unit::TestCase
   def test_time_duration
     tc = ShogiServer::StopWatchClock.new(1, 1500, 60)