Commit adb62666 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: wire multiplayer module into Chess and Ludo UI

Chess (live mode):
- Fetches opponent profile, shows real name in opponent bar
- Emotes sync to opponent via game_state (send + receive)
- Connection status monitored (green/yellow/red dot)
- Incoming emotes shown as floating animation
- mp.cleanup() on game end

Ludo (live mode):
- Emotes sync to opponent via ludo-match.php
- Connection status monitored
- Incoming emotes received and displayed
- Polling starts if not player's turn at game start

Both games:
- Fixed emoteWrap variable reference (was using wrong 'wrap')
- Shared multiplayer.js handles all common features
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent a220f7d0
...@@ -11,6 +11,7 @@ import * as juice from '../../../core/juice.js'; ...@@ -11,6 +11,7 @@ import * as juice from '../../../core/juice.js';
import { getOpeningName } from '../logic/openings.js'; import { getOpeningName } from '../logic/openings.js';
import { getMaterialAdvantage, formatAdvantage } from '../logic/material.js'; import { getMaterialAdvantage, formatAdvantage } from '../logic/material.js';
import * as emoteSystem from '../components/emotes.js'; import * as emoteSystem from '../components/emotes.js';
import * as mp from '../../../core/multiplayer.js';
let board, clock, gameState; let board, clock, gameState;
...@@ -153,10 +154,31 @@ export function mountGame(el, params) { ...@@ -153,10 +154,31 @@ export function mountGame(el, params) {
if (mode === 'live' && matchId) { if (mode === 'live' && matchId) {
startLivePolling(el); startLivePolling(el);
if (playerColor === 'b') { if (playerColor === 'b') {
// Waiting for white's first move
gameState.isPlayerTurn = false; gameState.isPlayerTurn = false;
clock.start('w'); clock.start('w');
} }
// Fetch and render opponent profile bar
const opponentId = params.opponentId || (playerColor === 'w' ? params.blackPlayerId : params.whitePlayerId);
if (opponentId) {
mp.fetchOpponentProfile(opponentId).then(opp => {
if (opp && !opp.error) {
const oppBar = el.querySelector('.chess-bar');
if (oppBar) {
const nameEl = oppBar.querySelector('#opponent-name');
if (nameEl) nameEl.textContent = opp.display_name || opp.username || 'خصم';
}
}
});
}
mp.startDisconnectWatch(matchId, 'chess', 60000);
// Synced emotes — send to opponent via server
mp.onEmoteReceived((emote) => {
const emoteContainer = el.querySelector('#board-container');
emoteSystem.showReceived(emoteContainer, emote.key === 'gg' ? '🤝' : emote.key === 'good_move' ? '👏' : '😮');
audio.play('notification');
});
} }
// Emote system // Emote system
...@@ -164,6 +186,10 @@ export function mountGame(el, params) { ...@@ -164,6 +186,10 @@ export function mountGame(el, params) {
emoteSystem.create(emoteContainer, (emote) => { emoteSystem.create(emoteContainer, (emote) => {
audio.play('notification'); audio.play('notification');
emoteSystem.showReceived(emoteContainer, emote.emoji); emoteSystem.showReceived(emoteContainer, emote.emoji);
// Sync to opponent in live mode
if (gameState.mode === 'live' && matchId) {
mp.sendEmote(matchId, 'chess', emote.key);
}
}); });
bus.emit('game:started', { gameKey: 'chess', matchId, opponent: botId, mode }); bus.emit('game:started', { gameKey: 'chess', matchId, opponent: botId, mode });
...@@ -444,13 +470,23 @@ function startLivePolling(el) { ...@@ -444,13 +470,23 @@ function startLivePolling(el) {
const data = await net.post('game.php', { action: 'get', match_id: gameState.matchId }); const data = await net.post('game.php', { action: 'get', match_id: gameState.matchId });
if (!data || data.error) return; if (!data || data.error) return;
// Update connection status (opponent is alive if data comes back)
mp.updateConnectionStatus(true);
// Check for synced emotes from opponent
const myId = store.get('auth.userId');
const emoteData = mp.checkForEmote(data.game_state, myId);
if (emoteData) {
const emoteContainer = el.querySelector('#board-container');
emoteSystem.showReceived(emoteContainer, emoteData.key === 'gg' ? '🤝' : '👏');
audio.play('notification');
}
// Check if new move arrived // Check if new move arrived
if (data.move_count > lastKnownMoveCount) { if (data.move_count > lastKnownMoveCount) {
lastKnownMoveCount = data.move_count; lastKnownMoveCount = data.move_count;
// Apply opponent's move by loading the new FEN
const newFen = data.current_fen; const newFen = data.current_fen;
if (newFen && newFen !== engine.fen()) { if (newFen && newFen !== engine.fen()) {
// Find what move was made by comparing positions
engine.load(newFen); engine.load(newFen);
board.setPosition(newFen); board.setPosition(newFen);
audio.play('move', 'game'); audio.play('move', 'game');
...@@ -485,6 +521,7 @@ function stopLivePolling() { ...@@ -485,6 +521,7 @@ function stopLivePolling() {
function endGame(result, reason) { function endGame(result, reason) {
if (gameState.gameOver) return; if (gameState.gameOver) return;
stopLivePolling(); stopLivePolling();
mp.cleanup();
gameState.gameOver = true; gameState.gameOver = true;
clock.stop(); clock.stop();
board.interactive = false; board.interactive = false;
......
...@@ -8,6 +8,8 @@ import * as rules from '../logic/rules.js'; ...@@ -8,6 +8,8 @@ import * as rules from '../logic/rules.js';
import * as juice from '../../../core/juice.js'; import * as juice from '../../../core/juice.js';
import { getPiecePosition, getHomeBasePosition, SAFE_SQUARES, HOME_COLUMNS, SHARED_PATH } from '../logic/board-map.js'; import { getPiecePosition, getHomeBasePosition, SAFE_SQUARES, HOME_COLUMNS, SHARED_PATH } from '../logic/board-map.js';
import * as emoteSystem from '../../chess/components/emotes.js'; import * as emoteSystem from '../../chess/components/emotes.js';
import * as mp from '../../../core/multiplayer.js';
import * as net from '../../../core/net.js';
let game, validMoves, ctx, canvas, boardSize, cellSize; let game, validMoves, ctx, canvas, boardSize, cellSize;
let diceAnimating = false; let diceAnimating = false;
...@@ -82,13 +84,27 @@ export function mountGame(el, params) { ...@@ -82,13 +84,27 @@ export function mountGame(el, params) {
updatePanels(el); updatePanels(el);
el.querySelector('#roll-btn').addEventListener('click', () => handleRoll(el)); el.querySelector('#roll-btn').addEventListener('click', () => handleRoll(el));
// Emotes // Emotes + multiplayer
const emoteWrap = el.querySelector('#ludo-wrap'); const emoteWrap = el.querySelector('#ludo-wrap');
emoteSystem.create(emoteWrap, (emote) => { emoteSystem.create(emoteWrap, (emote) => {
audio.play('notification'); audio.play('notification');
emoteSystem.showReceived(wrap, emote.emoji); emoteSystem.showReceived(emoteWrap, emote.emoji);
if (game.mode === 'live' && matchId) {
mp.sendEmote(matchId, 'ludo', emote.key);
}
}); });
// Live mode setup
if (mode === 'live' && matchId) {
mp.startDisconnectWatch(matchId, 'ludo', 60000);
mp.onEmoteReceived((emote) => {
emoteSystem.showReceived(emoteWrap, emote.key === 'gg' ? '🤝' : '👏');
audio.play('notification');
});
// If not my turn at start, begin polling
if (!isMyTurn()) handleNonPlayerTurn(el);
}
bus.emit('game:started', { gameKey: 'ludo', mode }); bus.emit('game:started', { gameKey: 'ludo', mode });
} }
......
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