X-Git-Url: http://git.sourceforge.jp/view?p=shogi-server%2Fshogi-server.git;a=blobdiff_plain;f=shogi_server%2Fplayer.rb;h=471119b1647357de806ba3ebc074ab43f0bc485b;hp=e9280de3546ebb56c88b81cd27b5f048e8772c5e;hb=ec11b3359a4258d7808036231f959463e8cb608e;hpb=a903daee6aba644f82e2b058ec48c48521c10b1b diff --git a/shogi_server/player.rb b/shogi_server/player.rb index e9280de..471119b 100644 --- a/shogi_server/player.rb +++ b/shogi_server/player.rb @@ -1,7 +1,7 @@ ## $Id$ ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch) -## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org) +## Copyright (C) 2007-2012 Daigo Moriwaki (daigo at debian dot org) ## ## 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,6 +17,8 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +require 'shogi_server/command' + module ShogiServer # for a namespace class BasicPlayer @@ -25,9 +27,14 @@ class BasicPlayer @name = nil @password = nil @rate = 0 + @estimated_rate = 0 @win = 0 @loss = 0 @last_game_win = false + @rating_group = nil + @modified_at = nil + @sente = nil + @game_name = "" end # Idetifier of the player in the rating system @@ -42,6 +49,10 @@ class BasicPlayer # Score in the rating sysem attr_accessor :rate + # Estimated rate for unrated player (rate == 0) + # But this value is not persisted and cleared when player logs off. + attr_accessor :estimated_rate + # Number of games for win and loss in the rating system attr_accessor :win, :loss @@ -54,6 +65,22 @@ class BasicPlayer # Whether win the previous game or not attr_accessor :last_game_win + # true for Sente; false for Gote + attr_accessor :sente + + # game name + attr_accessor :game_name + + def is_human? + return [%r!_human$!, %r!_human@!].any? do |re| + re.match(@name) + end + end + + def is_computer? + return !is_human? + end + def modified_at @modified_at || Time.now end @@ -93,6 +120,16 @@ class BasicPlayer @player_id = @password = nil end end + + def set_sente_from_str(str) + case str + when "+" then @sente = true + when "-" then @sente = false + else + # str should be "*" + @sente = nil + end + end end @@ -106,9 +143,7 @@ class Player < BasicPlayer @protocol = nil # CSA or x1 @eol = eol || "\m" # favorite eol code @game = nil - @game_name = "" @mytime = 0 # set in start method also - @sente = nil @socket_buffer = [] @main_thread = Thread::current @write_queue = ShogiServer::TimeoutQueue.new(WRITE_THREAD_WATCH_INTERVAL) @@ -117,7 +152,7 @@ class Player < BasicPlayer end attr_accessor :socket, :status - attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente + attr_accessor :protocol, :eol, :game, :mytime attr_accessor :main_thread attr_reader :socket_buffer @@ -174,8 +209,8 @@ class Player < BasicPlayer log_debug("Terminating %s's write thread..." % [@name]) if @write_thread && @write_thread.alive? write_safe(nil) + Thread.pass # help the write_thread to terminate end - @player_logger.close if @player_logger log_debug("done.") rescue log_message(sprintf("user %s finish failed", @name)) @@ -217,6 +252,17 @@ class Player < BasicPlayer end # + # Wait for the write thread to finish. + # This method should be called just before this instance will be freed. + # + def wait_write_thread_finish(msec=1000) + while msec > 0 && @write_thread && @write_thread.alive? + sleep 0.1; msec -= 0.1 + end + @player_logger.close if @player_logger + end + + # # Note that sending a message is included in the giant lock. # def write_safe(str) @@ -240,6 +286,7 @@ class Player < BasicPlayer def run(csa_1st_str=nil) while ( csa_1st_str || str = gets_safe(@socket, (@socket_buffer.empty? ? Default_Timeout : 1)) ) + time = Time.now log(:info, :in, str) if str && str.instance_of?(String) $mutex.lock begin @@ -262,206 +309,27 @@ class Player < BasicPlayer return end str.chomp! if (str.class == String) # may be strip! ? - case str - when "" - # Application-level protocol for Keep-Alive - # If the server gets LF, it sends back LF. - # 30 sec rule (client may not send LF again within 30 sec) is not implemented yet. - write_safe("\n") - when /^[\+\-][^%]/ - if (@status == "game") - array_str = str.split(",") - move = array_str.shift - additional = array_str.shift - if /^'(.*)/ =~ additional - comment = array_str.unshift("'*#{$1.toeuc}") - end - s = @game.handle_one_move(move, self) - @game.fh.print("#{Kconv.toeuc(comment.first)}\n") if (comment && comment.first && !s) - return if (s && @protocol == LoginCSA::PROTOCOL) - end - when /^%[^%]/, :timeout - if (@status == "game") - s = @game.handle_one_move(str, self) - return if (s && @protocol == LoginCSA::PROTOCOL) - end - when :exception - log_error("Failed to receive a message from #{@name}.") - return - when /^REJECT/ - if (@status == "agree_waiting") - @game.reject(@name) - return if (@protocol == LoginCSA::PROTOCOL) - else - 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") - @status = "start_waiting" - if ((@game.sente.status == "start_waiting") && - (@game.gote.status == "start_waiting")) - @game.start - @game.sente.status = "game" - @game.gote.status = "game" - end - else - 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_safe( - %!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!) - when /^%%RATING/ - players = $league.rated_players - players.sort {|a,b| b.rate <=> a.rate}.each do |p| - write_safe("##[RATING] %s \t %4d @%s\n" % - [p.simple_player_id, p.rate, p.modified_at.strftime("%Y-%m-%d")]) - end - write_safe("##[RATING] +OK\n") - when /^%%VERSION/ - write_safe "##[VERSION] Shogi Server revision #{Revision}\n" - write_safe("##[VERSION] +OK\n") - when /^%%GAME\s*$/ - if ((@status == "connected") || (@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 (! Login::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 - - rival = nil - if (League::Floodgate.game_name?(game_name)) - if (my_sente_str != "*") - write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", my_sente_str, game_name)) - next - end - @sente = nil - else - 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 - write_safe(sprintf("##[ERROR] bad game option\n")) - next - end - end - if (rival) - @game_name = game_name - if ((my_sente_str == "*") && (rival.sente == nil)) - if (rand(2) == 0) - @sente = true - rival.sente = false - else - @sente = false - 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 - Game::new(@game_name, self, rival) - 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.players.each do |name, player| - if (player.protocol != LoginCSA::PROTOCOL) - 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.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") + delay = Time.now - time + if delay > 5 + log_warning("Detected a long delay: %.2f sec" % [delay]) + end + cmd = ShogiServer::Command.factory(str, self, time) + case cmd.call + when :return return - when /^CHALLENGE/ - # This command is only available for CSA's official testing server. - # So, this means nothing for this program. - write_safe("CHALLENGE ACCEPTED\n") - when /^\s*$/ - ## ignore null string + when :continue + # do nothing else - msg = "##[ERROR] unknown command %s\n" % [str] - write_safe(msg) - log_error(msg) + # TODO never reach + log_error("Detected a wrong return value for %s" % [cmd]) end + ensure $mutex.unlock end end # enf of while + log_warning("%s's socket was suddenly closed" % [@name]) end # def run end # class