+2013-02-23 Daigo Moriwaki <daigo at debian dot org>
+
+ * [shogi-server]
+ - New command: %%FORK <source_game> <new_buoy_game> [<nth-move>]
+ Fork a new game from the posistion where the n-th (starting from
+ one) move of a source game is played. The new game should be a
+ valid buoy game name. The default value of n is the position
+ where the previous position of the last one.
+ - The objective of this command: The shogi-server may be used as
+ the back end server of computer-human match where a human player
+ plays with a real board and someone, or a proxy, inputs moves to
+ the shogi-server. If the proxy happens to enter a wrong move,
+ with this command you can restart a new buoy game from the
+ previous stable position.
+ ex. %%FORK server-denou-14400-60+p1+p2+20130223185013 buoy_denou-14400-60
+ - Note that this feature does not provice accurate thinking
+ time estimate. The thinking time of the last posision is carried
+ over to the new game regardless nth-move.
+
2012-12-30 Daigo Moriwaki <daigo at debian dot org>
* [shogi-server]
attr_reader :moves
attr_reader :owner
attr_reader :count
+ attr_reader :sente_time
+ attr_reader :gote_time
- def initialize(game_name, moves, owner, count)
+ def initialize(game_name, moves, owner, count, sente_time, gote_time)
raise "owner name is required" if owner && !owner.instance_of?(String)
- @game_name, @moves, @owner, @count = game_name, moves, owner, count
+ @game_name, @moves, @owner, @count, @sente_time, @gote_time = game_name, moves, owner, count, sente_time, gote_time
end
def decrement_count
end
def ==(rhs)
- return (@game_name == rhs.game_name &&
- @moves == rhs.moves &&
- @owner == rhs.owner &&
- @count == rhs.count)
+ return (@game_name == rhs.game_name &&
+ @moves == rhs.moves &&
+ @owner == rhs.owner &&
+ @count == rhs.count &&
+ @sente_time == rhs.sente_time &&
+ @gote_time == rhs.gote_time)
end
end
class NilBuoyGame < BuoyGame
def initialize
- super(nil, nil, nil, 0)
+ super(nil, nil, nil, 0, nil, nil)
end
end
if @db.root?(buoy_game.game_name)
# error
else
- hash = {'moves' => buoy_game.moves,
- 'owner' => buoy_game.owner,
- 'count' => buoy_game.count}
+ hash = {'moves' => buoy_game.moves,
+ 'owner' => buoy_game.owner,
+ 'count' => buoy_game.count,
+ 'sente_time' => buoy_game.sente_time,
+ 'gote_time' => buoy_game.gote_time}
@db[buoy_game.game_name] = hash
end
end
def update_game(buoy_game)
@db.transaction do
if @db.root?(buoy_game.game_name)
- hash = {'moves' => buoy_game.moves,
- 'owner' => buoy_game.owner,
- 'count' => buoy_game.count}
+ hash = {'moves' => buoy_game.moves,
+ 'owner' => buoy_game.owner,
+ 'count' => buoy_game.count,
+ 'sene_time' => buoy_game.sente_time,
+ 'gote_time' => buoy_game.gote_time}
@db[buoy_game.game_name] = hash
else
# error
@db.transaction(true) do
hash = @db[game_name]
if hash
- moves = hash['moves']
- owner = hash['owner']
- count = hash['count'].to_i
- return BuoyGame.new(game_name, moves, owner, count)
+ moves = hash['moves']
+ owner = hash['owner']
+ count = hash['count'].to_i
+ sente_time = hash['sente_time'] ? hash['sente_time'].to_i : nil
+ gote_time = hash['gote_time'] ? hash['gote_time'].to_i : nil
+ return BuoyGame.new(game_name, moves, owner, count, sente_time, gote_time)
else
return NilBuoyGame.new
end
when /^%%GETBUOYCOUNT\s+(\S+)/
game_name = $1
cmd = GetBuoyCountCommand.new(str, player, game_name)
+ when /^%%FORK\s+(\S+)\s+(\S+)(.*)/
+ source_game = $1
+ new_buoy_game = $2
+ nth_move = nil
+ if $3 && /^\s+(\d+)/ =~ $3
+ nth_move = $3.to_i
+ end
+ cmd = ForkCommand.new(str, player, source_game, new_buoy_game, nth_move)
when /^\s*$/
cmd = SpaceCommand.new(str, player)
when /^%%%[^%]/
return :continue
end
buoy.decrement_count(buoy_game)
- Game::new(@player.game_name, @player, rival, board)
+ options = {:sente_time => buoy_game.sente_time,
+ :gote_time => buoy_game.gote_time}
+ Game::new(@player.game_name, @player, rival, board, options)
end
else
klass = Login.handicapped_game_name?(@game_name) || Board
@game_name = game_name
@moves = moves
@count = count
+ @sente_time = nil
+ @gote_time = nil
+ end
+
+ # ForkCommand may call this method to set remaining time of both
+ # players.
+ #
+ def set_initial_time(sente_time, gote_time)
+ @sente_time = sente_time
+ @gote_time = gote_time
end
def call
raise WrongMoves
end
- buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count)
+ buoy_game = BuoyGame.new(@game_name, @moves, @player.name, @count, @sente_time, @gote_time)
buoy.add_game(buoy_game)
@player.write_safe(sprintf("##[SETBUOY] +OK\n"))
log_info("A buoy game was created: %s by %s" % [@game_name, @player.name])
# found two players: p1 and p2
log_info("Starting a buoy game: %s with %s and %s" % [@game_name, p1.name, p2.name])
buoy.decrement_count(buoy_game)
- game = Game::new(@game_name, p1, p2, board)
+
+ # remaining time for ForkCommand
+ options = {:sente_time => buoy_game.sente_time,
+ :gote_time => buoy_game.gote_time}
+ game = Game::new(@game_name, p1, p2, board, options)
return :continue
rescue WrongMoves => e
end
end
+ # %%FORK <source_game> <new_buoy_game> [<nth-move>]
+ # Fork a new game from the posistion where the n-th (starting from 1) move
+ # of a source game is played. The new game should be a valid buoy game
+ # name. The default value of n is the position where the previous position
+ # of the last one.
+ #
+ class ForkCommand < Command
+ def initialize(str, player, source_game, new_buoy_game, nth_move)
+ super(str, player)
+ @source_game = source_game
+ @new_buoy_game = new_buoy_game
+ @nth_move = nth_move # may be nil
+ end
+
+ def call
+ game = $league.games[@source_game]
+ unless game
+ @player.write_safe(sprintf("##[ERROR] wrong source game name: %s\n", @source_game))
+ log_error "Received a wrong source game name: %s from %s." % [@source_game, @player.name]
+ return :continue
+ end
+
+ moves = game.read_moves
+ @nth_move = moves.size - 1 unless @nth_move
+ if @nth_move > moves.size or @nth_move < 1
+ @player.write_safe(sprintf("##[ERROR] number of moves to fork is out of range: %s.\n", moves.size))
+ log_error "Number of moves to fork is out of range: %s [%s]" % [@nth_move, @player.name]
+ return :continue
+ end
+ new_moves = moves[0...@nth_move]
+
+ buoy_cmd = SetBuoyCommand.new(@str, @player, @new_buoy_game, new_moves.join(""), 1)
+ buoy_cmd.set_initial_time(game.sente.mytime, game.gote.mytime)
+ return buoy_cmd.call
+ end
+ end
+
end # module ShogiServer
end
- def initialize(game_name, player0, player1, board)
+ # options may or may not have follwing keys: :sente_time, :gote_time; they
+ # are used for %%FORK command to set remaining times at the restart.
+ #
+ def initialize(game_name, player0, player1, board, options={})
@monitors = Array::new # array of MonitorHandler*
@game_name = game_name
if (@game_name =~ /-(\d+)-(\d+)$/)
@fh.sync = true
@result = nil
+ @options = options.dup
+
propose
end
attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors
@gote.status = "game"
@sente.write_safe(sprintf("START:%s\n", @game_id))
@gote.write_safe(sprintf("START:%s\n", @game_id))
- @sente.mytime = @total_time
- @gote.mytime = @total_time
+ @sente.mytime = @options[:sente_time] || @total_time
+ @gote.mytime = @options[:gote_time] || @total_time
@start_time = Time.now
end
end
def propose_message(sg_flag)
+ time = @total_time
+ if @options[:sente_time] && sg_flag == "+"
+ time = @options[:sente_time]
+ elsif @options[:gote_time] && sg_flag == "-"
+ time = @options[:gote_time]
+ end
+
str = <<EOM
BEGIN Game_Summary
Protocol_Version:1.1
To_Move:#{@board.teban ? "+" : "-"}
BEGIN Time
Time_Unit:1sec
-Total_Time:#{@total_time}
+Total_Time:#{time}
Byoyomi:#{@byoyomi}
Least_Time_Per_Move:#{Least_Time_Per_Move}
END Time
return false
end
+
+ # Read the .csa file and returns an array of moves.
+ # ex. ["+7776FU", "-3334FU"]
+ #
+ def read_moves
+ ret = []
+ IO.foreach(@logfile) do |line|
+ if /^[\+\-]\d{4}[A-Z]{2}/ =~ line
+ ret << line.chomp
+ end
+ end
+ return ret
+ end
private
require 'TC_floodgate_history'
require 'TC_floodgate_next_time_generator'
require 'TC_floodgate_thread.rb'
+require 'TC_fork'
require 'TC_functional'
require 'TC_game'
require 'TC_game_result'
class TestBuoyGame < Test::Unit::TestCase
def test_equal
- g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
- g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+ assert_equal g1, g2
+ end
+
+ def test_equal2
+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
assert_equal g1, g2
end
def test_not_equal
- g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
- g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2)
+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2, nil, nil)
+ assert_not_equal g1, g2
+ end
+
+ def test_not_equal2
+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 200)
+ assert_not_equal g1, g2
+ end
+
+ def test_not_equal3
+ g1 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, nil)
+ g2 = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 200)
assert_not_equal g1, g2
end
end
end
def test_add_game
- game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1)
+ game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, nil, nil)
+ @buoy.add_game(game)
+ assert !@buoy.is_new_game?("buoy_1234-900-0")
+ game2 = @buoy.get_game(game.game_name)
+ assert_equal game, game2
+
+ @buoy.delete_game game
+ assert @buoy.is_new_game?("buoy_1234-900-0")
+ end
+
+ def test_add_game2
+ game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 1, 10, 20)
@buoy.add_game(game)
assert !@buoy.is_new_game?("buoy_1234-900-0")
game2 = @buoy.get_game(game.game_name)
end
def test_update_game
- game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2)
+ game = ShogiServer::BuoyGame.new("buoy_1234-900-0", [], "p1", 2, nil, nil)
@buoy.add_game(game)
- g2 = ShogiServer::BuoyGame.new(game.game_name, game.moves, game.owner, game.count-1)
+ g2 = ShogiServer::BuoyGame.new(game.game_name, game.moves, game.owner, game.count-1, nil, nil)
@buoy.update_game(g2)
get = @buoy.get_game(g2.game_name)
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_void_command
cmd = ShogiServer::Command.factory("%%%HOGE", @p)
assert_instance_of(ShogiServer::VoidCommand, cmd)
assert !$p1.out.empty?
assert !$p2.out.empty?
buoy_game2 = @buoy.get_game("buoy_hoge-1500-0")
- assert_equal ShogiServer::BuoyGame.new("buoy_hoge-1500-0", "+7776FU", @p.name, 1), buoy_game2
+ assert_equal ShogiServer::BuoyGame.new("buoy_hoge-1500-0", "+7776FU", @p.name, 1, nil, nil), buoy_game2
end
def test_call_1
def test_call_error_duplicated_game_name
assert @buoy.is_new_game?("buoy_duplicated-1500-0")
- bg = ShogiServer::BuoyGame.new("buoy_duplicated-1500-0", ["+7776FU"], @p.name, 1)
+ bg = ShogiServer::BuoyGame.new("buoy_duplicated-1500-0", ["+7776FU"], @p.name, 1, nil, nil)
@buoy.add_game bg
assert !@buoy.is_new_game?("buoy_duplicated-1500-0")
#
class TestDeleteBuoyCommand < BaseTestBuoyCommand
def test_call
- buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1)
+ buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1, nil, nil)
assert @buoy.is_new_game?(buoy_game.game_name)
@buoy.add_game buoy_game
assert !@buoy.is_new_game?(buoy_game.game_name)
end
def test_call_not_exist
- buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1)
+ buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1, nil, nil)
assert @buoy.is_new_game?(buoy_game.game_name)
cmd = ShogiServer::DeleteBuoyCommand.new "%%DELETEBUOY", @p, buoy_game.game_name
rt = cmd.call
end
def test_call_another_player
- buoy_game = ShogiServer::BuoyGame.new("buoy_anotherplayer-1500-0", "+7776FU", "another_player", 1)
+ buoy_game = ShogiServer::BuoyGame.new("buoy_anotherplayer-1500-0", "+7776FU", "another_player", 1, nil, nil)
assert @buoy.is_new_game?(buoy_game.game_name)
@buoy.add_game(buoy_game)
assert !@buoy.is_new_game?(buoy_game.game_name)
#
class TestGetBuoyCountCommand < BaseTestBuoyCommand
def test_call
- buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1)
+ buoy_game = ShogiServer::BuoyGame.new("buoy_testdeletebuoy-1500-0", "+7776FU", @p.name, 1, nil, nil)
assert @buoy.is_new_game?(buoy_game.game_name)
@buoy.add_game buoy_game
assert !@buoy.is_new_game?(buoy_game.game_name)
end
def test_call_not_exist
- buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1)
+ buoy_game = ShogiServer::BuoyGame.new("buoy_notexist-1500-0", "+7776FU", @p.name, 1, nil, nil)
assert @buoy.is_new_game?(buoy_game.game_name)
cmd = ShogiServer::GetBuoyCountCommand.new "%%GETBUOYCOUNT", @p, buoy_game.game_name
rt = cmd.call
--- /dev/null
+$:.unshift File.join(File.dirname(__FILE__), "..")
+$topdir = File.expand_path File.dirname(__FILE__)
+require "baseclient"
+require "shogi_server/buoy.rb"
+
+class TestFork < BaseClient
+ def parse_game_name(player)
+ player.puts "%%LIST"
+ sleep 1
+ if /##\[LIST\] (.*)/ =~ player.message
+ return $1
+ end
+ end
+
+ def test_wrong_game
+ @admin = SocketPlayer.new "dummy", "admin", false
+ @admin.connect
+ @admin.reader
+ @admin.login
+
+ result, result2 = handshake do
+ @admin.puts "%%FORK wronggame-900-0 buoy_WrongGame-900-0"
+ sleep 1
+ end
+
+ assert /##\[ERROR\] wrong source game name/ =~ @admin.message
+ @admin.logout
+ end
+
+ def test_too_short_fork
+ @admin = SocketPlayer.new "dummy", "admin", false
+ @admin.connect
+ @admin.reader
+ @admin.login
+
+ result, result2 = handshake do
+ source_game = parse_game_name(@admin)
+ @admin.puts "%%FORK #{source_game} buoy_TooShortFork-900-0 0"
+ sleep 1
+ end
+
+ assert /##\[ERROR\] number of moves to fork is out of range/ =~ @admin.message
+ @admin.logout
+ end
+
+ def test_fork
+ buoy = ShogiServer::Buoy.new
+
+ @admin = SocketPlayer.new "dummy", "admin", "*"
+ @admin.connect
+ @admin.reader
+ @admin.login
+ assert buoy.is_new_game?("buoy_Fork-1500-0")
+
+ result, result2 = handshake do
+ source_game = parse_game_name(@admin)
+ @admin.puts "%%FORK #{source_game} buoy_Fork-1500-0"
+ sleep 1
+ end
+
+ assert buoy.is_new_game?("buoy_Fork-1500-0")
+ @p1 = SocketPlayer.new "buoy_Fork", "p1", true
+ @p2 = SocketPlayer.new "buoy_Fork", "p2", false
+ @p1.connect
+ @p2.connect
+ @p1.reader
+ @p2.reader
+ @p1.login
+ @p2.login
+ sleep 1
+ @p1.game
+ @p2.game
+ sleep 1
+ @p1.agree
+ @p2.agree
+ sleep 1
+ assert /^Total_Time:1499/ =~ @p1.message
+ assert /^Total_Time:1499/ =~ @p2.message
+ @p2.move("-3334FU")
+ sleep 1
+ @p1.toryo
+ sleep 1
+ @p2.logout
+ @p1.logout
+
+ @admin.logout
+ end
+end