OSDN Git Service

Implemented Swiss pairing.
[shogi-server/shogi-server.git] / shogi_server / pairing.rb
1 ## $Id$
2
3 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
4 ## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
5 ##
6 ## This program is free software; you can redistribute it and/or modify
7 ## it under the terms of the GNU General Public License as published by
8 ## the Free Software Foundation; either version 2 of the License, or
9 ## (at your option) any later version.
10 ##
11 ## This program is distributed in the hope that it will be useful,
12 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 ## GNU General Public License for more details.
15 ##
16 ## You should have received a copy of the GNU General Public License
17 ## along with this program; if not, write to the Free Software
18 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 require 'shogi_server/util'
21
22 module ShogiServer
23
24   class Pairing
25
26     class << self
27       def default_factory
28         return swiss_pairing
29       end
30
31       def sort_by_rate_with_randomness
32         return [LogPlayers.new,
33                 ExcludeSacrificeGps500.new,
34                 MakeEven.new,
35                 SortByRateWithRandomness.new(1200, 2400),
36                 StartGame.new]
37       end
38
39       def random_pairing
40         return [LogPlayers.new,
41                 ExcludeSacrificeGps500.new,
42                 MakeEven.new,
43                 Randomize.new,
44                 StartGame.new]
45       end
46
47       def swiss_pairing
48         history = ShogiServer::League::Floodgate::History.factory
49         return [LogPlayers.new,
50                 ExcludeSacrificeGps500.new,
51                 MakeEven.new,
52                 Swiss.new(history),
53                 StartGame.new]
54       end
55
56       def match(players)
57         logics = default_factory
58         logics.inject(players) do |result, item|
59           item.match(result)
60           result
61         end
62       end
63     end # class << self
64
65
66     def match(players)
67       # to be implemented
68     end
69
70     def include_newbie?(players)
71       return players.find{|a| a.rate == 0} == nil ? false : true
72     end
73
74     def less_than_one?(players)
75       if players.size < 1
76         log_warning("Floodgate: There should be at least one player.")
77         return true
78       else
79         return false
80       end
81     end
82
83     def log_players(players)
84       str_array = players.map do |one|
85         if block_given?
86           yield one
87         else
88           one.name
89         end
90       end
91       if str_array.empty?
92         log_message("Floodgate: [Players] None is here.")
93       else
94         log_message("Floodgate: [Players] %s." % [str_array.join(", ")])
95       end
96     end
97   end # Pairing
98
99
100   class LogPlayers < Pairing
101     def match(players)
102       log_players(players)
103     end
104   end
105
106   class StartGame < Pairing
107     def match(players)
108       super
109       if players.size < 2
110         log_warning("Floodgate: There should be more than one player: %d" % [players.size])
111         return
112       end
113       if players.size.odd?
114         log_warning("Floodgate: There are odd players: %d. %s will not be matched." % 
115                     [players.size, players.last.name])
116       end
117
118       log_players(players)
119       while (players.size >= 2) do
120         pair = players.shift(2)
121         pair.shuffle!
122         start_game(pair.first, pair.last)
123       end
124     end
125
126     def start_game(p1, p2)
127       log_message("Floodgate: BLACK %s; WHITE %s" % [p1.name, p2.name])
128       p1.sente = true
129       p2.sente = false
130       Game.new(p1.game_name, p1, p2)
131     end
132   end
133
134   class Randomize < Pairing
135     def match(players)
136       super
137       log_message("Floodgate: Randomize... before")
138       log_players(players)
139       players.shuffle!
140       log_message("Floodgate: Randomized after")
141       log_players(players)
142     end
143   end # RadomPairing
144
145   class SortByRate < Pairing
146     def match(players)
147       super
148       log_message("Floodgate: Ordered by rate")
149       players.sort! {|a,b| a.rate <=> b.rate} # decendent order
150       log_players(players)
151     end
152   end
153
154   class SortByRateWithRandomness < Pairing
155     def initialize(rand1, rand2)
156       super()
157       @rand1, @rand2 = rand1, rand2
158     end
159
160     def match(players, desc=false)
161       super(players)
162       cur_rate = Hash.new
163       players.each{|a| cur_rate[a] = a.rate ? a.rate + rand(@rand1) : rand(@rand2)}
164       players.sort!{|a,b| cur_rate[a] <=> cur_rate[b]}
165       players.reverse! if desc
166       log_players(players) do |one|
167         "%s %d (+ randomness %d)" % [one.name, one.rate, cur_rate[one] - one.rate]
168       end
169     end
170   end
171
172   class Swiss < Pairing
173     def initialize(history)
174       super()
175       @history = history
176     end
177
178     def match(players)
179       super
180       winners = players.find_all {|pl| @history.last_win?(pl.player_id)}
181       rest    = players - winners
182
183       log_message("Floodgate: %d winners" % [winners.size])
184       sbrwr_winners = SortByRateWithRandomness.new(800, 2500)
185       sbrwr_winners.match(winners, true)
186       log_players(winners)
187
188       log_message("Floodgate: and the rest: %d" % [rest.size])
189       sbrwr_losers = SortByRateWithRandomness.new(200, 400)
190       sbrwr_losers.match(rest, true)
191       log_players(rest)
192
193       players.clear
194       [winners, rest].each do |group|
195         group.each {|pl| players << pl}
196       end
197     end
198   end
199
200   class DeletePlayerAtRandom < Pairing
201     def match(players)
202       super
203       return if less_than_one?(players)
204       one = players.choice
205       log_message("Floodgate: Deleted %s at random" % [one.name])
206       players.delete(one)
207       log_players(players)
208     end
209   end
210
211   class DeletePlayerAtRandomExcept < Pairing
212     def initialize(except)
213       super()
214       @except = except
215     end
216
217     def match(players)
218       super
219       log_message("Floodgate: Deleting a player at rondom except %s" % [@except.name])
220       players.delete(@except)
221       DeletePlayerAtRandom.new.match(players)
222       players.push(@except)
223     end
224   end
225   
226   class DeleteMostPlayingPlayer < Pairing
227     def match(players)
228       super
229       one = players.max_by {|a| a.win + a.loss}
230       log_message("Floodgate: Deleted the most playing player: %s (%d)" % [one.name, one.win + one.loss])
231       players.delete(one)
232       log_players(players)
233     end
234   end
235
236   class DeleteLeastRatePlayer < Pairing
237     def match(players)
238       super
239       one = players.min_by {|a| a.rate}
240       log_message("Floodgate: Deleted the least rate player %s (%d)" % [one.name, one.rate])
241       players.delete(one)
242       log_players(players)
243     end
244   end
245
246   class ExcludeSacrifice < Pairing
247     attr_reader :sacrifice
248
249     # @sacrifice a player id to be eliminated
250     def initialize(sacrifice)
251       super()
252       @sacrifice = sacrifice
253     end
254
255     def match(players)
256       super
257       if @sacrifice && 
258          players.size.odd? && 
259          players.find{|a| a.player_id == @sacrifice}
260          log_message("Floodgate: Deleting the sacrifice %s" % [@sacrifice])
261          players.delete_if{|a| a.player_id == @sacrifice}
262          log_players(players)
263       end
264     end
265   end # class ExcludeSacrifice
266
267   class ExcludeSacrificeGps500 < ExcludeSacrifice
268     def initialize
269       super("gps500+e293220e3f8a3e59f79f6b0efffaa931")
270     end
271   end
272
273   class MakeEven < Pairing
274     def match(players)
275       super
276       return if players.size.even?
277       log_message("Floodgate: there are odd players: %d. Deleting one..." % 
278                   [players.size])
279       DeletePlayerAtRandom.new.match(players)
280     end
281   end
282
283 end # ShogiServer