Commit 1aa5577b authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: ludo — reduce capture probability, add pawn stacking grid on shared cells

- Dramatically reduce lucky roll weights (nearCapture +1.0→+0.3, capturePerDie +0.6→+0.15)
- Reduce six-boosting when pieces are home (1.5→0.8, 0.6→0.4) to lower triple-six frequency
- Pawns sharing a cell now render in a grid layout: 2 pawns side-by-side, 3 in triangle, 4 in 2x2
- Stacked pawns shrink proportionally (72% for 2, 60% for 3-4) so players can count them
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 189ce786
......@@ -101,9 +101,9 @@ function computeDiceWeights(ctx) {
const w = [1, 1, 1, 1, 1, 1];
if (ctx.piecesHome === 4) {
w[5] += 1.5;
w[5] += 0.8;
} else if (ctx.piecesHome >= 3) {
w[5] += 0.6;
w[5] += 0.4;
}
const deficit = ctx.avgOthersProgress - ctx.progress;
......@@ -123,13 +123,13 @@ function computeDiceWeights(ctx) {
w[5] += 0.3;
}
// "Lucky roll" — boost the exact die value needed to capture an exposed opponent
// "Lucky roll" — subtle boost toward capture dice (should feel lucky, not guaranteed)
if (ctx.nearCapture > 0) {
const idx = ctx.nearCapture - 1;
w[idx] += 1.0;
w[idx] += 0.3;
}
for (let i = 0; i < 6; i++) {
if (ctx.capturePerDie[i]) w[i] += 0.6;
if (ctx.capturePerDie[i]) w[i] += 0.15;
}
return w;
......
......@@ -777,41 +777,59 @@ function drawBoard() {
}
// Pieces — with highlight for selectable ones
// First, group pieces by their cell position to handle stacking
const cellOccupants = new Map(); // key: "x,y" -> [{pIdx, pieceIdx, piece}]
game.players.forEach((player, pIdx) => {
player.pieces.forEach((piece, pieceIdx) => {
if (piece.finished) return;
let pos;
if (piece.pos === -1) pos = getHomeBasePosition(pIdx, pieceIdx, cs);
else pos = getPiecePosition(piece.pos, pIdx, cs);
if (pos) {
const pieceId = `${pIdx}-${pieceIdx}`;
const isHighlighted = highlightedPieces.includes(pieceId);
const r = cs * 0.42;
// Apply vertical bounce offset if present
const bounceY = piece._bounceOffset || 0;
const drawY = pos.y + bounceY;
// Glow ring for selectable pieces
if (isHighlighted) {
ctx.beginPath(); ctx.arc(pos.x, drawY, r + 4, 0, Math.PI*2);
ctx.strokeStyle = '#E4AC38'; ctx.lineWidth = 3; ctx.stroke();
ctx.beginPath(); ctx.arc(pos.x, drawY, r + 7, 0, Math.PI*2);
ctx.strokeStyle = 'rgba(228,172,56,0.3)'; ctx.lineWidth = 2; ctx.stroke();
}
if (!pos) return;
const key = `${Math.round(pos.x)},${Math.round(pos.y)}`;
if (!cellOccupants.has(key)) cellOccupants.set(key, []);
cellOccupants.get(key).push({ pIdx, pieceIdx, piece, pos });
});
});
// Shadow
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, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill();
// Base
ctx.beginPath(); ctx.ellipse(pos.x, drawY + 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, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke();
ctx.beginPath(); ctx.ellipse(pos.x, drawY + 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, drawY - r*0.35, r*0.2, 0, Math.PI*2); ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fill();
// Grid offsets for 1-4 pawns sharing a cell
const STACK_OFFSETS = [
[[0, 0]],
[[-0.35, -0.3], [0.35, 0.3]],
[[-0.35, -0.35], [0.35, -0.35], [0, 0.35]],
[[-0.35, -0.35], [0.35, -0.35], [-0.35, 0.35], [0.35, 0.35]],
];
cellOccupants.forEach((occupants) => {
const count = occupants.length;
const offsets = STACK_OFFSETS[Math.min(count, 4) - 1];
const scale = count === 1 ? 1 : count === 2 ? 0.72 : 0.6;
occupants.forEach((occ, idx) => {
const { pIdx, pieceIdx, piece, pos } = occ;
const pieceId = `${pIdx}-${pieceIdx}`;
const isHighlighted = highlightedPieces.includes(pieceId);
const r = cs * 0.42 * scale;
const offset = offsets[idx % offsets.length];
const ox = pos.x + offset[0] * cs;
const oy = pos.y + offset[1] * cs;
const bounceY = piece._bounceOffset || 0;
const drawY = oy + bounceY;
if (isHighlighted) {
ctx.beginPath(); ctx.arc(ox, drawY, r + 3, 0, Math.PI*2);
ctx.strokeStyle = '#E4AC38'; ctx.lineWidth = 2.5; ctx.stroke();
ctx.beginPath(); ctx.arc(ox, drawY, r + 6, 0, Math.PI*2);
ctx.strokeStyle = 'rgba(228,172,56,0.3)'; ctx.lineWidth = 1.5; ctx.stroke();
}
ctx.beginPath(); ctx.arc(ox, oy + 2, r, 0, Math.PI*2); ctx.fillStyle = 'rgba(0,0,0,0.2)'; ctx.fill();
ctx.beginPath(); ctx.arc(ox, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill();
ctx.beginPath(); ctx.ellipse(ox, drawY + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.fillStyle = COLORS[pIdx]; ctx.fill();
ctx.beginPath(); ctx.arc(ox, drawY - r*0.15, r*0.65, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
ctx.beginPath(); ctx.ellipse(ox, drawY + r*0.4, r*0.8, r*0.4, 0, 0, Math.PI*2); ctx.strokeStyle = '#fff'; ctx.lineWidth = 1; ctx.stroke();
ctx.beginPath(); ctx.arc(ox - r*0.2, drawY - r*0.35, r*0.18, 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