Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add matchmaking #473

Merged

Conversation

AttackingOrDefending
Copy link
Member

Adds matchmaking. The variant is chosen by random from all the supported variants. If challenge_days is enabled then the challenge will always be correspondence, even if it is a variant game (e.g. crazyhouse correspondence).

closes #472

matchmaking.py Outdated Show resolved Hide resolved
lichess.py Show resolved Hide resolved
The bot would crash if there was no matchmaking section in config.yml.
lichess-bot.py Outdated Show resolved Hide resolved
@MarkZH
Copy link
Collaborator

MarkZH commented May 8, 2022

This works pretty well. It'll be nice to have my bot play more games without me having to manually start them.

One change to cut down on unnecessary debugging output: In Lichess.api_get(), put the line response.encoding = "utf-8" above return response.text if get_raw_text else response.json() on line 58. Otherwise, when running lichess-bot with the -v argument, the following will be printed during every challenge as requests tries to guess the encoding:

2022-05-08 01:03:26,186: SHIFT_JIS Japanese prober hit error at byte 7197
2022-05-08 01:03:26,193: EUC-JP Japanese prober hit error at byte 3604
2022-05-08 01:03:26,200: GB2312 Chinese prober hit error at byte 7197
2022-05-08 01:03:26,204: EUC-KR Korean prober hit error at byte 3604
2022-05-08 01:03:26,208: CP949 Korean prober hit error at byte 3604
2022-05-08 01:03:26,215: Big5 Chinese prober hit error at byte 7196
2022-05-08 01:03:26,219: EUC-TW Taiwan prober hit error at byte 3604
2022-05-08 01:03:26,530: windows-1251 Russian confidence = 0.01
2022-05-08 01:03:26,530: KOI8-R Russian confidence = 0.01
2022-05-08 01:03:26,531: ISO-8859-5 Russian confidence = 0.0
2022-05-08 01:03:26,531: MacCyrillic Russian confidence = 0.0
2022-05-08 01:03:26,531: IBM866 Russian confidence = 0.03250665496557114
2022-05-08 01:03:26,532: IBM855 Russian confidence = 0.11458595875363828
2022-05-08 01:03:26,532: ISO-8859-7 Greek confidence = 0.0
2022-05-08 01:03:26,533: windows-1253 Greek confidence = 0.0
2022-05-08 01:03:26,533: ISO-8859-5 Bulgarian confidence = 0.0
2022-05-08 01:03:26,534: windows-1251 Bulgarian confidence = 0.0
2022-05-08 01:03:26,534: TIS-620 Thai confidence = 0.061683636349056596
2022-05-08 01:03:26,534: ISO-8859-9 Turkish confidence = 0.49579768822036685
2022-05-08 01:03:26,535: windows-1255 Hebrew confidence = 0.0
2022-05-08 01:03:26,535: windows-1255 Hebrew confidence = 0.0
2022-05-08 01:03:26,535: windows-1255 Hebrew confidence = 0.0
2022-05-08 01:03:26,535: windows-1251 Russian confidence = 0.01
2022-05-08 01:03:26,536: KOI8-R Russian confidence = 0.01
2022-05-08 01:03:26,536: ISO-8859-5 Russian confidence = 0.0
2022-05-08 01:03:26,536: MacCyrillic Russian confidence = 0.0
2022-05-08 01:03:26,536: IBM866 Russian confidence = 0.03250665496557114
2022-05-08 01:03:26,537: IBM855 Russian confidence = 0.11458595875363828
2022-05-08 01:03:26,537: ISO-8859-7 Greek confidence = 0.0
2022-05-08 01:03:26,537: windows-1253 Greek confidence = 0.0
2022-05-08 01:03:26,537: ISO-8859-5 Bulgarian confidence = 0.0
2022-05-08 01:03:26,537: windows-1251 Bulgarian confidence = 0.0
2022-05-08 01:03:26,537: TIS-620 Thai confidence = 0.061683636349056596
2022-05-08 01:03:26,538: ISO-8859-9 Turkish confidence = 0.49579768822036685
2022-05-08 01:03:26,538: windows-1255 Hebrew confidence = 0.0
2022-05-08 01:03:26,538: windows-1255 Hebrew confidence = 0.0
2022-05-08 01:03:26,538: windows-1255 Hebrew confidence = 0.0

README.md Outdated Show resolved Hide resolved
config.yml.default Outdated Show resolved Hide resolved
matchmaking.py Outdated Show resolved Hide resolved
@TheYoBots
Copy link
Contributor

It got this error? Is this normal?

Traceback (most recent call last):
File "//lichess-bot.py", line 805, in <module>
  start(li, user_profile, CONFIG, logging_level, args.logfile)
File "//lichess-bot.py", line 229, in start
  matchmaker.challenge()
File "/matchmaking.py", line 66, in challenge
  bot_username, base_time, increment, days, variant = self.choose_opponent()
File "/matchmaking.py", line 61, in choose_opponent
  online_bots = list(filter(lambda bot: bot["username"] != self.username and min_rating <= ((bot["perfs"].get(game_type) or {}).get("rating") or 0) <= max_rating, online_bots))
File "/matchmaking.py", line 61, in <lambda>
  online_bots = list(filter(lambda bot: bot["username"] != self.username and min_rating <= ((bot["perfs"].get(game_type) or {}).get("rating") or 0) <= max_rating, online_bots))
KeyError: 'perfs'

@TheYoBots
Copy link
Contributor

TheYoBots commented May 10, 2022

Verbose logs:

2022-05-10 15:15:55,808: Challenging a random bot
2022-05-10 15:15:55,810: Resetting dropped connection: lichess.org
2022-05-10 15:15:55,867: https://lichess.org:443 "GET /api/bot/online HTTP/1.1" 200 None
Traceback (most recent call last):
  File "//lichess-bot.py", line 805, in <module>
    start(li, user_profile, CONFIG, logging_level, args.logfile)
  File "//lichess-bot.py", line 229, in start
    matchmaker.challenge()
  File "/matchmaking.py", line 66, in challenge
    bot_username, base_time, increment, days, variant = self.choose_opponent()
  File "/matchmaking.py", line 61, in choose_opponent
    online_bots = list(filter(lambda bot: bot["username"] != self.username and min_rating <= ((bot["perfs"].get(game_type) or {}).get("rating") or 0) <= max_rating, online_bots))
  File "/matchmaking.py", line 61, in <lambda>
    online_bots = list(filter(lambda bot: bot["username"] != self.username and min_rating <= ((bot["perfs"].get(game_type) or {}).get("rating") or 0) <= max_rating, online_bots))
KeyError: 'perfs'
2022-05-10 15:16:01,818: Starting new HTTPS connection (1): lichess.org:443
2022-05-10 15:16:01,902: https://lichess.org:443 "GET /api/stream/event HTTP/1.1" 200 None

Might have been caused by this 9d06571 ?

lichess-bot.py Outdated Show resolved Hide resolved
@AttackingOrDefending
Copy link
Member Author

This wasn't caused by 9d06571. After filtering the bots that didn't have perfs, it turned out that the bots that didn't have perfs were because the accounts were closed.

Sometimes 'bot' didn't have 'perfs'. This was caused because the account was closed. We use bot.get("disabled") to first check if the account is closed.
@TheYoBots
Copy link
Contributor

This is the error I'm facing now.

Traceback (most recent call last):
  File "//lichess-bot.py", line 805, in <module>
    start(li, user_profile, CONFIG, logging_level, args.logfile)
  File "//lichess-bot.py", line 227, in start
    if queued_processes + busy_processes < max_games and not challenge_queue and matchmaker.should_create_challenge():
  File "/matchmaking.py", line 19, in should_create_challenge
    matchmaking_enabled = self.matchmaking_cfg.get("allow_matchmaking")

@AttackingOrDefending
Copy link
Member Author

Can you send more of the logs (both before and after the error message)?

@TheYoBots
Copy link
Contributor

Sorry, it seems I made a change that caused this and I just fixed it. I was about to comment the fix here and realise it was already fine here. Sorry for the false warning.

matchmaking.py Show resolved Hide resolved
matchmaking.py Outdated Show resolved Hide resolved
matchmaking.py Outdated Show resolved Hide resolved
matchmaking.py Outdated Show resolved Hide resolved
@TheYoBots
Copy link
Contributor

After a while of running matchmaking, I faced this error:

2022-05-15 23:23:31,440:  Challenging a random bot
2022-05-15 23:23:35,565:  Will challenge caissa-x for a standard game.
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/requests/models.py", line 910, in json
return complexjson.loads(self.text, **kwargs)
File "/usr/lib/python3.9/json/__init__.py", line 346, in loads
return _default_decoder.decode(s)
File "/usr/lib/python3.9/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.9/json/decoder.py", line 355, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "//lichess-bot.py", line 805, in <module>
start(li, user_profile, CONFIG, logging_level, args.logfile)
File "//lichess-bot.py", line 229, in start
matchmaker.challenge()
File "/matchmaking.py", line 75, in challenge
challenge_id = self.create_challenge(bot_username, base_time, increment, days, variant) if bot_username else None
File "/matchmaking.py", line 40, in create_challenge
challenge_id = self.li.challenge(username, params).get("challenge", {}).get("id")
File "/lichess.py", line 135, in challenge
return self.api_post(ENDPOINTS["challenge"].format(username), payload=params, raise_for_status=False)
File "/usr/local/lib/python3.9/dist-packages/backoff/_sync.py", line 110, in retry
ret = target(*args, **kwargs)
File "/lichess.py", line 75, in api_post
return response.json()
File "/usr/local/lib/python3.9/dist-packages/requests/models.py", line 917, in json
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: [Errno Expecting value] Too many requests. Try again later.: 0
Process Process-3:
Traceback (most recent call last):
File "/usr/lib/python3.9/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/usr/lib/python3.9/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "//lichess-bot.py", line 76, in do_correspondence_ping
control_queue.put_nowait({"type": "correspondence_ping"})
File "<string>", line 2, in put_nowait
File "/usr/lib/python3.9/multiprocessing/managers.py", line 808, in _callmethod
conn.send((self._id, methodname, args, kwds))
File "/usr/lib/python3.9/multiprocessing/connection.py", line 211, in send
self._send_bytes(_ForkingPickler.dumps(obj))
File "/usr/lib/python3.9/multiprocessing/connection.py", line 416, in _send_bytes
self._send(header + buf)
File "/usr/lib/python3.9/multiprocessing/connection.py", line 373, in _send
n = write(self._handle, buf)
BrokenPipeError: [Errno 32] Broken pipe

Always update self.last_challenge_created to avoid hitting the api rate limits.
@AttackingOrDefending
Copy link
Member Author

Did this happen only once or did it happen multiple times? I can add

try:
    code
except ConnectionError:
    retry

But I think that this can be caused by any request, not only the requests that matchmaking makes. I thought that maybe the bot hit the api rate limits and because it continued to make requests, lichess closed the connection. I removed an if statement, so now even if the bot doesn't have a possible opponent, it won't use get_online_bots continuously.

@TheYoBots
Copy link
Contributor

I think it works fine now, should just make sure not to exceed lichess' API rate limits. Maybe documentation on that somewhere could be useful.

@TheYoBots
Copy link
Contributor

I was wondering if an option similar to CHALLENGE_TIMEOUT from here could be added.

CHALLENGE_TIMEOUT : start attempting auto challenges after being idle for that many minutes ( default : 60 )

@MarkZH
Copy link
Collaborator

MarkZH commented May 22, 2022

Two things:

@TheYoBots What's the difference betweeen challenge timeout and challenge interval? I would like to limit the number of options the user has to think about.

@AttackingOrDefending In Matchmaking.should_create_challenge(), the time_has_passed check and the ten_seconds_passed check do the same thing, essentially establishing 10 seconds as the minumum time between challenges. Given the rate-limiting documentation, wouldn't a better minimum be 60 seconds?

@TheYoBots
Copy link
Contributor

@TheYoBots What's the difference betweeen challenge timeout and challenge interval? I would like to limit the number of options the user has to think about.

Challenge timeout would be the amount of time to wait after a game is played (this game was not created by the matchmaking option) to start matchmaking. But if this has been covered by challenge interval it could be skipped.

Increase time between challenges from 10 to 20 seconds.
@AttackingOrDefending
Copy link
Member Author

self.last_challenge_created is updated every time we create a challenge even if it isn't accepted. We always want to wait at least 10 seconds to not hit the api rate limits, so ten_seconds_passed is used. time_has_passed isn't always used because in the code it is time_has_passed or challenge_expired, so if challenge_expired we don't want to wait challenge_interval minutes before making another attempt at playing a game. challenge_interval is used if the previous challenge was accepted and challenge_expired is used if the previous challenge wasn't accepted. So time_has_passed and ten_seconds_passed have some differences.

I believe that waiting 60 seconds isn't useful because I doubt that lichess allows only 1 challenge per minute. I increased it to 20 seconds just to be safe.

@AttackingOrDefending
Copy link
Member Author

The bot now uses challenge_timeout. I didn't think keeping challenge_interval was useful, so it was removed.

@MarkZH
Copy link
Collaborator

MarkZH commented Jun 2, 2022

@AttackingOrDefending @TheYoBots Any more changes or comments? Is this ready to merge?

@AttackingOrDefending
Copy link
Member Author

It is ready from me.

lichess-bot.py Outdated Show resolved Hide resolved
@MarkZH
Copy link
Collaborator

MarkZH commented Jun 4, 2022

@AttackingOrDefending I've run this PR for more than 3 hours with no apparent problems. Consider this approved. Go ahead and merge this if you're ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: Implement matchmaking
4 participants