OSDN Git Service

* [shogi-server] - shogi_server/league/floodgate_thread.rb: Added a log message.
[shogi-server/shogi-server.git] / shogi_server / command.rb
index d661f90..b09b95a 100644 (file)
@@ -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
@@ -69,6 +69,9 @@ module ShogiServer
         my_sente_str = $3
         cmd = GameChallengeCommand.new(str, player, 
                                        command_name, game_name, my_sente_str)
+      when /^%%(GAME|CHALLENGE)\s+(\S+)/
+        msg = "A turn identifier is required"
+        cmd = ErrorCommand.new(str, player, msg)
       when /^%%CHAT\s+(.+)/
         message = $1
         cmd = ChatCommand.new(str, player, message, $league.players)
@@ -94,8 +97,25 @@ module ShogiServer
       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 /^%%FORK\s+(\S+)$/
+        source_game   = $1
+        new_buoy_game = nil
+        nth_move      = nil
+        cmd = ForkCommand.new(str, player, source_game, new_buoy_game, nth_move)
       when /^\s*$/
         cmd = SpaceCommand.new(str, player)
+      when /^%%%[^%]/
+        # TODO: just ignore commands specific to 81Dojo.
+        # Need to discuss with 81Dojo people.
+        cmd = VoidCommand.new(str, player)
       else
         cmd = ErrorCommand.new(str, player)
       end
@@ -112,6 +132,18 @@ module ShogiServer
     attr_accessor :time
   end
 
+  # Dummy command which does nothing.
+  #
+  class VoidCommand < Command
+    def initialize(str, player)
+      super
+    end
+
+    def call
+      return :continue
+    end
+  end
+
   # Application-level protocol for Keep-Alive.
   # If the server receives an LF, it sends back an LF.  Note that the 30 sec
   # rule (client may not send LF again within 30 sec) is not implemented
@@ -460,11 +492,21 @@ module ShogiServer
       @command_name = command_name
       @game_name    = game_name
       @my_sente_str = my_sente_str
+      player.set_sente_from_str(@my_sente_str)
     end
 
     def call
       if (! Login::good_game_name?(@game_name))
-        @player.write_safe(sprintf("##[ERROR] bad game name\n"))
+        @player.write_safe(sprintf("##[ERROR] bad game name: %s.\n", @game_name))
+        if (/^(.+)-\d+-\d+$/ =~ @game_name)
+          if Login::good_identifier?($1)
+            # do nothing
+          else
+            @player.write_safe(sprintf("##[ERROR] invalid identifiers are found or too many characters are used.\n"))
+          end
+        else
+          @player.write_safe(sprintf("##[ERROR] game name should consist of three parts like game-1500-60.\n"))
+        end
         return :continue
       elsif ((@player.status == "connected") || (@player.status == "game_waiting"))
         ## continue
@@ -474,14 +516,6 @@ module ShogiServer
       end
 
       rival = nil
-      if (Buoy.game_name?(@game_name))
-        if (@my_sente_str != "*")
-          @player.write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", @my_sente_str, @game_name))
-          return :continue
-        end
-        @player.sente = nil
-      end # really end
-
       if (League::Floodgate.game_name?(@game_name))
         if (@my_sente_str != "*")
           @player.write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", @my_sente_str, @game_name))
@@ -489,42 +523,18 @@ module ShogiServer
         end
         @player.sente = nil
       else
-        if (@my_sente_str == "*") && !Login.handicapped_game_name?(@game_name)
-          rival = $league.get_player("game_waiting", @game_name, nil, @player) # no preference
-        elsif (@my_sente_str == "+")
-          rival = $league.get_player("game_waiting", @game_name, false, @player) # rival must be gote
-        elsif (@my_sente_str == "-")
-          rival = $league.get_player("game_waiting", @game_name, true, @player) # rival must be sente
-        else
-          @player.write_safe(sprintf("##[ERROR] bad game option\n"))
-          return :continue
+        rival = $league.find_rival(@player, @game_name)
+        if rival.instance_of?(Symbol)  
+          # An error happened. rival is not a player instance, but an error
+          # symobl that must be returned to the main routine immediately.
+          return rival
         end
       end
 
       if (rival)
         @player.game_name = @game_name
-        
-        if ((@my_sente_str == "*") && (rival.sente == nil))
-          if (rand(2) == 0)
-            @player.sente = true
-            rival.sente = false
-          else
-            @player.sente = false
-            rival.sente = true
-          end
-        elsif (rival.sente == true) # rival has higher priority
-          @player.sente = false
-        elsif (rival.sente == false)
-          @player.sente = true
-        elsif (@my_sente_str == "+")
-          @player.sente = true
-          rival.sente = false
-        elsif (@my_sente_str == "-")
-          @player.sente = false
-          rival.sente = true
-        else
-          ## never reached
-        end
+        Game::decide_turns(@player, @my_sente_str, rival)
+
         if (Buoy.game_name?(@game_name))
           buoy = Buoy.new # TODO config
           if buoy.is_new_game?(@game_name)
@@ -559,13 +569,6 @@ module ShogiServer
         if (@command_name == "GAME")
           @player.status = "game_waiting"
           @player.game_name = @game_name
-          if (@my_sente_str == "+")
-            @player.sente = true
-          elsif (@my_sente_str == "-")
-            @player.sente = false
-          else
-            @player.sente = nil
-          end
         else                # challenge
           @player.write_safe(sprintf("##[ERROR] can't find rival for %s\n", @game_name))
           @player.status = "connected"
@@ -688,14 +691,19 @@ module ShogiServer
   # Command for an error
   #
   class ErrorCommand < Command
-    def initialize(str, player)
-      super
+    def initialize(str, player, msg=nil)
+      super(str, player)
+      @msg = msg || "unknown command"
     end
+    attr_reader :msg
 
     def call
-      msg = "##[ERROR] unknown command %s\n" % [@str]
-      @player.write_safe(msg)
-      log_error(msg)
+      cmd = @str.chomp
+      # Aim to hide a possible password
+      cmd.gsub!(/LOGIN\s*(\w+)\s+.*/i, 'LOGIN \1...')
+      @msg = "##[ERROR] %s: %s\n" % [@msg, cmd]
+      @player.write_safe(@msg)
+      log_error(@msg)
       return :continue
     end
   end
@@ -743,15 +751,32 @@ module ShogiServer
       @player.write_safe(sprintf("##[SETBUOY] +OK\n"))
       log_info("A buoy game was created: %s by %s" % [@game_name, @player.name])
 
-      # if two players, who are not @player, are waiting for a new game, start it
-      p1 = $league.get_player("game_waiting", @game_name, true, @player)
-      return :continue unless p1
-      p2 = $league.get_player("game_waiting", @game_name, false, @player)
-      return :continue unless p2
-
+      # if two players are waiting for this buoy game, start it
+      candidates = $league.find_all_players do |player|
+        player.status == "game_waiting" && 
+        player.game_name == @game_name &&
+        player.name != @player.name
+      end
+      if candidates.empty?
+        log_info("No players found for a buoy game. Wait for players: %s" % [@game_name])
+        return :continue 
+      end
+      p1 = candidates.first
+      p2 = $league.find_rival(p1, @game_name)
+      if p2.nil?
+        log_info("No opponent found for a buoy game. Wait for the opponent: %s by %s" % [@game_name, p1.name])
+        return :continue
+      elsif p2.instance_of?(Symbol)  
+        # An error happened. rival is not a player instance, but an error
+        # symobl that must be returned to the main routine immediately.
+        return p2
+      end
+      # 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)
       return :continue
+
     rescue WrongMoves => e
       @player.write_safe(sprintf("##[ERROR] wrong moves: %s\n", @moves))
       log_error "Received wrong moves: %s from %s. [%s]" % [@moves, @player.name, e.message]
@@ -806,6 +831,72 @@ module ShogiServer
         @player.write_safe("##[GETBUOYCOUNT] %s\n" % [buoy_game.count])
       end
       @player.write_safe("##[GETBUOYCOUNT] +OK\n")
+      return :continue
+    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
+    attr_reader :new_buoy_game
+
+    def decide_new_buoy_game_name
+      name       = nil
+      total_time = nil
+      byo_time   = nil
+
+      if @source_game.split("+").size >= 2 &&
+         /^([^-]+)-(\d+)-(\d+)/ =~ @source_game.split("+")[1]
+        name       = $1
+        total_time = $2
+        byo_time   = $3
+      end
+      if name == nil || total_time == nil || byo_time == nil
+        @player.write_safe(sprintf("##[ERROR] wrong source game name to make a new buoy game name: %s\n", @source_game))
+        log_error "Received a wrong source game name to make a new buoy game name: %s from %s." % [@source_game, @player.name]
+        return :continue
+      end
+      @new_buoy_game = "buoy_%s_%d-%s-%s" % [name, @nth_move, total_time, byo_time]
+      @player.write_safe(sprintf("##[FORK]: new buoy game name: %s\n", @new_buoy_game))
+      @player.write_safe("##[FORK] +OK\n")
+    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 # [["+7776FU","T2"],["-3334FU","T5"]]
+      @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_str = ""
+      moves[0...@nth_move].each do |m|
+        new_moves_str << m.join(",")
+      end
+
+      unless @new_buoy_game
+        decide_new_buoy_game_name
+      end
+
+      buoy_cmd = SetBuoyCommand.new(@str, @player, @new_buoy_game, new_moves_str, 1)
+      return buoy_cmd.call
     end
   end