OSDN Git Service

Refactored shogi_server/pairing.rb
authorbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Sun, 5 Oct 2008 09:19:20 +0000 (09:19 +0000)
committerbeatles <beatles@b8c68f68-1e22-0410-b08e-880e1f8202b4>
Sun, 5 Oct 2008 09:19:20 +0000 (09:19 +0000)
shogi-server
shogi_server/pairing.rb
test/TC_floodgate.rb

index 4b048bd..fc19b06 100755 (executable)
@@ -220,11 +220,7 @@ end
 
 def main
   
-  #[ShogiServer::League::Floodgate, ShogiServer::Pairing].each do |klass|
-  #  Dependencies.unloadable klass
-  #end
-
-  setup_watchdog_for_giant_lock
+ setup_watchdog_for_giant_lock
 
   $options = parse_command_line
   if (ARGV.length != 2)
index f203f66..8f292a9 100644 (file)
@@ -24,178 +24,207 @@ module ShogiServer
   class Pairing
 
     class << self
-      def default_pairing
-        #return SwissPairing.new
-        #return ExcludeSacrifice.new(SwissPairing.new)
-        #return RandomPairing.new
-        return ExcludeSacrifice.new(RandomPairing.new)
+      def default_factory
+        return sort_by_rate_with_randomness
       end
-    end
 
-    def match(players)
-      if players.size < 2
-        log_message("Floodgate[%s]: too few players [%d]" % 
-                    [self.class, players.size])
-      else
-        log_message("Floodgate[%s]: found %d players. Pairing them..." % 
-                    [self.class, players.size])
+      def sort_by_rate_with_randomness
+        return [ExcludeSacrificeGps500.new,
+                MakeEven.new,
+                SortByRateWithRandomness.new(1200, 2400),
+                StartGame.new]
       end
-    end
 
-    def start_game(p1, p2)
-      p1.sente = true
-      p2.sente = false
-      Game.new(p1.game_name, p1, p2)
+      def random_pairing
+        return [ExcludeSacrificeGps500.new,
+                MakeEven.new,
+                RandomPairing.new,
+                StartGame.new]
+      end
+
+      def match(players)
+        logics = default_factory
+        logics.inject(players) do |result, item|
+          item.match(result)
+        end
+      end
+    end # class << self
+
+
+    def match(players)
+      # to be implemented
     end
 
     def include_newbie?(players)
       return players.find{|a| a.rate == 0} == nil ? false : true
     end
 
-    def delete_player_at_random(players)
-      return players.delete_at(rand(players.size))
-    end
-
-    def delete_player_at_random_except(players, a_player)
-      candidates = players - [a_player]
-      return delete_player_at_random(candidates)
-    end
-    
-    def delete_most_playing_player(players)
-      # TODO ??? undefined method `<=>' for nil:NilClass
-      max_player = players.max {|a,b| a.win + a.loss <=> b.win + b.loss}
-      return players.delete(max_player)
+    def less_than_one?(players)
+      if players.size < 1
+        log_warning("Floodgate: At least one player is required")
+        return true
+      else
+        return true
+      end
     end
 
-    def delete_least_rate_player(players)
-      min_player = players.min {|a,b| a.rate <=> b.rate}
-      return players.delete(min_player)
+    def log_players(players)
+      str_array = players.map do |one|
+        if block_given?
+          yield one
+        else
+          one.name
+        end
+      end
+      log_message("Floodgate: [Players] %s" % [str_array.join(", ")])
     end
+  end # Pairing
 
-    def pairing_and_start_game(players)
-      return if players.size < 2
-      if players.size.odd?
-        log_warning("#Players should be even: %d" % [players.size])
+  class StartGame < Pairing
+    def match(players)
+      super
+      if players.size < 2
+        log_warning("There should be more than one player: %d" % [players.size])
         return
       end
-
-      sorted = players.shuffle
-      pairs = []
-      while !sorted.empty? do
-        pairs << sorted.shift(2)
+      if players.size.odd?
+        log_warning("There are odd players: %d" % [players.size])
       end
-      pairs.each do |pair|
+
+      log_players(players)
+      while (players.size >= 2) do
+        pair = players.shift(2)
+        pair.shuffle!
         start_game(pair.first, pair.last)
       end
     end
 
-    def pairing_and_start_game_by_rate(players, randomness1, randomness2)
-      return if players.size < 2
-      if players.size % 2 == 1
-        log_warning("#Players should be even: %d" % [players.size])
-        return
-      end
+    def start_game
+      log_message("Floodgate: BLACK %s; WHITE %s" % [p1.name, p2.name])
+      p1.sente = true
+      p2.sente = false
+      Game.new(p1.game_name, p1, p2)
+    end
+  end
+
+  class Randomize < Pairing
+    def match(players)
+      super
+      log_message("Floodgate: Randomize... before")
+      log_players(players)
+      players.shuffle!
+      log_message("Floodgate: Randomized after")
+      log_players(players)
+    end
+  end # RadomPairing
+
+  class SortByRate < Pairing
+    def match(players)
+      super
+      log_message("Floodgate: Ordered by rate")
+      players.sort! {|a,b| a.rate <=> b.rate} # decendent order
+      log_players(players)
+    end
+  end
+
+  class SortByRateWithRandomness < Pairing
+    def initialize(rand1, rand2)
+      super()
+      @rand1, @rand2 = rand1, rand2
+    end
+
+    def match(players)
+      super
       cur_rate = Hash.new
-      players.each{ |a| cur_rate[a] = a.rate ? a.rate+rand(randomness1) : rand(randomness2) }
-      sorted = players.sort{ |a,b| cur_rate[a] <=> cur_rate[b] }
-      log_message("Floodgate[%s]: %s (randomness %d)" % [self.class, sorted.map{|a| a.name}.join(","), randomness1])
-
-      pairs = [[sorted.shift]]
-      while !sorted.empty? do
-        if pairs.last.size < 2
-          pairs.last << sorted.shift
-        else
-          pairs << [sorted.shift]
-        end 
-      end
-      pairs.each do |pair|
-        start_game(pair.choice, pair.choice)
-        if (rand(2) == 0)
-          start_game(pair.first, pair.last)
-        else
-          start_game(pair.last, pair.first)
-        end
+      players.each{|a| cur_rate[a] = a.rate ? a.rate + rand(@rand1) : rand(@rand2)}
+      players.sort!{|a,b| cur_rate[a] <=> cur_rate[b]}
+      log_players(players) do |one|
+        "%s %d (randomness %d)" % [one.name, one.rate, cur_rate[one] - one.rate]
       end
     end
-  end # Pairing
+  end
 
-  class RandomPairing < Pairing
+  class DeletePlayerAtRandom < Pairing
     def match(players)
       super
-      return if players.size < 2
+      return if less_than_one?(players)
+      one = players.choice
+      log_message("Floodgate: Deleted %s at random" % [one.name])
+      players.delete(one)
+      log_players(players)
+    end
+  end
 
-      if players.size % 2 == 1
-        delete_player_at_random(players)
-      end
-      pairing_and_start_game(players)
+  class DeletePlayerAtRandomExcept < Pairing
+    def initialize(except)
+      super()
+      @except = except
     end
-  end # RadomPairing
 
-  class SwissPairing < Pairing
     def match(players)
       super
-      return if players.size < 2
-
-      win_players = players.find_all {|a| a.last_game_win? }
-      log_message("Floodgate[%s]: win_players %s" % [self.class, win_players.map{|a| a.name}.join(",")])
-      if (win_players.size < players.size / 2)
-        x1_players = players.find_all {|a| a.rate > 0 && !a.last_game_win? && a.protocol != LoginCSA::PROTOCOL }
-        x1_players = x1_players.sort{ rand < 0.5 ? 1 : -1 }
-        while (win_players.size < players.size / 2) && !x1_players.empty? do
-          win_players << x1_players.shift
-        end
-        log_message("Floodgate[%s]: win_players (adhoc x1 collection) %s" % [self.class, win_players.map{|a| a.name}.join(",")])
-      end
-      remains     = players - win_players
-#      split_winners = (win_players.size >= (2+rand(2)))
-      split_winners = false
-      if split_winners
-        if win_players.size % 2 == 1
-#          if include_newbie?(win_players)
-            remains << delete_player_at_random(win_players)
-#          else
-#            remains << delete_least_rate_player(win_players)
-#          end
-        end         
-        pairing_and_start_game_by_rate(win_players, 800, 2500)
-      else
-        remains.concat(win_players)
-      end
-      return if remains.size < 2
-      if remains.size % 2 == 1
-        delete_player_at_random(remains)
-        # delete_most_playing_player(remains)
-      end
-      if split_winners
-        pairing_and_start_game_by_rate(remains, 200, 400)
-      else
-        # pairing_and_start_game_by_rate(remains, 800, 2400)
-        pairing_and_start_game_by_rate(remains, 1200, 2400)
-      end
+      log_message("Floodgate: Deleting a player at rondom except %s" % [@except.name])
+      players.delete(@except)
+      DeletePlayerAtRandom.new.match(players)
+      players.push(@except)
+    end
+  end
+  
+  class DeleteMostPlayingPlayer < Pairing
+    def match(players)
+      super
+      one = players.max_by {|a| a.win + a.loss}
+      log_message("Floodgate: Deleted the most playing player: %s (%d)" % [one.name, one.win + one.loss])
+      players.delete(one)
+      log_players(players)
+    end
+  end
+
+  class DeleteLeastRatePlayer < Pairing
+    def match(players)
+      super
+      one = players.min_by {|a| a.rate}
+      log_message("Floodgate: Deleted the least rate player %s (%d)" % [one.name, one.rate])
+      players.delete(one)
+      log_players(players)
     end
-  end # SwissPairing
+  end
 
-  class ExcludeSacrifice
-    attr_accessor :sacrifice
+  class ExcludeSacrifice < Pairing
+    attr_reader :sacrifice
 
-    def initialize(pairing)
-      @pairing  = pairing
-      @sacrifice = "gps500+e293220e3f8a3e59f79f6b0efffaa931"
+    # @sacrifice a player id to be eliminated
+    def initialize(sacrifice)
+      super()
+      @sacrifice = sacrifice
     end
 
     def match(players)
+      super
       if @sacrifice && 
-         players.size % 2 == 1 && 
+         players.size.odd? && 
          players.find{|a| a.player_id == @sacrifice}
-        log_message("Floodgate: first, exclude %s" % [@sacrifice])
-        players.delete_if{|a| a.player_id == @sacrifice}
+         log_message("Floodgate: Deleting the sacrifice %s" % [@sacrifice])
+         players.delete_if{|a| a.player_id == @sacrifice}
+         log_players(players)
       end
-      @pairing.match(players)
     end
+  end # class ExcludeSacrifice
 
-    # Delegate to @pairing
-    def method_missing(message, *arg)
-      @pairing.send(message, *arg)
+  class ExcludeSacrificeGps500 < ExcludeSacrifice
+    def initialize
+      super("gps500+e293220e3f8a3e59f79f6b0efffaa931")
     end
-  end # class ExcludeSacrifice
+  end
+
+  class MakeEven < Pairing
+    def match(players)
+      super
+      return if players.even?
+      log_message("Floodgate: there are odd players: %d. Deleting one..." % 
+                  [players.size])
+      DeletePlayerAtRandom.new.match(players)
+    end
+  end
+
 end # ShogiServer
index cbbc7ff..7b0bc0d 100644 (file)
@@ -3,12 +3,13 @@ require 'test/unit'
 require 'shogi_server'
 require 'shogi_server/player'
 require 'shogi_server/pairing'
+require 'shogi_server/league/floodgate'
 
 class MockLogger
   def debug(str)
   end
   def info(str)
-    # puts str
+    #puts str
   end
   def warn(str)
   end
@@ -21,6 +22,10 @@ def log_message(msg)
   $logger.info(msg)
 end
 
+def log_warning(msg)
+  $logger.warn(msg)
+end
+
 class TestFloodgate < Test::Unit::TestCase
   def setup
     @fg = ShogiServer::League::Floodgate.new(nil)
@@ -41,161 +46,235 @@ end
 class TestPairing < Test::Unit::TestCase  
   def setup
     @pairing= ShogiServer::Pairing.new
+    $pairs = []
+    def @pairing.start_game(p1,p2)
+      $pairs << [p1,p2]
+    end
     @a = ShogiServer::BasicPlayer.new
+    @a.name = "a"
     @a.win  = 1
     @a.loss = 2
     @a.rate = 0
+    @a.last_game_win = false
     @b = ShogiServer::BasicPlayer.new
+    @b.name = "b"
     @b.win  = 10
     @b.loss = 20
     @b.rate = 1500
+    @b.last_game_win = true
     @c = ShogiServer::BasicPlayer.new
+    @c.name = "c"
     @c.win  = 100
     @c.loss = 200
     @c.rate = 1000
+    @c.last_game_win = true
+    @d = ShogiServer::BasicPlayer.new
+    @d.name = "d"
+    @d.win  = 1000
+    @d.loss = 2000
+    @d.rate = 1800
+    @d.last_game_win = true
   end
 
-  def test_delete_most_playing_player
-    players = [@a, @b, @c]
-    @pairing.delete_most_playing_player(players)
-    assert_equal([@a,@b], players)
-  end
-
-  def test_delete_least_rate_player
-    players = [@a, @b, @c]
-    @pairing.delete_least_rate_player(players)
-    assert_equal([@b,@c], players)
+  def test_include_newbie
+    assert(@pairing.include_newbie?([@a]))
+    assert(!@pairing.include_newbie?([@b]))
+    assert(@pairing.include_newbie?([@b,@a]))
+    assert(!@pairing.include_newbie?([@b,@c]))
   end
 end
 
-class TestRandomPairing < Test::Unit::TestCase  
+class TestStartGame < Test::Unit::TestCase
   def setup
-    @pairing= ShogiServer::RandomPairing.new
+    @pairing= ShogiServer::StartGame.new
     $called = 0
     def @pairing.start_game(p1,p2)
       $called += 1
     end
     @a = ShogiServer::BasicPlayer.new
+    @a.name = "a"
     @a.win  = 1
     @a.loss = 2
+    @a.rate = 0
     @b = ShogiServer::BasicPlayer.new
+    @b.name = "b"
     @b.win  = 10
     @b.loss = 20
+    @b.rate = 1500
     @c = ShogiServer::BasicPlayer.new
+    @c.name = "c"
     @c.win  = 100
     @c.loss = 200
+    @c.rate = 1000
+    @d = ShogiServer::BasicPlayer.new
+    @d.name = "d"
+    @d.win  = 1000
+    @d.loss = 2000
+    @d.rate = 2000
   end
 
-  def test_random_match_1
+  def test_match_two_players
+    players = [@a,@b]
+    @pairing.match(players)
+    assert_equal(1, $called)
+  end
+
+  def test_match_one_player
     players = [@a]
     @pairing.match(players)
     assert_equal(0, $called)
   end
 
-  def test_random_match_2
-    players = [@a,@b]
+  def test_match_zero_player
+    players = []
     @pairing.match(players)
-    assert_equal(1, $called)
+    assert_equal(0, $called)
   end
-  
-  def test_random_match_3
-    players = [@a, @b, @c]
+
+  def test_match_three_players
+    players = [@a,@b,@c]
     @pairing.match(players)
     assert_equal(1, $called)
   end
+
+  def test_match_four_players
+    players = [@a,@b,@c,@d]
+    @pairing.match(players)
+    assert_equal(2, $called)
+  end
 end
 
-class TestSwissPairing < Test::Unit::TestCase  
+class TestDeleteMostPlayingPlayer < Test::Unit::TestCase
   def setup
-    @pairing= ShogiServer::SwissPairing.new
-    $pairs = []
-    def @pairing.start_game(p1,p2)
-      $pairs << [p1,p2]
-    end
+    @pairing= ShogiServer::DeleteMostPlayingPlayer.new
     @a = ShogiServer::BasicPlayer.new
-    @a.name = "a"
     @a.win  = 1
     @a.loss = 2
     @a.rate = 0
-    @a.last_game_win = false
     @b = ShogiServer::BasicPlayer.new
-    @b.name = "b"
     @b.win  = 10
     @b.loss = 20
     @b.rate = 1500
-    @b.last_game_win = true
     @c = ShogiServer::BasicPlayer.new
-    @c.name = "c"
     @c.win  = 100
     @c.loss = 200
     @c.rate = 1000
-    @c.last_game_win = true
-    @d = ShogiServer::BasicPlayer.new
-    @d.name = "d"
-    @d.win  = 1000
-    @d.loss = 2000
-    @d.rate = 1800
-    @d.last_game_win = true
   end
 
-  def sort(players)
-    return players.sort{|a,b| a.name <=> b.name}
+  def test_match
+    players = [@a, @b, @c]
+    @pairing.match(players)
+    assert_equal([@a,@b], players)
   end
+end
 
-  def test_include_newbie
-    assert(@pairing.include_newbie?([@a]))
-    assert(!@pairing.include_newbie?([@b]))
-    assert(@pairing.include_newbie?([@b,@a]))
-    assert(!@pairing.include_newbie?([@b,@c]))
+class TestLeastRatePlayer < Test::Unit::TestCase  
+  def setup
+    @pairing= ShogiServer::DeleteLeastRatePlayer.new
+    @a = ShogiServer::BasicPlayer.new
+    @a.win  = 1
+    @a.loss = 2
+    @a.rate = 0
+    @b = ShogiServer::BasicPlayer.new
+    @b.win  = 10
+    @b.loss = 20
+    @b.rate = 1500
+    @c = ShogiServer::BasicPlayer.new
+    @c.win  = 100
+    @c.loss = 200
+    @c.rate = 1000
   end
 
-  def test_match_1
-    @pairing.match([@a])
-    assert_equal(0, $pairs.size)
+ def test_match
+    players = [@a, @b, @c]
+    @pairing.match(players)
+    assert_equal([@b,@c], players)
   end
-  
-  def test_match_2
-    @pairing.match([@b])
-    assert_equal(0, $pairs.size)
+end
+
+class TestRandomize < Test::Unit::TestCase  
+  def setup
+    srand(10) # makes the random number generator determistic
+    @pairing = ShogiServer::Randomize.new
+    @a = ShogiServer::BasicPlayer.new
+    @a.name = "a"
+    @a.win  = 1
+    @a.loss = 2
+    @b = ShogiServer::BasicPlayer.new
+    @b.name = "b"
+    @b.win  = 10
+    @b.loss = 20
+    @c = ShogiServer::BasicPlayer.new
+    @c.name = "c"
+    @c.win  = 100
+    @c.loss = 200
   end
-  
-  def test_match_3
-    @pairing.match([@a,@b])
-    assert_equal(1, $pairs.size)
-    assert_equal(sort([@a,@b]), sort($pairs.first))
+
+  def test_match
+    players = [@a, @b, @c]
+    @pairing.match(players)
+    assert_equal([@b,@a,@c], players)
   end
-  
-  def test_match_4
-    @pairing.match([@c,@b])
-    assert_equal(1, $pairs.size)
-    assert_equal(sort([@b,@c]), sort($pairs.first))
+end
+
+class TestSortByRate < Test::Unit::TestCase  
+  def setup
+    @pairing = ShogiServer::SortByRate.new
+    @a = ShogiServer::BasicPlayer.new
+    @a.name = "a"
+    @a.win  = 1
+    @a.loss = 2
+    @a.rate = 1500
+    @b = ShogiServer::BasicPlayer.new
+    @b.name = "b"
+    @b.win  = 10
+    @b.loss = 20
+    @b.rate = 2000
+    @c = ShogiServer::BasicPlayer.new
+    @c.name = "c"
+    @c.win  = 100
+    @c.loss = 200
+    @c.rate = 700
   end
-  
-  def test_match_5
-    @pairing.match([@c,@b,@a])
-    assert_equal(1, $pairs.size)
-    assert_equal(sort([@b,@c]), sort($pairs.first))
+
+  def test_match
+    players = [@a, @b, @c]
+    @pairing.match(players)
+    assert_equal([@c,@a,@b], players)
   end
-  
-  def test_match_6
-    @pairing.match([@c,@b,@a,@d])
-    assert_equal(2, $pairs.size)
-    assert_equal(sort([@b,@d]), sort($pairs.first))
-    assert_equal(sort([@a,@c]), sort($pairs.last))
+end
+
+class TestSortByRateWithRandomness < Test::Unit::TestCase  
+  def setup
+    srand(10) # makes the random number generator determistic
+    @pairing = ShogiServer::SortByRateWithRandomness.new(1200, 2400)
+    @a = ShogiServer::BasicPlayer.new
+    @a.name = "a"
+    @a.win  = 1
+    @a.loss = 2
+    @a.rate = 1500
+    @b = ShogiServer::BasicPlayer.new
+    @b.name = "b"
+    @b.win  = 10
+    @b.loss = 20
+    @b.rate = 2000
+    @c = ShogiServer::BasicPlayer.new
+    @c.name = "c"
+    @c.win  = 100
+    @c.loss = 200
+    @c.rate = 700
+  end
+
+  def test_match
+    players = [@a, @b, @c]
+    @pairing.match(players)
+    assert_equal([@c,@b,@a], players)
   end
 end
 
 class TestExcludeSacrifice < Test::Unit::TestCase  
-  class Dummy
-    attr_reader :players
-    def match(players)
-      @players = players
-    end
-  end
-  
   def setup
-    @dummy = Dummy.new
-    @obj = ShogiServer::ExcludeSacrifice.new(@dummy)
+    @obj = ShogiServer::ExcludeSacrificeGps500.new
     @a = ShogiServer::BasicPlayer.new
     @a.player_id   = "a"
     @a.name = "a"
@@ -205,7 +284,7 @@ class TestExcludeSacrifice < Test::Unit::TestCase
     @a.last_game_win = false
     @b = ShogiServer::BasicPlayer.new
     @b.player_id   = "gps500+e293220e3f8a3e59f79f6b0efffaa931"
-    @b.name = "b"
+    @b.name = "gps500"
     @b.win  = 10
     @b.loss = 20
     @b.rate = 1500
@@ -220,28 +299,33 @@ class TestExcludeSacrifice < Test::Unit::TestCase
   end
 
   def test_match_1
-    @obj.match([@a])
-    assert_equal(1, @dummy.players.size)
+    players = [@a]
+    @obj.match(players)
+    assert_equal([@a], players)
   end
   
   def test_match_2
-    @obj.match([@b])
-    assert_equal(0, @dummy.players.size)
+    players = [@b]
+    @obj.match(players)
+    assert_equal([], players)
   end
   
   def test_match_3
-    @obj.match([@a, @b])
-    assert_equal(2, @dummy.players.size)
+    players = [@a, @b]
+    @obj.match(players)
+    assert_equal([@a,@b], players)
   end
 
   def test_match_4
-    @obj.match([@a, @b, @c])
-    assert_equal(2, @dummy.players.size)
+    players = [@a, @b, @c]
+    @obj.match(players)
+    assert_equal([@a, @c], players)
   end
 
   def test_match_5
-    @obj.match([@a, @c])
-    assert_equal(2, @dummy.players.size)
+    players = [@a, @c]
+    @obj.match(players)
+    assert_equal([@a,@c], players)
   end
 end