From b0a2884dfa2bde82572e2156a7b6ec142bed51f5 Mon Sep 17 00:00:00 2001 From: Daigo Moriwaki Date: Mon, 13 Oct 2014 16:19:58 +0900 Subject: [PATCH] New feature: Zero least time per move. New command line option: --least-time-per-move n --- changelog | 5 + shogi-server | 18 +- shogi_server.rb | 1 - shogi_server/game.rb | 6 +- shogi_server/time_clock.rb | 54 +++++- test/TC_ALL.rb | 1 + test/TC_fork.rb | 50 ++++-- test/TC_game.rb | 15 +- test/TC_game_least_0.rb | 411 +++++++++++++++++++++++++++++++++++++++++++++ test/TC_time_clock.rb | 63 +++++-- test/baseclient.rb | 12 ++ 11 files changed, 593 insertions(+), 43 deletions(-) create mode 100644 test/TC_game_least_0.rb diff --git a/changelog b/changelog index 0d877cc..25da4bb 100644 --- a/changelog +++ b/changelog @@ -12,6 +12,11 @@ - A new log summary type, "max_moves_draw", has been assigned for games drawing with max moves. 'summary:max_moves_draw:name_sente draw:name_gote draw + - Least time per move: + - New command line option: --least-time-per-move n + This opotion specifies a least time in second per move, which + was previously 1 sec but is now by default 1, meaning that a + decimal fraction of time for a move will be truncated. 2014-07-19 Daigo Moriwaki diff --git a/shogi-server b/shogi-server index a20c410..d6695b7 100755 --- a/shogi-server +++ b/shogi-server @@ -76,6 +76,10 @@ OPTIONS port_number a port number for the server to listen. 4081 is often used. + --least-time-per-move n + Least time in second per move: 0, 1 (default 1). + - 0: The new rule that CSA introduced in November 2014. + - 1: The old rule before it. --max-moves n when a game with the n-th move played does not end, make the game a draw. Default 256. 0 disables this feature. @@ -194,11 +198,12 @@ end def parse_command_line options = Hash::new parser = GetoptLong.new( - ["--daemon", GetoptLong::REQUIRED_ARGUMENT], - ["--floodgate-games", GetoptLong::REQUIRED_ARGUMENT], - ["--max-moves", GetoptLong::REQUIRED_ARGUMENT], - ["--pid-file", GetoptLong::REQUIRED_ARGUMENT], - ["--player-log-dir", GetoptLong::REQUIRED_ARGUMENT]) + ["--daemon", GetoptLong::REQUIRED_ARGUMENT], + ["--floodgate-games", GetoptLong::REQUIRED_ARGUMENT], + ["--least-time-per-move", GetoptLong::REQUIRED_ARGUMENT], + ["--max-moves", GetoptLong::REQUIRED_ARGUMENT], + ["--pid-file", GetoptLong::REQUIRED_ARGUMENT], + ["--player-log-dir", GetoptLong::REQUIRED_ARGUMENT]) parser.quiet = true begin parser.each_option do |name, arg| @@ -270,6 +275,9 @@ def check_command_line $options["max-moves"] ||= 256 $options["max-moves"] = $options["max-moves"].to_i + + $options["least-time-per-move"] ||= 1 + $options["least-time-per-move"] = $options["least-time-per-move"].to_i end # See if a file can be created in the directory. diff --git a/shogi_server.rb b/shogi_server.rb index f1161c2..dd227ae 100644 --- a/shogi_server.rb +++ b/shogi_server.rb @@ -49,7 +49,6 @@ Max_Identifier_Length = 32 Default_Timeout = 60 # for single socket operation Default_Game_Name = "default-1500-0" One_Time = 10 -Least_Time_Per_Move = 1 Login_Time = 300 # time for LOGIN Revision = "20131215" diff --git a/shogi_server/game.rb b/shogi_server/game.rb index a10cb65..4ddd121 100644 --- a/shogi_server/game.rb +++ b/shogi_server/game.rb @@ -71,7 +71,7 @@ class Game @total_time = $1.to_i @byoyomi = $2.to_i - @time_clock = TimeClock::factory(Least_Time_Per_Move, @game_name) + @time_clock = TimeClock::factory($options["least-time-per-move"], @game_name) end if (player0.sente) @@ -369,7 +369,7 @@ BEGIN Time Time_Unit:#{@time_clock.time_unit} Total_Time:#{@total_time} Byoyomi:#{@byoyomi} -Least_Time_Per_Move:#{Least_Time_Per_Move} +Least_Time_Per_Move:#{$options["least-time-per-move"]} Remaining_Time+:#{@sente.mytime} Remaining_Time-:#{@gote.mytime} Last_Move:#{@last_move} @@ -403,7 +403,7 @@ BEGIN Time Time_Unit:#{@time_clock.time_unit} Total_Time:#{@total_time} Byoyomi:#{@byoyomi} -Least_Time_Per_Move:#{Least_Time_Per_Move} +Least_Time_Per_Move:#{$options["least-time-per-move"]} END Time BEGIN Position #{@board.initial_string.chomp} diff --git a/shogi_server/time_clock.rb b/shogi_server/time_clock.rb index 6426fda..1198042 100644 --- a/shogi_server/time_clock.rb +++ b/shogi_server/time_clock.rb @@ -36,7 +36,11 @@ class TimeClock if (byoyomi_str == "060") @time_clock = StopWatchClock.new(least_time_per_move, total_time, byoyomi) else - @time_clock = ChessClock.new(least_time_per_move, total_time, byoyomi) + if least_time_per_move == 0 + @time_clock = ChessClockWithLeastZero.new(least_time_per_move, total_time, byoyomi) + else + @time_clock = ChessClock.new(least_time_per_move, total_time, byoyomi) + end end end @@ -48,7 +52,7 @@ class TimeClock # Returns thinking time duration # - def time_duration(start_time, end_time) + def time_duration(mytime, start_time, end_time) # implement this return 9999999 end @@ -69,7 +73,7 @@ class TimeClock # Updates a player's remaining time and returns thinking time. # def process_time(player, start_time, end_time) - t = time_duration(start_time, end_time) + t = time_duration(player.mytime, start_time, end_time) player.mytime -= t if (player.mytime < 0) @@ -87,12 +91,12 @@ class ChessClock < TimeClock super end - def time_duration(start_time, end_time) + def time_duration(mytime, start_time, end_time) return [(end_time - start_time).floor, @least_time_per_move].max end def timeout?(player, start_time, end_time) - t = time_duration(start_time, end_time) + t = time_duration(player.mytime, start_time, end_time) if ((player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0))) @@ -107,6 +111,42 @@ class ChessClock < TimeClock end end +# Calculates thinking time with chess clock, truncating decimal seconds for +# thinking time. This is a new rule that CSA introduced in November 2014. +# +# least_time_per_move should be 0. +# byoyomi should be more than 0. +# +class ChessClockWithLeastZero < ChessClock + def initialize(least_time_per_move, total_time, byoyomi) + if least_time_per_move != 0 + raise ArgumentError, "least_time_per_move #{least_time_per_move} should be 0." + end + super + end + + def time_duration(mytime, start_time, end_time) + t = end_time - start_time + if @byoyomi > 0 + # If the remaining thinking time covers the duration t, floor it. + if mytime + @byoyomi - t > 0 + t = t.floor + else + t = t.ceil + end + else + # Thinking time only + t = t.floor + end + + return t + end + + def to_s + return "ChessClockWithLeastZero: LeastTimePerMove %d; TotalTime %d; Byoyomi %d" % [@least_time_per_move, @total_time, @byoyomi] + end +end + class StopWatchClock < TimeClock def initialize(least_time_per_move, total_time, byoyomi) super @@ -116,13 +156,13 @@ class StopWatchClock < TimeClock return "1min" end - def time_duration(start_time, end_time) + def time_duration(mytime, start_time, end_time) t = [(end_time - start_time).floor, @least_time_per_move].max return (t / @byoyomi) * @byoyomi end def timeout?(player, start_time, end_time) - t = time_duration(start_time, end_time) + t = time_duration(player.mytime, start_time, end_time) if (player.mytime <= t) return true diff --git a/test/TC_ALL.rb b/test/TC_ALL.rb index a21726d..256a0cc 100644 --- a/test/TC_ALL.rb +++ b/test/TC_ALL.rb @@ -13,6 +13,7 @@ require 'TC_floodgate_thread.rb' require 'TC_fork' require 'TC_functional' require 'TC_game' +require 'TC_game_least_0' require 'TC_game_result' require 'TC_handicapped_boards' # This game has more thatn 256 moves. diff --git a/test/TC_fork.rb b/test/TC_fork.rb index b788fd1..cfdf9e1 100644 --- a/test/TC_fork.rb +++ b/test/TC_fork.rb @@ -16,8 +16,11 @@ class TestFork < BaseClient def test_wrong_game @admin = SocketPlayer.new "dummy", "admin", false @admin.connect + sleep 0.1 @admin.reader + sleep 0.1 @admin.login + sleep 0.1 result, result2 = handshake do @admin.puts "%%FORK wronggame-900-0 buoy_WrongGame-900-0" @@ -31,8 +34,11 @@ class TestFork < BaseClient def test_too_short_fork @admin = SocketPlayer.new "dummy", "admin", false @admin.connect + sleep 0.1 @admin.reader + sleep 0.1 @admin.login + sleep 0.1 result, result2 = handshake do source_game = parse_game_name(@admin) @@ -49,8 +55,12 @@ class TestFork < BaseClient @admin = SocketPlayer.new "dummy", "admin", "*" @admin.connect + sleep 0.1 @admin.reader + sleep 0.1 @admin.login + sleep 0.1 + assert buoy.is_new_game?("buoy_Fork-1500-0") result, result2 = handshake do @@ -63,26 +73,34 @@ class TestFork < BaseClient @p1 = SocketPlayer.new "buoy_Fork", "p1", true @p2 = SocketPlayer.new "buoy_Fork", "p2", false @p1.connect + sleep 0.1 @p2.connect + sleep 0.1 @p1.reader + sleep 0.1 @p2.reader + sleep 0.1 @p1.login + sleep 0.1 @p2.login - sleep 1 + sleep 0.1 @p1.game + sleep 0.1 @p2.game - sleep 1 @p1.agree + sleep 0.1 @p2.agree - sleep 1 + sleep 0.1 assert /^Total_Time:1500/ =~ @p1.message assert /^Total_Time:1500/ =~ @p2.message @p2.move("-3334FU") - sleep 1 + sleep 0.1 @p1.toryo - sleep 1 + sleep 0.1 @p2.logout + sleep 0.1 @p1.logout + sleep 0.1 @admin.logout end @@ -92,8 +110,11 @@ class TestFork < BaseClient @admin = SocketPlayer.new "dummy", "admin", "*" @admin.connect + sleep 0.1 @admin.reader + sleep 0.1 @admin.login + sleep 0.1 result, result2 = handshake do source_game = parse_game_name(@admin) @@ -106,26 +127,35 @@ class TestFork < BaseClient @p1 = SocketPlayer.new "buoy_TestFork_1", "p1", true @p2 = SocketPlayer.new "buoy_TestFork_1", "p2", false @p1.connect + sleep 0.1 @p2.connect + sleep 0.1 @p1.reader + sleep 0.1 @p2.reader + sleep 0.1 @p1.login + sleep 0.1 @p2.login - sleep 1 + sleep 0.1 @p1.game + sleep 0.1 @p2.game - sleep 1 + sleep 0.1 @p1.agree + sleep 0.1 @p2.agree - sleep 1 + sleep 0.1 assert /^Total_Time:1500/ =~ @p1.message assert /^Total_Time:1500/ =~ @p2.message @p2.move("-3334FU") - sleep 1 + sleep 0.1 @p1.toryo - sleep 1 + sleep 0.1 @p2.logout + sleep 0.1 @p1.logout + sleep 0.1 @admin.logout end diff --git a/test/TC_game.rb b/test/TC_game.rb index 8d7d493..08ffcf2 100644 --- a/test/TC_game.rb +++ b/test/TC_game.rb @@ -5,6 +5,9 @@ require 'shogi_server/board' require 'shogi_server/game' require 'shogi_server/player' +$options = {} +$options["least-time-per-move"] = 1 + def log_message(str) $stderr.puts str end @@ -52,7 +55,7 @@ BEGIN Time Time_Unit:1sec Total_Time:1500 Byoyomi:0 -Least_Time_Per_Move:1 +Least_Time_Per_Move:#{$options["least-time-per-move"]} END Time BEGIN Position P1-KY-KE-GI-KI-OU-KI-GI-KE-KY @@ -86,7 +89,7 @@ BEGIN Time Time_Unit:1sec Total_Time:1500 Byoyomi:0 -Least_Time_Per_Move:1 +Least_Time_Per_Move:#{$options["least-time-per-move"]} END Time BEGIN Position P1-KY-KE-GI-KI-OU-KI-GI-KE-KY @@ -154,7 +157,7 @@ BEGIN Time Time_Unit:1sec Total_Time:1500 Byoyomi:0 -Least_Time_Per_Move:1 +Least_Time_Per_Move:#{$options["least-time-per-move"]} END Time BEGIN Position P1-KY-KE-GI-KI-OU-KI-GI-KE-KY @@ -189,7 +192,7 @@ BEGIN Time Time_Unit:1sec Total_Time:1500 Byoyomi:0 -Least_Time_Per_Move:1 +Least_Time_Per_Move:#{$options["least-time-per-move"]} END Time BEGIN Position P1-KY-KE-GI-KI-OU-KI-GI-KE-KY @@ -261,7 +264,7 @@ BEGIN Time Time_Unit:1sec Total_Time:1500 Byoyomi:0 -Least_Time_Per_Move:1 +Least_Time_Per_Move:#{$options["least-time-per-move"]} END Time BEGIN Position P1-KY-KE-GI-KI-OU-KI-GI-KE-KY @@ -297,7 +300,7 @@ BEGIN Time Time_Unit:1sec Total_Time:1500 Byoyomi:0 -Least_Time_Per_Move:1 +Least_Time_Per_Move:#{$options["least-time-per-move"]} END Time BEGIN Position P1-KY-KE-GI-KI-OU-KI-GI-KE-KY diff --git a/test/TC_game_least_0.rb b/test/TC_game_least_0.rb new file mode 100644 index 0000000..3429f07 --- /dev/null +++ b/test/TC_game_least_0.rb @@ -0,0 +1,411 @@ +$:.unshift File.join(File.dirname(__FILE__), "..") +require 'test/unit' +require 'test/mock_player' +require 'shogi_server/board' +require 'shogi_server/game' +require 'shogi_server/player' + +$options = {} +$options["least-time-per-move"] = 0 + +def log_message(str) + $stderr.puts str +end + +def log_warning(str) + $stderr.puts str +end + +def log_error(str) + $stderr.puts str +end + +$league = ShogiServer::League.new(File.dirname(__FILE__)) +$league.event = "test" + +class TestGameWithLeastZero < Test::Unit::TestCase + + def test_new + game_name = "hoge-1500-10" + 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 = <