OSDN Git Service

f203f665bcc2115df1e108d1ba2b50df3e406fba
[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_pairing
28         #return SwissPairing.new
29         #return ExcludeSacrifice.new(SwissPairing.new)
30         #return RandomPairing.new
31         return ExcludeSacrifice.new(RandomPairing.new)
32       end
33     end
34
35     def match(players)
36       if players.size < 2
37         log_message("Floodgate[%s]: too few players [%d]" % 
38                     [self.class, players.size])
39       else
40         log_message("Floodgate[%s]: found %d players. Pairing them..." % 
41                     [self.class, players.size])
42       end
43     end
44
45     def start_game(p1, p2)
46       p1.sente = true
47       p2.sente = false
48       Game.new(p1.game_name, p1, p2)
49     end
50
51     def include_newbie?(players)
52       return players.find{|a| a.rate == 0} == nil ? false : true
53     end
54
55     def delete_player_at_random(players)
56       return players.delete_at(rand(players.size))
57     end
58
59     def delete_player_at_random_except(players, a_player)
60       candidates = players - [a_player]
61       return delete_player_at_random(candidates)
62     end
63     
64     def delete_most_playing_player(players)
65       # TODO ??? undefined method `<=>' for nil:NilClass
66       max_player = players.max {|a,b| a.win + a.loss <=> b.win + b.loss}
67       return players.delete(max_player)
68     end
69
70     def delete_least_rate_player(players)
71       min_player = players.min {|a,b| a.rate <=> b.rate}
72       return players.delete(min_player)
73     end
74
75     def pairing_and_start_game(players)
76       return if players.size < 2
77       if players.size.odd?
78         log_warning("#Players should be even: %d" % [players.size])
79         return
80       end
81
82       sorted = players.shuffle
83       pairs = []
84       while !sorted.empty? do
85         pairs << sorted.shift(2)
86       end
87       pairs.each do |pair|
88         start_game(pair.first, pair.last)
89       end
90     end
91
92     def pairing_and_start_game_by_rate(players, randomness1, randomness2)
93       return if players.size < 2
94       if players.size % 2 == 1
95         log_warning("#Players should be even: %d" % [players.size])
96         return
97       end
98       cur_rate = Hash.new
99       players.each{ |a| cur_rate[a] = a.rate ? a.rate+rand(randomness1) : rand(randomness2) }
100       sorted = players.sort{ |a,b| cur_rate[a] <=> cur_rate[b] }
101       log_message("Floodgate[%s]: %s (randomness %d)" % [self.class, sorted.map{|a| a.name}.join(","), randomness1])
102
103       pairs = [[sorted.shift]]
104       while !sorted.empty? do
105         if pairs.last.size < 2
106           pairs.last << sorted.shift
107         else
108           pairs << [sorted.shift]
109         end 
110       end
111       pairs.each do |pair|
112         start_game(pair.choice, pair.choice)
113         if (rand(2) == 0)
114           start_game(pair.first, pair.last)
115         else
116           start_game(pair.last, pair.first)
117         end
118       end
119     end
120   end # Pairing
121
122   class RandomPairing < Pairing
123     def match(players)
124       super
125       return if players.size < 2
126
127       if players.size % 2 == 1
128         delete_player_at_random(players)
129       end
130       pairing_and_start_game(players)
131     end
132   end # RadomPairing
133
134   class SwissPairing < Pairing
135     def match(players)
136       super
137       return if players.size < 2
138
139       win_players = players.find_all {|a| a.last_game_win? }
140       log_message("Floodgate[%s]: win_players %s" % [self.class, win_players.map{|a| a.name}.join(",")])
141       if (win_players.size < players.size / 2)
142         x1_players = players.find_all {|a| a.rate > 0 && !a.last_game_win? && a.protocol != LoginCSA::PROTOCOL }
143         x1_players = x1_players.sort{ rand < 0.5 ? 1 : -1 }
144         while (win_players.size < players.size / 2) && !x1_players.empty? do
145           win_players << x1_players.shift
146         end
147         log_message("Floodgate[%s]: win_players (adhoc x1 collection) %s" % [self.class, win_players.map{|a| a.name}.join(",")])
148       end
149       remains     = players - win_players
150 #      split_winners = (win_players.size >= (2+rand(2)))
151       split_winners = false
152       if split_winners
153         if win_players.size % 2 == 1
154 #          if include_newbie?(win_players)
155             remains << delete_player_at_random(win_players)
156 #          else
157 #            remains << delete_least_rate_player(win_players)
158 #          end
159         end         
160         pairing_and_start_game_by_rate(win_players, 800, 2500)
161       else
162         remains.concat(win_players)
163       end
164       return if remains.size < 2
165       if remains.size % 2 == 1
166         delete_player_at_random(remains)
167         # delete_most_playing_player(remains)
168       end
169       if split_winners
170         pairing_and_start_game_by_rate(remains, 200, 400)
171       else
172         # pairing_and_start_game_by_rate(remains, 800, 2400)
173         pairing_and_start_game_by_rate(remains, 1200, 2400)
174       end
175     end
176   end # SwissPairing
177
178   class ExcludeSacrifice
179     attr_accessor :sacrifice
180
181     def initialize(pairing)
182       @pairing  = pairing
183       @sacrifice = "gps500+e293220e3f8a3e59f79f6b0efffaa931"
184     end
185
186     def match(players)
187       if @sacrifice && 
188          players.size % 2 == 1 && 
189          players.find{|a| a.player_id == @sacrifice}
190         log_message("Floodgate: first, exclude %s" % [@sacrifice])
191         players.delete_if{|a| a.player_id == @sacrifice}
192       end
193       @pairing.match(players)
194     end
195
196     # Delegate to @pairing
197     def method_missing(message, *arg)
198       @pairing.send(message, *arg)
199     end
200   end # class ExcludeSacrifice
201 end # ShogiServer