lichess-bot-devs / lichess-bot

A bridge between Lichess bots and chess engines
GNU Affero General Public License v3.0
764 stars 448 forks source link

Matchmaking based on Variant #787

Closed TheYoBots closed 1 year ago

TheYoBots commented 1 year ago

Is your feature request related to a problem? Please describe. It isn't completely related to a problem. But I though the matchmaking feature would be more useful if for different variants I could set different time controls. For example, I want to play various time controls in standard chess so that I don't have a provisional rating in different "standard game types" (like bullet, blitz, rapid, classical). But for variants I don't want to challenge other opponents in these same time controls, I just need a single time control per variant

Describe the solution you'd like So I'd suggest something similar to what is done for adding opening books. where for each variant I can choose different time controls to challenge opponents. These options could remain the same for all variants:

  allow_matchmaking: false    # Set it to 'true' to challenge other bots.
  challenge_timeout: 30       # Create a challenge after being idle for 'challenge_timeout' minutes. The minimum is 1 minute.
# opponent_min_rating: 600    # Opponents rating should be above this value (600 is the minimum rating in lichess).
# opponent_max_rating: 4000   # Opponents rating should be below this value (4000 is the maximum rating in lichess).
  opponent_rating_difference: 300 # The maximum difference in rating between the bot's rating and opponent's rating.
  opponent_allow_tos_violation: false # Set to 'false' to prevent challenging bots that violated Lichess Terms of Service.
  challenge_mode: "random"     # Set it to the mode in which challenges are sent. Possible options are 'casual', 'rated' and 'random'.
  challenge_filter: none    # If a bot declines a challenge, do not issue a similar challenge to that bot. Possible options are 'none', 'coarse', and 'fine'.

and then for respective variants, the initial time and increment could be changed:

  standard:
    challenge_initial_time:    # Initial time in seconds of the challenge (to be chosen at random).
      - 60
      - 180
    challenge_increment:       # Increment in seconds of the challenge (to be chosen at random).
      - 1
      - 2
#   challenge_days:           # Days for correspondence challenge (to be chosen at random).
#     - 1
#     - 2
# chess960:
#   challenge_initial_time:    # Initial time in seconds of the challenge (to be chosen at random).
#     - 60
#     - 180
#   challenge_increment:       # Increment in seconds of the challenge (to be chosen at random).
#     - 1
#     - 2
#   challenge_days:           # Days for correspondence challenge (to be chosen at random).
#     - 1
#     - 2
# etc.
# Use the same pattern for 'giveaway' (antichess), 'crazyhouse', 'horde', 'kingofthehill', 'racingkings' and '3check' as well.

Additional context So I suppose this is how I would want the matchmaking config to be:

matchmaking:
  allow_matchmaking: false         # Set it to 'true' to challenge other bots.
  challenge_timeout: 30            # Create a challenge after being idle for 'challenge_timeout' minutes. The minimum is 1 minute.
# opponent_min_rating: 600         # Opponents rating should be above this value (600 is the minimum rating in lichess).
# opponent_max_rating: 4000        # Opponents rating should be below this value (4000 is the maximum rating in lichess).
  opponent_rating_difference: 300  # The maximum difference in rating between the bot's rating and opponent's rating.
  opponent_allow_tos_violation: false # Set to 'true' to allow challenging bots that violated the Lichess Terms of Service.
  challenge_mode: "random"         # Set it to the mode in which challenges are sent. Possible options are 'casual', 'rated' and 'random'.
  challenge_filter: none           # If a bot declines a challenge, do not issue a similar challenge to that bot. Possible options are 'none', 'coarse', and 'fine'.
  standard:
    challenge_initial_time:        # Initial time in seconds of the challenge (to be chosen at random).
      - 60
      - 180
    challenge_increment:           # Increment in seconds of the challenge (to be chosen at random).
      - 1
      - 2
#   challenge_days:                # Days for correspondence challenge (to be chosen at random).
#     - 1
#     - 2
# chess960:
#   challenge_initial_time:
#     - 60
#     - 180
#   challenge_increment:
#     - 1
#     - 2
#   challenge_days:
#     - 1
#     - 2
# etc.
# Use the same pattern for 'giveaway' (antichess), 'crazyhouse', 'horde', 'kingofthehill', 'racingkings' and '3check' as well.
MarkZH commented 1 year ago

I'm trying to understand the goal of this feature. To take your example, if your bot plays a wide variety of time controls in standard chess, what's the problem with playing a wide variety of time controls for variants? For example, in the case of the opening books, these are separated by variant because using the wrong book could result in illegal moves being chosen.

TheYoBots commented 1 year ago

So, the only reason I want my bot to play 25+1 (for example) is so that it doesn't have a provisional classical rating. But when it comes to variants, I'd rather not play long games when it doesn't matter what time control you are playing a variant, so maybe I just want to play an 8+1 in variants.

EmptikBest commented 1 year ago

+1 would like this too

mheinzel commented 1 year ago

Just mentioning that this is supported by the feature described in https://github.com/lichess-bot-devs/lichess-bot/pull/493#issuecomment-1175459973, which I have implemented in my fork.

This gives maximal flexibility, at the cost of making things somewhat complex. Note however that adding this feature now would result in even more complexity as when it was originally proposed, since in the meantime another way of randomly choosing between multiple time controls has been added.

So that ship has probably sailed, but maybe it can still serve as a reference (e.g. for the implementation).


A note on the design suggested by @TheYoBots: The change should be backwards compatible, which adds some implementation complexity. Specifically, how would a config in the existing format be handled? If there is no standard, chess960 etc. key in the matchmaking section, it internally treats it as having an entry for each supported variant, each with the same time control settings?

MarkZH commented 1 year ago

How's this for an idea? Instead of trying to stuff all of this complexity into a single configuration file, the user creates multiple configuration files and lichess-bot can rotate between them on some interval. The meta-config file would look like this:

rotation_interval:
    games: 100
    time: 180 # minutes
configurations:
    - config.standard.yml
    - config.variants.yml
    - config.standard.no_books.yml
    - config.casual.bullet.yml
    - ...

The rotation_interval section specifies how often lichess-bot restarts with the next configuration file, looping through the list in the order given. Here, it's after 100 games or 3 hours, whichever comes first. A missing value defaults to infinity. Then, the configurations section contains the list of configuration files. The user would start lichess-bot with python3 lichess-bot.py --multiconfig meta.config.py.

With this setup, anything in a user's configuration file--not just matchmaking time controls--can be specialized for whatever purpose. The configurations files might even belong to different bot accounts.


Note: Some care would have to be taken with correspondence games to make sure that they continue with the same configuration file.

TheYoBots commented 1 year ago

I don't think restarting often to change config using a meta-config file is the best option. Instead, simply reading the next config in the list instead of the previous one would be better. But what if a game is in play? Will the next config in the list be called immediately or wait for the game to finish? Would it be possible to make the line dynamic and adhere to the meta-config without using the --config arg? https://github.com/lichess-bot-devs/lichess-bot/blob/bf41362351b97e01df35d51a0427b5b68315d1dd/lichess-bot.py#L966 Why do I feel this only increases the complexity?

AttackingOrDefending commented 1 year ago

I prefer the idea by mheinzel. We can add random time controls inside each random time control. Example:

matchmaking:
  allow_matchmaking: true
  challenge_timeout: 30
  challenge_variant: "standard"
  challenge_mode: "rated"
  challenge_initial_time: 
    - 60
    - 120
  challenge_increment: 
    - 1
    - 2
  opponent_min_rating: 1000
  opponent_max_rating: 2000
  challenges:
    # Keep all settings from the top level
    - challenge_name: "standard"

    # Override time control
    - challenge_name: "Rapid"
      challenge_initial_time: 
        - 5
        - 10
      challenge_increment:
        - 0
        - 1
        - 2
        - 3

    - challenge_name: "Classical"
      challenge_initial_time:
        - 1800
        - 3600
      challenge_increment: 30

    - challenge_name: "Chess960"
      challenge_variant: "chess960"
      challenge_initial_time: 60
      challenge_increment: 1

    - challenge_name: "Atomic"
      challenge_variant: "atomic"

This uses both the current setup and the idea by mheinzel.

MarkZH commented 1 year ago

I think something that could be more workable looks like this:

matchmaking:
  allow_matchmaking: true
  challenge_timeout: 30
  challenge_variant: "standard"
  challenge_mode: "rated"
  challenge_initial_time: 
    - 60
    - 120
  challenge_increment: 
    - 1
    - 2
  opponent_min_rating: 1000
  opponent_max_rating: 2000
  challenge_overrides:
    Rapid:
      challenge_initial_time: 
        - 5
        - 10
      challenge_increment:
        - 0
        - 1
        - 2
        - 3

    Classical:
      challenge_initial_time:
        - 1800
        - 3600
      challenge_increment: 30

    Chess960:
      challenge_variant: "chess960"
      challenge_initial_time: 60
      challenge_increment: 1

    Atomic:
      challenge_variant: "atomic"

The names of the subsections under challenge_overrides: don't matter. They are just keys for lookup. Then, in Matchmaking.choose_opponent():

def choose_opponent(self):
    challenge_override = random.choice(list(self.matchmaking_cfg.challenge_overrides.keys()) + [None])
    this_matchup_cfg = self.override_cfg(challenge_override)

The method override_cfg() creates a new matchmaking configuration with the configuration from the randomly chosen key by making the appropriate replacements in self.matchmaking_cfg. If None is chosen, the default configuration is used unchanged.

TheYoBots commented 1 year ago

So would matchmaking_overrides ignore these options?

  challenge_variant: "standard"
  challenge_mode: "rated"
  challenge_initial_time: 
    - 60
    - 120
  challenge_increment: 
    - 1
    - 2

If it does then why does that section even have to be there. And doesn't this simply just lead to the same thing I initially proposed?

matchmaking:
  allow_matchmaking: false         # Set it to 'true' to challenge other bots.
  challenge_timeout: 30            # Create a challenge after being idle for 'challenge_timeout' minutes. The minimum is 1 minute.
# opponent_min_rating: 600         # Opponents rating should be above this value (600 is the minimum rating in lichess).
# opponent_max_rating: 4000        # Opponents rating should be below this value (4000 is the maximum rating in lichess).
  opponent_rating_difference: 300  # The maximum difference in rating between the bot's rating and opponent's rating.
  opponent_allow_tos_violation: false # Set to 'true' to allow challenging bots that violated the Lichess Terms of Service.
  challenge_mode: "random"         # Set it to the mode in which challenges are sent. Possible options are 'casual', 'rated' and 'random'.
  challenge_filter: none           # If a bot declines a challenge, do not issue a similar challenge to that bot. Possible options are 'none', 'coarse', and 'fine'.
  standard:
    challenge_initial_time:        # Initial time in seconds of the challenge (to be chosen at random).
      - 60
      - 180
    challenge_increment:           # Increment in seconds of the challenge (to be chosen at random).
      - 1
      - 2
#   challenge_days:                # Days for correspondence challenge (to be chosen at random).
#     - 1
#     - 2
# chess960:
#   challenge_initial_time:
#     - 60
#     - 180
#   challenge_increment:
#     - 1
#     - 2
#   challenge_days:
#     - 1
#     - 2
# etc.
# Use the same pattern for 'giveaway' (antichess), 'crazyhouse', 'horde', 'kingofthehill', 'racingkings' and '3check' as well.
AttackingOrDefending commented 1 year ago

It will override them only if they exist inside the section. These are the defaults, so they don't have to be copy and pasted to every section.

matchmaking:
  allow_matchmaking: true
  challenge_timeout: 30
  challenge_variant: "standard"
  challenge_mode: "rated"
  challenge_initial_time: 
    - 60
    - 120
  challenge_increment: 
    - 1
    - 2
  opponent_min_rating: 1000
  opponent_max_rating: 2000
  challenge_overrides:
    Atomic:
      challenge_variant: "atomic"
      challenge_mode: "casual"

    Chess960:
      challenge_variant: "chess960"
      challenge_initial_time: 60
      challenge_increment: 1

With the above config, Atomic will use the default time controls and min/max ratings (as specified in the main section of the matchmaking config), while Chess960 will use a different time control, but the default game mode and min/max ratings. In your config this will corrspond to:

  standard:
    challenge_initial_time:        # Initial time in seconds of the challenge (to be chosen at random).
      - 60
      - 180
    challenge_increment:           # Increment in seconds of the challenge (to be chosen at random).
      - 1
      - 2
    challenge_mode: "rated"
    opponent_min_rating: 1000
    opponent_max_rating: 2000

  atomic:
    challenge_mode: "caual"
    challenge_initial_time:        # Initial time in seconds of the challenge (to be chosen at random).
      - 60
      - 180
    challenge_increment:           # Increment in seconds of the challenge (to be chosen at random).
      - 1
      - 2
    opponent_min_rating: 1000
    opponent_max_rating: 2000

  chess960:
    challenge_initial_time:        # Initial time in seconds of the challenge (to be chosen at random).
      - 60
    challenge_increment:           # Increment in seconds of the challenge (to be chosen at random).
      - 1
    challenge_mode: "rated"
    opponent_min_rating: 1000
    opponent_max_rating: 2000

The first config will lead to less repetition. Another advantage to the first config is that names don't mean anything. One can add two or move sections for the same time control/variant. For example:

challenge_overrides:
    Bullet2:
      challenge_mode: "casual"

    Bullet3:
      opponent_min_rating: 2000

This way the bot has a 2/3 chance of playing rated.