• R/O
  • HTTP
  • SSH
  • HTTPS

shogi-server: コミット

shogi-server source


コミットメタ情報

リビジョンd168a77d26b0ee995991ee798ef1f675bdb30528 (tree)
日時2013-02-24 17:42:43
作者Daigo Moriwaki <beatles@user...>
コミッターDaigo Moriwaki

ログメッセージ

Implemented a new command: %%FORK

変更サマリ

差分

--- a/changelog
+++ b/changelog
@@ -1,3 +1,22 @@
1+2013-02-23 Daigo Moriwaki <daigo at debian dot org>
2+
3+ * [shogi-server]
4+ - New command: %%FORK <source_game> <new_buoy_game> [<nth-move>]
5+ Fork a new game from the posistion where the n-th (starting from
6+ one) move of a source game is played. The new game should be a
7+ valid buoy game name. The default value of n is the position
8+ where the previous position of the last one.
9+ - The objective of this command: The shogi-server may be used as
10+ the back end server of computer-human match where a human player
11+ plays with a real board and someone, or a proxy, inputs moves to
12+ the shogi-server. If the proxy happens to enter a wrong move,
13+ with this command you can restart a new buoy game from the
14+ previous stable position.
15+ ex. %%FORK server-denou-14400-60+p1+p2+20130223185013 buoy_denou-14400-60
16+ - Note that this feature does not provice accurate thinking
17+ time estimate. The thinking time of the last posision is carried
18+ over to the new game regardless nth-move.
19+
120 2012-12-30 Daigo Moriwaki <daigo at debian dot org>
221
322 * [shogi-server]
--- a/shogi_server/buoy.rb
+++ b/shogi_server/buoy.rb
@@ -10,10 +10,12 @@ module ShogiServer
1010 attr_reader :moves
1111 attr_reader :owner
1212 attr_reader :count
13+ attr_reader :sente_time
14+ attr_reader :gote_time
1315
14- def initialize(game_name, moves, owner, count)
16+ def initialize(game_name, moves, owner, count, sente_time, gote_time)
1517 raise "owner name is required" if owner && !owner.instance_of?(String)
16- @game_name, @moves, @owner, @count = game_name, moves, owner, count
18+ @game_name, @moves, @owner, @count, @sente_time, @gote_time = game_name, moves, owner, count, sente_time, gote_time
1719 end
1820
1921 def decrement_count
@@ -21,16 +23,18 @@ module ShogiServer
2123 end
2224
2325 def ==(rhs)
24- return (@game_name == rhs.game_name &&
25- @moves == rhs.moves &&
26- @owner == rhs.owner &&
27- @count == rhs.count)
26+ return (@game_name == rhs.game_name &&
27+ @moves == rhs.moves &&
28+ @owner == rhs.owner &&
29+ @count == rhs.count &&
30+ @sente_time == rhs.sente_time &&
31+ @gote_time == rhs.gote_time)
2832 end
2933 end
3034
3135 class NilBuoyGame < BuoyGame
3236 def initialize
33- super(nil, nil, nil, 0)
37+ super(nil, nil, nil, 0, nil, nil)
3438 end
3539 end
3640
@@ -62,9 +66,11 @@ module ShogiServer
6266 if @db.root?(buoy_game.game_name)
6367 # error
6468 else
65- hash = {'moves' => buoy_game.moves,
66- 'owner' => buoy_game.owner,
67- 'count' => buoy_game.count}
69+ hash = {'moves' => buoy_game.moves,
70+ 'owner' => buoy_game.owner,
71+ 'count' => buoy_game.count,
72+ 'sente_time' => buoy_game.sente_time,
73+ 'gote_time' => buoy_game.gote_time}
6874 @db[buoy_game.game_name] = hash
6975 end
7076 end
@@ -73,9 +79,11 @@ module ShogiServer
7379 def update_game(buoy_game)
7480 @db.transaction do
7581 if @db.root?(buoy_game.game_name)
76- hash = {'moves' => buoy_game.moves,
77- 'owner' => buoy_game.owner,
78- 'count' => buoy_game.count}
82+ hash = {'moves' => buoy_game.moves,
83+ 'owner' => buoy_game.owner,
84+ 'count' => buoy_game.count,
85+ 'sene_time' => buoy_game.sente_time,
86+ 'gote_time' => buoy_game.gote_time}
7987 @db[buoy_game.game_name] = hash
8088 else
8189 # error
@@ -93,10 +101,12 @@ module ShogiServer
93101 @db.transaction(true) do
94102 hash = @db[game_name]
95103 if hash
96- moves = hash['moves']
97- owner = hash['owner']
98- count = hash['count'].to_i
99- return BuoyGame.new(game_name, moves, owner, count)
104+ moves = hash['moves']
105+ owner = hash['owner']
106+ count = hash['count'].to_i
107+ sente_time = hash['sente_time'] ? hash['sente_time'].to_i : nil
108+ gote_time = hash['gote_time'] ? hash['gote_time'].to_i : nil
109+ return BuoyGame.new(game_name, moves, owner, count, sente_time, gote_time)
100110 else
101111 return NilBuoyGame.new
102112 end
--- a/shogi_server/command.rb
+++ b/shogi_server/command.rb
@@ -94,6 +94,14 @@ module ShogiServer
9494 when /^%%GETBUOYCOUNT\s+(\S+)/
9595 game_name = $1
9696 cmd = GetBuoyCountCommand.new(str, player, game_name)
97+ when /^%%FORK\s+(\S+)\s+(\S+)(.*)/
98+ source_game = $1
99+ new_buoy_game = $2
100+ nth_move = nil
101+ if $3 && /^\s+(\d+)/ =~ $3
102+ nth_move = $3.to_i
103+ end
104+ cmd = ForkCommand.new(str, player, source_game, new_buoy_game, nth_move)
97105 when /^\s*$/
98106 cmd = SpaceCommand.new(str, player)
99107 when /^%%%[^%]/
@@ -532,7 +540,9 @@ module ShogiServer
532540 return :continue
533541 end
534542 buoy.decrement_count(buoy_game)
535- Game::new(@player.game_name, @player, rival, board)
543+ options = {:sente_time => buoy_game.sente_time,
544+ :gote_time => buoy_game.gote_time}
545+ Game::new(@player.game_name, @player, rival, board, options)
536546 end
537547 else
538548 klass = Login.handicapped_game_name?(@game_name) || Board
@@ -692,6 +702,16 @@ module ShogiServer
692702 @game_name = game_name
693703 @moves = moves
694704 @count = count
705+ @sente_time = nil
706+ @gote_time = nil
707+ end
708+
709+ # ForkCommand may call this method to set remaining time of both
710+ # players.
711+ #
712+ def set_initial_time(sente_time, gote_time)
713+ @sente_time = sente_time
714+ @gote_time = gote_time
695715 end
696716
697717 def call
@@ -721,7 +741,7 @@ module ShogiServer
721741 raise WrongMoves
722742 end
723743
724- buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count)
744+ buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count, @sente_time, @gote_time)
725745 buoy.add_game(buoy_game)
726746 @player.write_safe(sprintf("##[SETBUOY] +OK\n"))
727747 log_info("A buoy game was created: %s by %s" % [@game_name, @player.name])
@@ -749,7 +769,11 @@ module ShogiServer
749769 # found two players: p1 and p2
750770 log_info("Starting a buoy game: %s with %s and %s" % [@game_name, p1.name, p2.name])
751771 buoy.decrement_count(buoy_game)
752- game = Game::new(@game_name, p1, p2, board)
772+
773+ # remaining time for ForkCommand
774+ options = {:sente_time => buoy_game.sente_time,
775+ :gote_time => buoy_game.gote_time}
776+ game = Game::new(@game_name, p1, p2, board, options)
753777 return :continue
754778
755779 rescue WrongMoves => e
@@ -809,4 +833,41 @@ module ShogiServer
809833 end
810834 end
811835
836+ # %%FORK <source_game> <new_buoy_game> [<nth-move>]
837+ # Fork a new game from the posistion where the n-th (starting from 1) move
838+ # of a source game is played. The new game should be a valid buoy game
839+ # name. The default value of n is the position where the previous position
840+ # of the last one.
841+ #
842+ class ForkCommand < Command
843+ def initialize(str, player, source_game, new_buoy_game, nth_move)
844+ super(str, player)
845+ @source_game = source_game
846+ @new_buoy_game = new_buoy_game
847+ @nth_move = nth_move # may be nil
848+ end
849+
850+ def call
851+ game = $league.games[@source_game]
852+ unless game
853+ @player.write_safe(sprintf("##[ERROR] wrong source game name: %s\n", @source_game))
854+ log_error "Received a wrong source game name: %s from %s." % [@source_game, @player.name]
855+ return :continue
856+ end
857+
858+ moves = game.read_moves
859+ @nth_move = moves.size - 1 unless @nth_move
860+ if @nth_move > moves.size or @nth_move < 1
861+ @player.write_safe(sprintf("##[ERROR] number of moves to fork is out of range: %s.\n", moves.size))
862+ log_error "Number of moves to fork is out of range: %s [%s]" % [@nth_move, @player.name]
863+ return :continue
864+ end
865+ new_moves = moves[0...@nth_move]
866+
867+ buoy_cmd = SetBuoyCommand.new(@str, @player, @new_buoy_game, new_moves.join(""), 1)
868+ buoy_cmd.set_initial_time(game.sente.mytime, game.gote.mytime)
869+ return buoy_cmd.call
870+ end
871+ end
872+
812873 end # module ShogiServer
--- a/shogi_server/game.rb
+++ b/shogi_server/game.rb
@@ -63,7 +63,10 @@ class Game
6363 end
6464
6565
66- def initialize(game_name, player0, player1, board)
66+ # options may or may not have follwing keys: :sente_time, :gote_time; they
67+ # are used for %%FORK command to set remaining times at the restart.
68+ #
69+ def initialize(game_name, player0, player1, board, options={})
6770 @monitors = Array::new # array of MonitorHandler*
6871 @game_name = game_name
6972 if (@game_name =~ /-(\d+)-(\d+)$/)
@@ -116,6 +119,8 @@ class Game
116119 @fh.sync = true
117120 @result = nil
118121
122+ @options = options.dup
123+
119124 propose
120125 end
121126 attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors
@@ -307,8 +312,8 @@ class Game
307312 @gote.status = "game"
308313 @sente.write_safe(sprintf("START:%s\n", @game_id))
309314 @gote.write_safe(sprintf("START:%s\n", @game_id))
310- @sente.mytime = @total_time
311- @gote.mytime = @total_time
315+ @sente.mytime = @options[:sente_time] || @total_time
316+ @gote.mytime = @options[:gote_time] || @total_time
312317 @start_time = Time.now
313318 end
314319
@@ -372,6 +377,13 @@ EOM
372377 end
373378
374379 def propose_message(sg_flag)
380+ time = @total_time
381+ if @options[:sente_time] && sg_flag == "+"
382+ time = @options[:sente_time]
383+ elsif @options[:gote_time] && sg_flag == "-"
384+ time = @options[:gote_time]
385+ end
386+
375387 str = <<EOM
376388 BEGIN Game_Summary
377389 Protocol_Version:1.1
@@ -386,7 +398,7 @@ Rematch_On_Draw:NO
386398 To_Move:#{@board.teban ? "+" : "-"}
387399 BEGIN Time
388400 Time_Unit:1sec
389-Total_Time:#{@total_time}
401+Total_Time:#{time}
390402 Byoyomi:#{@byoyomi}
391403 Least_Time_Per_Move:#{Least_Time_Per_Move}
392404 END Time
@@ -408,6 +420,19 @@ EOM
408420
409421 return false
410422 end
423+
424+ # Read the .csa file and returns an array of moves.
425+ # ex. ["+7776FU", "-3334FU"]
426+ #
427+ def read_moves
428+ ret = []
429+ IO.foreach(@logfile) do |line|
430+ if /^[\+\-]\d{4}[A-Z]{2}/ =~ line
431+ ret << line.chomp
432+ end
433+ end
434+ return ret
435+ end
411436
412437 private
413438
--- 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'
--- a/test/TC_buoy.rb
+++ b/test/TC_buoy.rb
@@ -9,14 +9,32 @@ require 'mock_log_message'
99
1010 class TestBuoyGame < Test::Unit::TestCase
1111 def test_equal
12- g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
13- g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
12+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
13+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
14+ assert_equal g1, g2
15+ end
16+
17+ def test_equal2
18+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
19+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
1420 assert_equal g1, g2
1521 end
1622
1723 def test_not_equal
18- g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
19- g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2)
24+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
25+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2, nil, nil)
26+ assert_not_equal g1, g2
27+ end
28+
29+ def test_not_equal2
30+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
31+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 200)
32+ assert_not_equal g1, g2
33+ end
34+
35+ def test_not_equal3
36+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, nil)
37+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 200)
2038 assert_not_equal g1, g2
2139 end
2240 end
@@ -49,7 +67,18 @@ class TestBuoy < Test::Unit::TestCase
4967 end
5068
5169 def test_add_game
52- game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
70+ game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
71+ @buoy.add_game(game)
72+ assert !@buoy.is_new_game?("buoy_1234-900-0")
73+ game2 = @buoy.get_game(game.game_name)
74+ assert_equal game, game2
75+
76+ @buoy.delete_game game
77+ assert @buoy.is_new_game?("buoy_1234-900-0")
78+ end
79+
80+ def test_add_game2
81+ game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
5382 @buoy.add_game(game)
5483 assert !@buoy.is_new_game?("buoy_1234-900-0")
5584 game2 = @buoy.get_game(game.game_name)
@@ -60,9 +89,9 @@ class TestBuoy < Test::Unit::TestCase
6089 end
6190
6291 def test_update_game
63- game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2)
92+ game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2, nil, nil)
6493 @buoy.add_game(game)
65- g2 = ShogiServer::BuoyGame.new(game.game_name, game.moves, game.owner, game.count-1)
94+ g2 = ShogiServer::BuoyGame.new(game.game_name, game.moves, game.owner, game.count-1, nil, nil)
6695 @buoy.update_game(g2)
6796
6897 get = @buoy.get_game(g2.game_name)
--- a/test/TC_command.rb
+++ b/test/TC_command.rb
@@ -228,6 +228,11 @@ 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+
231236 def test_void_command
232237 cmd = ShogiServer::Command.factory("%%%HOGE", @p)
233238 assert_instance_of(ShogiServer::VoidCommand, cmd)
@@ -837,7 +842,7 @@ class TestSetBuoyCommand < BaseTestBuoyCommand
837842 assert !$p1.out.empty?
838843 assert !$p2.out.empty?
839844 buoy_game2 = @buoy.get_game("buoy_hoge-1500-0")
840- assert_equal ShogiServer::BuoyGame.new("buoy_hoge-1500-0", "+7776FU", @p.name, 1), buoy_game2
845+ assert_equal ShogiServer::BuoyGame.new("buoy_hoge-1500-0", "+7776FU", @p.name, 1, nil, nil), buoy_game2
841846 end
842847
843848 def test_call_1
@@ -862,7 +867,7 @@ class TestSetBuoyCommand < BaseTestBuoyCommand
862867
863868 def test_call_error_duplicated_game_name
864869 assert @buoy.is_new_game?("buoy_duplicated-1500-0")
865- bg = ShogiServer::BuoyGame.new("buoy_duplicated-1500-0", ["+7776FU"], @p.name, 1)
870+ bg = ShogiServer::BuoyGame.new("buoy_duplicated-1500-0", ["+7776FU"], @p.name, 1, nil, nil)
866871 @buoy.add_game bg
867872 assert !@buoy.is_new_game?("buoy_duplicated-1500-0")
868873
@@ -900,7 +905,7 @@ end
900905 #
901906 class TestDeleteBuoyCommand < BaseTestBuoyCommand
902907 def test_call
903- buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1)
908+ buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1, nil, nil)
904909 assert @buoy.is_new_game?(buoy_game.game_name)
905910 @buoy.add_game buoy_game
906911 assert !@buoy.is_new_game?(buoy_game.game_name)
@@ -913,7 +918,7 @@ class TestDeleteBuoyCommand < BaseTestBuoyCommand
913918 end
914919
915920 def test_call_not_exist
916- buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1)
921+ buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1, nil, nil)
917922 assert @buoy.is_new_game?(buoy_game.game_name)
918923 cmd = ShogiServer::DeleteBuoyCommand.new "%%DELETEBUOY", @p, buoy_game.game_name
919924 rt = cmd.call
@@ -924,7 +929,7 @@ class TestDeleteBuoyCommand < BaseTestBuoyCommand
924929 end
925930
926931 def test_call_another_player
927- buoy_game = ShogiServer::BuoyGame.new("buoy_anotherplayer-1500-0", "+7776FU", "another_player", 1)
932+ buoy_game = ShogiServer::BuoyGame.new("buoy_anotherplayer-1500-0", "+7776FU", "another_player", 1, nil, nil)
928933 assert @buoy.is_new_game?(buoy_game.game_name)
929934 @buoy.add_game(buoy_game)
930935 assert !@buoy.is_new_game?(buoy_game.game_name)
@@ -941,7 +946,7 @@ end
941946 #
942947 class TestGetBuoyCountCommand < BaseTestBuoyCommand
943948 def test_call
944- buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1)
949+ buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1, nil, nil)
945950 assert @buoy.is_new_game?(buoy_game.game_name)
946951 @buoy.add_game buoy_game
947952 assert !@buoy.is_new_game?(buoy_game.game_name)
@@ -952,7 +957,7 @@ class TestGetBuoyCountCommand < BaseTestBuoyCommand
952957 end
953958
954959 def test_call_not_exist
955- buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1)
960+ buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1, nil, nil)
956961 assert @buoy.is_new_game?(buoy_game.game_name)
957962 cmd = ShogiServer::GetBuoyCountCommand.new "%%GETBUOYCOUNT", @p, buoy_game.game_name
958963 rt = cmd.call
--- /dev/null
+++ b/test/TC_fork.rb
@@ -0,0 +1,88 @@
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:1499/ =~ @p1.message
78+ assert /^Total_Time:1499/ =~ @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+end
旧リポジトリブラウザで表示