From f128a5d0b7bebc9e96a4fa3a5ede28d43d8ee499 Mon Sep 17 00:00:00 2001 From: Daigo Moriwaki Date: Sun, 3 Apr 2016 09:53:05 +0000 Subject: [PATCH] Fix #36230: Support Fischer Time Control --- changelog | 23 +++++++ shogi_server/buoy.rb | 2 +- shogi_server/command.rb | 4 +- shogi_server/game.rb | 49 +++++++++++--- shogi_server/league/floodgate.rb | 2 +- shogi_server/login.rb | 4 +- shogi_server/time_clock.rb | 53 ++++++++------- test/TC_buoy.rb | 1 + test/TC_command.rb | 40 ++++++++++++ test/TC_game.rb | 135 +++++++++++++++++++++++++++++++++++++-- test/TC_game_least_0.rb | 12 ++-- test/TC_login.rb | 32 ++++++++++ test/TC_time_clock.rb | 66 ++++++++++++++++++- 13 files changed, 372 insertions(+), 51 deletions(-) diff --git a/changelog b/changelog index e448a2a..c7c1c52 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,26 @@ +2016-04-02 Daigo Moriwaki + + * [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:" for Fischer Time Control + - Kifu files can include the following comment for Fischer Time + Control: + - "'Increment:" + 2015-12-13 Daigo Moriwaki * [shogi-server] Enhance capability of Floodgate configuration file diff --git a/shogi_server/buoy.rb b/shogi_server/buoy.rb index e2f88ee..ec6a1ef 100644 --- a/shogi_server/buoy.rb +++ b/shogi_server/buoy.rb @@ -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 = {}) diff --git a/shogi_server/command.rb b/shogi_server/command.rb index 5b25200..b3e5ca2 100644 --- a/shogi_server/command.rb +++ b/shogi_server/command.rb @@ -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 diff --git a/shogi_server/game.rb b/shogi_server/game.rb index 2d0b0a5..9bcecfb 100644 --- a/shogi_server/game.rb +++ b/shogi_server/game.rb @@ -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 = < 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 diff --git a/test/TC_buoy.rb b/test/TC_buoy.rb index 03a18d2..091e940 100644 --- a/test/TC_buoy.rb +++ b/test/TC_buoy.rb @@ -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 diff --git a/test/TC_command.rb b/test/TC_command.rb index 5e231fa..3487b94 100644 --- a/test/TC_command.rb +++ b/test/TC_command.rb @@ -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) diff --git a/test/TC_game.rb b/test/TC_game.rb index 52227e0..6fba0f0 100644 --- a/test/TC_game.rb +++ b/test/TC_game.rb @@ -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 = <