shogi-server source
Révision | 850fc7e54e8bb0d1667f1c3ac3076f3540c66943 (tree) |
---|---|
l'heure | 2013-09-07 16:52:57 |
Auteur | Daigo Moriwaki <beatles@user...> |
Commiter | Daigo Moriwaki |
Merge branch 'wdoor-stable-fork' into wdoor-stable
@@ -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 | + | |
1 | 38 | 2012-12-30 Daigo Moriwaki <daigo at debian dot org> |
2 | 39 | |
3 | 40 | * [shogi-server] |
@@ -43,17 +43,42 @@ EOF | ||
43 | 43 | |
44 | 44 | # Split a moves line into an array of a move string. |
45 | 45 | # 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"]] | |
48 | 50 | # |
49 | 51 | def Board.split_moves(moves) |
50 | 52 | ret = [] |
51 | 53 | |
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 | |
57 | 82 | |
58 | 83 | return ret |
59 | 84 | end |
@@ -230,14 +255,21 @@ EOF | ||
230 | 255 | |
231 | 256 | # Set up a board starting with a position after the moves. |
232 | 257 | # 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"]] | |
234 | 260 | # |
235 | 261 | def set_from_moves(moves) |
236 | 262 | initial() |
237 | 263 | return :normal if moves.empty? |
238 | 264 | rt = nil |
239 | 265 | 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 | |
241 | 273 | raise ArgumentError, "bad moves: #{moves}" unless rt == :normal |
242 | 274 | end |
243 | 275 | @initial_moves = moves.dup |
@@ -21,7 +21,7 @@ module ShogiServer | ||
21 | 21 | end |
22 | 22 | |
23 | 23 | def ==(rhs) |
24 | - return (@game_name == rhs.game_name && | |
24 | + return (@game_name == rhs.game_name && | |
25 | 25 | @moves == rhs.moves && |
26 | 26 | @owner == rhs.owner && |
27 | 27 | @count == rhs.count) |
@@ -69,6 +69,9 @@ module ShogiServer | ||
69 | 69 | my_sente_str = $3 |
70 | 70 | cmd = GameChallengeCommand.new(str, player, |
71 | 71 | 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) | |
72 | 75 | when /^%%CHAT\s+(.+)/ |
73 | 76 | message = $1 |
74 | 77 | cmd = ChatCommand.new(str, player, message, $league.players) |
@@ -94,6 +97,19 @@ module ShogiServer | ||
94 | 97 | when /^%%GETBUOYCOUNT\s+(\S+)/ |
95 | 98 | game_name = $1 |
96 | 99 | 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) | |
97 | 113 | when /^\s*$/ |
98 | 114 | cmd = SpaceCommand.new(str, player) |
99 | 115 | when /^%%%[^%]/ |
@@ -481,7 +497,16 @@ module ShogiServer | ||
481 | 497 | |
482 | 498 | def call |
483 | 499 | 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 | |
485 | 510 | return :continue |
486 | 511 | elsif ((@player.status == "connected") || (@player.status == "game_waiting")) |
487 | 512 | ## continue |
@@ -666,9 +691,9 @@ module ShogiServer | ||
666 | 691 | # Command for an error |
667 | 692 | # |
668 | 693 | 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" | |
672 | 697 | end |
673 | 698 | attr_reader :msg |
674 | 699 |
@@ -676,7 +701,7 @@ module ShogiServer | ||
676 | 701 | cmd = @str.chomp |
677 | 702 | # Aim to hide a possible password |
678 | 703 | 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] | |
680 | 705 | @player.write_safe(@msg) |
681 | 706 | log_error(@msg) |
682 | 707 | return :continue |
@@ -809,4 +834,69 @@ module ShogiServer | ||
809 | 834 | end |
810 | 835 | end |
811 | 836 | |
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 | + | |
812 | 902 | end # module ShogiServer |
@@ -19,6 +19,7 @@ | ||
19 | 19 | |
20 | 20 | require 'shogi_server/league/floodgate' |
21 | 21 | require 'shogi_server/game_result' |
22 | +require 'shogi_server/time_clock' | |
22 | 23 | require 'shogi_server/util' |
23 | 24 | |
24 | 25 | module ShogiServer # for a namespace |
@@ -69,6 +70,8 @@ class Game | ||
69 | 70 | if (@game_name =~ /-(\d+)-(\d+)$/) |
70 | 71 | @total_time = $1.to_i |
71 | 72 | @byoyomi = $2.to_i |
73 | + | |
74 | + @time_clock = TimeClock::factory(Least_Time_Per_Move, @game_name) | |
72 | 75 | end |
73 | 76 | |
74 | 77 | if (player0.sente) |
@@ -87,7 +90,16 @@ class Game | ||
87 | 90 | @sente.game = self |
88 | 91 | @gote.game = self |
89 | 92 | |
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 | |
91 | 103 | @current_turn = @board.initial_moves.size |
92 | 104 | |
93 | 105 | @sente.status = "agree_waiting" |
@@ -110,6 +122,7 @@ class Game | ||
110 | 122 | $league.games[@game_id] = self |
111 | 123 | |
112 | 124 | log_message(sprintf("game created %s", @game_id)) |
125 | + log_message(" " + @time_clock.to_s) | |
113 | 126 | |
114 | 127 | @start_time = nil |
115 | 128 | @fh = open(@logfile, "w") |
@@ -118,7 +131,7 @@ class Game | ||
118 | 131 | |
119 | 132 | propose |
120 | 133 | 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 | |
122 | 135 | attr_accessor :last_move, :current_turn |
123 | 136 | attr_reader :result, :prepared_time |
124 | 137 |
@@ -218,22 +231,16 @@ class Game | ||
218 | 231 | return nil |
219 | 232 | end |
220 | 233 | |
221 | - finish_flag = true | |
222 | 234 | @end_time = end_time |
223 | - t = [(@end_time - @start_time).floor, Least_Time_Per_Move].max | |
224 | - | |
235 | + finish_flag = true | |
225 | 236 | 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)) | |
228 | 239 | status = :timeout |
229 | 240 | elsif (str == :timeout) |
230 | 241 | return false # time isn't expired. players aren't swapped. continue game |
231 | 242 | 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) | |
237 | 244 | move_status = @board.handle_one_move(str, @sente == @current_player) |
238 | 245 | # log_debug("move_status: %s for %s's %s" % [move_status, @sente == @current_player ? "BLACK" : "WHITE", str]) |
239 | 246 |
@@ -332,8 +339,14 @@ class Game | ||
332 | 339 | unless @board.initial_moves.empty? |
333 | 340 | @fh.puts "'buoy game starting with %d moves" % [@board.initial_moves.size] |
334 | 341 | @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 | |
337 | 350 | end |
338 | 351 | end |
339 | 352 | end |
@@ -392,7 +405,14 @@ Least_Time_Per_Move:#{Least_Time_Per_Move} | ||
392 | 405 | END Time |
393 | 406 | BEGIN Position |
394 | 407 | #{@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")} | |
396 | 416 | END Position |
397 | 417 | END Game_Summary |
398 | 418 | EOM |
@@ -408,6 +428,21 @@ EOM | ||
408 | 428 | |
409 | 429 | return false |
410 | 430 | 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 | |
411 | 446 | |
412 | 447 | private |
413 | 448 |
@@ -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 |
@@ -9,6 +9,7 @@ require 'TC_floodgate' | ||
9 | 9 | require 'TC_floodgate_history' |
10 | 10 | require 'TC_floodgate_next_time_generator' |
11 | 11 | require 'TC_floodgate_thread.rb' |
12 | +require 'TC_fork' | |
12 | 13 | require 'TC_functional' |
13 | 14 | require 'TC_game' |
14 | 15 | require 'TC_game_result' |
@@ -23,6 +24,7 @@ require 'TC_oute_sennichite' | ||
23 | 24 | require 'TC_pairing' |
24 | 25 | require 'TC_player' |
25 | 26 | require 'TC_rating' |
27 | +require 'TC_time_clock' | |
26 | 28 | require 'TC_uchifuzume' |
27 | 29 | require 'TC_usi' |
28 | 30 | require 'TC_util' |
@@ -228,6 +228,16 @@ class TestFactoryMethod < Test::Unit::TestCase | ||
228 | 228 | assert_instance_of(ShogiServer::GetBuoyCountCommand, cmd) |
229 | 229 | end |
230 | 230 | |
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 | + | |
231 | 241 | def test_void_command |
232 | 242 | cmd = ShogiServer::Command.factory("%%%HOGE", @p) |
233 | 243 | assert_instance_of(ShogiServer::VoidCommand, cmd) |
@@ -237,29 +247,29 @@ class TestFactoryMethod < Test::Unit::TestCase | ||
237 | 247 | cmd = ShogiServer::Command.factory("should_be_error", @p) |
238 | 248 | assert_instance_of(ShogiServer::ErrorCommand, cmd) |
239 | 249 | cmd.call |
240 | - assert_match /unknown command should_be_error/, cmd.msg | |
250 | + assert_match /unknown command: should_be_error/, cmd.msg | |
241 | 251 | end |
242 | 252 | |
243 | 253 | def test_error_login |
244 | 254 | cmd = ShogiServer::Command.factory("LOGIN hoge foo", @p) |
245 | 255 | assert_instance_of(ShogiServer::ErrorCommand, cmd) |
246 | 256 | cmd.call |
247 | - assert_no_match /unknown command LOGIN hoge foo/, cmd.msg | |
257 | + assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg | |
248 | 258 | |
249 | 259 | cmd = ShogiServer::Command.factory("LOGin hoge foo", @p) |
250 | 260 | assert_instance_of(ShogiServer::ErrorCommand, cmd) |
251 | 261 | cmd.call |
252 | - assert_no_match /unknown command LOGIN hoge foo/, cmd.msg | |
262 | + assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg | |
253 | 263 | |
254 | 264 | cmd = ShogiServer::Command.factory("LOGIN hoge foo", @p) |
255 | 265 | assert_instance_of(ShogiServer::ErrorCommand, cmd) |
256 | 266 | cmd.call |
257 | - assert_no_match /unknown command LOGIN hoge foo/, cmd.msg | |
267 | + assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg | |
258 | 268 | |
259 | 269 | cmd = ShogiServer::Command.factory("LOGINhoge foo", @p) |
260 | 270 | assert_instance_of(ShogiServer::ErrorCommand, cmd) |
261 | 271 | cmd.call |
262 | - assert_no_match /unknown command LOGIN hoge foo/, cmd.msg | |
272 | + assert_no_match /unknown command: LOGIN hoge foo/, cmd.msg | |
263 | 273 | end |
264 | 274 | end |
265 | 275 |
@@ -939,6 +949,28 @@ end | ||
939 | 949 | |
940 | 950 | # |
941 | 951 | # |
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 | +# | |
942 | 974 | class TestGetBuoyCountCommand < BaseTestBuoyCommand |
943 | 975 | def test_call |
944 | 976 | buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1) |
@@ -1051,4 +1083,3 @@ class TestMonitorHandler2 < Test::Unit::TestCase | ||
1051 | 1083 | @player.out.join) |
1052 | 1084 | end |
1053 | 1085 | end |
1054 | - |
@@ -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 |
@@ -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 | + |