Commit 41f869f8 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: Ludo local-multi rolls correct player + display opponent profile in live

Critical bug: handleRoll used myPlayerIndex (always 0) instead of
game.currentPlayer, so in local-multi the dice always moved Red's
pieces regardless of whose turn it was.

Fixes:
- handleRoll uses game.currentPlayer for dice animation, getValidMoves,
  and getBotMove (no longer hardcoded to player 0)
- updatePanels checks game.currentPlayer.finished (not myPlayerIndex)
- Turn status badge shows player name + color in local-multi mode
- Live multiplayer: fetch and display opponent avatar (not just name)
- Update PLAYER_NAMES array when opponent profile loads
- All juice.burst/fireworkBurst use boardSlot for correct colors
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent f9dcacf7
...@@ -231,7 +231,7 @@ export function mountGame(el, params) { ...@@ -231,7 +231,7 @@ export function mountGame(el, params) {
audio.play('sfx_emote', 'ui'); audio.play('sfx_emote', 'ui');
}); });
// Fetch opponent profiles and update player panels // Fetch opponent profiles and update player panels (name + avatar)
if (params.players) { if (params.players) {
params.players.forEach((pId, i) => { params.players.forEach((pId, i) => {
if (i === myPlayerIndex || pId.startsWith('bot')) return; if (i === myPlayerIndex || pId.startsWith('bot')) return;
...@@ -240,7 +240,13 @@ export function mountGame(el, params) { ...@@ -240,7 +240,13 @@ export function mountGame(el, params) {
const panel = el.querySelector(`#pp-${i}`); const panel = el.querySelector(`#pp-${i}`);
if (panel) { if (panel) {
const nameEl = panel.querySelector('.pp-name'); const nameEl = panel.querySelector('.pp-name');
if (nameEl) nameEl.textContent = profile.display_name || profile.username || 'لاعب'; const displayName = profile.display_name || profile.username || 'لاعب';
if (nameEl) nameEl.textContent = displayName;
PLAYER_NAMES[i] = displayName;
const avatarEl = panel.querySelector('.pp-avatar');
if (avatarEl && profile.avatar_url) {
avatarEl.innerHTML = `<img src="${profile.avatar_url}" style="width:100%;height:100%;object-fit:cover;border-radius:50%;">`;
}
} }
} }
}); });
...@@ -294,12 +300,13 @@ function findPlayerByUserId(userId) { ...@@ -294,12 +300,13 @@ function findPlayerByUserId(userId) {
} }
async function handleRoll(el) { async function handleRoll(el) {
if (diceAnimating || game.rolled || game.gameOver || !isMyTurn() || game.players[myPlayerIndex].finished) return; const activePlayer = game.currentPlayer;
if (diceAnimating || game.rolled || game.gameOver || !isMyTurn() || game.players[activePlayer].finished) return;
clearTurnTimer(); clearTurnTimer();
diceAnimating = true; diceAnimating = true;
updatePanels(el); updatePanels(el);
const dice = await animateDice(el, myPlayerIndex); const dice = await animateDice(el, activePlayer);
game.diceValue = dice; game.diceValue = dice;
game.rolled = true; game.rolled = true;
diceAnimating = false; diceAnimating = false;
...@@ -311,7 +318,7 @@ async function handleRoll(el) { ...@@ -311,7 +318,7 @@ async function handleRoll(el) {
juice.starBurst(rect.left + 31, rect.top + 31, 8); juice.starBurst(rect.left + 31, rect.top + 31, 8);
} }
validMoves = rules.getValidMoves(game, myPlayerIndex, dice); validMoves = rules.getValidMoves(game, activePlayer, dice);
if (validMoves.length === 0) { if (validMoves.length === 0) {
setTimeout(() => { setTimeout(() => {
game.rolled = false; game.rolled = false;
...@@ -331,7 +338,7 @@ async function handleRoll(el) { ...@@ -331,7 +338,7 @@ async function handleRoll(el) {
// Auto-pick best move on timeout // Auto-pick best move on timeout
if (selectionListener) { canvas.removeEventListener('click', selectionListener); selectionListener = null; } if (selectionListener) { canvas.removeEventListener('click', selectionListener); selectionListener = null; }
highlightedPieces = []; highlightedPieces = [];
const best = rules.getBotMove(game, myPlayerIndex, dice); const best = rules.getBotMove(game, activePlayer, dice);
if (best) doMove(el, best); if (best) doMove(el, best);
}); });
} }
...@@ -452,7 +459,8 @@ async function botLoop(el) { ...@@ -452,7 +459,8 @@ async function botLoop(el) {
} else if (move.type === 'finish') { } else if (move.type === 'finish') {
audio.play('notification'); audio.play('notification');
const boardRect = canvas.getBoundingClientRect(); const boardRect = canvas.getBoundingClientRect();
fireworkBurst(boardRect.left + boardRect.width / 2, boardRect.top + boardRect.height / 2, COLORS[game.currentPlayer]); const finishSlot = game.players[game.currentPlayer]?.boardSlot ?? game.currentPlayer;
fireworkBurst(boardRect.left + boardRect.width / 2, boardRect.top + boardRect.height / 2, COLORS[finishSlot]);
} }
if (move.type === 'capture' && Math.random() > 0.5) { if (move.type === 'capture' && Math.random() > 0.5) {
...@@ -759,7 +767,7 @@ async function animateMove(el, move) { ...@@ -759,7 +767,7 @@ async function animateMove(el, move) {
const rect = canvas.getBoundingClientRect(); const rect = canvas.getBoundingClientRect();
const sx = rect.left + (pos.x / boardSize) * rect.width; const sx = rect.left + (pos.x / boardSize) * rect.width;
const sy = rect.top + (pos.y / boardSize) * rect.height; const sy = rect.top + (pos.y / boardSize) * rect.height;
juice.burst?.(sx, sy, { count: 6, colors: [COLORS[pIdx], '#fff'], size: 4, spread: 30, duration: 400 }); juice.burst?.(sx, sy, { count: 6, colors: [COLORS[enterSlot], '#fff'], size: 4, spread: 30, duration: 400 });
} }
afterMove(el, move); afterMove(el, move);
return; return;
...@@ -796,7 +804,7 @@ async function animateMove(el, move) { ...@@ -796,7 +804,7 @@ async function animateMove(el, move) {
const rect = canvas.getBoundingClientRect(); const rect = canvas.getBoundingClientRect();
const sx = rect.left + (pos.x / boardSize) * rect.width; const sx = rect.left + (pos.x / boardSize) * rect.width;
const sy = rect.top + (pos.y / boardSize) * rect.height; const sy = rect.top + (pos.y / boardSize) * rect.height;
juice.burst?.(sx, sy, { count: 5, colors: [COLORS[pIdx], '#fff'], size: 4, spread: 25, duration: 350 }); juice.burst?.(sx, sy, { count: 5, colors: [COLORS[landSlot], '#fff'], size: 4, spread: 25, duration: 350 });
} }
juice.hapticLight(); juice.hapticLight();
} }
...@@ -1133,7 +1141,7 @@ function updatePanels(el) { ...@@ -1133,7 +1141,7 @@ function updatePanels(el) {
const diceBox = el.querySelector('#dice-box'); const diceBox = el.querySelector('#dice-box');
const timerBar = el.querySelector('#turn-timer-bar'); const timerBar = el.querySelector('#turn-timer-bar');
const turnStatus = el.querySelector('#turn-status'); const turnStatus = el.querySelector('#turn-status');
const myTurn = isMyTurn() && !game.players[myPlayerIndex].finished && !game.gameOver; const myTurn = isMyTurn() && !game.players[game.currentPlayer].finished && !game.gameOver;
const currentName = PLAYER_NAMES[game.currentPlayer] || ''; const currentName = PLAYER_NAMES[game.currentPlayer] || '';
const isBot = currentName.startsWith('Bot'); const isBot = currentName.startsWith('Bot');
...@@ -1154,9 +1162,12 @@ function updatePanels(el) { ...@@ -1154,9 +1162,12 @@ function updatePanels(el) {
turnStatus.style.display = 'none'; turnStatus.style.display = 'none';
} else if (myTurn) { } else if (myTurn) {
turnStatus.style.display = 'block'; turnStatus.style.display = 'block';
turnStatus.style.background = 'linear-gradient(135deg,#E4AC38,#F59E0B)'; const slot = game.players[game.currentPlayer]?.boardSlot ?? game.currentPlayer;
turnStatus.style.color = '#000'; turnStatus.style.background = game.mode === 'local-multi'
turnStatus.textContent = 'دورك!'; ? COLORS[slot]
: 'linear-gradient(135deg,#E4AC38,#F59E0B)';
turnStatus.style.color = game.mode === 'local-multi' && slot === 2 ? '#000' : '#000';
turnStatus.textContent = game.mode === 'local-multi' ? `دور ${currentName}` : 'دورك!';
turnStatus.style.animation = 'fadeIn 0.3s ease-out'; turnStatus.style.animation = 'fadeIn 0.3s ease-out';
} else { } else {
turnStatus.style.display = 'block'; turnStatus.style.display = 'block';
...@@ -1401,7 +1412,7 @@ function endGame(el) { ...@@ -1401,7 +1412,7 @@ function endGame(el) {
audio.play('win', 'reward'); audio.play('win', 'reward');
juice.screenFlash('rgba(76,175,80,0.12)', 600); juice.screenFlash('rgba(76,175,80,0.12)', 600);
if (place === 1) { if (place === 1) {
fireworkBurst(window.innerWidth / 2, window.innerHeight / 3, COLORS[myPlayerIndex]); fireworkBurst(window.innerWidth / 2, window.innerHeight / 3, COLORS[game.players[myPlayerIndex]?.boardSlot ?? myPlayerIndex]);
} }
} else { } else {
audio.play('lose', 'game'); audio.play('lose', 'game');
......
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