Skip to content

Commit 85cddbb

Browse files
committed
first attempt at making balance better at handling high elo players on AD
1 parent a538a67 commit 85cddbb

1 file changed

Lines changed: 49 additions & 22 deletions

File tree

balance.py

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# You should have received a copy of the GNU General Public License
1717
# along with minqlx. If not, see <http://www.gnu.org/licenses/>.
1818

19+
from statistics import mean
1920
import minqlx
2021
import requests
2122
import itertools
@@ -28,6 +29,7 @@
2829
CACHE_EXPIRE = 60*10 # 10 minutes TTL.
2930
DEFAULT_RATING = 1500
3031
UNTRACKED_RATING = 9999
32+
AD_ELO_THRESHOLD = 1400
3133
SUPPORTED_GAMETYPES = ("ad", "ca", "ctf", "dom", "ft", "tdm")
3234
# Externally supported game types. Used by !getrating for game types the API works with.
3335
EXT_SUPPORTED_GAMETYPES = ("ad", "ca", "ctf", "dom", "ft", "tdm", "duel", "ffa")
@@ -82,7 +84,7 @@ def handle_round_countdown(self, *args, **kwargs):
8284
def f():
8385
self.execute_suggestion()
8486
f()
85-
87+
8688
self.in_countdown = True
8789

8890
def handle_round_start(self, *args, **kwargs):
@@ -100,7 +102,7 @@ def f():
100102
if len(players["red"] + players["blue"]) % 2 != 0:
101103
self.msg("Teams were ^6NOT^7 balanced due to the total number of players being an odd number.")
102104
return
103-
105+
104106
players = dict([(p.steam_id, gt) for p in players["red"] + players["blue"]])
105107
self.add_request(players, self.callback_balance, minqlx.CHAT_CHANNEL)
106108
f()
@@ -166,7 +168,7 @@ def fetch_ratings(self, players, request_id):
166168
last_status = res.status_code
167169
if res.status_code != requests.codes.ok:
168170
continue
169-
171+
170172
js = res.json()
171173
if "players" not in js:
172174
last_status = -1
@@ -181,14 +183,14 @@ def fetch_ratings(self, players, request_id):
181183
with self.ratings_lock:
182184
if sid not in self.ratings:
183185
self.ratings[sid] = {}
184-
186+
185187
for gt in p:
186188
p[gt]["time"] = t
187189
p[gt]["local"] = False
188190
self.ratings[sid][gt] = p[gt]
189191
if self.ratings[sid][gt]["elo"] == 0 and self.ratings[sid][gt]["games"] == 0:
190192
self.ratings[sid][gt]["elo"] = DEFAULT_RATING
191-
193+
192194
if sid in players and gt == players[sid]:
193195
# The API gave us the game type we wanted, so we remove it.
194196
del players[sid]
@@ -304,13 +306,13 @@ def callback_getrating(self, players, channel, gametype):
304306
name = player.name
305307
else:
306308
name = sid
307-
309+
308310
channel.reply("{} has a rating of ^6{}^7 in {}.".format(name, self.ratings[sid][gametype]["elo"], gametype.upper()))
309311

310312
def cmd_setrating(self, player, msg, channel):
311313
if len(msg) < 3:
312314
return minqlx.RET_USAGE
313-
315+
314316
try:
315317
sid = int(msg[1])
316318
target_player = None
@@ -323,7 +325,7 @@ def cmd_setrating(self, player, msg, channel):
323325
except minqlx.NonexistentPlayerError:
324326
player.tell("Invalid client ID. Use either a client ID or a SteamID64.")
325327
return minqlx.RET_STOP_ALL
326-
328+
327329
try:
328330
rating = int(msg[2])
329331
except ValueError:
@@ -334,7 +336,7 @@ def cmd_setrating(self, player, msg, channel):
334336
name = target_player.name
335337
else:
336338
name = sid
337-
339+
338340
gt = self.game.type_short
339341
self.db[RATING_KEY.format(sid, gt)] = rating
340342

@@ -350,7 +352,7 @@ def cmd_setrating(self, player, msg, channel):
350352
def cmd_remrating(self, player, msg, channel):
351353
if len(msg) < 2:
352354
return minqlx.RET_USAGE
353-
355+
354356
try:
355357
sid = int(msg[1])
356358
target_player = None
@@ -363,12 +365,12 @@ def cmd_remrating(self, player, msg, channel):
363365
except minqlx.NonexistentPlayerError:
364366
player.tell("Invalid client ID. Use either a client ID or a SteamID64.")
365367
return minqlx.RET_STOP_ALL
366-
368+
367369
if target_player:
368370
name = target_player.name
369371
else:
370372
name = sid
371-
373+
372374
gt = self.game.type_short
373375
del self.db[RATING_KEY.format(sid, gt)]
374376

@@ -389,7 +391,7 @@ def cmd_balance(self, player, msg, channel):
389391
if len(teams["red"] + teams["blue"]) % 2 != 0:
390392
player.tell("The total number of players should be an even number.")
391393
return minqlx.RET_STOP_ALL
392-
394+
393395
players = dict([(p.steam_id, gt) for p in teams["red"] + teams["blue"]])
394396
self.add_request(players, self.callback_balance, minqlx.CHAT_CHANNEL)
395397

@@ -453,12 +455,12 @@ def cmd_teams(self, player, msg, channel):
453455
if gt not in SUPPORTED_GAMETYPES:
454456
player.tell("This game mode is not supported by the balance plugin.")
455457
return minqlx.RET_STOP_ALL
456-
458+
457459
teams = self.teams()
458460
if len(teams["red"]) != len(teams["blue"]):
459461
player.tell("Both teams should have the same number of players.")
460462
return minqlx.RET_STOP_ALL
461-
463+
462464
teams = dict([(p.steam_id, gt) for p in teams["red"] + teams["blue"]])
463465
self.add_request(teams, self.callback_teams, channel)
464466

@@ -514,7 +516,7 @@ def cmd_agree(self, player, msg, channel):
514516
"""After the bot suggests a switch, players in question can use this to agree to the switch."""
515517
if self.suggested_pair and not all(self.suggested_agree):
516518
p1, p2 = self.suggested_pair
517-
519+
518520
if p1 == player:
519521
self.suggested_agree[0] = True
520522
elif p2 == player:
@@ -534,7 +536,7 @@ def cmd_ratings(self, player, msg, channel):
534536
if gt not in EXT_SUPPORTED_GAMETYPES:
535537
player.tell("This game mode is not supported by the balance plugin.")
536538
return minqlx.RET_STOP_ALL
537-
539+
538540
players = dict([(p.steam_id, gt) for p in self.players()])
539541
self.add_request(players, self.callback_ratings, channel)
540542

@@ -569,6 +571,29 @@ def callback_ratings(self, players, channel):
569571

570572
def suggest_switch(self, teams, gametype):
571573
"""Suggest a switch based on average team ratings."""
574+
575+
if gametype == "ad":
576+
# when there is an even amount of players with elo >= AD_ELO_THRESHOLD
577+
# make sure they are divided evenly over the teams
578+
elos_red = [
579+
(p, self.ratings[p.steam_id][gametype]["elo"]) for p in teams["red"]
580+
]
581+
elos_red_high = sorted([p for p in elos_red if p[1] >= AD_ELO_THRESHOLD], key=lambda x: x[1])
582+
elos_red_low = sorted([p for p in elos_red if p[1] < AD_ELO_THRESHOLD], key=lambda x: x[1])
583+
elos_blue = [
584+
(p, self.ratings[p.steam_id][gametype]["elo"]) for p in teams["blue"]
585+
]
586+
elos_blue_high = sorted([p for p in elos_blue if p[1] >= AD_ELO_THRESHOLD], key=lambda x: x[1])
587+
elos_blue_low = sorted([p for p in elos_blue if p[1] < AD_ELO_THRESHOLD], key=lambda x: x[1])
588+
total_high_elos = len(elos_red_high) + len(elos_blue_high)
589+
590+
if total_high_elos > 1 and len(elos_red_high) != len(elos_blue_high):
591+
if len(elos_red_high) > len(elos_blue_high):
592+
return ((elos_red_high[-1], elos_blue_low[-1]), 0)
593+
else:
594+
return ((elos_red_low[-1], elos_blue_high[-1]), 0)
595+
596+
572597
avg_red = self.team_average(teams["red"], gametype)
573598
avg_blue = self.team_average(teams["blue"], gametype)
574599
cur_diff = abs(avg_red - avg_blue)
@@ -599,9 +624,11 @@ def team_average(self, team, gametype):
599624
"""Calculates the average rating of a team."""
600625
avg = 0
601626
if team:
602-
for p in team:
603-
avg += self.ratings[p.steam_id][gametype]["elo"]
604-
avg /= len(team)
627+
elos = [self.ratings[p.steam_id][gametype]["elo"] for p in team]
628+
# for ad we skip the high elo players when calculating the avg
629+
if gametype == "ad":
630+
elos = [elo for elo in elos if elo < AD_ELO_THRESHOLD]
631+
avg = mean(elos)
605632

606633
return avg
607634

@@ -612,9 +639,9 @@ def execute_suggestion(self):
612639
p2.update()
613640
except minqlx.NonexistentPlayerError:
614641
return
615-
642+
616643
if p1.team != "spectator" and p2.team != "spectator":
617644
self.switch(self.suggested_pair[0], self.suggested_pair[1])
618-
645+
619646
self.suggested_pair = None
620647
self.suggested_agree = [False, False]

0 commit comments

Comments
 (0)