OSDN Git Service

When running with the debug mode, Thread.abort_on_exception is true.
[shogi-server/shogi-server.git] / shogi-server
index 3ab2267..597f675 100755 (executable)
@@ -44,6 +44,7 @@ class TCPSocket
   def gets_safe(t = nil)
     if (t && t > 0)
       begin
+        return :exception if closed?
         timeout(t) do
           return self.gets
         end
@@ -55,6 +56,7 @@ class TCPSocket
       end
     else
       begin
+        return nil if closed?
         return self.gets
       rescue
         return nil
@@ -87,8 +89,6 @@ Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
-
-
 class League
 
   class Floodgate
@@ -109,8 +109,9 @@ class League
         Thread.pass
         while (true)
           begin
-            sleep(20)
+            sleep(10)
             next if Time.now < @next_time
+            @league.reload
             match_game
             charge
           rescue Exception => ex 
@@ -148,36 +149,8 @@ class League
         Floodgate.game_name?(pl.game_name) &&
         pl.sente == nil
       end
-      random_match(players)
-    end
-
-    def random_match(players)
-      if players.size < 2
-        log_message("Floodgate: too few players [%d]" % [players.size])
-        return
-      end
-      log_message("Floodgate: found %d players. Making games..." % [players.size])
-      random_players = players.sort{ rand < 0.5 ? 1 : -1 }
-      pairs = [[random_players.shift]]
-      while !random_players.empty? do
-        if pairs.last.size < 2
-          pairs.last << random_players.shift
-        else
-          pairs << [random_players.shift]
-        end 
-      end
-      if pairs.last.size < 2
-        pairs.pop
-      end
-      pairs.each do |pair|
-        start_game(pair.first, pair.last)
-      end
-    end
-
-    def start_game(p1, p2)
-      p1.sente = true
-      p2.sente = false
-      Game.new(p1.game_name, p1, p2)
+      load File.join(File.dirname(__FILE__), "pairing.rb")
+      Pairing.default_pairing.match(players)
     end
   end # class Floodgate
 
@@ -193,6 +166,9 @@ class League
   attr_accessor :players, :games, :event, :dir
 
   def shutdown
+    @mutex.synchronize do
+      @players.each {|a| save(a)}
+    end
     @floodgate.shutdown
   end
 
@@ -210,10 +186,17 @@ class League
   
   def delete(player)
     @mutex.synchronize do
+      save(player)
       @players.delete(player.name)
     end
   end
 
+  def reload
+    @mutex.synchronize do
+      @players.each {|player| load(player)}
+    end
+  end
+
   def find_all_players
     found = nil
     @mutex.synchronize do
@@ -241,18 +224,34 @@ class League
   
   def load(player)
     hash = search(player.id)
-    if hash
-      # a current user
-      player.name         = hash['name']
-      player.rate         = hash['rate']
-      player.modified_at  = hash['last_modified']
-      player.rating_group = hash['rating_group']
+    return unless hash
+
+    # a current user
+    player.name          = hash['name']
+    player.rate          = hash['rate'] || 0
+    player.modified_at   = hash['last_modified']
+    player.rating_group  = hash['rating_group']
+    player.win           = hash['win']  || 0
+    player.loss          = hash['loss'] || 0
+    player.last_game_win = hash['last_game_win'] || false
+  end
+
+  def save(player)
+    @db.transaction do
+      break unless  @db["players"]
+      @db["players"].each do |group, players|
+        hash = players[player.id]
+        if hash
+          hash['last_game_win'] = player.last_game_win
+          break
+        end
+      end
     end
   end
 
   def search(id)
     hash = nil
-    @db.transaction do
+    @db.transaction(true) do
       break unless  @db["players"]
       @db["players"].each do |group, players|
         hash = players[id]
@@ -401,6 +400,13 @@ end
 
 
 class BasicPlayer
+  def initialize
+    @id = nil
+    @name = nil
+    @password = nil
+    @last_game_win = false
+  end
+
   # Idetifier of the player in the rating system
   attr_accessor :id
 
@@ -412,6 +418,9 @@ class BasicPlayer
 
   # Score in the rating sysem
   attr_accessor :rate
+
+  # Number of games for win and loss in the rating system
+  attr_accessor :win, :loss
   
   # Group in the rating system
   attr_accessor :rating_group
@@ -419,10 +428,8 @@ class BasicPlayer
   # Last timestamp when the rate was modified
   attr_accessor :modified_at
 
-  def initialize
-    @name = nil
-    @password = nil
-  end
+  # Whether win the previous game or not
+  attr_accessor :last_game_win
 
   def modified_at
     @modified_at || Time.now
@@ -439,6 +446,10 @@ class BasicPlayer
     @id != nil
   end
 
+  def last_game_win?
+    return @last_game_win
+  end
+
   def simple_id
     if @trip
       simple_name = @name.gsub(/@.*?$/, '')
@@ -547,7 +558,7 @@ class Player < BasicPlayer
       begin
         $mutex.lock
 
-        if (@writer_thread == nil || @writer_thread.status == false)
+        if (@writer_thread == nil || !@writer_thread.status)
           # The writer_thread has been killed because of socket errors.
           return
         end
@@ -674,7 +685,7 @@ class Player < BasicPlayer
           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)
+              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
@@ -1286,7 +1297,7 @@ class Board
         return false
       end
     else                        # gote
-      if ((rival_ou.y != 0) &&
+      if ((rival_ou.y != 1) &&
           (@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
@@ -1296,9 +1307,8 @@ class Board
         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)
@@ -1316,17 +1326,30 @@ class Board
         if (@array[x][y] &&
             (@array[x][y].sente != sente) &&
             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
+          
+          names = []
           if (@array[x][y].promoted)
-            name = @array[x][y].promoted_name
+            names << @array[x][y].promoted_name
           else
-            name = @array[x][y].name
+            names << @array[x][y].name
+            if @array[x][y].promoted_name && 
+               @array[x][y].move_to?(fu_x, fu_y, @array[x][y].promoted_name)
+              names << @array[x][y].promoted_name 
+            end
           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
+          names.map! do |name|
+            tmp_board = Marshal.load(Marshal.dump(self))
+            s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
+            if s == :illegal
+              s # result
+            else
+              tmp_board.checkmated?(! sente) # result
+            end
           end
+          all_illegal = names.find {|a| a != :illegal}
+          raise "internal error: legal move not found" if all_illegal == nil
+          r = names.find {|a| a == false} # good move
+          return false if r == false # found good move
         end
         y = y + 1
       end
@@ -1577,6 +1600,8 @@ class GameResultWin < GameResult
   def initialize(winner, loser)
     super
     @winner, @loser = winner, loser
+    @winner.last_game_win = true
+    @loser.last_game_win  = false
   end
 
   def to_s
@@ -1587,7 +1612,11 @@ class GameResultWin < GameResult
 end
 
 class GameResultDraw < GameResult
-
+  def initialize(p1, p2)
+    super
+    p1.last_game_win = false
+    p2.last_game_win = false
+  end
 end
 
 class Game
@@ -2140,7 +2169,7 @@ end
 
 def write_pid_file(file)
   open(file, "w") do |fh|
-    fh.print Process::pid, "\n"
+    fh.puts "#{$$}"
   end
 end
 
@@ -2179,9 +2208,8 @@ def main
   LEAGUE.event = ARGV.shift
   port = ARGV.shift
 
-  write_pid_file($options["pid-file"]) if ($options["pid-file"])
-
-  dir = $options["daemon"] || nil
+  dir = $options["daemon"]
+  dir = File.expand_path(dir) if dir
   if dir && ! File.exist?(dir)
     FileUtils.mkdir(dir)
   end
@@ -2195,6 +2223,15 @@ def main
   config[:Port]       = port
   config[:ServerType] = WEBrick::Daemon if $options["daemon"]
   config[:Logger]     = $logger
+  if $options["pid-file"]
+    pid_file = File.expand_path($options["pid-file"])
+    config[:StartCallback] = Proc.new do
+      write_pid_file(pid_file)
+    end
+    config[:StopCallback] = Proc.new do
+      FileUtils.rm(pid_file, :force => true)
+    end
+  end
 
   server = WEBrick::GenericServer.new(config)
   ["INT", "TERM"].each do |signal| 
@@ -2273,7 +2310,7 @@ if ($0 == __FILE__)
   STDOUT.sync = true
   STDERR.sync = true
   TCPSocket.do_not_reverse_lookup = true
-  Thread.abort_on_exception = true
+  Thread.abort_on_exception = $DEBUG ? true : false
 
   LEAGUE = ShogiServer::League::new
   main