Commit 512110c0 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: domino tile placement — pip orientation + corner gaps + sizing

- Add flipPips flag: when chain goes LEFT or UP, tile.left/right are
  drawn on the correct screen side (previously always left=left/top)
- Fix corner turn offset: uses actual tile cross-axis extent instead
  of fixed TILE_H, preventing overlaps/gaps at corners
- Increase TILE_W from 24→26 for better proportions (less squished)
- Increase GAP slightly (1→2) so tiles don't visually merge
- Lower auto-zoom min to 0.3 for large chains
- Fix invalid hand tiles: use saturate(0.3)+brightness(0.85) instead
  of opacity+brightness that made them look solid dark gray
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 38adbafe
...@@ -265,7 +265,7 @@ export class DominoBoard { ...@@ -265,7 +265,7 @@ export class DominoBoard {
// Draw chain tiles // Draw chain tiles
for (const lt of this.layout.tiles) { for (const lt of this.layout.tiles) {
drawTile(ctx, lt.x, lt.y, lt.w, lt.h, lt.tile, { rotation: 0 }); drawTile(ctx, lt.x, lt.y, lt.w, lt.h, lt.tile, { rotation: 0, flipPips: lt.flipPips });
} }
// Endpoint indicators // Endpoint indicators
......
...@@ -15,7 +15,7 @@ const PIP_POSITIONS = { ...@@ -15,7 +15,7 @@ const PIP_POSITIONS = {
}; };
export function drawTile(ctx, x, y, w, h, tile, options = {}) { export function drawTile(ctx, x, y, w, h, tile, options = {}) {
const { rotation = 0, alpha = 1, ghost = false, invalid = false, highlight = false } = options; const { rotation = 0, alpha = 1, ghost = false, invalid = false, highlight = false, flipPips = false } = options;
ctx.save(); ctx.save();
ctx.translate(x, y); ctx.translate(x, y);
...@@ -77,6 +77,12 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) { ...@@ -77,6 +77,12 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) {
const isDouble = tile.left === tile.right; const isDouble = tile.left === tile.right;
const isLandscape = w > h; const isLandscape = w > h;
// Determine which value goes in which half
// When flipPips is true (chain goes LEFT or UP), the "left" value
// is on the right/bottom side of the tile on screen
const firstVal = flipPips ? tile.right : tile.left;
const secondVal = flipPips ? tile.left : tile.right;
if (isDouble) { if (isDouble) {
ctx.fillStyle = DOUBLE_ACCENT; ctx.fillStyle = DOUBLE_ACCENT;
ctx.beginPath(); ctx.beginPath();
...@@ -100,8 +106,8 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) { ...@@ -100,8 +106,8 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) {
const spread = Math.min(hh * 0.55, 6); const spread = Math.min(hh * 0.55, 6);
const pipR = Math.min(2.5, hh * 0.14); const pipR = Math.min(2.5, hh * 0.14);
drawPips(ctx, -hw / 2, 0, spread, pipR, tile.left); drawPips(ctx, -hw / 2, 0, spread, pipR, firstVal);
drawPips(ctx, hw / 2, 0, spread, pipR, tile.right); drawPips(ctx, hw / 2, 0, spread, pipR, secondVal);
} else { } else {
// Divider: horizontal line at center // Divider: horizontal line at center
ctx.beginPath(); ctx.beginPath();
...@@ -113,8 +119,8 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) { ...@@ -113,8 +119,8 @@ export function drawTile(ctx, x, y, w, h, tile, options = {}) {
const spread = Math.min(hw * 0.55, 6); const spread = Math.min(hw * 0.55, 6);
const pipR = Math.min(2.5, hw * 0.14); const pipR = Math.min(2.5, hw * 0.14);
drawPips(ctx, 0, -hh / 2, spread, pipR, tile.left); drawPips(ctx, 0, -hh / 2, spread, pipR, firstVal);
drawPips(ctx, 0, hh / 2, spread, pipR, tile.right); drawPips(ctx, 0, hh / 2, spread, pipR, secondVal);
} }
} }
......
...@@ -189,14 +189,14 @@ export class DominoHand { ...@@ -189,14 +189,14 @@ export class DominoHand {
transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1), transition: transform 0.25s cubic-bezier(0.34,1.56,0.64,1),
border-color 0.2s, opacity 0.2s, box-shadow 0.2s, border-color 0.2s, opacity 0.2s, box-shadow 0.2s,
filter 0.2s; filter 0.2s;
opacity: 0.45; opacity: 0.55;
user-select: none; user-select: none;
touch-action: none; touch-action: none;
flex-shrink: 0; flex-shrink: 0;
box-shadow: 0 3px 8px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.7); box-shadow: 0 3px 8px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.7);
position: relative; position: relative;
transform-origin: center bottom; transform-origin: center bottom;
filter: brightness(0.7); filter: saturate(0.3) brightness(0.85);
} }
.dh-tile.dh-playable { .dh-tile.dh-playable {
opacity: 1; opacity: 1;
......
...@@ -3,18 +3,18 @@ ...@@ -3,18 +3,18 @@
// Doubles are perpendicular to chain direction. // Doubles are perpendicular to chain direction.
// Non-doubles align with chain direction (landscape in horizontal chain). // Non-doubles align with chain direction (landscape in horizontal chain).
export const TILE_W = 24; // narrow dimension export const TILE_W = 26; // narrow dimension
export const TILE_H = 48; // long dimension export const TILE_H = 48; // long dimension
export const GAP = 1; // flush contact between connected tiles on board export const GAP = 2; // small gap between tiles
const MARGIN = 30; const MARGIN = 40;
// Direction vectors for chain growth // Direction vectors for chain growth
const DIRS = [ const DIRS = [
{ dx: 1, dy: 0 }, // right { dx: 1, dy: 0 }, // 0: right
{ dx: 0, dy: 1 }, // down { dx: 0, dy: 1 }, // 1: down
{ dx: -1, dy: 0 }, // left { dx: -1, dy: 0 }, // 2: left
{ dx: 0, dy: -1 }, // up { dx: 0, dy: -1 }, // 3: up
]; ];
export function computeLayout(chain, canvasW, canvasH) { export function computeLayout(chain, canvasW, canvasH) {
...@@ -34,35 +34,29 @@ export function computeLayout(chain, canvasW, canvasH) { ...@@ -34,35 +34,29 @@ export function computeLayout(chain, canvasW, canvasH) {
const piece = chain[i]; const piece = chain[i];
const isDouble = piece.left === piece.right; const isDouble = piece.left === piece.right;
const dir = DIRS[dirIdx]; const dir = DIRS[dirIdx];
const isHorizontal = dir.dx !== 0; // chain goes left/right const isHorizontal = dir.dx !== 0;
// Tile rendered dimensions (already in screen orientation, no rotation needed) // Tile dimensions based on orientation
let w, h; let w, h;
if (isHorizontal) { if (isHorizontal) {
// Chain goes horizontally
if (isDouble) { if (isDouble) {
// Doubles perpendicular: narrow along chain, tall across w = TILE_W; h = TILE_H; // perpendicular: narrow along chain, tall across
w = TILE_W;
h = TILE_H;
} else { } else {
// Non-doubles aligned: long along chain, narrow across w = TILE_H; h = TILE_W; // aligned: long along chain, narrow across
w = TILE_H;
h = TILE_W;
} }
} else { } else {
// Chain goes vertically
if (isDouble) { if (isDouble) {
// Doubles perpendicular: wide across chain, narrow along w = TILE_H; h = TILE_W; // perpendicular: wide across chain, narrow along
w = TILE_H;
h = TILE_W;
} else { } else {
// Non-doubles aligned: narrow across, long along chain w = TILE_W; h = TILE_H; // aligned: narrow across, long along chain
w = TILE_W;
h = TILE_H;
} }
} }
tiles.push({ tile: piece, x: cx, y: cy, w, h, rotation: 0, isDouble, dirIdx }); // flipPips: when chain goes LEFT or UP, the "left" value is on the
// trailing side (right or bottom on screen), so renderer must flip
const flipPips = (dirIdx === 2 || dirIdx === 3);
tiles.push({ tile: piece, x: cx, y: cy, w, h, rotation: 0, isDouble, dirIdx, flipPips });
// Calculate advance to next tile position // Calculate advance to next tile position
if (i < chain.length - 1) { if (i < chain.length - 1) {
...@@ -71,13 +65,11 @@ export function computeLayout(chain, canvasW, canvasH) { ...@@ -71,13 +65,11 @@ export function computeLayout(chain, canvasW, canvasH) {
// Half-extent of current tile in chain direction // Half-extent of current tile in chain direction
const curHalf = isHorizontal ? w / 2 : h / 2; const curHalf = isHorizontal ? w / 2 : h / 2;
// Half-extent of next tile in chain direction
let nextHalf; // Half-extent of next tile in chain direction (same dir)
if (isHorizontal) { const nextW = isHorizontal ? (nextIsDouble ? TILE_W : TILE_H) : (nextIsDouble ? TILE_H : TILE_W);
nextHalf = nextIsDouble ? TILE_W / 2 : TILE_H / 2; const nextH = isHorizontal ? (nextIsDouble ? TILE_H : TILE_W) : (nextIsDouble ? TILE_W : TILE_H);
} else { const nextHalf = isHorizontal ? nextW / 2 : nextH / 2;
nextHalf = nextIsDouble ? TILE_W / 2 : TILE_H / 2;
}
const advance = curHalf + GAP + nextHalf; const advance = curHalf + GAP + nextHalf;
const nextX = cx + dir.dx * advance; const nextX = cx + dir.dx * advance;
...@@ -89,10 +81,22 @@ export function computeLayout(chain, canvasW, canvasH) { ...@@ -89,10 +81,22 @@ export function computeLayout(chain, canvasW, canvasH) {
if (Math.abs(nextX) > boundW / 2 - MARGIN || Math.abs(nextY) > boundH / 2 - MARGIN) { if (Math.abs(nextX) > boundW / 2 - MARGIN || Math.abs(nextY) > boundH / 2 - MARGIN) {
// Turn clockwise // Turn clockwise
const oldDirIdx = dirIdx;
dirIdx = (dirIdx + 1) % 4; dirIdx = (dirIdx + 1) % 4;
const newDir = DIRS[dirIdx]; const newDir = DIRS[dirIdx];
// Offset perpendicular from current position to start new row const newIsHorizontal = newDir.dx !== 0;
const perpOffset = (TILE_H / 2 + GAP + TILE_H / 2);
// Current tile's cross-axis half (perpendicular to old direction = along new direction)
const curCrossHalf = isHorizontal ? h / 2 : w / 2;
// Next tile dimensions in NEW direction
const newNextW = newIsHorizontal ? (nextIsDouble ? TILE_W : TILE_H) : (nextIsDouble ? TILE_H : TILE_W);
const newNextH = newIsHorizontal ? (nextIsDouble ? TILE_H : TILE_W) : (nextIsDouble ? TILE_W : TILE_H);
// Next tile's half in new chain direction
const newNextHalf = newIsHorizontal ? newNextW / 2 : newNextH / 2;
// Perpendicular offset: clear current tile's cross edge + gap + next tile's chain half
const perpOffset = curCrossHalf + GAP + newNextHalf;
cx += newDir.dx * perpOffset; cx += newDir.dx * perpOffset;
cy += newDir.dy * perpOffset; cy += newDir.dy * perpOffset;
} else { } else {
...@@ -170,5 +174,5 @@ export function getAutoZoom(bounds, canvasW, canvasH) { ...@@ -170,5 +174,5 @@ export function getAutoZoom(bounds, canvasW, canvasH) {
const scaleX = canvasW / chainW; const scaleX = canvasW / chainW;
const scaleY = canvasH / chainH; const scaleY = canvasH / chainH;
const needed = Math.min(scaleX, scaleY); const needed = Math.min(scaleX, scaleY);
return Math.min(1, Math.max(0.35, needed)); return Math.min(1, Math.max(0.3, needed));
} }
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