Commit 25a80cf0 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: multiplayer dominoes — fix invite/accept flow + add domino to challenge menus

- friends.php: create domino invites in domino_matches table (not chess matches)
- friends.php: check-invites scans domino_matches + ludo_matches tables
- friends.php: accept/decline route to correct game table by game_key
- domino-match.php: merge hands on move (preserve other player's hand data)
- game.js: host deals and syncs both hands; guest waits for deal via polling
- game.js: new rounds in live mode dealt by host only
- lobby.js: pass playerIndex from accept response
- chat.js: add domino option to in-chat challenge dialog
- friends.js: pass game_key on accept/decline calls
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 3e5614d1
...@@ -158,8 +158,15 @@ function handleMove(string $userId, array $input): void { ...@@ -158,8 +158,15 @@ function handleMove(string $userId, array $input): void {
$update['board'] = is_string($board) ? $board : json_encode($board); $update['board'] = is_string($board) ? $board : json_encode($board);
} }
if (isset($input['hands'])) { if (isset($input['hands'])) {
$hands = $input['hands']; $newHands = is_string($input['hands']) ? json_decode($input['hands'], true) : $input['hands'];
$update['hands'] = is_string($hands) ? $hands : json_encode($hands); // Merge with existing hands to preserve other player's data
$existingMatch = $sdb->get('domino_matches', ['id' => 'eq.' . $matchId, 'select' => 'hands', 'limit' => 1]);
$existingHands = [];
if (!empty($existingMatch) && !isset($existingMatch['error']) && !empty($existingMatch[0]['hands'])) {
$existingHands = is_string($existingMatch[0]['hands']) ? json_decode($existingMatch[0]['hands'], true) : $existingMatch[0]['hands'];
}
$mergedHands = array_merge($existingHands ?: [], $newHands ?: []);
$update['hands'] = json_encode($mergedHands);
} }
if (isset($input['boneyard'])) { if (isset($input['boneyard'])) {
$boneyard = $input['boneyard']; $boneyard = $input['boneyard'];
......
...@@ -198,13 +198,58 @@ if ($method === 'POST') { ...@@ -198,13 +198,58 @@ if ($method === 'POST') {
$timeControl = $input['time_control'] ?? 'rapid_10_0'; $timeControl = $input['time_control'] ?? 'rapid_10_0';
if (!$targetId) jsonError('target_id required'); if (!$targetId) jsonError('target_id required');
// Create a private lobby match if ($gameKey === 'domino') {
$players = [$userId, $targetId];
$match = $sdb->insert('domino_matches', [
'status' => 'waiting',
'players' => json_encode($players),
'current_turn' => 0,
'board' => '[]',
'hands' => '{}',
'boneyard' => '[]',
'moves' => '[]',
'scores' => json_encode(['0' => 0, '1' => 0]),
'game_state' => json_encode(['mode' => 'live', 'move_count' => 0, 'round' => 1, 'invite_from' => $userId, 'invite_to' => $targetId, 'invite_t' => time()]),
'host_id' => $userId,
'created_by' => $userId
]);
if (isset($match['error'])) jsonError($match['error']);
$matchId = $match[0]['id'] ?? $match['id'] ?? null;
jsonResponse([
'match_id' => $matchId,
'color' => 'player1',
'game_key' => $gameKey,
'time_control' => $timeControl
]);
} elseif ($gameKey === 'ludo') {
$players = [$userId, $targetId];
$match = $sdb->insert('ludo_matches', [
'status' => 'waiting',
'players' => json_encode($players),
'current_turn' => 0,
'board_state' => '{}',
'game_state' => json_encode(['mode' => 'live', 'invite_from' => $userId, 'invite_to' => $targetId, 'invite_t' => time()]),
'host_id' => $userId
]);
if (isset($match['error'])) jsonError($match['error']);
$matchId = $match[0]['id'] ?? $match['id'] ?? null;
jsonResponse([
'match_id' => $matchId,
'color' => 'player1',
'game_key' => $gameKey,
'time_control' => $timeControl
]);
} else {
// Chess
$initialTime = 600000; $initialTime = 600000;
if (strpos($timeControl, 'bullet') !== false) $initialTime = 60000; if (strpos($timeControl, 'bullet') !== false) $initialTime = 60000;
elseif (strpos($timeControl, 'blitz') !== false) $initialTime = 300000; elseif (strpos($timeControl, 'blitz') !== false) $initialTime = 300000;
elseif (strpos($timeControl, 'classical') !== false) $initialTime = 3600000; elseif (strpos($timeControl, 'classical') !== false) $initialTime = 3600000;
// Randomly assign colors
$isWhite = rand(0, 1) === 0; $isWhite = rand(0, 1) === 0;
$whiteId = $isWhite ? $userId : $targetId; $whiteId = $isWhite ? $userId : $targetId;
$blackId = $isWhite ? $targetId : $userId; $blackId = $isWhite ? $targetId : $userId;
...@@ -237,9 +282,13 @@ if ($method === 'POST') { ...@@ -237,9 +282,13 @@ if ($method === 'POST') {
'time_control' => $timeControl 'time_control' => $timeControl
]); ]);
} }
}
if ($action === 'check-invites') { if ($action === 'check-invites') {
// Check if there are pending invites for this user $result = [];
$now = time();
// Check chess matches table
$invites = $sdb->get('matches', [ $invites = $sdb->get('matches', [
'or' => "(white_player_id.eq.{$userId},black_player_id.eq.{$userId})", 'or' => "(white_player_id.eq.{$userId},black_player_id.eq.{$userId})",
'status' => 'eq.waiting', 'status' => 'eq.waiting',
...@@ -249,37 +298,129 @@ if ($method === 'POST') { ...@@ -249,37 +298,129 @@ if ($method === 'POST') {
'limit' => 5 'limit' => 5
]); ]);
if (!is_array($invites) || isset($invites['error'])) { if (is_array($invites) && !isset($invites['error'])) {
jsonResponse(['invites' => []]); foreach ($invites as $inv) {
$gs = is_array($inv['game_state']) ? $inv['game_state'] : json_decode($inv['game_state'] ?? '{}', true);
$inviteTo = $gs['invite_to'] ?? null;
$inviteT = $gs['invite_t'] ?? 0;
if ($inviteTo !== $userId) continue;
if ($now - $inviteT > 120) continue;
$result[] = [
'match_id' => $inv['id'],
'game_key' => $inv['game_key'] ?? 'chess',
'time_control' => $inv['time_control'] ?? 'rapid_10_0',
'from_id' => $gs['invite_from'] ?? null,
'created_at' => $inv['created_at']
];
}
} }
// Filter: only show invites TO this user (not ones I sent) and recent (< 2 min) // Check domino_matches table
$result = []; $dominoInvites = $sdb->get('domino_matches', [
$now = time(); 'status' => 'eq.waiting',
foreach ($invites as $inv) { 'select' => 'id,players,game_state,created_at',
'order' => 'created_at.desc',
'limit' => 5
]);
if (is_array($dominoInvites) && !isset($dominoInvites['error'])) {
foreach ($dominoInvites as $inv) {
$players = is_array($inv['players']) ? $inv['players'] : json_decode($inv['players'] ?? '[]', true);
if (!in_array($userId, $players)) continue;
$gs = is_array($inv['game_state']) ? $inv['game_state'] : json_decode($inv['game_state'] ?? '{}', true);
$inviteTo = $gs['invite_to'] ?? null;
$inviteT = $gs['invite_t'] ?? 0;
if ($inviteTo !== $userId) continue;
if ($now - $inviteT > 120) continue;
$result[] = [
'match_id' => $inv['id'],
'game_key' => 'domino',
'time_control' => 'standard',
'from_id' => $gs['invite_from'] ?? null,
'created_at' => $inv['created_at']
];
}
}
// Check ludo_matches table
$ludoInvites = $sdb->get('ludo_matches', [
'status' => 'eq.waiting',
'select' => 'id,players,game_state,created_at',
'order' => 'created_at.desc',
'limit' => 5
]);
if (is_array($ludoInvites) && !isset($ludoInvites['error'])) {
foreach ($ludoInvites as $inv) {
$players = is_array($inv['players']) ? $inv['players'] : json_decode($inv['players'] ?? '[]', true);
if (!in_array($userId, $players)) continue;
$gs = is_array($inv['game_state']) ? $inv['game_state'] : json_decode($inv['game_state'] ?? '{}', true); $gs = is_array($inv['game_state']) ? $inv['game_state'] : json_decode($inv['game_state'] ?? '{}', true);
$inviteTo = $gs['invite_to'] ?? null; $inviteTo = $gs['invite_to'] ?? null;
$inviteT = $gs['invite_t'] ?? 0; $inviteT = $gs['invite_t'] ?? 0;
if ($inviteTo !== $userId) continue; if ($inviteTo !== $userId) continue;
if ($now - $inviteT > 120) continue; // Expired after 2 min if ($now - $inviteT > 120) continue;
$inviteFrom = $gs['invite_from'] ?? null;
$result[] = [ $result[] = [
'match_id' => $inv['id'], 'match_id' => $inv['id'],
'game_key' => $inv['game_key'], 'game_key' => 'ludo',
'time_control' => $inv['time_control'], 'time_control' => 'standard',
'from_id' => $inviteFrom, 'from_id' => $gs['invite_from'] ?? null,
'created_at' => $inv['created_at'] 'created_at' => $inv['created_at']
]; ];
} }
}
jsonResponse(['invites' => $result]); jsonResponse(['invites' => $result]);
} }
if ($action === 'accept-invite') { if ($action === 'accept-invite') {
$matchId = $input['match_id'] ?? ''; $matchId = $input['match_id'] ?? '';
$gameKey = $input['game_key'] ?? '';
if (!$matchId) jsonError('match_id required'); if (!$matchId) jsonError('match_id required');
// Verify the match is for this user and is still waiting if ($gameKey === 'domino') {
$matches = $sdb->get('domino_matches', [
'id' => 'eq.' . $matchId,
'status' => 'eq.waiting',
'select' => 'id,players',
'limit' => 1
]);
if (!is_array($matches) || isset($matches['error']) || empty($matches)) {
jsonError('Invite not found or expired');
}
$sdb->update('domino_matches', [
'status' => 'in_progress'
], ['id' => 'eq.' . $matchId]);
$players = is_array($matches[0]['players']) ? $matches[0]['players'] : json_decode($matches[0]['players'] ?? '[]', true);
$playerIndex = array_search($userId, $players);
jsonResponse([
'match_id' => $matchId,
'color' => 'player' . ($playerIndex + 1),
'player_index' => $playerIndex !== false ? $playerIndex : 1,
'started' => true
]);
} elseif ($gameKey === 'ludo') {
$matches = $sdb->get('ludo_matches', [
'id' => 'eq.' . $matchId,
'status' => 'eq.waiting',
'select' => 'id,players',
'limit' => 1
]);
if (!is_array($matches) || isset($matches['error']) || empty($matches)) {
jsonError('Invite not found or expired');
}
$sdb->update('ludo_matches', [
'status' => 'in_progress'
], ['id' => 'eq.' . $matchId]);
$players = is_array($matches[0]['players']) ? $matches[0]['players'] : json_decode($matches[0]['players'] ?? '[]', true);
$playerIndex = array_search($userId, $players);
jsonResponse([
'match_id' => $matchId,
'color' => 'player' . ($playerIndex + 1),
'player_index' => $playerIndex !== false ? $playerIndex : 1,
'started' => true
]);
} else {
// Chess (default)
$matches = $sdb->get('matches', [ $matches = $sdb->get('matches', [
'id' => 'eq.' . $matchId, 'id' => 'eq.' . $matchId,
'status' => 'eq.waiting', 'status' => 'eq.waiting',
...@@ -292,7 +433,6 @@ if ($method === 'POST') { ...@@ -292,7 +433,6 @@ if ($method === 'POST') {
jsonError('Invite not found or expired'); jsonError('Invite not found or expired');
} }
// Start the match
$sdb->update('matches', [ $sdb->update('matches', [
'status' => 'in_progress', 'status' => 'in_progress',
'started_at' => gmdate('c') 'started_at' => gmdate('c')
...@@ -307,15 +447,29 @@ if ($method === 'POST') { ...@@ -307,15 +447,29 @@ if ($method === 'POST') {
'started' => true 'started' => true
]); ]);
} }
}
if ($action === 'decline-invite') { if ($action === 'decline-invite') {
$matchId = $input['match_id'] ?? ''; $matchId = $input['match_id'] ?? '';
$gameKey = $input['game_key'] ?? '';
if (!$matchId) jsonError('match_id required'); if (!$matchId) jsonError('match_id required');
if ($gameKey === 'domino') {
$sdb->update('domino_matches', [
'status' => 'completed',
'result' => 'declined'
], ['id' => 'eq.' . $matchId, 'status' => 'eq.waiting']);
} elseif ($gameKey === 'ludo') {
$sdb->update('ludo_matches', [
'status' => 'completed',
'result' => 'declined'
], ['id' => 'eq.' . $matchId, 'status' => 'eq.waiting']);
} else {
$sdb->update('matches', [ $sdb->update('matches', [
'status' => 'completed', 'status' => 'completed',
'result' => 'declined' 'result' => 'declined'
], ['id' => 'eq.' . $matchId, 'status' => 'eq.waiting']); ], ['id' => 'eq.' . $matchId, 'status' => 'eq.waiting']);
}
jsonResponse(['success' => true]); jsonResponse(['success' => true]);
} }
......
...@@ -23,7 +23,10 @@ export function mountGame(el, params) { ...@@ -23,7 +23,10 @@ export function mountGame(el, params) {
scene.enterGameMode(); scene.enterGameMode();
state = createInitialState(mode, matchId, playerIndex, botLevel, players); state = createInitialState(mode, matchId, playerIndex, botLevel, players);
if (mode !== 'live') {
dealNewRound(); dealNewRound();
}
el.innerHTML = buildLayout(mode); el.innerHTML = buildLayout(mode);
...@@ -179,7 +182,37 @@ function setupLiveMultiplayer(el, matchId) { ...@@ -179,7 +182,37 @@ function setupLiveMultiplayer(el, matchId) {
fetchOpponentProfile(el); fetchOpponentProfile(el);
mp.startDisconnectWatch?.(matchId, 'domino', 60000); mp.startDisconnectWatch?.(matchId, 'domino', 60000);
if (state.myPlayerIndex === 0) {
dealAndSyncToServer(el, matchId);
} else {
fetchInitialState(el, matchId); fetchInitialState(el, matchId);
}
}
async function dealAndSyncToServer(el, matchId) {
dealNewRound();
board.setChain(state.chain);
updateUI(el);
refreshHand();
try {
await net.post('domino-match.php', {
action: 'move',
match_id: matchId,
board: state.chain,
boneyard: state.boneyard,
hands: JSON.stringify({ '0': state.hands[0], '1': state.hands[1] }),
current_turn: state.currentPlayer,
game_state: JSON.stringify({
move_count: 0,
round: 1,
left_end: state.leftEnd,
right_end: state.rightEnd,
scores: { '0': 0, '1': 0 },
dealt: true,
})
});
} catch (e) {}
} }
async function fetchOpponentProfile(el) { async function fetchOpponentProfile(el) {
...@@ -203,9 +236,16 @@ async function fetchOpponentProfile(el) { ...@@ -203,9 +236,16 @@ async function fetchOpponentProfile(el) {
} catch (e) {} } catch (e) {}
} }
async function fetchInitialState(el, matchId) { async function fetchInitialState(el, matchId, retries = 0) {
try { try {
const data = await net.post('domino-match.php', { action: 'get', match_id: matchId }); const data = await net.post('domino-match.php', { action: 'get', match_id: matchId });
// If hands not dealt yet (host hasn't synced), retry
if ((!data?.my_hand || data.my_hand.length === 0) && retries < 10) {
setTimeout(() => fetchInitialState(el, matchId, retries + 1), 1000);
return;
}
if (data && data.board) { if (data && data.board) {
const board_data = typeof data.board === 'string' ? JSON.parse(data.board) : data.board; const board_data = typeof data.board === 'string' ? JSON.parse(data.board) : data.board;
if (board_data.length > 0) { if (board_data.length > 0) {
...@@ -698,20 +738,27 @@ function showRoundOverlay(el, winnerIdx, points) { ...@@ -698,20 +738,27 @@ function showRoundOverlay(el, winnerIdx, points) {
function startNewRound(el) { function startNewRound(el) {
state.roundNumber++; state.roundNumber++;
dealNewRound();
state.currentPlayer = state.matchScores[0] <= state.matchScores[1] ? 0 : 1; state.currentPlayer = state.matchScores[0] <= state.matchScores[1] ? 0 : 1;
if (state.mode === 'live') { if (state.mode === 'live') {
if (state.myPlayerIndex === 0) {
dealNewRound();
syncNewRoundToServer(); syncNewRoundToServer();
board.setChain(state.chain);
updateUI(el);
refreshHand();
} }
// Guest will receive the new round data via poll
} else {
dealNewRound();
board.setChain(state.chain); board.setChain(state.chain);
updateUI(el); updateUI(el);
refreshHand(); refreshHand();
if (state.mode === 'bot' && state.currentPlayer !== state.myPlayerIndex) { if (state.currentPlayer !== state.myPlayerIndex) {
scheduleBotTurn(el); scheduleBotTurn(el);
} }
}
} }
async function syncNewRoundToServer() { async function syncNewRoundToServer() {
...@@ -722,7 +769,7 @@ async function syncNewRoundToServer() { ...@@ -722,7 +769,7 @@ async function syncNewRoundToServer() {
match_id: state.matchId, match_id: state.matchId,
board: state.chain, board: state.chain,
boneyard: state.boneyard, boneyard: state.boneyard,
hands: JSON.stringify({ [state.myPlayerIndex]: state.hands[state.myPlayerIndex] }), hands: JSON.stringify({ '0': state.hands[0], '1': state.hands[1] }),
current_turn: state.currentPlayer, current_turn: state.currentPlayer,
game_state: JSON.stringify({ game_state: JSON.stringify({
move_count: state.moveCount, move_count: state.moveCount,
...@@ -730,6 +777,7 @@ async function syncNewRoundToServer() { ...@@ -730,6 +777,7 @@ async function syncNewRoundToServer() {
round: state.roundNumber, round: state.roundNumber,
left_end: state.leftEnd, left_end: state.leftEnd,
right_end: state.rightEnd, right_end: state.rightEnd,
dealt: true,
}) })
}); });
} catch (e) {} } catch (e) {}
......
...@@ -7,12 +7,14 @@ import { emoji } from '../../../core/theme.js'; ...@@ -7,12 +7,14 @@ import { emoji } from '../../../core/theme.js';
let pollTimer = null; let pollTimer = null;
let matchId = null; let matchId = null;
let currentGameKey = 'chess';
let lobbyState = 'waiting'; // waiting | ready | starting let lobbyState = 'waiting'; // waiting | ready | starting
export function mountLobby(el, params = {}) { export function mountLobby(el, params = {}) {
matchId = params.matchId; matchId = params.matchId;
currentGameKey = params.gameKey || 'chess';
const color = params.color; const color = params.color;
const gameKey = params.gameKey || 'chess'; const gameKey = currentGameKey;
const timeControl = params.timeControl || 'rapid_10_0'; const timeControl = params.timeControl || 'rapid_10_0';
const friendProfile = params.friendProfile || {}; const friendProfile = params.friendProfile || {};
const isHost = params.isHost ?? true; const isHost = params.isHost ?? true;
...@@ -187,7 +189,7 @@ function startGame(el, params) { ...@@ -187,7 +189,7 @@ function startGame(el, params) {
scene.replace('domino-game', { scene.replace('domino-game', {
mode: 'live', mode: 'live',
matchId: params.matchId, matchId: params.matchId,
playerIndex: params.isHost ? 0 : 1, playerIndex: params.playerIndex ?? (params.isHost ? 0 : 1),
isFriendly: true isFriendly: true
}); });
} }
...@@ -198,7 +200,7 @@ async function cancelAndLeave(el) { ...@@ -198,7 +200,7 @@ async function cancelAndLeave(el) {
if (matchId && lobbyState === 'waiting') { if (matchId && lobbyState === 'waiting') {
try { try {
await net.post('friends.php', { action: 'decline-invite', match_id: matchId }); await net.post('friends.php', { action: 'decline-invite', match_id: matchId, game_key: currentGameKey });
} catch (e) {} } catch (e) {}
} }
......
...@@ -265,6 +265,7 @@ function showInviteFromChat(el) { ...@@ -265,6 +265,7 @@ function showInviteFromChat(el) {
<div style="display:flex;gap:8px;justify-content:center;margin-bottom:12px;"> <div style="display:flex;gap:8px;justify-content:center;margin-bottom:12px;">
<button class="cig active" data-game="chess" style="flex:1;padding:10px;border-radius:10px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">♟ شطرنج</button> <button class="cig active" data-game="chess" style="flex:1;padding:10px;border-radius:10px;background:#2563EB;border:2px solid #2563EB;color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">♟ شطرنج</button>
<button class="cig" data-game="domino" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🁣 دومينو</button>
<button class="cig" data-game="ludo" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🎲 لودو</button> <button class="cig" data-game="ludo" style="flex:1;padding:10px;border-radius:10px;background:#1a1a2e;border:2px solid rgba(255,255,255,0.08);color:#94a3b8;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;">🎲 لودو</button>
</div> </div>
...@@ -290,7 +291,7 @@ function showInviteFromChat(el) { ...@@ -290,7 +291,7 @@ function showInviteFromChat(el) {
dialog.querySelectorAll('.cig').forEach(b => { b.style.background = '#1a1a2e'; b.style.borderColor = 'rgba(255,255,255,0.08)'; b.style.color = '#94a3b8'; b.classList.remove('active'); }); dialog.querySelectorAll('.cig').forEach(b => { b.style.background = '#1a1a2e'; b.style.borderColor = 'rgba(255,255,255,0.08)'; b.style.color = '#94a3b8'; b.classList.remove('active'); });
btn.style.background = '#2563EB'; btn.style.borderColor = '#2563EB'; btn.style.color = '#fff'; btn.classList.add('active'); btn.style.background = '#2563EB'; btn.style.borderColor = '#2563EB'; btn.style.color = '#fff'; btn.classList.add('active');
selectedGame = btn.dataset.game; selectedGame = btn.dataset.game;
dialog.querySelector('#cig-time').style.display = selectedGame === 'ludo' ? 'none' : 'flex'; dialog.querySelector('#cig-time').style.display = (selectedGame === 'ludo' || selectedGame === 'domino') ? 'none' : 'flex';
}); });
}); });
...@@ -321,7 +322,7 @@ function showInviteFromChat(el) { ...@@ -321,7 +322,7 @@ function showInviteFromChat(el) {
await net.post('chat.php', { await net.post('chat.php', {
action: 'send', action: 'send',
friend_id: friendId, friend_id: friendId,
content: `أرسل تحدي ${selectedGame === 'ludo' ? 'لودو' : 'شطرنج'}`, content: `أرسل تحدي ${selectedGame === 'ludo' ? 'لودو' : selectedGame === 'domino' ? 'دومينو' : 'شطرنج'}`,
message_type: 'invite', message_type: 'invite',
metadata: { game_key: selectedGame, time_control: selectedTc, match_id: res.match_id } metadata: { game_key: selectedGame, time_control: selectedTc, match_id: res.match_id }
}); });
......
...@@ -120,18 +120,18 @@ async function checkInvites(el) { ...@@ -120,18 +120,18 @@ async function checkInvites(el) {
btn.disabled = true; btn.disabled = true;
btn.textContent = '...'; btn.textContent = '...';
try { try {
const res = await net.post('friends.php', { action: 'accept-invite', match_id: btn.dataset.acceptInvite }); const inv = invites.find(i => i.match_id === btn.dataset.acceptInvite);
const res = await net.post('friends.php', { action: 'accept-invite', match_id: btn.dataset.acceptInvite, game_key: inv?.game_key || 'chess' });
if (res.error) { btn.textContent = 'خطأ'; return; } if (res.error) { btn.textContent = 'خطأ'; return; }
audio.play('reward'); audio.play('reward');
juice.hapticSuccess(); juice.hapticSuccess();
// Navigate to lobby then game
const inv = invites.find(i => i.match_id === btn.dataset.acceptInvite);
scene.push('game-lobby', { scene.push('game-lobby', {
matchId: res.match_id, matchId: res.match_id,
color: res.color, color: res.color,
gameKey: inv?.game_key || 'chess', gameKey: inv?.game_key || 'chess',
timeControl: inv?.time_control || 'rapid_10_0', timeControl: inv?.time_control || 'rapid_10_0',
friendId: inv?.from_id, friendId: inv?.from_id,
playerIndex: res.player_index,
isHost: false isHost: false
}); });
} catch (e) { } catch (e) {
...@@ -143,7 +143,8 @@ async function checkInvites(el) { ...@@ -143,7 +143,8 @@ async function checkInvites(el) {
banner.querySelectorAll('[data-decline-invite]').forEach(btn => { banner.querySelectorAll('[data-decline-invite]').forEach(btn => {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
try { try {
await net.post('friends.php', { action: 'decline-invite', match_id: btn.dataset.declineInvite }); const inv = invites.find(i => i.match_id === btn.dataset.declineInvite);
await net.post('friends.php', { action: 'decline-invite', match_id: btn.dataset.declineInvite, game_key: inv?.game_key || 'chess' });
btn.closest('div[style*="display:flex"]').remove(); btn.closest('div[style*="display:flex"]').remove();
if (banner.children.length === 0) banner.style.display = 'none'; if (banner.children.length === 0) banner.style.display = 'none';
} catch (e) {} } catch (e) {}
......
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