FAForever / server

The servercode for the Forged Alliance Forever lobby
http://www.faforever.com
GNU General Public License v3.0
66 stars 61 forks source link

Better TMM map pool selection algorithm #950

Closed Blodir closed 1 year ago

Blodir commented 1 year ago

I wrote a TMM map pool selection algorithm that should be strictly better than the current solution. Given map pool player rating groups (your rating and the one below, <500, 500-1000, 1000-1500, 1500+ currently) and a list of player ratings, we find intersections of the preferred rating groups of each player and randomly pick a group from the intersections. If the set of players has no intersecting map preferences at all, we take the (higher) rating group of the lowest rated player (this way nobody can ever get maps that are above their rating bracket).

The motivating example that this fixes: R = [900, 1300, 1400, 1500, 1800, 2000, 2100, 2200]. Currently matches like this can get maps from the <500 pool (eg. this has happened to me before), with the new algorithm you'll be guaranteed a map from the 500-1000 pool. If the 900 turned into a 1100, then we'd be guaranteed a map from the 1000-1500 pool (rather than randomly choosing from the 500-1000 and 1000-1500 like it appears to currently do).

Forum discussion: https://forum.faforever.com/topic/5405/better-tmm-map-pool-selection-algorithm

Anyways, here's the algorithm in python:

import random
from typing import List, Set

RATING_GROUPS = [float('-inf'), 500, 1000, 1500]

def getPlayerGroups(r: int) -> Set[int]:
  groups = []
  for i, g in enumerate(RATING_GROUPS):
    if r >= g:
      groups.append(i)
  if (len(groups) < 2):
    groups.append(0)
  return { groups[len(groups)-2], groups[len(groups)-1] }

# given player ratings R find a set of map groups that satisfy all players
def selectMapGroup(R: List[int]) -> int:
  intersections = set(range(len(RATING_GROUPS)))
  lowestRating = float('inf')
  lowestGroups = { 0 }
  for r in R:
    groups = getPlayerGroups(r)
    if r < lowestRating:
      lowestRating = r
      lowestGroups = groups
    intersections = intersections.intersection(groups)
  if len(intersections) == 0: # fallback to lowest rating's group if there are no intersections
    l = list(lowestGroups)
    l.sort()
    return l[len(l)-1] # select the highest from the lowest rating groups

  return random.choice(list(intersections))
Askaholic commented 1 year ago

So the way the map pool selection algorithm currently works is that it picks the map pool according to the lowest rated player. Since the rating brackets are non-overlapping this always leaves only 1 choice (technically if your rating is exactly equal to a boundary you could get either pool, but this is extremely unlikely so not a problem in practice). So if the lowest rated player is a 900 then the 500-1000 pool will be used.

I think the confusion comes from the fact that the pools are set up to be additive. So the 1000-1500 map pool actually contains all the maps from both of the lower brackets PLUS some extra maps. However, this is a choice that the matchmaker team can make when setting up the map pools and is not something that has to do with the map pool selection algorithm.

Sheikah45 commented 1 year ago

Yes as Askaholic said the 500-1000 map pool contains all the <500 maps which is why you may can get them as a 900 rating player.

So it is not clear if this is an issue with the map selection algorithm.

Blodir commented 1 year ago

I see, I was under the impression that the current solution worked in the following way:

In fact I'm fairly sure archsimkat also thought that's how it works, since I've talked to him about the TMM maps quite a bit and that's likely where I got the wrong idea from. Anyways, the pool should not be additive. We'll see what the matchmaker team says about that, but I don't think anyone will disagree with that. Nobody at 1800+ wants to play Primus Alfa Mini for instance (which I recently had the misfortune of doing).

My solution was designed to work within the constraints of what I (we) thought was how the system was supposed to work. That being said there's not that much of a difference between that and picking just the lowest rating players rating bracket (without all the previous brackets). Choosing the bracket below the lowest rating players' bracket would matter only when everyone in the game are in the same rating bracket. However I would hope that this would be the case as often as possible, and if we managed to achieve such (given a hypothetical large growth spurt for instance) then it might be too monotone to just play maps from only 1500+ pool rather than 1000-1500 and 1500+. I suppose a discussion must be had about which approach should be chosen, but the current additive map pool is definitely off the table.

Blodir commented 1 year ago

According to Morax the way that I thought it works (described above) is how it was going to work based on Yudi's system on this spreadsheet https://docs.google.com/spreadsheets/d/1IiCUKeUf_oha_dovqnbYY2GwVluDLt-ehEfaSmAsCsc/edit#gid=0, so that's probably where the confusion originates from.

Askaholic commented 1 year ago

I think I vaguely remember that too. The main reason we made the map pool selection so simple was so that the behavior could be customized through the map pool setup without needing to change code. The only thing that really needs to happen in code is deciding which players rating to use for the map pool selection, which we decided to use the lowest rating for.

I think the additive map pools where you basically unlock more maps as you gain rating was how FTX originally wanted to set it up, and it’s been used ever since.

I’m going to close this issue as there is no need to change any code for this. Discussion on how to change the map pool setup should happen on the forums/discord.

BlackYps commented 1 year ago

The matchmaker team does want to change from lowest rated player to average. So a code change is needed. Maybe it should be be a different issue than this though, as this one was explicitly about changing the algorithm. Related: https://github.com/FAForever/downlords-faf-client/issues/2869

Blodir commented 1 year ago

@Askaholic according to Ftx the way that I thought it works (as described in the bullet points https://github.com/FAForever/server/issues/950#issuecomment-1370217527) is accurate. It was apparently changed sometime around a year ago.

I personally support my solution over choosing the average rating and 1 pool below it (which is what the TMM team has been proposing and agreed upon recently), since my solution conserves the property that nobody can get a map that is above them in rating bracket. My solution also does not require any changes to the client.

harzer99 commented 1 year ago

I am still strongly in favor of going with the average. Especially now that slightly larger rating discrepancies are favored again. Currently the different pools of the brackets have some overlap in order to increase variety while keeping the effort it takes to curate a pool low. I don't see this solution as an improvement over the current state. I think predictability of the pool is overvalued. Noone is prepping for maps in TMM and for the people being confused there would be other solutions. For instance start showing the whole pool, write wiki page etc.

ChessBerry commented 1 year ago

"Noone is prepping for maps in TMM" seems like a very strong statement. Even if you don't prep a BO for every slot on every map, which I agree literally nobody does, it's nice to know what maps you can get. Maybe load them up if you haven't seen them before, check the reclaim, look at the terrain, etc. Whatever the implemented solution is going to be, the tmm map list shown in the client should include all maps a player can possibly play on.

Is there are a discussion thread somewhere about why the TMM team wants to change to the average rating for map selection?