OSDN Git Service

Debugged Floodgate
[shogi-server/shogi-server.git] / shogi_server / pairing.rb
index 18f0925..992dbcb 100644 (file)
 ## 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/util'
+
 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 [LogPlayers.new,
+                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 [LogPlayers.new,
+                ExcludeSacrificeGps500.new,
+                MakeEven.new,
+                Randomize.new,
+                StartGame.new]
+      end
+
+      def match(players)
+        logics = default_factory
+        logics.inject(players) do |result, item|
+          item.match(result)
+          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))
+    def less_than_one?(players)
+      if players.size < 1
+        log_warning("Floodgate: There should be at least one player.")
+        return true
+      else
+        return false
+      end
     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 log_players(players)
+      str_array = players.map do |one|
+        if block_given?
+          yield one
+        else
+          one.name
+        end
+      end
+      if str_array.empty?
+        log_message("Floodgate: [Players] None is here.")
+      else
+        log_message("Floodgate: [Players] %s." % [str_array.join(", ")])
+      end
     end
+  end # Pairing
 
-    def delete_least_rate_player(players)
-      min_player = players.min {|a,b| a.rate <=> b.rate}
-      return players.delete(min_player)
+
+  class LogPlayers < Pairing
+    def match(players)
+      log_players(players)
     end
+  end
 
-    def pairing_and_start_game(players)
-      return if players.size < 2
-      if players.size % 2 == 1
-        log_warning("#Players should be even: %d" % [players.size])
+  class StartGame < Pairing
+    def match(players)
+      super
+      if players.size < 2
+        log_warning("Floodgate: There should be more than one player: %d" % [players.size])
         return
       end
-      sorted = players.sort{ rand < 0.5 ? 1 : -1 }
-
-      pairs = [[sorted.shift]]
-      while !sorted.empty? do
-        if pairs.last.size < 2
-          pairs.last << sorted.shift
-        else
-          pairs << [sorted.shift]
-        end 
+      if players.size.odd?
+        log_warning("Floodgate: There are odd players: %d. %s will not be matched." % 
+                    [players.size, players.last.name])
       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
-  end # Pairing
 
-  class RandomPairing < Pairing
+    def start_game(p1, p2)
+      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
-      return if players.size < 2
-
-      if players.size % 2 == 1
-        delete_player_at_random(players)
-      end
-      pairing_and_start_game(players)
+      log_message("Floodgate: Randomize... before")
+      log_players(players)
+      players.shuffle!
+      log_message("Floodgate: Randomized after")
+      log_players(players)
     end
   end # RadomPairing
 
-  class SwissPairing < Pairing
+  class SortByRate < Pairing
     def match(players)
       super
-      return if players.size < 2
-
-      win_players = players.find_all {|a| a.last_game_win?}
-      remains     = players - win_players
-      if win_players.size >= 2
-        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(win_players)
-      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)
+      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(@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
-      pairing_and_start_game(remains)
     end
-  end # SwissPairing
+  end
 
-  class ExcludeSacrifice
-    attr_accessor :sacrifice
+  class DeletePlayerAtRandom < Pairing
+    def match(players)
+      super
+      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
 
-    def initialize(pairing)
-      @pairing  = pairing
-      @sacrifice = "gps500+e293220e3f8a3e59f79f6b0efffaa931"
+  class DeletePlayerAtRandomExcept < Pairing
+    def initialize(except)
+      super()
+      @except = except
     end
 
     def match(players)
+      super
+      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
+
+  class ExcludeSacrifice < Pairing
+    attr_reader :sacrifice
+
+    # @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.size.even?
+      log_message("Floodgate: there are odd players: %d. Deleting one..." % 
+                  [players.size])
+      DeletePlayerAtRandom.new.match(players)
+    end
+  end
+
 end # ShogiServer