• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

shogi-server source


Commit MetaInfo

Revisão850fc7e54e8bb0d1667f1c3ac3076f3540c66943 (tree)
Hora2013-09-07 16:52:57
AutorDaigo Moriwaki <beatles@user...>
CommiterDaigo Moriwaki

Mensagem de Log

Merge branch 'wdoor-stable-fork' into wdoor-stable

Mudança Sumário

Diff

--- a/changelog
+++ b/changelog
@@ -1,3 +1,40 @@
1+2013-04-07 Daigo Moriwaki <daigo at debian dot org>
2+
3+ * [shogi-server]
4+ - shogi_server/{game,time_clock}.rb:
5+ Adds variations of thinking time calculation: ChessClock
6+ (current) and StopWatchClock (new).
7+ StopWatchClock, which is usually used at official games of human
8+ professional players, is a clock where thiking time less than a
9+ miniute is regarded as zero.
10+ To select StopWatchClock, use a special game name with "060"
11+ byoyomi time. ex. "gamename_1500_060".
12+
13+2013-03-31 Daigo Moriwaki <daigo at debian dot org>
14+
15+ * [shogi-server]
16+ - %%FORK command: %%FORK <source_game> [<new_buoy_game>] [<nth-move>]
17+ The new_buoy_game parameter is now optional. If it is not
18+ supplied, Shogi-server generates a new buoy game name from
19+ source_game.
20+ - command.rb: More elaborate error messages for the %%GAME command.
21+
22+2013-02-23 Daigo Moriwaki <daigo at debian dot org>
23+
24+ * [shogi-server]
25+ - New command: %%FORK <source_game> <new_buoy_game> [<nth-move>]
26+ Fork a new game from the posistion where the n-th (starting from
27+ one) move of a source game is played. The new game should be a
28+ valid buoy game name. The default value of n is the position
29+ where the previous position of the last one.
30+ - The objective of this command: The shogi-server may be used as
31+ the back end server of computer-human match where a human player
32+ plays with a real board and someone, or a proxy, inputs moves to
33+ the shogi-server. If the proxy happens to enter a wrong move,
34+ with this command you can restart a new buoy game from the
35+ previous stable position.
36+ ex. %%FORK server-denou-14400-60+p1+p2+20130223185013 buoy_denou-14400-60
37+
138 2012-12-30 Daigo Moriwaki <daigo at debian dot org>
239
340 * [shogi-server]
--- a/shogi_server/board.rb
+++ b/shogi_server/board.rb
@@ -43,17 +43,42 @@ EOF
4343
4444 # Split a moves line into an array of a move string.
4545 # If it fails to parse the moves, it raises WrongMoves.
46- # @param moves a moves line. Ex. "+776FU-3334Fu"
47- # @return an array of a move string. Ex. ["+7776FU", "-3334FU"]
46+ # @param moves a moves line. Ex. "+776FU-3334FU" or
47+ # moves with times. Ex "+776FU,T2-3334FU,T5"
48+ # @return an array of a move string. Ex. ["+7776FU", "-3334FU"] or
49+ # an array of arrays. Ex. [["+7776FU","T2"], ["-3334FU", "T5"]]
4850 #
4951 def Board.split_moves(moves)
5052 ret = []
5153
52- rs = moves.gsub %r{[\+\-]\d{4}\w{2}} do |s|
53- ret << s
54- ""
55- end
56- raise WrongMoves, rs unless rs.empty?
54+ i=0
55+ tmp = ""
56+ while i<moves.size
57+ if moves[i,1] == "+" ||
58+ moves[i,1] == "-" ||
59+ i == moves.size - 1
60+ if i == moves.size - 1
61+ tmp << moves[i,1]
62+ end
63+ unless tmp.empty?
64+ a = tmp.split(",")
65+ if a[0].size != 7
66+ raise WrongMoves, a[0]
67+ end
68+ if a.size == 1 # "+7776FU"
69+ ret << a[0]
70+ else # "+7776FU,T2"
71+ unless /^T\d+/ =~ a[1]
72+ raise WrongMoves, a[1]
73+ end
74+ ret << a
75+ end
76+ tmp = ""
77+ end
78+ end
79+ tmp << moves[i,1]
80+ i += 1
81+ end
5782
5883 return ret
5984 end
@@ -230,14 +255,21 @@ EOF
230255
231256 # Set up a board starting with a position after the moves.
232257 # Failing to parse the moves raises an ArgumentError.
233- # @param moves an array of moves. ex. ["+7776FU", "-3334FU"]
258+ # @param moves an array of moves. ex. ["+7776FU", "-3334FU"] or
259+ # an array of arrays. ex. [["+7776FU","T2"], ["-3334FU","T5"]]
234260 #
235261 def set_from_moves(moves)
236262 initial()
237263 return :normal if moves.empty?
238264 rt = nil
239265 moves.each do |move|
240- rt = handle_one_move(move, @teban)
266+ rt = nil
267+ case move
268+ when Array
269+ rt = handle_one_move(move[0], @teban)
270+ when String
271+ rt = handle_one_move(move, @teban)
272+ end
241273 raise ArgumentError, "bad moves: #{moves}" unless rt == :normal
242274 end
243275 @initial_moves = moves.dup
--- a/shogi_server/buoy.rb
+++ b/shogi_server/buoy.rb
@@ -21,7 +21,7 @@ module ShogiServer
2121 end
2222
2323 def ==(rhs)
24- return (@game_name == rhs.game_name &&
24+ return (@game_name == rhs.game_name &&
2525 @moves == rhs.moves &&
2626 @owner == rhs.owner &&
2727 @count == rhs.count)
--- a/shogi_server/command.rb
+++ b/shogi_server/command.rb
@@ -69,6 +69,9 @@ module ShogiServer
6969 my_sente_str = $3
7070 cmd = GameChallengeCommand.new(str, player,
7171 command_name, game_name, my_sente_str)
72+ when /^%%(GAME|CHALLENGE)\s+(\S+)/
73+ msg = "A turn identifier is required"
74+ cmd = ErrorCommand.new(str, player, msg)
7275 when /^%%CHAT\s+(.+)/
7376 message = $1
7477 cmd = ChatCommand.new(str, player, message, $league.players)
@@ -94,6 +97,19 @@ module ShogiServer
9497 when /^%%GETBUOYCOUNT\s+(\S+)/
9598 game_name = $1
9699 cmd = GetBuoyCountCommand.new(str, player, game_name)
100+ when /^%%FORK\s+(\S+)\s+(\S+)(.*)/
101+ source_game = $1
102+ new_buoy_game = $2
103+ nth_move = nil
104+ if $3 && /^\s+(\d+)/ =~ $3
105+ nth_move = $3.to_i
106+ end
107+ cmd = ForkCommand.new(str, player, source_game, new_buoy_game, nth_move)
108+ when /^%%FORK\s+(\S+)$/
109+ source_game = $1
110+ new_buoy_game = nil
111+ nth_move = nil
112+ cmd = ForkCommand.new(str, player, source_game, new_buoy_game, nth_move)
97113 when /^\s*$/
98114 cmd = SpaceCommand.new(str, player)
99115 when /^%%%[^%]/
@@ -481,7 +497,16 @@ module ShogiServer
481497
482498 def call
483499 if (! Login::good_game_name?(@game_name))
484- @player.write_safe(sprintf("##[ERROR] bad game name\n"))
500+ @player.write_safe(sprintf("##[ERROR] bad game name: %s.\n", @game_name))
501+ if (/^(.+)-\d+-\d+$/ =~ @game_name)
502+ if Login::good_identifier?($1)
503+ # do nothing
504+ else
505+ @player.write_safe(sprintf("##[ERROR] invalid identifiers are found or too many characters are used.\n"))
506+ end
507+ else
508+ @player.write_safe(sprintf("##[ERROR] game name should consist of three parts like game-1500-60.\n"))
509+ end
485510 return :continue
486511 elsif ((@player.status == "connected") || (@player.status == "game_waiting"))
487512 ## continue
@@ -666,9 +691,9 @@ module ShogiServer
666691 # Command for an error
667692 #
668693 class ErrorCommand < Command
669- def initialize(str, player)
670- super
671- @msg = nil
694+ def initialize(str, player, msg=nil)
695+ super(str, player)
696+ @msg = msg || "unknown command"
672697 end
673698 attr_reader :msg
674699
@@ -676,7 +701,7 @@ module ShogiServer
676701 cmd = @str.chomp
677702 # Aim to hide a possible password
678703 cmd.gsub!(/LOGIN\s*(\w+)\s+.*/i, 'LOGIN \1...')
679- @msg = "##[ERROR] unknown command %s\n" % [cmd]
704+ @msg = "##[ERROR] %s: %s\n" % [@msg, cmd]
680705 @player.write_safe(@msg)
681706 log_error(@msg)
682707 return :continue
@@ -809,4 +834,69 @@ module ShogiServer
809834 end
810835 end
811836
837+ # %%FORK <source_game> <new_buoy_game> [<nth-move>]
838+ # Fork a new game from the posistion where the n-th (starting from 1) move
839+ # of a source game is played. The new game should be a valid buoy game
840+ # name. The default value of n is the position where the previous position
841+ # of the last one.
842+ #
843+ class ForkCommand < Command
844+ def initialize(str, player, source_game, new_buoy_game, nth_move)
845+ super(str, player)
846+ @source_game = source_game
847+ @new_buoy_game = new_buoy_game
848+ @nth_move = nth_move # may be nil
849+ end
850+ attr_reader :new_buoy_game
851+
852+ def decide_new_buoy_game_name
853+ name = nil
854+ total_time = nil
855+ byo_time = nil
856+
857+ if @source_game.split("+").size >= 2 &&
858+ /^([^-]+)-(\d+)-(\d+)/ =~ @source_game.split("+")[1]
859+ name = $1
860+ total_time = $2
861+ byo_time = $3
862+ end
863+ if name == nil || total_time == nil || byo_time == nil
864+ @player.write_safe(sprintf("##[ERROR] wrong source game name to make a new buoy game name: %s\n", @source_game))
865+ log_error "Received a wrong source game name to make a new buoy game name: %s from %s." % [@source_game, @player.name]
866+ return :continue
867+ end
868+ @new_buoy_game = "buoy_%s_%d-%s-%s" % [name, @nth_move, total_time, byo_time]
869+ @player.write_safe(sprintf("##[FORK]: new buoy game name: %s\n", @new_buoy_game))
870+ @player.write_safe("##[FORK] +OK\n")
871+ end
872+
873+ def call
874+ game = $league.games[@source_game]
875+ unless game
876+ @player.write_safe(sprintf("##[ERROR] wrong source game name: %s\n", @source_game))
877+ log_error "Received a wrong source game name: %s from %s." % [@source_game, @player.name]
878+ return :continue
879+ end
880+
881+ moves = game.read_moves # [["+7776FU","T2"],["-3334FU","T5"]]
882+ @nth_move = moves.size - 1 unless @nth_move
883+ if @nth_move > moves.size or @nth_move < 1
884+ @player.write_safe(sprintf("##[ERROR] number of moves to fork is out of range: %s.\n", moves.size))
885+ log_error "Number of moves to fork is out of range: %s [%s]" % [@nth_move, @player.name]
886+ return :continue
887+ end
888+ new_moves_str = ""
889+ moves[0...@nth_move].each do |m|
890+ new_moves_str << m.join(",")
891+ end
892+
893+ unless @new_buoy_game
894+ decide_new_buoy_game_name
895+ end
896+
897+ buoy_cmd = SetBuoyCommand.new(@str, @player, @new_buoy_game, new_moves_str, 1)
898+ return buoy_cmd.call
899+ end
900+ end
901+
812902 end # module ShogiServer
--- a/shogi_server/game.rb
+++ b/shogi_server/game.rb
@@ -19,6 +19,7 @@
1919
2020 require 'shogi_server/league/floodgate'
2121 require 'shogi_server/game_result'
22+require 'shogi_server/time_clock'
2223 require 'shogi_server/util'
2324
2425 module ShogiServer # for a namespace
@@ -69,6 +70,8 @@ class Game
6970 if (@game_name =~ /-(\d+)-(\d+)$/)
7071 @total_time = $1.to_i
7172 @byoyomi = $2.to_i
73+
74+ @time_clock = TimeClock::factory(Least_Time_Per_Move, @game_name)
7275 end
7376
7477 if (player0.sente)
@@ -87,7 +90,16 @@ class Game
8790 @sente.game = self
8891 @gote.game = self
8992
90- @last_move = @board.initial_moves.empty? ? "" : "%s,T1" % [@board.initial_moves.last]
93+ @last_move = ""
94+ unless @board.initial_moves.empty?
95+ last_move = @board.initial_moves.last
96+ case last_move
97+ when Array
98+ @last_move = last_move.join(",")
99+ when String
100+ @last_move = "%s,T1" % [last_move]
101+ end
102+ end
91103 @current_turn = @board.initial_moves.size
92104
93105 @sente.status = "agree_waiting"
@@ -110,6 +122,7 @@ class Game
110122 $league.games[@game_id] = self
111123
112124 log_message(sprintf("game created %s", @game_id))
125+ log_message(" " + @time_clock.to_s)
113126
114127 @start_time = nil
115128 @fh = open(@logfile, "w")
@@ -118,7 +131,7 @@ class Game
118131
119132 propose
120133 end
121- attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors
134+ attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors, :time_clock
122135 attr_accessor :last_move, :current_turn
123136 attr_reader :result, :prepared_time
124137
@@ -218,22 +231,16 @@ class Game
218231 return nil
219232 end
220233
221- finish_flag = true
222234 @end_time = end_time
223- t = [(@end_time - @start_time).floor, Least_Time_Per_Move].max
224-
235+ finish_flag = true
225236 move_status = nil
226- if ((@current_player.mytime - t <= -@byoyomi) &&
227- ((@total_time > 0) || (@byoyomi > 0)))
237+
238+ if (@time_clock.timeout?(@current_player, @start_time, @end_time))
228239 status = :timeout
229240 elsif (str == :timeout)
230241 return false # time isn't expired. players aren't swapped. continue game
231242 else
232- @current_player.mytime -= t
233- if (@current_player.mytime < 0)
234- @current_player.mytime = 0
235- end
236-
243+ t = @time_clock.process_time(@current_player, @start_time, @end_time)
237244 move_status = @board.handle_one_move(str, @sente == @current_player)
238245 # log_debug("move_status: %s for %s's %s" % [move_status, @sente == @current_player ? "BLACK" : "WHITE", str])
239246
@@ -332,8 +339,14 @@ class Game
332339 unless @board.initial_moves.empty?
333340 @fh.puts "'buoy game starting with %d moves" % [@board.initial_moves.size]
334341 @board.initial_moves.each do |move|
335- @fh.puts move
336- @fh.puts "T1"
342+ case move
343+ when Array
344+ @fh.puts move[0]
345+ @fh.puts move[1]
346+ when String
347+ @fh.puts move
348+ @fh.puts "T1"
349+ end
337350 end
338351 end
339352 end
@@ -392,7 +405,14 @@ Least_Time_Per_Move:#{Least_Time_Per_Move}
392405 END Time
393406 BEGIN Position
394407 #{@board.initial_string.chomp}
395-#{@board.initial_moves.collect {|m| m + ",T1"}.join("\n")}
408+#{@board.initial_moves.collect do |m|
409+ case m
410+ when Array
411+ m.join(",")
412+ when String
413+ m + ",T1"
414+ end
415+end.join("\n")}
396416 END Position
397417 END Game_Summary
398418 EOM
@@ -408,6 +428,21 @@ EOM
408428
409429 return false
410430 end
431+
432+ # Read the .csa file and returns an array of moves and times.
433+ # ex. [["+7776FU","T2"], ["-3334FU","T5"]]
434+ #
435+ def read_moves
436+ ret = []
437+ IO.foreach(@logfile) do |line|
438+ if /^[\+\-]\d{4}[A-Z]{2}/ =~ line
439+ ret << [line.chomp]
440+ elsif /^T\d*/ =~ line
441+ ret[-1] << line.chomp
442+ end
443+ end
444+ return ret
445+ end
411446
412447 private
413448
--- /dev/null
+++ b/shogi_server/time_clock.rb
@@ -0,0 +1,129 @@
1+## $Id$
2+
3+## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
4+## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
5+##
6+## This program is free software; you can redistribute it and/or modify
7+## it under the terms of the GNU General Public License as published by
8+## the Free Software Foundation; either version 2 of the License, or
9+## (at your option) any later version.
10+##
11+## This program is distributed in the hope that it will be useful,
12+## but WITHOUT ANY WARRANTY; without even the implied warranty of
13+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+## GNU General Public License for more details.
15+##
16+## You should have received a copy of the GNU General Public License
17+## along with this program; if not, write to the Free Software
18+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19+
20+module ShogiServer # for a namespace
21+
22+# Abstract class to caclulate thinking time.
23+#
24+class TimeClock
25+
26+ def TimeClock.factory(least_time_per_move, game_name)
27+ total_time_str = nil
28+ byoyomi_str = nil
29+ if (game_name =~ /-(\d+)-(\d+)$/)
30+ total_time_str = $1
31+ byoyomi_str = $2
32+ end
33+ total_time = total_time_str.to_i
34+ byoyomi = byoyomi_str.to_i
35+
36+ if (byoyomi_str == "060")
37+ @time_clock = StopWatchClock.new(least_time_per_move, total_time, byoyomi)
38+ else
39+ @time_clock = ChessClock.new(least_time_per_move, total_time, byoyomi)
40+ end
41+ end
42+
43+ def initialize(least_time_per_move, total_time, byoyomi)
44+ @least_time_per_move = least_time_per_move
45+ @total_time = total_time
46+ @byoyomi = byoyomi
47+ end
48+
49+ # Returns thinking time duration
50+ #
51+ def time_duration(start_time, end_time)
52+ # implement this
53+ return 9999999
54+ end
55+
56+ # If thinking time runs out, returns true; false otherwise.
57+ #
58+ def timeout?(player, start_time, end_time)
59+ # implement this
60+ return true
61+ end
62+
63+ # Updates a player's remaining time and returns thinking time.
64+ #
65+ def process_time(player, start_time, end_time)
66+ t = time_duration(start_time, end_time)
67+
68+ player.mytime -= t
69+ if (player.mytime < 0)
70+ player.mytime = 0
71+ end
72+
73+ return t
74+ end
75+end
76+
77+# Calculates thinking time with chess clock.
78+#
79+class ChessClock < TimeClock
80+ def initialize(least_time_per_move, total_time, byoyomi)
81+ super
82+ end
83+
84+ def time_duration(start_time, end_time)
85+ return [(end_time - start_time).floor, @least_time_per_move].max
86+ end
87+
88+ def timeout?(player, start_time, end_time)
89+ t = time_duration(start_time, end_time)
90+
91+ if ((player.mytime - t <= -@byoyomi) &&
92+ ((@total_time > 0) || (@byoyomi > 0)))
93+ return true
94+ else
95+ return false
96+ end
97+ end
98+
99+ def to_s
100+ return "ChessClock: LeastTimePerMove %d; TotalTime %d; Byoyomi %d" % [@least_time_per_move, @total_time, @byoyomi]
101+ end
102+end
103+
104+class StopWatchClock < TimeClock
105+ def initialize(least_time_per_move, total_time, byoyomi)
106+ super
107+ end
108+
109+ def time_duration(start_time, end_time)
110+ t = [(end_time - start_time).floor, @least_time_per_move].max
111+ return (t / @byoyomi) * @byoyomi
112+ end
113+
114+ def timeout?(player, start_time, end_time)
115+ t = time_duration(start_time, end_time)
116+
117+ if (player.mytime <= t)
118+ return true
119+ else
120+ return false
121+ end
122+ end
123+
124+ def to_s
125+ return "StopWatchClock: LeastTimePerMove %d; TotalTime %d; Byoyomi %d" % [@least_time_per_move, @total_time, @byoyomi]
126+ end
127+end
128+
129+end
--- a/test/TC_ALL.rb
+++ b/test/TC_ALL.rb
@@ -9,6 +9,7 @@ require 'TC_floodgate'
99 require 'TC_floodgate_history'
1010 require 'TC_floodgate_next_time_generator'
1111 require 'TC_floodgate_thread.rb'
12+require 'TC_fork'
1213 require 'TC_functional'
1314 require 'TC_game'
1415 require 'TC_game_result'
@@ -23,6 +24,7 @@ require 'TC_oute_sennichite'
2324 require 'TC_pairing'
2425 require 'TC_player'
2526 require 'TC_rating'
27+require 'TC_time_clock'
2628 require 'TC_uchifuzume'
2729 require 'TC_usi'
2830 require 'TC_util'
--- a/test/TC_command.rb
+++ b/test/TC_command.rb
@@ -228,6 +228,16 @@ class TestFactoryMethod < Test::Unit::TestCase
228228 assert_instance_of(ShogiServer::GetBuoyCountCommand, cmd)
229229 end
230230
231+ def test_fork_command
232+ cmd = ShogiServer::Command.factory("%%FORK server-denou-14400-60+p1+p2+20130223185013 buoy_denou-14400-60", @p)
233+ assert_instance_of(ShogiServer::ForkCommand, cmd)
234+ end
235+
236+ def test_fork_command2
237+ cmd = ShogiServer::Command.factory("%%FORK server-denou-14400-60+p1+p2+20130223185013", @p)
238+ assert_instance_of(ShogiServer::ForkCommand, cmd)
239+ end
240+
231241 def test_void_command
232242 cmd = ShogiServer::Command.factory("%%%HOGE", @p)
233243 assert_instance_of(ShogiServer::VoidCommand, cmd)
@@ -237,29 +247,29 @@ class TestFactoryMethod < Test::Unit::TestCase
237247 cmd = ShogiServer::Command.factory("should_be_error", @p)
238248 assert_instance_of(ShogiServer::ErrorCommand, cmd)
239249 cmd.call
240- assert_match /unknown command should_be_error/, cmd.msg
250+ assert_match /unknown command: should_be_error/, cmd.msg
241251 end
242252
243253 def test_error_login
244254 cmd = ShogiServer::Command.factory("LOGIN hoge foo", @p)
245255 assert_instance_of(ShogiServer::ErrorCommand, cmd)
246256 cmd.call
247- assert_no_match /unknown command LOGIN hoge foo/, cmd.msg
257+ assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg
248258
249259 cmd = ShogiServer::Command.factory("LOGin hoge foo", @p)
250260 assert_instance_of(ShogiServer::ErrorCommand, cmd)
251261 cmd.call
252- assert_no_match /unknown command LOGIN hoge foo/, cmd.msg
262+ assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg
253263
254264 cmd = ShogiServer::Command.factory("LOGIN hoge foo", @p)
255265 assert_instance_of(ShogiServer::ErrorCommand, cmd)
256266 cmd.call
257- assert_no_match /unknown command LOGIN hoge foo/, cmd.msg
267+ assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg
258268
259269 cmd = ShogiServer::Command.factory("LOGINhoge foo", @p)
260270 assert_instance_of(ShogiServer::ErrorCommand, cmd)
261271 cmd.call
262- assert_no_match /unknown command LOGIN hoge foo/, cmd.msg
272+ assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg
263273 end
264274 end
265275
@@ -939,6 +949,28 @@ end
939949
940950 #
941951 #
952+class TestForkCommand < Test::Unit::TestCase
953+ def setup
954+ @player = MockPlayer.new
955+ end
956+
957+ def test_new_buoy_game_name
958+ src = "%%FORK server+denou-14400-60+p1+p2+20130223185013"
959+ c = ShogiServer::ForkCommand.new src, @player, "server+denou-14400-60+p1+p2+20130223185013", nil, 13
960+ c.decide_new_buoy_game_name
961+ assert_equal "buoy_denou_13-14400-60", c.new_buoy_game
962+ end
963+
964+ def test_new_buoy_game_name2
965+ src = "%%FORK server+denou-14400-060+p1+p2+20130223185013"
966+ c = ShogiServer::ForkCommand.new src, @player, "server+denou-14400-060+p1+p2+20130223185013", nil, 13
967+ c.decide_new_buoy_game_name
968+ assert_equal "buoy_denou_13-14400-060", c.new_buoy_game
969+ end
970+end
971+
972+#
973+#
942974 class TestGetBuoyCountCommand < BaseTestBuoyCommand
943975 def test_call
944976 buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1)
@@ -1051,4 +1083,3 @@ class TestMonitorHandler2 < Test::Unit::TestCase
10511083 @player.out.join)
10521084 end
10531085 end
1054-
--- /dev/null
+++ b/test/TC_fork.rb
@@ -0,0 +1,131 @@
1+$:.unshift File.join(File.dirname(__FILE__), "..")
2+$topdir = File.expand_path File.dirname(__FILE__)
3+require "baseclient"
4+require "shogi_server/buoy.rb"
5+
6+class TestFork < BaseClient
7+ def parse_game_name(player)
8+ player.puts "%%LIST"
9+ sleep 1
10+ if /##\[LIST\] (.*)/ =~ player.message
11+ return $1
12+ end
13+ end
14+
15+ def test_wrong_game
16+ @admin = SocketPlayer.new "dummy", "admin", false
17+ @admin.connect
18+ @admin.reader
19+ @admin.login
20+
21+ result, result2 = handshake do
22+ @admin.puts "%%FORK wronggame-900-0 buoy_WrongGame-900-0"
23+ sleep 1
24+ end
25+
26+ assert /##\[ERROR\] wrong source game name/ =~ @admin.message
27+ @admin.logout
28+ end
29+
30+ def test_too_short_fork
31+ @admin = SocketPlayer.new "dummy", "admin", false
32+ @admin.connect
33+ @admin.reader
34+ @admin.login
35+
36+ result, result2 = handshake do
37+ source_game = parse_game_name(@admin)
38+ @admin.puts "%%FORK #{source_game} buoy_TooShortFork-900-0 0"
39+ sleep 1
40+ end
41+
42+ assert /##\[ERROR\] number of moves to fork is out of range/ =~ @admin.message
43+ @admin.logout
44+ end
45+
46+ def test_fork
47+ buoy = ShogiServer::Buoy.new
48+
49+ @admin = SocketPlayer.new "dummy", "admin", "*"
50+ @admin.connect
51+ @admin.reader
52+ @admin.login
53+ assert buoy.is_new_game?("buoy_Fork-1500-0")
54+
55+ result, result2 = handshake do
56+ source_game = parse_game_name(@admin)
57+ @admin.puts "%%FORK #{source_game} buoy_Fork-1500-0"
58+ sleep 1
59+ end
60+
61+ assert buoy.is_new_game?("buoy_Fork-1500-0")
62+ @p1 = SocketPlayer.new "buoy_Fork", "p1", true
63+ @p2 = SocketPlayer.new "buoy_Fork", "p2", false
64+ @p1.connect
65+ @p2.connect
66+ @p1.reader
67+ @p2.reader
68+ @p1.login
69+ @p2.login
70+ sleep 1
71+ @p1.game
72+ @p2.game
73+ sleep 1
74+ @p1.agree
75+ @p2.agree
76+ sleep 1
77+ assert /^Total_Time:1500/ =~ @p1.message
78+ assert /^Total_Time:1500/ =~ @p2.message
79+ @p2.move("-3334FU")
80+ sleep 1
81+ @p1.toryo
82+ sleep 1
83+ @p2.logout
84+ @p1.logout
85+
86+ @admin.logout
87+ end
88+
89+ def test_fork2
90+ buoy = ShogiServer::Buoy.new
91+
92+ @admin = SocketPlayer.new "dummy", "admin", "*"
93+ @admin.connect
94+ @admin.reader
95+ @admin.login
96+
97+ result, result2 = handshake do
98+ source_game = parse_game_name(@admin)
99+ @admin.puts "%%FORK #{source_game}" # nil for new_buoy_game name
100+ sleep 1
101+ assert /##\[FORK\]: new buoy game name: buoy_TestFork_1-1500-0/ =~ @admin.message
102+ end
103+
104+ assert buoy.is_new_game?("buoy_TestFork_1-1500-0")
105+ @p1 = SocketPlayer.new "buoy_TestFork_1", "p1", true
106+ @p2 = SocketPlayer.new "buoy_TestFork_1", "p2", false
107+ @p1.connect
108+ @p2.connect
109+ @p1.reader
110+ @p2.reader
111+ @p1.login
112+ @p2.login
113+ sleep 1
114+ @p1.game
115+ @p2.game
116+ sleep 1
117+ @p1.agree
118+ @p2.agree
119+ sleep 1
120+ assert /^Total_Time:1500/ =~ @p1.message
121+ assert /^Total_Time:1500/ =~ @p2.message
122+ @p2.move("-3334FU")
123+ sleep 1
124+ @p1.toryo
125+ sleep 1
126+ @p2.logout
127+ @p1.logout
128+
129+ @admin.logout
130+ end
131+end
--- /dev/null
+++ b/test/TC_time_clock.rb
@@ -0,0 +1,92 @@
1+$:.unshift File.join(File.dirname(__FILE__), "..")
2+require 'test/unit'
3+require 'test/mock_player'
4+require 'shogi_server/board'
5+require 'shogi_server/game'
6+require 'shogi_server/player'
7+
8+class DummyPlayer
9+ def initialize(mytime)
10+ @mytime = mytime
11+ end
12+ attr_reader :mytime
13+end
14+
15+class TestTimeClockFactor < Test::Unit::TestCase
16+ def test_chess_clock
17+ c = ShogiServer::TimeClock::factory(1, "hoge-900-0")
18+ assert_instance_of(ShogiServer::ChessClock, c)
19+
20+ c = ShogiServer::TimeClock::factory(1, "hoge-1500-60")
21+ assert_instance_of(ShogiServer::ChessClock, c)
22+ end
23+
24+ def test_stop_watch_clock
25+ c = ShogiServer::TimeClock::factory(1, "hoge-1500-060")
26+ assert_instance_of(ShogiServer::StopWatchClock, c)
27+ end
28+end
29+
30+class TestChessClock < Test::Unit::TestCase
31+ def test_time_duration
32+ tc = ShogiServer::ChessClock.new(1, 1500, 60)
33+ assert_equal(1, tc.time_duration(100.1, 100.9))
34+ assert_equal(1, tc.time_duration(100, 101))
35+ assert_equal(1, tc.time_duration(100.1, 101.9))
36+ assert_equal(2, tc.time_duration(100.1, 102.9))
37+ assert_equal(2, tc.time_duration(100, 102))
38+ end
39+
40+ def test_without_byoyomi
41+ tc = ShogiServer::ChessClock.new(1, 1500, 0)
42+
43+ p = DummyPlayer.new 100
44+ assert(!tc.timeout?(p, 100, 101))
45+ assert(!tc.timeout?(p, 100, 199))
46+ assert(tc.timeout?(p, 100, 200))
47+ assert(tc.timeout?(p, 100, 201))
48+ end
49+
50+ def test_with_byoyomi
51+ tc = ShogiServer::ChessClock.new(1, 1500, 60)
52+
53+ p = DummyPlayer.new 100
54+ assert(!tc.timeout?(p, 100, 101))
55+ assert(!tc.timeout?(p, 100, 259))
56+ assert(tc.timeout?(p, 100, 260))
57+ assert(tc.timeout?(p, 100, 261))
58+
59+ p = DummyPlayer.new 30
60+ assert(!tc.timeout?(p, 100, 189))
61+ assert(tc.timeout?(p, 100, 190))
62+ end
63+
64+ def test_with_byoyomi2
65+ tc = ShogiServer::ChessClock.new(1, 0, 60)
66+
67+ p = DummyPlayer.new 0
68+ assert(!tc.timeout?(p, 100, 159))
69+ assert(tc.timeout?(p, 100, 160))
70+ end
71+end
72+
73+class TestStopWatchClock < Test::Unit::TestCase
74+ def test_time_duration
75+ tc = ShogiServer::StopWatchClock.new(1, 1500, 60)
76+ assert_equal(0, tc.time_duration(100.1, 100.9))
77+ assert_equal(0, tc.time_duration(100, 101))
78+ assert_equal(0, tc.time_duration(100, 159.9))
79+ assert_equal(60, tc.time_duration(100, 160))
80+ assert_equal(60, tc.time_duration(100, 219))
81+ assert_equal(120, tc.time_duration(100, 220))
82+ end
83+
84+ def test_with_byoyomi
85+ tc = ShogiServer::StopWatchClock.new(1, 600, 60)
86+
87+ p = DummyPlayer.new 60
88+ assert(!tc.timeout?(p, 100, 159))
89+ assert(tc.timeout?(p, 100, 160))
90+ end
91+end
92+