Skip to content

Commit

Permalink
Fix auto pairing rating range checking
Browse files Browse the repository at this point in the history
  • Loading branch information
gbtami committed Dec 24, 2024
1 parent 82fb505 commit c2ee837
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 69 deletions.
67 changes: 65 additions & 2 deletions server/auto_pair.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,64 @@
from itertools import product

from const import BYOS
from misc import time_control_str
from newid import new_id
from seek import Seek
from utils import join_seek
from websocket_utils import ws_send_json


def add_to_auto_pairings(app_state, user, data):
"""Add auto pairing to app_state and while doing this
tries to find a compatible other auto pairing or seek"""

auto_variant_tc = None
matching_user = None
matching_seek = None

rrmin = data["rrmin"]
rrmax = data["rrmax"]
rrmin = rrmin if (rrmin != -1000) else -10000
rrmax = rrmax if (rrmax != 1000) else 10000
app_state.auto_pairing_users[user] = (rrmin, rrmax)

for variant_tc in product(data["variants"], data["tcs"]):
variant_tc = (
variant_tc[0][0],
variant_tc[0][1],
variant_tc[1][0],
variant_tc[1][1],
variant_tc[1][2],
)
variant, chess960, base, inc, byoyomi_period = variant_tc
# We don't want to create non byo variant with byo TC combinations
if (byoyomi_period > 0 and variant not in BYOS) or variant.startswith("bughouse"):
continue

if variant_tc not in app_state.auto_pairings:
app_state.auto_pairings[variant_tc] = set()
app_state.auto_pairings[variant_tc].add(user)

if (matching_user is None) and (matching_seek is None):
# Try to find the same combo in auto_pairings
matching_user = find_matching_user(app_state, user, variant_tc)
auto_variant_tc = variant_tc

if (matching_user is None) and (matching_seek is None):
# Maybe there is a matching normal seek
matching_seek = find_matching_seek(app_state, user, variant_tc)
auto_variant_tc = variant_tc

user.ready_for_auto_pairing = True

return auto_variant_tc, matching_user, matching_seek


async def auto_pair(app_state, user, auto_variant_tc, other_user=None, matching_seek=None):
"""If matching_seek is not None accept it, else create a new one and accpt it by other_user"""
if matching_seek is None:
variant, chess960, base, inc, byoyomi_period = auto_variant_tc

variant, chess960, base, inc, byoyomi_period = auto_variant_tc
if matching_seek is None:
seek_id = await new_id(None if app_state.db is None else app_state.db.seek)
seek = Seek(
seek_id,
Expand Down Expand Up @@ -40,11 +90,23 @@ async def auto_pair(app_state, user, auto_variant_tc, other_user=None, matching_
for other_user_ws in other_user.lobby_sockets:
await ws_send_json(other_user_ws, response)

tc = time_control_str(base, inc, byoyomi_period)
tail960 = "960" if chess960 else ""
msg = "**AUTO PAIR** %s - %s **%s%s** %s" % (
user.username,
other_user.username,
variant,
tail960,
tc,
)
await app_state.discord.send_to_discord("accept_seek", msg)

return True


def find_matching_user(app_state, user, variant_tc):
"""Return first compatible user from app_state.auto_pairing_users if there is any, else None"""

variant, chess960, _, _, _ = variant_tc
return next(
(
Expand All @@ -65,6 +127,7 @@ def find_matching_user(app_state, user, variant_tc):

def find_matching_seek(app_state, user, variant_tc):
"""Return first compatible seek from app_state.seeks if there is any, else None"""

variant, chess960, base, inc, byoyomi_period = variant_tc
return next(
(
Expand Down
9 changes: 5 additions & 4 deletions server/pychess_global_app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,12 @@ async def init_from_db(self):
await load_tournament(self, doc["_id"])
self.tournaments_loaded.set()

already_scheduled = await get_scheduled_tournaments(self)
new_tournaments_data = new_scheduled_tournaments(already_scheduled)
await create_scheduled_tournaments(self, new_tournaments_data)
if not isinstance(self.db_client, AsyncMongoMockClient):
already_scheduled = await get_scheduled_tournaments(self)
new_tournaments_data = new_scheduled_tournaments(already_scheduled)
await create_scheduled_tournaments(self, new_tournaments_data)

asyncio.create_task(generate_shield(self), name="generate-shield")
asyncio.create_task(generate_shield(self), name="generate-shield")

if "highscore" not in db_collections:
await generate_highscore(self)
Expand Down
47 changes: 26 additions & 21 deletions server/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,40 +373,45 @@ def remove_ws_for_game(self, game_id, ws) -> bool:
return False

def auto_compatible_with_other_user(self, other_user, variant, chess960):
"""Users are compatible when their auto pairing rating ranges are overlapped
and the users are not blocked by each other"""
"""Users are compatible when their auto pairing rating ranges are ok
and the users are not blocked by any direction"""

rating = self.get_rating_value(variant, chess960)
rr = self.app_state.auto_pairing_users[self]
a = (rating + rr[0], rating + rr[1])
self_rating = self.get_rating_value(variant, chess960)
self_rrmin, self_rrmax = self.app_state.auto_pairing_users[self]

rating = other_user.get_rating_value(variant, chess960)
rr = self.app_state.auto_pairing_users[other_user]
b = (rating + rr[0], rating + rr[1])
other_rating = other_user.get_rating_value(variant, chess960)
other_rrmin, other_rrmax = self.app_state.auto_pairing_users[other_user]

return (other_user.username not in self.blocked) and (
self.username not in other_user.blocked and max(a[0], b[0]) <= min(a[1], b[1])
return (
(other_user.username not in self.blocked)
and (self.username not in other_user.blocked)
and self_rating >= other_rating + other_rrmin
and self_rating <= other_rating + other_rrmax
and other_rating >= self_rating + self_rrmin
and other_rating <= self_rating + self_rrmax
)

def auto_compatible_with_seek(self, seek):
"""Seek is auto pairing compatible when the rating ranges are overlapped
and the users are not blocked by each other"""
"""Seek is auto pairing compatible when the rating ranges are ok
and the users are not blocked by any direction"""

rating = self.get_rating_value(seek.variant, seek.chess960)
rr = self.app_state.auto_pairing_users[self]
a = (rating + rr[0], rating + rr[1])
self_rating = self.get_rating_value(seek.variant, seek.chess960)
seek_user = self.app_state.users[seek.creator.username]

other_user = seek.creator
rating = other_user.get_rating_value(seek.variant, seek.chess960)
b = (rating + seek.rrmin, rating + seek.rrmax)
auto_rrmin, auto_rrmax = self.app_state.auto_pairing_users[self]

return (other_user.username not in self.blocked) and (
self.username not in other_user.blocked and max(a[0], b[0]) <= min(a[1], b[1])
return (
(seek_user.username not in self.blocked)
and (self.username not in seek_user.blocked)
and self_rating >= seek.rating + seek.rrmin
and self_rating <= seek.rating + seek.rrmax
and seek.rating >= self_rating + auto_rrmin
and seek.rating <= self_rating + auto_rrmax
)

def compatible_with_seek(self, seek):
"""Seek is compatible when my rating is inside the seek rating range
and the users are not blocked by each other"""
and the users are not blocked by any direction"""

self_rating = self.get_rating_value(seek.variant, seek.chess960)
seek_user = self.app_state.users[seek.creator.username]
Expand Down
43 changes: 3 additions & 40 deletions server/wsl.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations
import asyncio
import logging
from itertools import product

import aiohttp_session
from aiohttp import web
Expand All @@ -18,11 +17,11 @@
)
from auto_pair import (
auto_pair,
find_matching_seek,
add_to_auto_pairings,
find_matching_user,
)
from chat import chat_response
from const import ANON_PREFIX, BYOS, STARTED
from const import ANON_PREFIX, STARTED
from misc import server_state
from newid import new_id
from const import TYPE_CHECKING
Expand Down Expand Up @@ -372,42 +371,7 @@ async def handle_create_auto_pairing(app_state, ws, user, data):
if no:
return

auto_variant_tc = None
matching_user = None
matching_seek = None

rrmin = data["rrmin"]
rrmax = data["rrmax"]
rrmin = rrmin if (rrmin != -1000) else -10000
rrmax = rrmax if (rrmax != 1000) else 10000
app_state.auto_pairing_users[user] = (rrmin, rrmax)

for variant_tc in product(data["variants"], data["tcs"]):
variant_tc = (
variant_tc[0][0],
variant_tc[0][1],
variant_tc[1][0],
variant_tc[1][1],
variant_tc[1][2],
)
variant, chess960, base, inc, byoyomi_period = variant_tc
# We don't want to create non byo variant with byo TC combinations
if (byoyomi_period > 0 and variant not in BYOS) or variant.startswith("bughouse"):
continue

if variant_tc not in app_state.auto_pairings:
app_state.auto_pairings[variant_tc] = set()
app_state.auto_pairings[variant_tc].add(user)

if (matching_user is None) and (matching_seek is None):
# Try to find the same combo in auto_pairings
matching_user = find_matching_user(app_state, user, variant_tc)
auto_variant_tc = variant_tc

if (matching_user is None) and (matching_seek is None):
# Maybe there is a matching normal seek
matching_seek = find_matching_seek(app_state, user, variant_tc)
auto_variant_tc = variant_tc
auto_variant_tc, matching_user, matching_seek = add_to_auto_pairings(app_state, user, data)

auto_paired = False
if (matching_user is not None) or (matching_seek is not None):
Expand All @@ -417,7 +381,6 @@ async def handle_create_auto_pairing(app_state, ws, user, data):
)

if not auto_paired:
user.ready_for_auto_pairing = True
for user_ws in user.lobby_sockets:
await ws_send_json(user_ws, {"type": "auto_pairing_on"})

Expand Down
8 changes: 6 additions & 2 deletions tests/test_alice.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,9 @@ def test_castling(self):
board.legal_moves()
board.push(move)

FEN_OOO = "1|q|k|r3r/1pp|bpp|b1/|P1|n|pP1|p1/2PP4/6n1/2|N1|B|N1p/1|p|Q2PPP/R3K|B1R w KQ - 1 14"
FEN_OOO = (
"1|q|k|r3r/1pp|bpp|b1/|P1|n|pP1|p1/2PP4/6n1/2|N1|B|N1p/1|p|Q2PPP/R3K|B1R w KQ - 1 14"
)
self.assertEqual(board.fen, FEN_OOO)

board.pop()
Expand All @@ -207,7 +209,9 @@ def test_castling(self):
board.legal_moves()
board.push(move)

FEN_OO = "r|q3|r|k1/1pp|bpp|b1/|P1|n|pP1|p1/2PP4/6n1/2|N1|B|N1p/1|p|Q2PPP/R3K|B1R w KQ - 1 14"
FEN_OO = (
"r|q3|r|k1/1pp|bpp|b1/|P1|n|pP1|p1/2PP4/6n1/2|N1|B|N1p/1|p|Q2PPP/R3K|B1R w KQ - 1 14"
)
self.assertEqual(board.fen, FEN_OO)

board.pop()
Expand Down
Loading

0 comments on commit c2ee837

Please sign in to comment.