forked from netsuso/foos-tournament
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmatch_solver.rb
220 lines (194 loc) · 6.37 KB
/
match_solver.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
class Solver
@debug = false
@@max_attempts = 20
@@scores_nplayed = {
0 => 0,
1 => 1000,
2 => 600,
3 => 400,
4 => 300,
5 => 200,
6 => 100,
}
def initialize(one2one)
@one2one = one2one
end
def get_one2one_score(confrontations)
if @@scores_nplayed.key?(confrontations)
return @@scores_nplayed[confrontations]
else
return 0
end
end
def match_score(players, one2one, one2one_diff = {})
score = 0
for p in 0..2
player = players[p]
for r in p+1..3
rival = players[r]
confrontations = one2one[player][rival]
if one2one_diff.include?(player) and one2one_diff[player].include?(rival)
confrontations += one2one_diff[player][rival]
end
score += get_one2one_score(confrontations)
end
end
return score
end
def get_random_solution(player_list)
nmatches = player_list.length / 4
for i in 0..500000
solution = player_list.shuffle
wrong = false
for m in 0...nmatches
players = solution[m*4..m*4+3]
if players.uniq.length < 4
wrong = true
end
end
if wrong == false
return solution
end
end
raise 'Too many attempts to find a random solution with no repeated players'
end
def solve(player_list)
if player_list.length % 4 != 0
raise 'The list of players to solve a solution is not multiple of 4'
end
nmatches = player_list.length / 4
attempts = 1
while attempts <= @@max_attempts
begin
solution = get_random_solution(player_list)
rescue Exception => e
raise 'No possible solution with this combination of players'
end
one2one = Marshal.load(Marshal.dump(@one2one))
for m in 0...nmatches
players = solution[m*4..m*4+3]
pl1 = players[0]
pl2 = players[1]
pl3 = players[2]
pl4 = players[3]
one2one[pl1][pl2] += 1
one2one[pl1][pl3] += 1
one2one[pl1][pl4] += 1
one2one[pl2][pl1] += 1
one2one[pl2][pl3] += 1
one2one[pl2][pl4] += 1
one2one[pl3][pl1] += 1
one2one[pl3][pl2] += 1
one2one[pl3][pl4] += 1
one2one[pl4][pl1] += 1
one2one[pl4][pl2] += 1
one2one[pl4][pl3] += 1
end
score = 0
for m in 0...nmatches
players = solution[m*4..m*4+3]
score += match_score(players, one2one)
end
if attempts == 1
best_solution = solution.dup
best_score = score
best_one2one = Marshal.load(Marshal.dump(one2one))
puts "Initial random solution with score #{score}" if @debug
else
puts "Shuffling for a new random solution" if @debug
end
while true
found_best = false
for match_to_change1 in (0...nmatches).to_a.shuffle
orig_match1 = solution[match_to_change1*4..match_to_change1*4+3]
for match_to_change2 in ((0...nmatches).to_a - [match_to_change1]).shuffle
orig_match2 = solution[match_to_change2*4..match_to_change2*4+3]
for position_to_swap1 in (0..3).to_a.shuffle
player_to_swap1 = orig_match1[position_to_swap1]
if orig_match2.include?(player_to_swap1)
next
end
for position_to_swap2 in (0..3).to_a.shuffle
player_to_swap2 = orig_match2[position_to_swap2]
if orig_match1.include?(player_to_swap2)
next
end
new_solution = solution.dup
position1 = match_to_change1*4 + position_to_swap1
position2 = match_to_change2*4 + position_to_swap2
new_solution[position1] = player_to_swap2
new_solution[position2] = player_to_swap1
one2one_diff = {}
for p in orig_match1 + orig_match2
one2one_diff[p] = {}
for q in orig_match1 + orig_match2
one2one_diff[p][q] = 0
end
end
for r in 0..3
if r != position_to_swap1
rival = orig_match1[r]
one2one_diff[player_to_swap1][rival] -= 1
one2one_diff[rival][player_to_swap1] -= 1
one2one_diff[player_to_swap2][rival] += 1
one2one_diff[rival][player_to_swap2] += 1
end
if r != position_to_swap2
rival = orig_match2[r]
one2one_diff[player_to_swap2][rival] -= 1
one2one_diff[rival][player_to_swap2] -= 1
one2one_diff[player_to_swap1][rival] += 1
one2one_diff[rival][player_to_swap1] += 1
end
end
new_score = 0
for m in 0...nmatches
players = new_solution[m*4..m*4+3]
new_score += match_score(players, one2one, one2one_diff)
end
if new_score > score
found_best = true
break
end
end
break if found_best
end
break if found_best
end
break if found_best
end
if not found_best
puts "No best neighbour, found a local maximum" if @debug
break
end
score = new_score
solution = new_solution
for p in 0..3
if p != position_to_swap1
rival = orig_match1[p]
one2one[player_to_swap1][rival] -= 1
one2one[rival][player_to_swap1] -= 1
one2one[player_to_swap2][rival] += 1
one2one[rival][player_to_swap2] += 1
end
if p != position_to_swap2
rival = orig_match2[p]
one2one[player_to_swap2][rival] -= 1
one2one[rival][player_to_swap2] -= 1
one2one[player_to_swap1][rival] += 1
one2one[rival][player_to_swap1] += 1
end
end
if score > best_score
best_solution = solution.dup
best_score = score
best_one2one = Marshal.load(Marshal.dump(one2one))
puts "Found a better solution (score #{best_score})" if @debug
end
end
attempts += 1
end
puts "Score for the best solution is #{best_score}" if @debug
return best_solution, best_score, best_one2one
end
end