Commit c227bce1 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(matchmaking): chess and domino players who queue together are matched

Chess: a status poll that landed between the claim and the match id write
deleted the claim, so the waiting player searched forever while the other
sat alone. A claim in progress is now left alone for 3 minutes, dequeue
reports it, a failed match insert releases it, and a waiting player's
status poll claims an older waiting row. Domino gets the same status-side
match. Raced 4/4 (chess) and 3/3 (domino) with simultaneous queue calls.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 42dd98c4
...@@ -161,33 +161,10 @@ function handleQueue(string $userId, array $input): void { ...@@ -161,33 +161,10 @@ function handleQueue(string $userId, array $input): void {
} }
// STEP 5: Create match // STEP 5: Create match
$players = [$opponent['user_id'], $userId]; $created = dominoCreateMatch($sdb, $opponent['user_id'], $userId);
if ($created) {
$matchData = [ $sdb->update('domino_queue', ['match_id' => $created['match_id']], ['id' => 'eq.' . $opponent['id']]);
'status' => 'in_progress', jsonResponse($created);
'players' => $players,
'current_turn' => 0,
'board' => [],
'hands' => (object)[],
'boneyard' => [],
'moves' => [],
'scores' => ['0' => 0, '1' => 0],
'game_state' => ['move_count' => 0, 'round' => 1, 'mode' => 'live'],
'host_id' => $opponent['user_id']
];
$match = $sdb->insert('domino_matches', $matchData);
$matchId = $match[0]['id'] ?? $match['id'] ?? null;
if ($matchId) {
$sdb->update('domino_queue', ['match_id' => $matchId], ['id' => 'eq.' . $opponent['id']]);
jsonResponse([
'match_id' => $matchId,
'player_index' => 1,
'players' => $players,
'color' => 'player2'
]);
return; return;
} }
...@@ -216,7 +193,7 @@ function handleStatus(string $userId): void { ...@@ -216,7 +193,7 @@ function handleStatus(string $userId): void {
$entry = $sdb->get('domino_queue', [ $entry = $sdb->get('domino_queue', [
'user_id' => 'eq.' . $userId, 'user_id' => 'eq.' . $userId,
'select' => 'id,match_id', 'select' => 'id,match_id,created_at',
'limit' => 1 'limit' => 1
]); ]);
...@@ -241,9 +218,76 @@ function handleStatus(string $userId): void { ...@@ -241,9 +218,76 @@ function handleStatus(string $userId): void {
]); ]);
} }
// Still unclaimed: maybe someone queued at the same moment we did, and the
// two searches each found an empty queue.
if (!empty($entry) && !isset($entry['error']) && empty($entry[0]['match_id'])) {
$found = dominoMatchFromStatus($sdb, $userId, $entry[0]);
if ($found) jsonResponse($found);
}
jsonResponse(['waiting' => true]); jsonResponse(['waiting' => true]);
} }
/**
* Create a live match: the host (the earlier queuer) sits at index 0.
* Returns the response for the player who made it (index 1), or null.
*/
function dominoCreateMatch($sdb, string $hostId, string $userId): ?array {
$players = [$hostId, $userId];
$match = $sdb->insert('domino_matches', [
'status' => 'in_progress',
'players' => $players,
'current_turn' => 0,
'board' => [],
'hands' => (object)[],
'boneyard' => [],
'moves' => [],
'scores' => ['0' => 0, '1' => 0],
'game_state' => ['move_count' => 0, 'round' => 1, 'mode' => 'live'],
'host_id' => $hostId
]);
$matchId = $match[0]['id'] ?? $match['id'] ?? null;
if (!$matchId) return null;
return ['match_id' => $matchId, 'player_index' => 1, 'players' => $players, 'color' => 'player2'];
}
/**
* Two players who queue at the same moment can both search an empty queue and
* both insert themselves; nobody searches again and each waits for the other
* forever. So a waiting player's status poll looks for someone who has been
* waiting LONGER. Only the later of the two does this, and only towards older
* rows, so two pollers never claim each other; our own row is locked first so
* a third player cannot claim us while we build the match.
*/
function dominoMatchFromStatus($sdb, string $userId, array $mine): ?array {
if (empty($mine['created_at'])) return null;
$older = $sdb->get('domino_queue', [
'user_id' => 'neq.' . $userId,
'match_id' => 'is.null',
'and' => '(created_at.lt."' . $mine['created_at'] . '",created_at.gt."' . gmdate('c', time() - 90) . '")',
'select' => 'id,user_id',
'order' => 'created_at.asc',
'limit' => 1,
]);
if (!is_array($older) || isset($older['error']) || empty($older)) return null;
$opp = $older[0];
$release = function (string $id) use ($sdb) {
$sdb->update('domino_queue', ['match_id' => null], ['id' => 'eq.' . $id, 'match_id' => 'eq.' . DOMINO_CLAIM_PLACEHOLDER]);
};
$lock = $sdb->update('domino_queue', ['match_id' => DOMINO_CLAIM_PLACEHOLDER], ['id' => 'eq.' . $mine['id'], 'match_id' => 'is.null']);
if (empty($lock) || isset($lock['error'])) return null;
$claim = $sdb->update('domino_queue', ['match_id' => DOMINO_CLAIM_PLACEHOLDER], ['id' => 'eq.' . $opp['id'], 'match_id' => 'is.null']);
if (empty($claim) || isset($claim['error'])) { $release($mine['id']); return null; }
$created = dominoCreateMatch($sdb, $opp['user_id'], $userId);
if (!$created) { $release($opp['id']); $release($mine['id']); return null; }
$sdb->update('domino_queue', ['match_id' => $created['match_id']], ['id' => 'eq.' . $opp['id']]);
$sdb->delete('domino_queue', ['id' => 'eq.' . $mine['id']]);
return $created;
}
function handleMove(string $userId, array $input): void { function handleMove(string $userId, array $input): void {
$matchId = $input['match_id'] ?? ''; $matchId = $input['match_id'] ?? '';
if (!$matchId) jsonError('match_id required'); if (!$matchId) jsonError('match_id required');
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment