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) {
audio.play('sfx_emote', 'ui');
});
// Fetch opponent profiles and update player panels
// Fetch opponent profiles and update player panels (name + avatar)
if (params.players) {
params.players.forEach((pId, i) => {
if (i === myPlayerIndex || pId.startsWith('bot')) return;
......@@ -240,7 +240,13 @@ export function mountGame(el, params) {
const panel = el.querySelector(`#pp-${i}`);
if (panel) {
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) {
}
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();
diceAnimating = true;
updatePanels(el);
const dice = await animateDice(el, myPlayerIndex);
const dice = await animateDice(el, activePlayer);
game.diceValue = dice;
game.rolled = true;
diceAnimating = false;
......@@ -311,7 +318,7 @@ async function handleRoll(el) {
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) {
setTimeout(() => {
game.rolled = false;
......@@ -331,7 +338,7 @@ async function handleRoll(el) {
// Auto-pick best move on timeout
if (selectionListener) { canvas.removeEventListener('click', selectionListener); selectionListener = null; }
highlightedPieces = [];
const best = rules.getBotMove(game, myPlayerIndex, dice);
const best = rules.getBotMove(game, activePlayer, dice);
if (best) doMove(el, best);
});
}
......@@ -452,7 +459,8 @@ async function botLoop(el) {
} else if (move.type === 'finish') {
audio.play('notification');
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) {
......@@ -759,7 +767,7 @@ async function animateMove(el, move) {
const rect = canvas.getBoundingClientRect();
const sx = rect.left + (pos.x / boardSize) * rect.width;
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);
return;
......@@ -796,7 +804,7 @@ async function animateMove(el, move) {
const rect = canvas.getBoundingClientRect();
const sx = rect.left + (pos.x / boardSize) * rect.width;
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();
}
......@@ -1133,7 +1141,7 @@ function updatePanels(el) {
const diceBox = el.querySelector('#dice-box');
const timerBar = el.querySelector('#turn-timer-bar');
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 isBot = currentName.startsWith('Bot');
......@@ -1154,9 +1162,12 @@ function updatePanels(el) {
turnStatus.style.display = 'none';
} else if (myTurn) {
turnStatus.style.display = 'block';
turnStatus.style.background = 'linear-gradient(135deg,#E4AC38,#F59E0B)';
turnStatus.style.color = '#000';
turnStatus.textContent = 'دورك!';
const slot = game.players[game.currentPlayer]?.boardSlot ?? game.currentPlayer;
turnStatus.style.background = game.mode === 'local-multi'
? 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';
} else {
turnStatus.style.display = 'block';
......@@ -1401,7 +1412,7 @@ function endGame(el) {
audio.play('win', 'reward');
juice.screenFlash('rgba(76,175,80,0.12)', 600);
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 {
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