#! /usr/bin/env ruby
## -*-Ruby-*- $RCSfile$ $Revision$ $Name$
-## Copyright (C) 2004 773@2ch
+## Copyright (C) 2004 nanami@2ch
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
return nil
end
end
- def gets_safe
- begin
- return self.gets
- rescue
- return nil
+ def gets_safe(t = nil)
+ if (t && t > 0)
+ begin
+ timeout(t) do
+ return self.gets
+ end
+ rescue TimeoutError
+ return :timeout
+ rescue
+ return nil
+ end
+ else
+ begin
+ return self.gets
+ rescue
+ return nil
+ end
end
end
def write_safe(str)
class League
def initialize
- @hash = Hash::new
+ @games = Hash::new
+ @players = Hash::new
end
- attr_accessor :hash
+ attr_accessor :players, :games
def add(player)
- @hash[player.name] = player
+ @players[player.name] = player
end
def delete(player)
- @hash.delete(player.name)
+ @players.delete(player.name)
end
def duplicated?(player)
- if (@hash[player.name])
+ if (@players[player.name])
return true
else
return false
end
end
def get_player(status, game_name, sente, searcher=nil)
- @hash.each do |name, player|
+ @players.each do |name, player|
if ((player.status == status) &&
(player.game_name == game_name) &&
((player.sente == nil) || (player.sente == sente)) &&
end
return nil
end
- def new_game(game_name, player0, player1)
- game = Game::new(game_name, player0, player1)
- end
end
-
-
-
class Player
def initialize(str, socket)
@name = nil
@password = nil
@socket = socket
- @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game
+ @status = "connected" # game_waiting -> agree_waiting -> start_waiting -> game -> finished
@protocol = nil # CSA or x1
@eol = "\m" # favorite eol code
@game = nil
@game_name = ""
- @mytime = Total_Time
+ @mytime = Total_Time # set in start method also
@sente = nil
@watchdog_thread = nil
attr_accessor :protocol, :eol, :game, :mytime, :watchdog_thread, :game_name, :sente
def finish
- log_message(sprintf("user %s finish", @name))
- Thread::kill(@watchdog_thread) if @watchdog_thread
- @socket.close if (! @socket.closed?)
+ if (@status != "finished")
+ @status = "finished"
+ log_message(sprintf("user %s finish", @name))
+ Thread::kill(@watchdog_thread) if @watchdog_thread
+ @socket.close if (! @socket.closed?)
+ end
end
def watchdog(time)
(status == "agree_waiting") ||
(status == "game"))
if (@sente)
- return sprintf("%s %s %s +", @name, @status, @game_name)
+ return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
elsif (@sente == false)
- return sprintf("%s %s %s -", @name, @status, @game_name)
+ return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
elsif (@sente == nil)
- return sprintf("%s %s %s +-", @name, @status, @game_name)
+ return sprintf("%s %s %s %s +-", @name, @protocol, @status, @game_name)
end
else
- return sprintf("%s %s", @name, @status)
+ return sprintf("%s %s %s", @name, @protocol, @status)
end
end
log_message(sprintf("user %s run in CSA mode", @name))
csa_1st_str = "%%GAME default +-"
end
-
- while (csa_1st_str || (str = @socket.gets_safe))
+ while (csa_1st_str || (str = @socket.gets_safe(@mytime)))
begin
$mutex.lock
if (csa_1st_str)
str = csa_1st_str
csa_1st_str = nil
end
- str.chomp!
+ if (@status == "finished")
+ return
+ end
+ str.chomp! if (str.class == String)
case str
- when /^[\+\-%][^%]/
+ when /^[\+\-%][^%]/, :timeout
if (@status == "game")
s = @game.handle_one_move(str, self)
return if (s && @protocol == "CSA")
@game.gote.status = "game"
end
else
- write_safe("## you are in %s status. AGREE is valid in agree_waiting status\n", @status)
+ write_safe("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status)
next
end
when /^%%HELP/
if ((@status == "connected") || (@status == "game_waiting"))
@status = "game_waiting"
else
- write_safe(sprintf("## you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
+ write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
next
end
@status = "game_waiting"
end
end
rival.sente = rival_sente
- LEAGUE.new_game(@game_name, self, rival)
+ Game::new(@game_name, self, rival)
self.status = "agree_waiting"
rival.status = "agree_waiting"
end
- when /^%%CHAT\s+(\S+)/
+ when /^%%CHAT\s+(.+)/
message = $1
- LEAGUE.hash.each do |name, player|
+ LEAGUE.players.each do |name, player|
if (player.protocol != "CSA")
s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message))
player.status = "zombie" if (! s)
end
end
+ when /^%%LIST/
+ buf = Array::new
+ LEAGUE.games.each do |id, game|
+ buf.push(sprintf("##[LIST] %s\n", id))
+ end
+ buf.push("##[LIST] +OK\n")
+ write_safe(buf.join)
+ when /^%%SHOW\s+(\S+)/
+ id = $1
+ if (LEAGUE.games[id])
+ write_safe(LEAGUE.games[id].board.to_s.gsub(/^/, '##[SHOW] '))
+ end
+ write_safe("##[SHOW] +OK\n")
when /^%%WHO/
buf = Array::new
- LEAGUE.hash.each do |name, player|
+ LEAGUE.players.each do |name, player|
buf.push(sprintf("##[WHO] %s\n", player.to_s))
end
buf.push("##[WHO] +OK\n")
write_safe(buf.join)
when /^LOGOUT/
write_safe("LOGOUT:completed\n")
- finish
return
else
- write_safe(sprintf("## unknown command %s\n", str))
+ write_safe(sprintf("##[ERROR] unknown command %s\n", str))
end
ensure
$mutex.unlock
end
end
+class Piece
+ PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
+ def initialize(name, sente)
+ @name = name
+ @sente = sente
+ @promoted = false
+ end
+ attr_accessor :name, :promoted, :sente
+
+ def promoted_name
+ PROMOTE[name]
+ end
+
+ def to_s
+ if (@sente)
+ sg = "+"
+ else
+ sg = "-"
+ end
+ if (@promoted)
+ n = PROMOTE[@name]
+ else
+ n = @name
+ end
+ return sg + n
+ end
+end
+
+
+
class Board
+ def initialize
+ @sente_hands = Array::new
+ @gote_hands = Array::new
+ @array = [[], [], [], [], [], [], [], [], [], []]
+ end
+ attr_accessor :array, :sente_hands, :gote_hands
+
+ def initial
+ @array[1][1] = Piece::new("KY", false)
+ @array[2][1] = Piece::new("KE", false)
+ @array[3][1] = Piece::new("GI", false)
+ @array[4][1] = Piece::new("KI", false)
+ @array[5][1] = Piece::new("OU", false)
+ @array[6][1] = Piece::new("KI", false)
+ @array[7][1] = Piece::new("GI", false)
+ @array[8][1] = Piece::new("KE", false)
+ @array[9][1] = Piece::new("KY", false)
+ @array[2][2] = Piece::new("KA", false)
+ @array[8][2] = Piece::new("HI", false)
+ @array[1][3] = Piece::new("FU", false)
+ @array[2][3] = Piece::new("FU", false)
+ @array[3][3] = Piece::new("FU", false)
+ @array[4][3] = Piece::new("FU", false)
+ @array[5][3] = Piece::new("FU", false)
+ @array[6][3] = Piece::new("FU", false)
+ @array[7][3] = Piece::new("FU", false)
+ @array[8][3] = Piece::new("FU", false)
+ @array[9][3] = Piece::new("FU", false)
+
+ @array[1][9] = Piece::new("KY", true)
+ @array[2][9] = Piece::new("KE", true)
+ @array[3][9] = Piece::new("GI", true)
+ @array[4][9] = Piece::new("KI", true)
+ @array[5][9] = Piece::new("OU", true)
+ @array[6][9] = Piece::new("KI", true)
+ @array[7][9] = Piece::new("GI", true)
+ @array[8][9] = Piece::new("KE", true)
+ @array[9][9] = Piece::new("KY", true)
+ @array[2][8] = Piece::new("HI", true)
+ @array[8][8] = Piece::new("KA", true)
+ @array[1][7] = Piece::new("FU", true)
+ @array[2][7] = Piece::new("FU", true)
+ @array[3][7] = Piece::new("FU", true)
+ @array[4][7] = Piece::new("FU", true)
+ @array[5][7] = Piece::new("FU", true)
+ @array[6][7] = Piece::new("FU", true)
+ @array[7][7] = Piece::new("FU", true)
+ @array[8][7] = Piece::new("FU", true)
+ @array[9][7] = Piece::new("FU", true)
+ end
+
+ def get_piece_from_hands(hands, name)
+ p = hands.find { |i|
+ i.name == name
+ }
+ if (p)
+ hands.delete(p)
+ end
+ return p
+ end
+
+ def handle_one_move(str)
+ if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
+ p = $1
+ x0 = $2.to_i
+ y0 = $3.to_i
+ x1 = $4.to_i
+ y1 = $5.to_i
+ name = $6
+ elsif (str =~ /^%/)
+ return true
+ else
+ return false # illegal move
+ end
+ if (p == "+")
+ sente = true
+ hands = @sente_hands
+ else
+ sente = false
+ hands = @gote_hands
+ end
+ if (@array[x1][y1])
+ if (@array[x1][y1] == sente) # this is mine
+ return false
+ end
+ hands.push(@array[x1][y1])
+ @array[x1][y1] = nil
+ end
+ if ((x0 == 0) && (y0 == 0))
+ p = get_piece_from_hands(hands, name)
+ return false if (! p) # i don't have this one
+ @array[x1][y1] = p
+ p.sente = sente
+ p.promoted = false
+ else
+ @array[x1][y1] = @array[x0][y0]
+ @array[x0][y0] = nil
+ if (@array[x1][y1].name != name) # promoted ?
+ return false if (@array[x1][y1].promoted_name != name) # can't promote
+ @array[x1][y1].promoted = true
+ end
+ end
+ return true # legal move
+ end
+
+ def to_s
+ a = Array::new
+ y = 1
+ while (y <= 9)
+ a.push(sprintf("P%d", y))
+ x = 9
+ while (x >= 1)
+ piece = @array[x][y]
+ if (piece)
+ s = piece.to_s
+ else
+ s = " * "
+ end
+ a.push(s)
+ x = x - 1
+ end
+ a.push(sprintf("\n"))
+ y = y + 1
+ end
+ if (! sente_hands.empty?)
+ a.push("P+")
+ sente_hands.each do |p|
+ a.push("00" + p.name)
+ end
+ a.push("\n")
+ end
+ if (! gote_hands.empty?)
+ a.push("P-")
+ gote_hands.each do |p|
+ a.push("00" + p.name)
+ end
+ a.push("\n")
+ end
+ return a.join
+ end
end
class Game
@sente.game = self
@gote.game = self
+
@sente.status = "agree_waiting"
@gote.status = "agree_waiting"
@id = sprintf("%s-%s-%s-%s", @game_name, @sente.name, @gote.name, Time::new.strftime("%Y%m%d%H%M%S"))
+ LEAGUE.games[@id] = self
+
+
log_message(sprintf("game created %s %s %s", game_name, sente.name, gote.name))
@logfile = @id + ".csa"
@board = Board::new
+ @board.initial
@start_time = nil
@fh = nil
log_message(sprintf("game finished %s %s %s", game_name, sente.name, gote.name))
@fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
@fh.close
+
+ @sente.game = nil
+ @gote.game = nil
@sente.status = "connected"
@gote.status = "connected"
if (@current_player.protocol == "CSA")
@current_player.finish
end
+ if (@next_player.protocol == "CSA")
+ @next_player.finish
+ end
+ LEAGUE.games.delete(@id)
end
def handle_one_move(str, player)
@end_time = Time::new
t = @end_time - @start_time
t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
- @sente.write_safe(sprintf("%s,T%d\n", str, t))
- @gote.write_safe(sprintf("%s,T%d\n", str, t))
- @fh.printf("%s\nT%d\n", str, t)
- @current_player.mytime = @current_player.mytime - t
- if (@current_player.mytime < 0)
+ if (str != :timeout)
+ legal_move = @board.handle_one_move(str)
+ if (legal_move)
+ @sente.write_safe(sprintf("%s,T%d\n", str, t))
+ @gote.write_safe(sprintf("%s,T%d\n", str, t))
+ @fh.printf("%s\nT%d\n", str, t)
+ else
+ @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
+ end
+ @current_player.mytime = @current_player.mytime - t
+ else
+ @current_player.mytime = 0
+ end
+ if (!legal_move)
+ illegal_end()
+ finish_flag = true
+ elsif (@current_player.mytime <= 0)
timeout_end()
finish_flag = true
elsif (str =~ /%KACHI/)
kachi_end()
finish_flag = true
elsif (str =~ /%TORYO/)
- toryo_end
+ toryo_end()
finish_flag = true
end
(@current_player, @next_player) = [@next_player, @current_player]
@start_time = Time::new
- finish if (finish_flag)
return finish_flag
end
end
+ def illegal_end
+ @current_player.status = "connected"
+ @next_player.status = "connected"
+ @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
+ @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+ end
+
def timeout_end
@current_player.status = "connected"
@next_player.status = "connected"
log_message(sprintf("game started %s %s %s", game_name, sente.name, gote.name))
@sente.write_safe(sprintf("START:%s\n", @id))
@gote.write_safe(sprintf("START:%s\n", @id))
+ @mytime = Total_Time
@start_time = Time::new
end
def propose_message(sg_flag)
str = <<EOM
+BEGIN Game_Summary
+Protocol_Version:1.0
Protocol_Mode:Server
Format:Shogi 1.0
Game_ID:#{@id}
player = Player::new(str, client)
if (LEAGUE.duplicated?(player))
client.write_safe("LOGIN:incorrect" + eol)
- client.write_safe(sprintf("username %s is already connected%s", player.name, eol))
+ client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
client.close
Thread::kill(Thread::current)
end
break
else
client.write_safe("LOGIN:incorrect" + eol)
- client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol)
+ client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
client.close
Thread::kill(Thread::current)
end
end # login loop
log_message(sprintf("user %s login", player.name))
player.run
- LEAGUE.delete(player)
- log_message(sprintf("user %s logout", player.name))
+ begin
+ $mutex.lock
+ if (player.game)
+ player.game.finish
+ elsif (player.status != "finished")
+ player.finish
+ end
+ LEAGUE.delete(player)
+ log_message(sprintf("user %s logout", player.name))
+ ensure
+ $mutex.unlock
+ end
end
end
end