OSDN Git Service

* [shogi-server]
authorbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Sat, 26 Dec 2009 01:40:20 +0000 (01:40 +0000)
committerbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Sat, 26 Dec 2009 01:40:20 +0000 (01:40 +0000)
  - The server can now provide multiple floodgate modes such as
    floodgate-900-0 and floodgate-3600-0.

changelog
shogi-server
shogi_server/game_result.rb
shogi_server/league/floodgate.rb
shogi_server/pairing.rb
shogi_server/util.rb
test/TC_ALL.rb
test/TC_floodgate.rb
test/TC_floodgate_next_time_generator.rb [new file with mode: 0644]

index 4ab7a46..028efba 100644 (file)
--- a/changelog
+++ b/changelog
@@ -1,3 +1,18 @@
+2009-12-26 Daigo Moriwaki <daigo at debian dot org> 
+
+       * [shogi-server]
+         - The server can now provide multiple floodgate modes such as
+           floodgate-900-0 and floodgate-3600-0.
+
+2009-12-25 Daigo Moriwaki <daigo at debian dot org>
+
+       * [shogi-server]
+         - shogi-server: The command line option --floodgate-history has
+           been deprectated. The server will decide history file names such
+           as 'floodgate_history_900_0.yaml' and
+           'floodgate_history_3600_0.yaml', and then put them in the top
+           directory.
+
 2009-12-20 Daigo Moriwaki <daigo at debian dot org>
 
        * [shogi-server]
index 54c4342..d3b4f5b 100755 (executable)
@@ -32,6 +32,7 @@ $config = nil
 $:.unshift File.dirname(__FILE__)
 require 'shogi_server'
 require 'shogi_server/config'
+require 'shogi_server/util'
 require 'tempfile'
 
 #################################################
@@ -70,9 +71,6 @@ OPTIONS
         --player-log-dir dir
                 log network messages for each player. Log files
                 will be put in the dir.
-        --floodgate-history
-                file name to record Floodgate game history
-                default: './floodgate_history.yaml'
 
 LICENSE
        GPL versoin 2 or later
@@ -118,8 +116,7 @@ def parse_command_line
   parser = GetoptLong.new(
     ["--daemon",            GetoptLong::REQUIRED_ARGUMENT],
     ["--pid-file",          GetoptLong::REQUIRED_ARGUMENT],
-    ["--player-log-dir",    GetoptLong::REQUIRED_ARGUMENT],
-    ["--floodgate-history", GetoptLong::REQUIRED_ARGUMENT])
+    ["--player-log-dir",    GetoptLong::REQUIRED_ARGUMENT])
   parser.quiet = true
   begin
     parser.each_option do |name, arg|
@@ -164,45 +161,19 @@ def check_command_line
 
   if $options["pid-file"] 
     $options["pid-file"] = File.expand_path($options["pid-file"], $topdir)
-    unless is_writable_file? $options["pid-file"]
+    unless ShogiServer::is_writable_file? $options["pid-file"]
       usage
       $stderr.puts "Can not create the pid file: %s" % [$options["pid-file"]]
       exit 4
     end
   end
 
-  $options["floodgate-history"] ||= File.join($topdir, "floodgate_history.yaml")
-  $options["floodgate-history"] = File.expand_path($options["floodgate-history"], $topdir)
-  unless is_writable_file? $options["floodgate-history"]
-    usage
-    $stderr.puts "Can not create the floodgate history file: %s" % [$options["floodgate-history"]]
-    exit 6
+  if $options["floodgate-history"]
+    $stderr.puts "WARNING: --floodgate-history has been deprecated."
+    $options["floodgate-history"] = nil
   end
 end
 
-# See if the file is writable. The file will be created if it does not exist
-# yet.
-# Return true if the file is writable, otherwise false.
-#
-def is_writable_file?(file)
-  if File.exist?(file)
-    if FileTest.file?(file)
-      return FileTest.writable_real?(file)
-    else
-      return false
-    end
-  end
-  
-  begin
-    open(file, "w") {|fh| } 
-    FileUtils.rm file
-  rescue
-    return false
-  end
-
-  return true
-end
-
 # See if a file can be created in the directory.
 # Return true if a file is writable in the directory, otherwise false.
 #
@@ -307,22 +278,38 @@ def setup_watchdog_for_giant_lock
   end
 end
 
-def setup_floodgate_900
-  return Thread.start do 
+def setup_floodgate(game_names)
+  return Thread.start(game_names) do |game_names|
     Thread.pass
-    game_name = "floodgate-900-0"
-    floodgate = ShogiServer::League::Floodgate.new($league, 
-                                                   {:game_name => game_name})
-    log_message("Flooddgate reloaded. The next match will start at %s." % 
-                [floodgate.next_time])
-
     while (true)
       begin
+        leagues = game_names.collect do |game_name|
+          floodgate = ShogiServer::League::Floodgate.new($league, 
+                                                         {:game_name => game_name})
+          log_message("Floodgate reloaded. The next match will start at %s." % 
+                      [floodgate.next_time])
+          floodgate
+        end
+        leagues.delete_if do |floodgate|
+          ret = false
+          unless floodgate.next_time 
+            log_error("Unsupported game name: %s" % floodgate.game_name)
+            ret = true
+          end
+          ret
+        end
+        if leagues.empty?
+          log_error("No valid Floodgate game names found")
+          return # exit from this thread
+        end
+
+        floodgate = leagues.min {|a,b| a.next_time <=> b.next_time}
         diff = floodgate.next_time - Time.now
         if diff > 0
           sleep(diff/2)
           next
         end
+        log_message("Starting Floodgate games...: %s" % [floodgate.game_name])
         $league.reload
         floodgate.match_game
         floodgate.charge
@@ -334,8 +321,8 @@ def setup_floodgate_900
         floodgate = ShogiServer::League::Floodgate.new($league, 
                                                        {:game_name => game_name,
                                                         :next_time => next_time})
-        log_message("Floodgate: The next match will start at %s." % 
-                    [floodgate.next_time])
+        log_message("Floodgate: The next match of %s will start at %s." % 
+                    [floodgate.game_name, floodgate.next_time])
       rescue Exception => ex 
         # ignore errors
         log_error("[in Floodgate's thread] #{ex} #{ex.backtrace}")
@@ -374,7 +361,7 @@ def main
     end
     setup_watchdog_for_giant_lock
     $league.setup_players_database
-    fg_thread = setup_floodgate_900
+    fg_thread = setup_floodgate(["floodgate-900-0", "floodgate-3600-0"])
   end
 
   config[:StopCallback] = Proc.new do
index 81a438a..0dd90f1 100644 (file)
@@ -109,10 +109,10 @@ class GameResult
     add_observer LoggingObserver.new
 
     if League::Floodgate.game_name?(@game.game_name) &&
-       @game.sente.player_id &&
-       @game.gote.player_id &&
-       $options["floodgate-history"]
-      add_observer League::Floodgate::History.factory
+       @game.sente.player_id && @game.gote.player_id
+      path = League::Floodgate.history_file_path(@game.game_name) 
+      history = League::Floodgate::History.factory(path)
+      add_observer history if history
     end
   end
 
index 4f6ac2b..e3902de 100644 (file)
@@ -7,14 +7,21 @@ module ShogiServer
 class League
   class Floodgate
     class << self
-      # "floodgate-900-0"
+      # ex. "floodgate-900-0"
       #
       def game_name?(str)
         return /^floodgate\-\d+\-\d+$/.match(str) ? true : false
       end
-    end
 
-    attr_reader :next_time, :league
+      def history_file_path(gamename)
+        return nil unless game_name?(gamename)
+        filename = "floodgate_history_%s.yaml" % [gamename.gsub("floodgate-", "").gsub("-","_")]
+        file = File.join($topdir, filename)
+        return Pathname.new(file)
+      end
+    end # class method
+
+    attr_reader :next_time, :league, :game_name
 
     def initialize(league, hash={})
       @league = league
@@ -28,21 +35,11 @@ class League
     end
 
     def charge
-      now = Time.now
-      unless $DEBUG
-        # each 30 minutes
-        if now.min < 30
-          @next_time = Time.mktime(now.year, now.month, now.day, now.hour, 30)
-        else
-          @next_time = Time.mktime(now.year, now.month, now.day, now.hour) + 3600
-        end
+      ntg = NextTimeGenerator.factory(@game_name)
+      if ntg
+        @next_time = ntg.call(Time.now)
       else
-        # for test, each 30 seconds
-        if now.sec < 30
-          @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30)
-        else
-          @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60
-        end
+        @next_time = nil
       end
     end
 
@@ -54,7 +51,53 @@ class League
       end
       Pairing.match(players)
     end
+    
+    #
+    #
+    class NextTimeGenerator
+      class << self
+        def factory(game_name)
+          ret = nil
+          if $DEBUG
+            ret = NextTimeGenerator_Debug.new
+          elsif game_name == "floodgate-900-0"
+            ret = NextTimeGenerator_Floodgate_900_0.new
+          elsif game_name == "floodgate-3600-0"
+            ret = NextTimeGenerator_Floodgate_3600_0.new
+          end
+          return ret
+        end
+      end
+    end
 
+    class NextTimeGenerator_Floodgate_900_0
+      def call(now)
+        # each 30 minutes
+        if now.min < 30
+          return Time.mktime(now.year, now.month, now.day, now.hour, 30)
+        else
+          return Time.mktime(now.year, now.month, now.day, now.hour) + 3600
+        end
+      end
+    end
+
+    class NextTimeGenerator_Floodgate_3600_0
+      def call(now)
+        # each 2 hours (odd hour)
+        return Time.mktime(now.year, now.month, now.day, now.hour) + ((now.hour%2)+1)*3600
+      end
+    end
+
+    class NextTimeGenerator_Debug
+      def call(now)
+        # for test, each 30 seconds
+        if now.sec < 30
+          return Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30)
+        else
+          return Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60
+        end
+      end
+    end
 
     #
     #
@@ -62,9 +105,12 @@ class League
       @@mutex = Mutex.new
 
       class << self
-        def factory
-          file = Pathname.new $options["floodgate-history"]
-          history = History.new file
+        def factory(pathname)
+          unless ShogiServer::is_writable_file?(pathname.to_s)
+            log_error("Failed to write a history file: %s" % [pathname]) 
+            return nil
+          end
+          history = History.new pathname
           history.load
           return history
         end
index 4e0b6d6..c4819c7 100644 (file)
@@ -45,11 +45,10 @@ module ShogiServer
       end
 
       def swiss_pairing
-        history = ShogiServer::League::Floodgate::History.factory
         return [LogPlayers.new,
                 ExcludeSacrificeGps500.new,
                 MakeEven.new,
-                Swiss.new(history),
+                Swiss.new,
                 StartGameWithoutHumans.new]
       end
 
@@ -251,15 +250,21 @@ module ShogiServer
   end
 
   class Swiss < Pairing
-    def initialize(history)
-      super()
-      @history = history
-    end
-
     def match(players)
       super
-      winners = players.find_all {|pl| @history.last_win?(pl.player_id)}
-      rest    = players - winners
+      if players.size < 3
+        log_message("Floodgate: players are small enough to skip Swiss pairing: %d" % [players.size])
+        return
+      end
+
+      path = ShogiServer::League::Floodgate.history_file_path(players.first.game_name)
+      history = ShogiServer::League::Floodgate::History.factory(path)
+
+      winners = []
+      if history
+        winners = players.find_all {|pl| history.last_win?(pl.player_id)}
+      end
+      rest = players - winners
 
       log_message("Floodgate: Ordering %d winners..." % [winners.size])
       sbrwr_winners = SortByRateWithRandomness.new(800, 2500)
index 3fef61a..ebf8c9d 100644 (file)
@@ -17,6 +17,9 @@
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+require 'fileutils'
+require 'pathname'
+
 module ShogiServer
 
   # Generate a random number such as i<=n<max
@@ -37,4 +40,31 @@ module ShogiServer
   end
   module_function :shuffle
 
+  # See if the file is writable. The file will be created if it does not exist
+  # yet.
+  # Return true if the file is writable, otherwise false.
+  #
+  def is_writable_file?(file)
+    if String === file
+      file = Pathname.new file
+    end
+    if file.exist?
+      if file.file?
+        return file.writable_real?
+      else
+        return false
+      end
+    end
+    
+    begin
+      file.open("w") {|fh| } 
+      file.delete
+    rescue
+      return false
+    end
+
+    return true
+  end
+  module_function :is_writable_file?
+
 end
index 58fd58a..a514722 100644 (file)
@@ -7,6 +7,7 @@ require 'TC_command'
 require 'TC_config'
 require 'TC_floodgate'
 require 'TC_floodgate_history'
+require 'TC_floodgate_next_time_generator'
 require 'TC_functional'
 require 'TC_game'
 require 'TC_game_result'
index 8831e54..31a2b17 100644 (file)
@@ -5,15 +5,20 @@ require 'shogi_server/player'
 require 'shogi_server/pairing'
 require 'shogi_server/league/floodgate'
 
+$topdir = File.expand_path File.dirname(__FILE__)
+
 class MockLogger
   def debug(str)
+    #puts str
   end
   def info(str)
     #puts str
   end
   def warn(str)
+    puts str
   end
   def error(str)
+    puts str
   end
 end
 
@@ -26,6 +31,10 @@ def log_warning(msg)
   $logger.warn(msg)
 end
 
+def log_error(msg)
+  $logger.error(msg)
+end
+
 class TestFloodgate < Test::Unit::TestCase
   def setup
     @fg = ShogiServer::League::Floodgate.new(nil)
@@ -268,50 +277,66 @@ class TestSwissPairing < Test::Unit::TestCase
     @a = ShogiServer::BasicPlayer.new
     @a.player_id = "a"
     @a.rate = 0
+    @a.game_name = "floodgate-900-0"
     @b = ShogiServer::BasicPlayer.new
     @b.player_id = "b"
     @b.rate = 1000
+    @b.game_name = "floodgate-900-0"
     @c = ShogiServer::BasicPlayer.new
     @c.player_id = "c"
     @c.rate = 1500
+    @c.game_name = "floodgate-900-0"
     @d = ShogiServer::BasicPlayer.new
     @d.player_id = "d"
     @d.rate = 2000
+    @d.game_name = "floodgate-900-0"
 
     @players = [@a, @b, @c, @d]
 
-    @file = Pathname.new(File.join(File.dirname(__FILE__), "floodgate_history.yaml"))
-    @history = ShogiServer::League::Floodgate::History.new @file
+    @file = Pathname.new(File.join(File.dirname(__FILE__), "floodgate_history_900_0.yaml"))
+    @history = ShogiServer::League::Floodgate::History.factory @file
 
-    @swiss = ShogiServer::Swiss.new @history
+    @swiss = ShogiServer::Swiss.new
   end
 
   def teardown
     @file.delete if @file.exist?
   end
 
+  def test_none
+    players = []
+    @swiss.match players
+    assert(players.empty?)
+  end
+
   def test_all_win
-    def @history.last_win?(player_id)
-      true
+    ShogiServer::League::Floodgate::History.class_eval do
+      def last_win?(player_id)
+        true
+      end
     end
     @swiss.match @players
     assert_equal([@d, @c, @b, @a], @players)
   end
 
   def test_all_lose
-    def @history.last_win?(player_id)
-      false
+    ShogiServer::League::Floodgate::History.class_eval do
+      def last_win?(player_id)
+        false
+      end
     end
     @swiss.match @players
     assert_equal([@d, @c, @b, @a], @players)
   end
 
   def test_one_win
-    def @history.last_win?(player_id)
-      if player_id == "a"
-        true
-      else
-        false
+    ShogiServer::League::Floodgate::History.class_eval do
+      def last_win?(player_id)
+        if player_id == "a"
+          true
+        else
+          false
+        end
       end
     end
     @swiss.match @players
@@ -319,11 +344,13 @@ class TestSwissPairing < Test::Unit::TestCase
   end
 
   def test_two_win
-    def @history.last_win?(player_id)
-      if player_id == "a" || player_id == "d"
-        true
-      else
-        false
+    ShogiServer::League::Floodgate::History.class_eval do
+      def last_win?(player_id)
+        if player_id == "a" || player_id == "d"
+          true
+        else
+          false
+        end
       end
     end
     @swiss.match @players
diff --git a/test/TC_floodgate_next_time_generator.rb b/test/TC_floodgate_next_time_generator.rb
new file mode 100644 (file)
index 0000000..03f17d3
--- /dev/null
@@ -0,0 +1,89 @@
+$:.unshift File.join(File.dirname(__FILE__), "..")
+require 'test/unit'
+require 'shogi_server'
+require 'shogi_server/league/floodgate'
+
+$topdir = File.expand_path File.dirname(__FILE__)
+
+class TestNextTimeGenerator_900_0 < Test::Unit::TestCase
+  def setup
+    @next = ShogiServer::League::Floodgate::NextTimeGenerator_Floodgate_900_0.new
+  end
+
+  def test_0_min
+    now = Time.mktime(2009,12,25,22,0)
+    assert_equal(Time.mktime(2009,12,25,22,30), @next.call(now))
+  end
+
+  def test_20_min
+    now = Time.mktime(2009,12,25,22,20)
+    assert_equal(Time.mktime(2009,12,25,22,30), @next.call(now))
+  end
+
+  def test_30_min
+    now = Time.mktime(2009,12,25,22,30)
+    assert_equal(Time.mktime(2009,12,25,23,00), @next.call(now))
+  end
+
+  def test_50_min
+    now = Time.mktime(2009,12,25,22,50)
+    assert_equal(Time.mktime(2009,12,25,23,00), @next.call(now))
+  end
+
+  def test_50_min_next_day
+    now = Time.mktime(2009,12,25,23,50)
+    assert_equal(Time.mktime(2009,12,26,0,0), @next.call(now))
+  end
+
+  def test_50_min_next_month
+    now = Time.mktime(2009,11,30,23,50)
+    assert_equal(Time.mktime(2009,12,1,0,0), @next.call(now))
+  end
+
+  def test_50_min_next_year
+    now = Time.mktime(2009,12,31,23,50)
+    assert_equal(Time.mktime(2010,1,1,0,0), @next.call(now))
+  end
+end
+
+class TestNextTimeGenerator_3600_0 < Test::Unit::TestCase
+  def setup
+    @next = ShogiServer::League::Floodgate::NextTimeGenerator_Floodgate_3600_0.new
+  end
+
+  def test_22_00
+    now = Time.mktime(2009,12,25,22,0)
+    assert_equal(Time.mktime(2009,12,25,23,0), @next.call(now))
+  end
+
+  def test_22_30
+    now = Time.mktime(2009,12,25,22,0)
+    assert_equal(Time.mktime(2009,12,25,23,0), @next.call(now))
+  end
+
+  def test_23_00
+    now = Time.mktime(2009,12,25,23,0)
+    assert_equal(Time.mktime(2009,12,26,1,0), @next.call(now))
+  end
+
+  def test_23_30
+    now = Time.mktime(2009,12,25,23,30)
+    assert_equal(Time.mktime(2009,12,26,1,0), @next.call(now))
+  end
+
+  def test_00_00
+    now = Time.mktime(2009,12,26,0,0)
+    assert_equal(Time.mktime(2009,12,26,1,0), @next.call(now))
+  end
+
+  def test_23_30_next_month
+    now = Time.mktime(2009,11,30,23,30)
+    assert_equal(Time.mktime(2009,12,1,1,0), @next.call(now))
+  end
+
+  def test_23_30_next_year
+    now = Time.mktime(2009,12,31,23,30)
+    assert_equal(Time.mktime(2010,1,1,1,0), @next.call(now))
+  end
+end
+