X-Git-Url: http://git.sourceforge.jp/view?a=blobdiff_plain;ds=sidebyside;f=shogi-server;h=df982eff6562741e15d4df7060e91994d4e0db02;hb=af742f051db24cdb1eba59619e17e64823e71035;hp=1d7a80bf731b307a18811c7eefee581c9b9b5388;hpb=fd6d6589ca6426133577ec9488070dc8b027a14f;p=shogi-server%2Fshogi-server.git diff --git a/shogi-server b/shogi-server index 1d7a80b..df982ef 100755 --- a/shogi-server +++ b/shogi-server @@ -1,7 +1,7 @@ #! /usr/bin/env ruby -## -*-Ruby-*- $RCSfile$ $Revision$ $Name$ +## $Id$ -## Copyright (C) 2004 nanami@2ch +## Copyright (C) 2004 NABEYA Kenichi (aka 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 @@ -17,10 +17,14 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -DEFAULT_TIMEOUT = 10 # for single socket operation -Total_Time = 1500 +Max_Write_Queue_Size = 1000 +Max_Identifier_Length = 32 +Default_Timeout = 60 # for single socket operation + +Default_Game_Name = "default-1500-0" + +One_Time = 10 Least_Time_Per_Move = 1 -Watchdog_Time = 30 # time for ping Login_Time = 300 # time for LOGIN Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.') @@ -34,12 +38,13 @@ require 'getoptlong' require 'thread' require 'timeout' require 'socket' -require 'ping' TCPSocket.do_not_reverse_lookup = true +Thread.abort_on_exception = true + class TCPSocket - def gets_timeout(t = DEFAULT_TIMEOUT) + def gets_timeout(t = Default_Timeout) begin timeout(t) do return self.gets @@ -81,81 +86,88 @@ end class League def initialize - @hash = Hash::new + @games = Hash::new + @players = Hash::new + @event = nil end - attr_accessor :hash + attr_accessor :players, :games, :event def add(player) - @hash[player.name] = player + @players[player.name] = player end def delete(player) - @hash.delete(player.name) - end - def duplicated?(player) - if (@hash[player.name]) - return true - else - return false - end + @players.delete(player.name) 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)) && + ((sente == nil) || (player.sente == nil) || (player.sente == sente)) && ((searcher == nil) || (player != searcher))) return player end 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 # set in start method also + @mytime = 0 # set in start method also @sente = nil - @watchdog_thread = nil - + @writer_thread = nil + @main_thread = nil + @write_queue = Queue::new login(str) end attr_accessor :name, :password, :socket, :status - 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?) + attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente + attr_accessor :main_thread, :writer_thread, :write_queue + def kill + log_message(sprintf("user %s killed", @name)) + if (@game) + @game.kill(self) + end + finish + Thread::kill(@main_thread) if @main_thread end - def watchdog(time) - while true + def finish + if (@status != "finished") + @status = "finished" + log_message(sprintf("user %s finish", @name)) + Thread::kill(@writer_thread) if @writer_thread begin - Ping.pingecho(@socket.addr[3]) + @socket.close if (! @socket.closed?) rescue + log_message(sprintf("user %s finish failed", @name)) end - sleep(time) + end + end + + def write_safe(str) + @write_queue.push(str.gsub(/[\r\n]+/, @eol)) + end + + def writer + while (str = @write_queue.pop) + @socket.write_safe(str) end end def to_s if ((status == "game_waiting") || + (status == "start_waiting") || (status == "agree_waiting") || (status == "game")) if (@sente) @@ -163,7 +175,7 @@ class Player elsif (@sente == false) return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name) elsif (@sente == nil) - return sprintf("%s %s %s %s +-", @name, @protocol, @status, @game_name) + return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name) end else return sprintf("%s %s %s", @name, @protocol, @status) @@ -174,10 +186,6 @@ class Player @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"') end - def write_safe(str) - @socket.write_safe(str.gsub(/[\r\n]+/, @eol)) - end - def login(str) str =~ /([\r\n]*)$/ @eol = $1 @@ -188,8 +196,9 @@ class Player else @protocol = "CSA" end - @watchdog_thread = Thread::start do - watchdog(Watchdog_Time) + @main_thread = Thread::current + @writer_thread = Thread::start do + writer() end end @@ -200,25 +209,53 @@ class Player write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol)) else log_message(sprintf("user %s run in CSA mode", @name)) - csa_1st_str = "%%GAME default +-" + if (good_game_name?(@password)) + csa_1st_str = "%%GAME #{@password} *" + else + csa_1st_str = "%%GAME #{Default_Game_Name} *" + end end - - while (csa_1st_str || (str = @socket.gets_safe(@mytime))) + while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout))) begin $mutex.lock if (csa_1st_str) str = csa_1st_str csa_1st_str = nil end + if (@write_queue.size > Max_Write_Queue_Size) + log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size)) + return + end + + if (@status == "finished") + return + end str.chomp! if (str.class == String) case str - when /^[\+\-%][^%]/, :timeout + when /^[\+\-][^%]/ + if (@status == "game") + array_str = str.split(",") + move = array_str.shift + additional = array_str.shift + if /^'(.*)/ =~ additional + comment = array_str.unshift("'*#{$1}") + end + s = @game.handle_one_move(move, self) + @game.fh.print("#{comment}\n") if (comment && !s) + return if (s && @protocol == "CSA") + end + when /^%[^%]/, :timeout if (@status == "game") s = @game.handle_one_move(str, self) return if (s && @protocol == "CSA") + end + when /^REJECT/ + if (@status == "agree_waiting") + @game.reject(@name) + return if (@protocol == "CSA") else - next + write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status)) end when /^AGREE/ if (@status == "agree_waiting") @@ -230,73 +267,137 @@ class Player @game.gote.status = "game" end else - write_safe("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status) - next + write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status)) + end + when /^%%SHOW\s+(\S+)/ + game_id = $1 + if (LEAGUE.games[game_id]) + write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] ')) + end + write_safe("##[SHOW] +OK\n") + when /^%%MONITORON\s+(\S+)/ + game_id = $1 + if (LEAGUE.games[game_id]) + LEAGUE.games[game_id].monitoron(self) + write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] ")) + write_safe("##[MONITOR][#{game_id}] +OK\n") + end + when /^%%MONITOROFF\s+(\S+)/ + game_id = $1 + if (LEAGUE.games[game_id]) + LEAGUE.games[game_id].monitoroff(self) end when /^%%HELP/ write_help - when /^%%GAME\s+(\S+)\s+([\+\-]+)/ + when /^%%GAME\s*$/ if ((@status == "connected") || (@status == "game_waiting")) - @status = "game_waiting" + @status = "connected" + @game_name = "" + else + write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status)) + end + when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/ + command_name = $1 + game_name = $2 + my_sente_str = $3 + if (! good_game_name?(game_name)) + write_safe(sprintf("##[ERROR] bad game name\n")) + next + elsif ((@status == "connected") || (@status == "game_waiting")) + ## continue else 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" - @game_name = $1 - sente_str = $2 - if (sente_str == "+") - @sente = true - rival_sente = false - elsif (sente_str == "-") - @sente = false - rival_sente = true + if ((my_sente_str == "*") || + (my_sente_str == "+") || + (my_sente_str == "-")) + ## ok else - @sente = nil - rival_sente = nil + write_safe(sprintf("##[ERROR] bad game option\n")) + next + end + + if (my_sente_str == "*") + rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference + elsif (my_sente_str == "+") + rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote + elsif (my_sente_str == "-") + rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente + else + ## never reached end - rival = LEAGUE.get_player("game_waiting", @game_name, rival_sente, self) - rival = LEAGUE.get_player("game_waiting", @game_name, nil, self) if (! rival) if (rival) - if (@sente == nil) + @game_name = game_name + if ((my_sente_str == "*") && (rival.sente == nil)) if (rand(2) == 0) @sente = true - rival_sente = false + rival.sente = false else @sente = false - rival_sente = true - end - elsif (rival_sente == nil) - if (@sente) - rival_sente = false - else - rival_sente = true + rival.sente = true end + elsif (rival.sente == true) # rival has higher priority + @sente = false + elsif (rival.sente == false) + @sente = true + elsif (my_sente_str == "+") + @sente = true + rival.sente = false + elsif (my_sente_str == "-") + @sente = false + rival.sente = true + else + ## never reached 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" + else # rival not found + if (command_name == "GAME") + @status = "game_waiting" + @game_name = game_name + if (my_sente_str == "+") + @sente = true + elsif (my_sente_str == "-") + @sente = false + else + @sente = nil + end + else # challenge + write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name)) + @status = "connected" + @game_name = "" + @sente = nil + end end 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) + player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 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 /^%%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/ + @status = "connected" write_safe("LOGOUT:completed\n") - finish return + when /^\s*$/ + ## ignore null string else write_safe(sprintf("##[ERROR] unknown command %s\n", str)) end @@ -307,12 +408,776 @@ class Player end end +class Piece + PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"} + def initialize(board, x, y, sente, promoted=false) + @board = board + @x = x + @y = y + @sente = sente + @promoted = promoted + + if ((x == 0) || (y == 0)) + if (sente) + hands = board.sente_hands + else + hands = board.gote_hands + end + hands.push(self) + hands.sort! {|a, b| + a.name <=> b.name + } + else + @board.array[x][y] = self + end + end + attr_accessor :promoted, :sente, :x, :y, :board + + def room_of_head?(x, y, name) + true + end + + def movable_grids + return adjacent_movable_grids + far_movable_grids + end + + def far_movable_grids + return [] + end + + def jump_to?(x, y) + if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9)) + if ((@board.array[x][y] == nil) || # dst is empty + (@board.array[x][y].sente != @sente)) # dst is enemy + return true + end + end + return false + end + + def put_to?(x, y) + if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9)) + if (@board.array[x][y] == nil) # dst is empty? + return true + end + end + return false + end + + def adjacent_movable_grids + grids = Array::new + if (@promoted) + moves = @promoted_moves + else + moves = @normal_moves + end + moves.each do |(dx, dy)| + if (@sente) + cand_y = @y - dy + else + cand_y = @y + dy + end + cand_x = @x + dx + if (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + end + end + return grids + end + + def move_to?(x, y, name) + return false if (! room_of_head?(x, y, name)) + return false if ((name != @name) && (name != @promoted_name)) + return false if (@promoted && (name != @promoted_name)) # can't un-promote + + if (! @promoted) + return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece + if (@sente) + return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote + else + return false if ((6 >= @y) && (6 >= y) && (name != @name)) + end + end + + if ((@x == 0) || (@y == 0)) + return jump_to?(x, y) + else + return movable_grids.include?([x, y]) + end + end + + def move_to(x, y) + if ((@x == 0) || (@y == 0)) + if (@sente) + @board.sente_hands.delete(self) + else + @board.gote_hands.delete(self) + end + @board.array[x][y] = self + elsif ((x == 0) || (y == 0)) + @promoted = false # clear promoted flag before moving to hands + if (@sente) + @board.sente_hands.push(self) + else + @board.gote_hands.push(self) + end + @board.array[@x][@y] = nil + else + @board.array[@x][@y] = nil + @board.array[x][y] = self + end + @x = x + @y = y + end + + def point + @point + end + + def name + @name + end + + def promoted_name + @promoted_name + end + + def to_s + if (@sente) + sg = "+" + else + sg = "-" + end + if (@promoted) + n = @promoted_name + else + n = @name + end + return sg + n + end +end + +class PieceFU < Piece + def initialize(*arg) + @point = 1 + @normal_moves = [[0, +1]] + @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]] + @name = "FU" + @promoted_name = "TO" + super + end + def room_of_head?(x, y, name) + if (name == "FU") + if (@sente) + return false if (y == 1) + else + return false if (y == 9) + end + ## 2fu check + c = 0 + iy = 1 + while (iy <= 9) + if ((iy != @y) && # not source position + @board.array[x][iy] && + (@board.array[x][iy].sente == @sente) && # mine + (@board.array[x][iy].name == "FU") && + (@board.array[x][iy].promoted == false)) + return false + end + iy = iy + 1 + end + end + return true + end +end + +class PieceKY < Piece + def initialize(*arg) + @point = 1 + @normal_moves = [] + @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]] + @name = "KY" + @promoted_name = "NY" + super + end + def room_of_head?(x, y, name) + if (name == "KY") + if (@sente) + return false if (y == 1) + else + return false if (y == 9) + end + end + return true + end + def far_movable_grids + grids = Array::new + if (@promoted) + return [] + else + if (@sente) # up + cand_x = @x + cand_y = @y - 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_y = cand_y - 1 + end + else # down + cand_x = @x + cand_y = @y + 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_y = cand_y + 1 + end + end + return grids + end + end +end +class PieceKE < Piece + def initialize(*arg) + @point = 1 + @normal_moves = [[+1, +2], [-1, +2]] + @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]] + @name = "KE" + @promoted_name = "NK" + super + end + def room_of_head?(x, y, name) + if (name == "KE") + if (@sente) + return false if ((y == 1) || (y == 2)) + else + return false if ((y == 9) || (y == 8)) + end + end + return true + end +end +class PieceGI < Piece + def initialize(*arg) + @point = 1 + @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]] + @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]] + @name = "GI" + @promoted_name = "NG" + super + end +end +class PieceKI < Piece + def initialize(*arg) + @point = 1 + @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]] + @promoted_moves = [] + @name = "KI" + @promoted_name = nil + super + end +end +class PieceKA < Piece + def initialize(*arg) + @point = 5 + @normal_moves = [] + @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]] + @name = "KA" + @promoted_name = "UM" + super + end + def far_movable_grids + grids = Array::new + ## up right + cand_x = @x - 1 + cand_y = @y - 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_x = cand_x - 1 + cand_y = cand_y - 1 + end + ## down right + cand_x = @x - 1 + cand_y = @y + 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_x = cand_x - 1 + cand_y = cand_y + 1 + end + ## up left + cand_x = @x + 1 + cand_y = @y - 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_x = cand_x + 1 + cand_y = cand_y - 1 + end + ## down left + cand_x = @x + 1 + cand_y = @y + 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_x = cand_x + 1 + cand_y = cand_y + 1 + end + return grids + end +end +class PieceHI < Piece + def initialize(*arg) + @point = 5 + @normal_moves = [] + @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]] + @name = "HI" + @promoted_name = "RY" + super + end + def far_movable_grids + grids = Array::new + ## up + cand_x = @x + cand_y = @y - 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_y = cand_y - 1 + end + ## down + cand_x = @x + cand_y = @y + 1 + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_y = cand_y + 1 + end + ## right + cand_x = @x - 1 + cand_y = @y + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_x = cand_x - 1 + end + ## down + cand_x = @x + 1 + cand_y = @y + while (jump_to?(cand_x, cand_y)) + grids.push([cand_x, cand_y]) + break if (! put_to?(cand_x, cand_y)) + cand_x = cand_x + 1 + end + return grids + end +end +class PieceOU < Piece + def initialize(*arg) + @point = 0 + @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]] + @promoted_moves = [] + @name = "OU" + @promoted_name = nil + super + end +end + class Board + def initialize + @sente_hands = Array::new + @gote_hands = Array::new + @history = Hash::new + @sente_history = Hash::new + @gote_history = Hash::new + @array = [[], [], [], [], [], [], [], [], [], []] + end + attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history + + def initial + PieceKY::new(self, 1, 1, false) + PieceKE::new(self, 2, 1, false) + PieceGI::new(self, 3, 1, false) + PieceKI::new(self, 4, 1, false) + PieceOU::new(self, 5, 1, false) + PieceKI::new(self, 6, 1, false) + PieceGI::new(self, 7, 1, false) + PieceKE::new(self, 8, 1, false) + PieceKY::new(self, 9, 1, false) + PieceKA::new(self, 2, 2, false) + PieceHI::new(self, 8, 2, false) + PieceFU::new(self, 1, 3, false) + PieceFU::new(self, 2, 3, false) + PieceFU::new(self, 3, 3, false) + PieceFU::new(self, 4, 3, false) + PieceFU::new(self, 5, 3, false) + PieceFU::new(self, 6, 3, false) + PieceFU::new(self, 7, 3, false) + PieceFU::new(self, 8, 3, false) + PieceFU::new(self, 9, 3, false) + + PieceKY::new(self, 1, 9, true) + PieceKE::new(self, 2, 9, true) + PieceGI::new(self, 3, 9, true) + PieceKI::new(self, 4, 9, true) + PieceOU::new(self, 5, 9, true) + PieceKI::new(self, 6, 9, true) + PieceGI::new(self, 7, 9, true) + PieceKE::new(self, 8, 9, true) + PieceKY::new(self, 9, 9, true) + PieceKA::new(self, 8, 8, true) + PieceHI::new(self, 2, 8, true) + PieceFU::new(self, 1, 7, true) + PieceFU::new(self, 2, 7, true) + PieceFU::new(self, 3, 7, true) + PieceFU::new(self, 4, 7, true) + PieceFU::new(self, 5, 7, true) + PieceFU::new(self, 6, 7, true) + PieceFU::new(self, 7, 7, true) + PieceFU::new(self, 8, 7, true) + PieceFU::new(self, 9, 7, true) + end + + def have_piece?(hands, name) + piece = hands.find { |i| + i.name == name + } + return piece + end + + def move_to(x0, y0, x1, y1, name, sente) + if (sente) + hands = @sente_hands + else + hands = @gote_hands + end + + if ((x0 == 0) || (y0 == 0)) + piece = have_piece?(hands, name) + return :illegal if (! piece.move_to?(x1, y1, name)) + piece.move_to(x1, y1) + else + return :illegal if (! @array[x0][y0].move_to?(x1, y1, name)) + if (@array[x0][y0].name != name) # promoted ? + @array[x0][y0].promoted = true + end + if (@array[x1][y1]) + if (@array[x1][y1].name == "OU") + return :outori # return board update + end + @array[x1][y1].sente = @array[x0][y0].sente + @array[x1][y1].move_to(0, 0) + hands.sort! {|a, b| + a.name <=> b.name + } + end + @array[x0][y0].move_to(x1, y1) + end + return true + end + + def look_for_ou(sente) + x = 1 + while (x <= 9) + y = 1 + while (y <= 9) + if (@array[x][y] && + (@array[x][y].name == "OU") && + (@array[x][y].sente == sente)) + return @array[x][y] + end + y = y + 1 + end + x = x + 1 + end + raise "can't find ou" + end + + def checkmated?(sente) # sente is loosing + ou = look_for_ou(sente) + x = 1 + while (x <= 9) + y = 1 + while (y <= 9) + if (@array[x][y] && + (@array[x][y].sente != sente)) + if (@array[x][y].movable_grids.include?([ou.x, ou.y])) + return true + end + end + y = y + 1 + end + x = x + 1 + end + return false + end + + def uchifuzume?(sente) + rival_ou = look_for_ou(! sente) # rival's ou + if (sente) # rival is gote + if ((rival_ou.y != 9) && + (@array[rival_ou.x][rival_ou.y + 1]) && + (@array[rival_ou.x][rival_ou.y + 1].name == "FU") && + (@array[rival_ou.x][rival_ou.y + 1].sente == sente)) # uchifu true + fu_x = rival_ou.x + fu_y = rival_ou.y + 1 + else + return false + end + else # gote + if ((rival_ou.y != 0) && + (@array[rival_ou.x][rival_ou.y - 1]) && + (@array[rival_ou.x][rival_ou.y - 1].name == "FU") && + (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true + fu_x = rival_ou.x + fu_y = rival_ou.y - 1 + else + return false + end + end + + ## case: rival_ou is moving + escaped = false + rival_ou.movable_grids.each do |(cand_x, cand_y)| + tmp_board = Marshal.load(Marshal.dump(self)) + s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente) + raise "internal error" if (s != true) + if (! tmp_board.checkmated?(! sente)) # good move + return false + end + end + + ## case: rival is capturing fu + x = 1 + while (x <= 9) + y = 1 + while (y <= 9) + if (@array[x][y] && + (@array[x][y].sente != sente) && + @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable + if (@array[x][y].promoted) + name = @array[x][y].promoted_name + else + name = @array[x][y].name + end + tmp_board = Marshal.load(Marshal.dump(self)) + s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente) + raise "internal error" if (s != true) + if (! tmp_board.checkmated?(! sente)) # good move + return false + end + end + y = y + 1 + end + x = x + 1 + end + return true + end + + def oute_sennichite?(sente) + if (checkmated?(! sente)) + str = to_s + if (sente) + if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times + return true + end + else + if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times + return true + end + end + end + return false + end + + def sennichite?(sente) + str = to_s + if (@history[str] && (@history[str] >= 3)) # already 3 times + return true + end + return false + end + + def good_kachi?(sente) + return false if (checkmated?(sente)) + ou = look_for_ou(sente) + return false if (sente && (ou.y >= 4)) + return false if (! sente && (ou.y <= 6)) + + number = 0 + point = 0 + + if (sente) + hands = @sente_hands + r = [1, 2, 3] + else + hands = @gote_hands + r = [7, 8, 9] + end + r.each do |y| + x = 1 + while (x <= 9) + if (@array[x][y] && + (@array[x][y].sente == sente) && + (@array[x][y].point > 0)) + point = point + @array[x][y].point + number = number + 1 + end + x = x + 1 + end + end + hands.each do |piece| + point = point + piece.point + end + + return false if (number < 10) + if (sente) + return false if (point < 28) + else + return false if (point < 27) + end + return true + end + + def handle_one_move(str) + if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/) + sg = $1 + x0 = $2.to_i + y0 = $3.to_i + x1 = $4.to_i + y1 = $5.to_i + name = $6 + elsif (str =~ /^%KACHI/) + if (@sente == @current_player) + sente = true + else + sente = false + end + if (good_kachi?(sente)) + return :kachi_win + else + return :kachi_lose + end + elsif (str =~ /^%TORYO/) + return :toryo + else + return :illegal + end + + if (((x0 == 0) || (y0 == 0)) && # source is not from hand + ((x0 != 0) || (y0 != 0))) + return :illegal + elsif ((x1 == 0) || (y1 == 0)) # destination is out of board + return :illegal + end + + if (sg == "+") + sente = true + hands = @sente_hands + else + sente = false + hands = @gote_hands + end + + ## source check + if ((x0 == 0) && (y0 == 0)) + return :illegal if (! have_piece?(hands, name)) + elsif (! @array[x0][y0]) + return :illegal # no piece + elsif (@array[x0][y0].sente != sente) + return :illegal # this is not mine + elsif (@array[x0][y0].name != name) + return :illegal if (@array[x0][y0].promoted_name != name) # can't promote + end + + ## destination check + if (@array[x1][y1] && + (@array[x1][y1].sente == sente)) # can't capture mine + return :illegal + elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1]) + return :illegal # can't put on existing piece + end + + tmp_board = Marshal.load(Marshal.dump(self)) + return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal) + return :oute_kaihimore if (tmp_board.checkmated?(sente)) + return :oute_sennichite if tmp_board.oute_sennichite?(sente) + return :sennichite if tmp_board.sennichite?(sente) + + if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente)) + return :uchifuzume + end + + move_to(x0, y0, x1, y1, name, sente) + str = to_s + + if (checkmated?(! sente)) + if (sente) + @sente_history[str] = (@sente_history[str] || 0) + 1 + else + @gote_history[str] = (@gote_history[str] || 0) + 1 + end + else + if (sente) + @sente_history.clear + else + @gote_history.clear + end + end + @history[str] = (@history[str] || 0) + 1 + return :normal + 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 + a.push("+\n") + return a.join + end end class Game + @@mutex = Mutex.new + @@time = 0 + def initialize(game_name, player0, player1) + @monitors = Array::new @game_name = game_name + if (@game_name =~ /-(\d+)-(\d+)$/) + @total_time = $1.to_i + @byoyomi = $2.to_i + end + if (player0.sente) @sente = player0 @gote = player1 @@ -325,88 +1190,309 @@ class Game @sente.game = self @gote.game = self + + @last_move = "" + @current_turn = 0 + @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")) - log_message(sprintf("game created %s %s %s", game_name, sente.name, gote.name)) - + + @id = sprintf("%s+%s+%s+%s+%s", + LEAGUE.event, @game_name, @sente.name, @gote.name, issue_current_time) @logfile = @id + ".csa" + + LEAGUE.games[@id] = self + + log_message(sprintf("game created %s", @id)) + @board = Board::new + @board.initial @start_time = nil @fh = nil propose end - attr_accessor :game_name, :sente, :gote, :id, :board, :current_player, :next_player, :fh + attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors + attr_accessor :last_move, :current_turn + + def monitoron(monitor) + @monitors.delete(monitor) + @monitors.push(monitor) + end + + def monitoroff(monitor) + @monitors.delete(monitor) + end + + def reject(rejector) + @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector)) + @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector)) + finish + end + + def kill(killer) + if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting")) + reject(killer.name) + elsif (@current_player == killer) + abnormal_lose() + finish + end + end def finish - log_message(sprintf("game finished %s %s %s", game_name, sente.name, gote.name)) + log_message(sprintf("game finished %s", @id)) @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 + @monitors = Array::new + @sente = nil + @gote = nil + @current_player = nil + @next_player = nil + LEAGUE.games.delete(@id) end def handle_one_move(str, player) - finish_flag = false + finish_flag = true if (@current_player == player) @end_time = Time::new - t = @end_time - @start_time + t = (@end_time - @start_time).ceil t = Least_Time_Per_Move if (t < Least_Time_Per_Move) - if (str != :timeout) - @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) + + move_status = nil + if ((@current_player.mytime - t <= -@byoyomi) && ((@total_time > 0) || (@byoyomi > 0))) + status = :timeout + elsif (str == :timeout) + return false # time isn't expired. players aren't swapped. continue game + else @current_player.mytime = @current_player.mytime - t + if (@current_player.mytime < 0) + @current_player.mytime = 0 + end + +# begin + move_status = @board.handle_one_move(str) +# rescue +# log_error("handle_one_move raise exception for #{str}") +# move_status = :illegal +# end + + if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore)) + @fh.printf("'ILLEGAL_MOVE(%s)\n", str) + else + if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite)) + @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) + @last_move = sprintf("%s,T%d", str, t) + @current_turn = @current_turn + 1 + end + + @monitors.each do |monitor| + monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] ")) + monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id)) + end + end + end + + if (@next_player.status != "game") # rival is logout or disconnected + abnormal_win() + elsif (status == :timeout) + timeout_lose() + elsif (move_status == :illegal) + illegal_lose() + elsif (move_status == :kachi_win) + kachi_win() + elsif (move_status == :kachi_lose) + kachi_lose() + elsif (move_status == :toryo) + toryo_lose() + elsif (move_status == :outori) + outori_win() + elsif (move_status == :sennichite) + sennichite_draw() + elsif (move_status == :oute_sennichite) + oute_sennichite_lose() + elsif (move_status == :uchifuzume) + uchifuzume_lose() + elsif (move_status == :oute_kaihimore) + oute_kaihimore_lose() else - @current_player.mytime = 0 - end - if (@current_player.mytime <= 0) - timeout_end() - finish_flag = true - elsif (str =~ /%KACHI/) - kachi_end() - finish_flag = true - elsif (str =~ /%TORYO/) - toryo_end - finish_flag = true + finish_flag = false end + finish() if finish_flag (@current_player, @next_player) = [@next_player, @current_player] @start_time = Time::new - finish if (finish_flag) return finish_flag end end - def timeout_end + def abnormal_win + @current_player.status = "connected" + @next_player.status = "connected" + @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n") + @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n") + @fh.printf("%%TORYO\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id)) + end + end + + def abnormal_lose + @current_player.status = "connected" + @next_player.status = "connected" + @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n") + @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n") + @fh.printf("%%TORYO\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id)) + end + end + + def sennichite_draw + @current_player.status = "connected" + @next_player.status = "connected" + @current_player.write_safe("#SENNICHITE\n#DRAW\n") + @next_player.write_safe("#SENNICHITE\n#DRAW\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id)) + end + end + + def oute_sennichite_lose + @current_player.status = "connected" + @next_player.status = "connected" + @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n") + @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id)) + end + end + + def illegal_lose + @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") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id)) + end + end + + def uchifuzume_lose + @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") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id)) + end + end + + def oute_kaihimore_lose + @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") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id)) + end + end + + def timeout_lose @current_player.status = "connected" @next_player.status = "connected" @current_player.write_safe("#TIME_UP\n#LOSE\n") @next_player.write_safe("#TIME_UP\n#WIN\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id)) + end + end + + def kachi_win + @current_player.status = "connected" + @next_player.status = "connected" + @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n") + @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n") + @fh.printf("%%KACHI\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id)) + end end - def kachi_end + def kachi_lose @current_player.status = "connected" @next_player.status = "connected" - @current_player.write_safe("#JISHOGI\n#WIN\n") - @next_player.write_safe("#JISHOGI\n#LOSE\n") + @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n") + @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n") + @fh.printf("%%KACHI\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id)) + end end - def toryo_end + def toryo_lose @current_player.status = "connected" @next_player.status = "connected" - @current_player.write_safe("#RESIGN\n#LOSE\n") - @next_player.write_safe("#RESIGN\n#WIN\n") + @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n") + @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n") + @fh.printf("%%TORYO\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id)) + end + end + + def outori_win + @current_player.status = "connected" + @next_player.status = "connected" + @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n") + @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n") + @fh.print(@board.to_s.gsub(/^/, "\'")) + @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name) + @monitors.each do |monitor| + monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id)) + end end def start - log_message(sprintf("game started %s %s %s", game_name, sente.name, gote.name)) + log_message(sprintf("game started %s", @id)) @sente.write_safe(sprintf("START:%s\n", @id)) @gote.write_safe(sprintf("START:%s\n", @id)) - @mytime = Total_Time + @sente.mytime = @total_time + @gote.mytime = @total_time @start_time = Time::new end @@ -426,21 +1512,56 @@ class Game @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S")) @fh.print <= 4) - client.close - Thread::kill(Thread::current) + if (LEAGUE.players[player.name]) + if ((LEAGUE.players[player.name].password == player.password) && + (LEAGUE.players[player.name].status != "game")) + log_message(sprintf("user %s login forcely", player.name)) + LEAGUE.players[player.name].kill + else + client.write_safe("LOGIN:incorrect" + eol) + client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4) + client.close + Thread::exit + return + end end LEAGUE.add(player) break else client.write_safe("LOGIN:incorrect" + 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 ensure $mutex.unlock end end # login loop + if (! player) + client.close + Thread::exit + return + end 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.kill(player) + end + player.finish # socket has been closed + LEAGUE.delete(player) + log_message(sprintf("user %s logout", player.name)) + ensure + $mutex.unlock + end end end end if ($0 == __FILE__) + LEAGUE = League::new main end