OSDN Git Service

Proposal implementation of a new command called MONITOR2{ON,OFF}.
[shogi-server/shogi-server.git] / shogi_server / pairing.rb
index 8f292a9..c4819c7 100644 (file)
@@ -25,27 +25,38 @@ module ShogiServer
 
     class << self
       def default_factory
-        return sort_by_rate_with_randomness
+        return swiss_pairing
       end
 
       def sort_by_rate_with_randomness
-        return [ExcludeSacrificeGps500.new,
+        return [LogPlayers.new,
+                ExcludeSacrificeGps500.new,
                 MakeEven.new,
                 SortByRateWithRandomness.new(1200, 2400),
-                StartGame.new]
+                StartGameWithoutHumans.new]
       end
 
       def random_pairing
-        return [ExcludeSacrificeGps500.new,
+        return [LogPlayers.new,
+                ExcludeSacrificeGps500.new,
                 MakeEven.new,
-                RandomPairing.new,
-                StartGame.new]
+                Randomize.new,
+                StartGameWithoutHumans.new]
+      end
+
+      def swiss_pairing
+        return [LogPlayers.new,
+                ExcludeSacrificeGps500.new,
+                MakeEven.new,
+                Swiss.new,
+                StartGameWithoutHumans.new]
       end
 
       def match(players)
         logics = default_factory
         logics.inject(players) do |result, item|
           item.match(result)
+          result
         end
       end
     end # class << self
@@ -53,6 +64,7 @@ module ShogiServer
 
     def match(players)
       # to be implemented
+      log_message("Floodgate: %s" % [self.class.to_s])
     end
 
     def include_newbie?(players)
@@ -61,10 +73,10 @@ module ShogiServer
 
     def less_than_one?(players)
       if players.size < 1
-        log_warning("Floodgate: At least one player is required")
+        log_warning("Floodgate: There should be at least one player.")
         return true
       else
-        return true
+        return false
       end
     end
 
@@ -76,34 +88,126 @@ module ShogiServer
           one.name
         end
       end
-      log_message("Floodgate: [Players] %s" % [str_array.join(", ")])
+      if str_array.empty?
+        log_message("Floodgate: [Players] Nobody found.")
+      else
+        log_message("Floodgate: [Players] %s." % [str_array.join(", ")])
+      end
     end
   end # Pairing
 
-  class StartGame < Pairing
+
+  class LogPlayers < Pairing
+    def match(players)
+      super
+      log_players(players)
+    end
+  end
+
+  class AbstractStartGame < Pairing
+    def start_game(p1, p2)
+      log_message("Floodgate: Starting a game: BLACK %s vs WHITE %s" % [p1.name, p2.name])
+      p1.sente = true
+      p2.sente = false
+      board = Board.new
+      board.initial
+      Game.new(p1.game_name, p1, p2, board)
+    end
+
+    def start_game_shuffle(pair)
+      pair.shuffle!
+      start_game(pair.first, pair.last)
+    end
+  end
+
+  class StartGame < AbstractStartGame
     def match(players)
       super
       if players.size < 2
-        log_warning("There should be more than one player: %d" % [players.size])
+        log_warning("Floodgate: There should be more than one player (%d)." % [players.size])
         return
       end
       if players.size.odd?
-        log_warning("There are odd players: %d" % [players.size])
+        log_warning("Floodgate: There are odd players (%d). %s will not be matched." % 
+                    [players.size, players.last.name])
       end
 
       log_players(players)
       while (players.size >= 2) do
         pair = players.shift(2)
-        pair.shuffle!
-        start_game(pair.first, pair.last)
+        start_game_shuffle(pair)
       end
     end
+  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)
+  # This tries to avoid a human-human match
+  #
+  class StartGameWithoutHumans < AbstractStartGame
+    def match(players)
+      super
+      log_players(players)
+      if players.size < 2
+        log_warning("Floodgate: There should be more than one player (%d)." % [players.size])
+        return
+      elsif players.size == 2
+        start_game_shuffle(players)
+        return
+      end
+
+      loop do 
+        humans = get_human_indexes(players)
+        log_message("Floodgate: There are (still) %d humans." % [humans.size])
+        break if humans.size < 2
+
+        pairing_possible = false
+        for i in 0..(humans.size-2)  # -2
+          next if humans[i].odd?
+          if humans[i]+1 == humans[i+1]
+            pairing_possible = i
+            break
+          end
+        end
+        unless pairing_possible
+          log_message("Floodgate: No possible human-human match found")
+          break
+        end
+
+        current_index = pairing_possible
+        j = (current_index == 0 ? current_index : current_index-1)
+        while j < players.size
+          break if players[j].is_computer?
+          j += 1
+        end
+
+        pairing_indexes = []
+        if j == players.size 
+          # no computer player found
+          pairing_indexes << current_index << current_index+1
+        else
+          # a comupter player found
+          pairing_indexes << current_index << j
+        end
+
+        pair = []
+        pair << players.delete_at(pairing_indexes.max)
+        pair << players.delete_at(pairing_indexes.min)
+        start_game_shuffle(pair)
+      end # loop
+
+      while (players.size >= 2) do
+        pair = players.shift(2)
+        start_game_shuffle(pair)
+      end
+    end
+
+    private
+
+    def get_human_indexes(players)
+      ret = []
+      for i in 0..(players.size-1)
+        ret << i if players[i].is_human?
+      end
+      return ret
     end
   end
 
@@ -133,13 +237,46 @@ module ShogiServer
       @rand1, @rand2 = rand1, rand2
     end
 
-    def match(players)
-      super
+    def match(players, desc=false)
+      super(players)
       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]}
+      players.reverse! if desc
       log_players(players) do |one|
-        "%s %d (randomness %d)" % [one.name, one.rate, cur_rate[one] - one.rate]
+        "%s %d (+ randomness %d)" % [one.name, one.rate, cur_rate[one] - one.rate]
+      end
+    end
+  end
+
+  class Swiss < Pairing
+    def match(players)
+      super
+      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)
+      sbrwr_winners.match(winners, true)
+
+      log_message("Floodgate: Ordering the rest (%d)..." % [rest.size])
+      sbrwr_losers = SortByRateWithRandomness.new(200, 400)
+      sbrwr_losers.match(rest, true)
+
+      players.clear
+      [winners, rest].each do |group|
+        group.each {|pl| players << pl}
       end
     end
   end
@@ -220,8 +357,8 @@ module ShogiServer
   class MakeEven < Pairing
     def match(players)
       super
-      return if players.even?
-      log_message("Floodgate: there are odd players: %d. Deleting one..." % 
+      return if players.size.even?
+      log_message("Floodgate: There are odd players (%d). Deleting one of them..." % 
                   [players.size])
       DeletePlayerAtRandom.new.match(players)
     end