Commit 8922b607 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(ludo): bigger pawns with pawn-shape, centered home bases, emotes, board polish

Pawns:
- Size increased from 0.32 to 0.42 radius (30% bigger)
- Pawn shape: round head on elliptical base (not just circle)
- White border, highlight spot, drop shadow
- Much more visible and satisfying

Home bases:
- Positions recentered within home zones (1.8/4.2 instead of 2/4)
- Inner white area with rounded corners
- Colored ring border inside home zone (like real Ludo boards)

Board:
- Start position squares colored per player
- Home zones have rounded inner area with colored ring

Emotes:
- 💬 emote button added to Ludo game (same system as chess)
- 8 preset emotes: GG, Good Move, Think, Hurry, Wow, Laugh, Angry, Hello
- Floating animation when sent
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent a01bee1c
...@@ -36,11 +36,12 @@ export const HOME_ENTRY = [0, 13, 26, 39]; // The square just before home column ...@@ -36,11 +36,12 @@ export const HOME_ENTRY = [0, 13, 26, 39]; // The square just before home column
export const SAFE_SQUARES = [0, 8, 13, 21, 26, 34, 39, 47]; export const SAFE_SQUARES = [0, 8, 13, 21, 26, 34, 39, 47];
// Home base positions (where pieces sit before entering the board) // Home base positions (where pieces sit before entering the board)
// Centered within the 6x6 home zone with proper spacing
export const HOME_BASES = [ export const HOME_BASES = [
[[2,2],[4,2],[2,4],[4,4]], // Red (top-left) [[1.8,1.8],[4.2,1.8],[1.8,4.2],[4.2,4.2]], // Red (top-left)
[[10,2],[12,2],[10,4],[12,4]], // Blue (top-right) [[9.8,1.8],[12.2,1.8],[9.8,4.2],[12.2,4.2]], // Blue (top-right)
[[10,10],[12,10],[10,12],[12,12]], // Green (bottom-right) [[9.8,9.8],[12.2,9.8],[9.8,12.2],[12.2,12.2]], // Green (bottom-right)
[[2,10],[4,10],[2,12],[4,12]], // Yellow (bottom-left) [[1.8,9.8],[4.2,9.8],[1.8,12.2],[4.2,12.2]], // Yellow (bottom-left)
]; ];
// Convert a board grid position [col,row] to pixel coordinates // Convert a board grid position [col,row] to pixel coordinates
......
...@@ -7,6 +7,7 @@ import { createCanvas, clear } from '../../../core/canvas.js'; ...@@ -7,6 +7,7 @@ import { createCanvas, clear } from '../../../core/canvas.js';
import * as rules from '../logic/rules.js'; 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';
let game, validMoves, ctx, canvas, boardSize, cellSize; let game, validMoves, ctx, canvas, boardSize, cellSize;
let diceAnimating = false; let diceAnimating = false;
...@@ -58,6 +59,14 @@ export function mountGame(el, params) { ...@@ -58,6 +59,14 @@ export function mountGame(el, params) {
drawBoard(); drawBoard();
updatePanels(el); updatePanels(el);
el.querySelector('#roll-btn').addEventListener('click', () => handleRoll(el)); el.querySelector('#roll-btn').addEventListener('click', () => handleRoll(el));
// Emotes
const wrap = el.querySelector('#ludo-wrap');
emoteSystem.create(wrap, (emote) => {
audio.play('notification');
emoteSystem.showReceived(wrap, emote.emoji);
});
bus.emit('game:started', { gameKey: 'ludo', mode }); bus.emit('game:started', { gameKey: 'ludo', mode });
} }
...@@ -141,13 +150,23 @@ function drawBoard() { ...@@ -141,13 +150,23 @@ function drawBoard() {
ctx.fillStyle = '#FAFAFA'; ctx.fillStyle = '#FAFAFA';
ctx.fillRect(0, 0, boardSize, boardSize); ctx.fillRect(0, 0, boardSize, boardSize);
// Home zones // Home zones — with inner white area and colored border
[[0,0,0],[1,9,0],[2,9,9],[3,0,9]].forEach(([p,c,r]) => { [[0,0,0],[1,9,0],[2,9,9],[3,0,9]].forEach(([p,c,r]) => {
ctx.fillStyle = COLORS[p]; ctx.fillStyle = COLORS[p];
ctx.fillRect(c*cs, r*cs, 6*cs, 6*cs); ctx.fillRect(c*cs, r*cs, 6*cs, 6*cs);
// Inner white rounded area
ctx.fillStyle = '#FAFAFA'; ctx.fillStyle = '#FAFAFA';
const ins = cs*0.7; const ins = cs*0.9;
ctx.fillRect(c*cs+ins, r*cs+ins, 6*cs-ins*2, 6*cs-ins*2); const w = 6*cs-ins*2;
ctx.beginPath();
ctx.roundRect(c*cs+ins, r*cs+ins, w, w, cs*0.4);
ctx.fill();
// Inner colored border ring
ctx.strokeStyle = COLORS[p];
ctx.lineWidth = 2;
ctx.beginPath();
ctx.roundRect(c*cs+ins+4, r*cs+ins+4, w-8, w-8, cs*0.3);
ctx.stroke();
}); });
// Cross paths (white) // Cross paths (white)
...@@ -181,7 +200,11 @@ function drawBoard() { ...@@ -181,7 +200,11 @@ function drawBoard() {
const cx = 7.5*cs, cy = 7.5*cs; const cx = 7.5*cs, cy = 7.5*cs;
for (let i = 0; i < 4; i++) { ctx.fillStyle = COLORS[i]; ctx.beginPath(); const a = i*Math.PI/2 - Math.PI/4; ctx.moveTo(cx,cy); ctx.lineTo(cx+Math.cos(a-0.7)*cs*2, cy+Math.sin(a-0.7)*cs*2); ctx.lineTo(cx+Math.cos(a+0.7)*cs*2, cy+Math.sin(a+0.7)*cs*2); ctx.closePath(); ctx.fill(); } for (let i = 0; i < 4; i++) { ctx.fillStyle = COLORS[i]; ctx.beginPath(); const a = i*Math.PI/2 - Math.PI/4; ctx.moveTo(cx,cy); ctx.lineTo(cx+Math.cos(a-0.7)*cs*2, cy+Math.sin(a-0.7)*cs*2); ctx.lineTo(cx+Math.cos(a+0.7)*cs*2, cy+Math.sin(a+0.7)*cs*2); ctx.closePath(); ctx.fill(); }
// Pieces // Start position colored squares
const startColors = [[6,1,'#FFCDD2'],[8,13,'#C8E6C9'],[1,8,'#FFF9C4'],[13,6,'#BBDEFB']];
startColors.forEach(([col,row,color]) => { ctx.fillStyle = color; ctx.fillRect(col*cs+1, row*cs+1, cs-2, cs-2); });
// Pieces — BIGGER with pawn shape
game.players.forEach((player, pIdx) => { game.players.forEach((player, pIdx) => {
player.pieces.forEach((piece, pieceIdx) => { player.pieces.forEach((piece, pieceIdx) => {
if (piece.finished) return; if (piece.finished) return;
...@@ -189,9 +212,18 @@ function drawBoard() { ...@@ -189,9 +212,18 @@ function drawBoard() {
if (piece.pos === -1) pos = getHomeBasePosition(pIdx, pieceIdx, cs); if (piece.pos === -1) pos = getHomeBasePosition(pIdx, pieceIdx, cs);
else pos = getPiecePosition(piece.pos, pIdx, cs); else pos = getPiecePosition(piece.pos, pIdx, cs);
if (pos) { if (pos) {
ctx.beginPath(); ctx.arc(pos.x, pos.y+1.5, cs*0.32, 0, Math.PI*2); ctx.fillStyle = 'rgba(0,0,0,0.15)'; ctx.fill(); const r = cs * 0.42;
ctx.beginPath(); ctx.arc(pos.x, pos.y, cs*0.32, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke(); // Shadow
ctx.beginPath(); ctx.arc(pos.x-cs*0.08, pos.y-cs*0.08, cs*0.1, 0, Math.PI*2); ctx.fillStyle = 'rgba(255,255,255,0.4)'; ctx.fill(); ctx.beginPath(); ctx.arc(pos.x, pos.y + 2, r, 0, Math.PI*2); ctx.fillStyle = 'rgba(0,0,0,0.2)'; ctx.fill();
// Body (pawn shape — circle on top of tapered base)
ctx.beginPath(); ctx.arc(pos.x, pos.y - r*0.15, r*0.65, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill();
// Base
ctx.beginPath(); ctx.ellipse(pos.x, pos.y + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill();
// Border
ctx.beginPath(); ctx.arc(pos.x, pos.y - r*0.15, r*0.65, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke();
ctx.beginPath(); ctx.ellipse(pos.x, pos.y + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
// Highlight
ctx.beginPath(); ctx.arc(pos.x - r*0.2, pos.y - r*0.35, r*0.2, 0, Math.PI*2); ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fill();
} }
}); });
}); });
......
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