X-Git-Url: http://git.sourceforge.jp/view?p=shogi-server%2Fshogi-server.git;a=blobdiff_plain;f=shogi_server%2Fplayer.rb;h=7e235f91b125635cd65bcdcefafaeb3ebd677899;hp=cfc66f795a90df1742f17ddacddf94622fb2fc5b;hb=e4a0423d429600312f940c4acd6a37be70218620;hpb=aa97cd70b20879008ebd912e3b748632f5b560c9 diff --git a/shogi_server/player.rb b/shogi_server/player.rb index cfc66f7..7e235f9 100644 --- a/shogi_server/player.rb +++ b/shogi_server/player.rb @@ -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 @@ -28,6 +30,10 @@ class BasicPlayer @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 @@ -54,6 +60,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 +115,16 @@ class BasicPlayer @player_id = @password = nil end end + + def set_sente_from_str(str) + case str + when "+": @sente = true + when "-": @sente = false + else + # str should be "*" + @sente = nil + end + end end @@ -106,9 +138,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 +147,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 @@ -172,9 +202,12 @@ class Player < BasicPlayer log_message(sprintf("user %s finish", @name)) begin log_debug("Terminating %s's write thread..." % [@name]) - write_safe(nil) - @write_thread.join + 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)) end @@ -200,10 +233,12 @@ class Player < BasicPlayer r[1].first.write(str) log(:info, :out, str) else - log_error("Sending a message to #{@name} timed up.") + log_error("Gave a try to send a message to #{@name}, but it timed out.") + break end rescue Exception => ex log_error("Failed to send a message to #{@name}. #{ex.class}: #{ex.message}\t#{ex.backtrace[0]}") + break end end # while loop log_error("%s's socket closed." % [@name]) if @socket.closed? @@ -236,6 +271,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 @@ -258,206 +294,26 @@ 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 end + ensure $mutex.unlock end end # enf of while + log_warning("%s's socket was suddenly closed" % [@name]) end # def run end # class