Search code examples
algorithmsortinggroupingcluster-analysisgame-development

Matchmaking algorithm for a multiplayer game


I want to group players in a tournament, for exemple MTG or Catan, in tables of 3 to 4 players minimizing the number of players that get byes (skip a round).

I have tried the following code:

    public static List<GroupMatch> createGroupMatches(List<Player> players, Integer targetPodSize, Integer minPodSize, Integer maxPodSize) {
        if (targetPodSize < minPodSize || targetPodSize > maxPodSize) {
            throw new IllegalArgumentException("targetPodSize must be between minPodSize and maxPodSize");
        }

        List<Player> rankedPlayers = getClassification(players);
        Integer playerCount = rankedPlayers.size();
        Integer remainder = (playerCount % targetPodSize);
        List<GroupMatch> groupMatches = new ArrayList<>();

        if (playerCount % targetPodSize == 0) {
            int j = 0;
            for (int i = 0; i < playerCount / targetPodSize; i++) {
                groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + targetPodSize)));
                j += targetPodSize;
            }
        } else if (((remainder + targetPodSize) % 2 == 0) && (remainder + targetPodSize > minPodSize)) {
            int smallpod = (remainder + targetPodSize) / 2;
            int j = 0;
            for (int i = 0; i < (playerCount - 2 * smallpod) / targetPodSize; i++) {
                groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + targetPodSize)));
                j += targetPodSize;
            }
            groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + smallpod)));
            j += smallpod;
            groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + smallpod)));
        } else if (remainder >= minPodSize) {
            int smallpod = remainder;
            int j = 0;
            for (int i = 0; i < (playerCount - smallpod) / targetPodSize; i++) {
                groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + targetPodSize)));
                j += targetPodSize;
            }
            groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + smallpod)));
        } else if ((remainder + targetPodSize) <= maxPodSize) {
            int smallpod = remainder + targetPodSize;
            int j = 0;
            for (int i = 0; i < (playerCount - smallpod) / targetPodSize; i++) {
                groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + targetPodSize)));
                j += targetPodSize;
            }
            groupMatches.add(new GroupMatch(rankedPlayers.subList(j, j + smallpod)));
        }

        return groupMatches;
    }

It arranges in an almost manual fashion, but fails to make multiple tables with sizes different from the target.

The algorithm is:

  • If the number of players is divisible by the target pod number, divide them equaly
  • If the remainder is suficient to split the last table in 2, divide the first players in pods until theres enought for the last two pods
  • If the remainder is suficient to make a pod,divide the first players in pods until theres enought for the last pod
  • If the remainder can be added to the last pod, divide the first players equaly and add the remaining players to the last pod

Solution

  • For tables of 3 or 4, start by dividing the number of players N by 3 to find T and R, where T=N/3 is the maximum number of tables needed, and R=N%3 is the number of players left over if 3 players are assigned to each table.

    Now, if you want to reduce the number of tables to the minimum, then you need to solve the equation:
    R + 3M <= T - M, where M is an integer. The logic behind that equation is that by removing one table, you increase the remainder by 3, and you want to continue doing that while the remainder is not greater than the number of tables.

    So 4M <= T-R. After finding M (just divide T-R by 4 and drop the remainder), subtract it from T to get the new table count, and add 3M to R to get the new remainder. Then you need R tables with 4 players, and T-R tables with 3 players.

    Examples:

    N = 24
    T = N/3 = 8
    R = N%3 = 0
    M = (T-R)/4 = (8-0)/4 = 2
    T = T-M = 8-2 = 6
    R = R+(3*M) = 0+(3*2) = 6
    Number of tables with 4 players is `R`, which is 6
    Number of tables with 3 players is `T-R`, which is 0
    Sanity check: 6*4 + 0*3 = 24
    
    N = 25
    T = N/3 = 8
    R = N%3 = 1
    M = (T-R)/4 = (8-1)/4 = 1
    T = T-M = 8-1 = 7
    R = R+(3*M) = 1+(3*1) = 4
    Number of tables with 4 players is `R`, which is 4
    Number of tables with 3 players is `T-R`, which is 3
    Sanity check: 4*4 + 3*3 = 25
      
    N = 27
    T = N/3 = 9
    R = N%3 = 0
    M = (T-R)/4 = (9-0)/4 = 2
    T = T-M = 9-2 = 7
    R = R+(3*M) = 0+(3*2) = 6
    Number of tables with 4 players is `R`, which is 6
    Number of tables with 3 players is `T-R`, which is 1
    Sanity check: 6*4 + 1*3 = 27