refactor: implement dynamic track layout for overflowing trains
- Replace fixed `TRACK_PITCH` tiling with `computeTrackLayout()` to dynamically fit tiles within track bounds
- Add `tileHalfW()` helper to correctly calculate width for double vs. single tiles
- Add truncation marks ("+N«") when the tile count exceeds visible track space
- Update marker placement, flash effects, and next-tile positioning to use the new dynamic layout coordinates
- Adjust score badge positioning to align relative to portrait radius
This commit is contained in:
parent
ed7da9fcf0
commit
4644b8e3af
|
|
@ -277,30 +277,62 @@ export default class MexicanTrainGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the horizontal half-width a tile occupies when painted.
|
||||||
|
tileHalfW(tile) {
|
||||||
|
return tile.left === tile.right ? TRACK_HALF / 2 : TRACK_HALF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns { start, positions[] } for the visible slice of a train.
|
||||||
|
// Positions are the x-centres of each visible tile (index start..end).
|
||||||
|
computeTrackLayout(key) {
|
||||||
|
const tiles = this.gs.trains[key].tiles;
|
||||||
|
if (tiles.length === 0) return { start: 0, positions: [] };
|
||||||
|
const GAP = 6;
|
||||||
|
const TRUNC = 16; // px reserved for the "+N«" truncation mark
|
||||||
|
const avail = TRACKS_RIGHT - TRACKS_LEFT;
|
||||||
|
|
||||||
|
// Walk backwards to find the first tile that still fits.
|
||||||
|
let accumulated = 0;
|
||||||
|
let start = tiles.length;
|
||||||
|
for (let t = tiles.length - 1; t >= 0; t--) {
|
||||||
|
const addW = (start < tiles.length ? GAP : 0) + this.tileHalfW(tiles[t]) * 2;
|
||||||
|
if (accumulated + addW > avail - (t > 0 ? TRUNC : 0)) break;
|
||||||
|
accumulated += addW;
|
||||||
|
start = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build centre-x positions for each visible tile.
|
||||||
|
const positions = [];
|
||||||
|
let x = TRACKS_LEFT + (start > 0 ? TRUNC : 0) + this.tileHalfW(tiles[start]);
|
||||||
|
for (let t = start; t < tiles.length; t++) {
|
||||||
|
positions.push(x);
|
||||||
|
if (t < tiles.length - 1) {
|
||||||
|
x += this.tileHalfW(tiles[t]) + GAP + this.tileHalfW(tiles[t + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { start, positions };
|
||||||
|
}
|
||||||
|
|
||||||
paintTracks() {
|
paintTracks() {
|
||||||
const g = this.trackGfx;
|
const g = this.trackGfx;
|
||||||
g.clear();
|
g.clear();
|
||||||
const rows = this.gs.players.length + 1;
|
const rows = this.gs.players.length + 1;
|
||||||
const maxTiles = Math.floor((TRACKS_RIGHT - TRACKS_LEFT) / TRACK_PITCH);
|
|
||||||
for (let i = 0; i < rows; i++) {
|
for (let i = 0; i < rows; i++) {
|
||||||
const key = this.rowKeyFor(i);
|
const key = this.rowKeyFor(i);
|
||||||
const y = this.rowY(i);
|
const y = this.rowY(i);
|
||||||
const tiles = this.gs.trains[key].tiles;
|
const tiles = this.gs.trains[key].tiles;
|
||||||
const start = Math.max(0, tiles.length - maxTiles);
|
if (tiles.length === 0) { this.clearTruncMark(i); continue; }
|
||||||
let drawX = TRACKS_LEFT + TRACK_HALF;
|
|
||||||
if (start > 0) {
|
const { start, positions } = this.computeTrackLayout(key);
|
||||||
this.maybeTruncMark(i, start);
|
if (start > 0) this.maybeTruncMark(i, start); else this.clearTruncMark(i);
|
||||||
drawX += 8;
|
|
||||||
} else {
|
for (let ti = 0; ti < positions.length; ti++) {
|
||||||
this.clearTruncMark(i);
|
const t = start + ti;
|
||||||
}
|
|
||||||
for (let t = start; t < tiles.length; t++) {
|
|
||||||
const tile = tiles[t];
|
const tile = tiles[t];
|
||||||
const isDouble = tile.left === tile.right;
|
const isDouble = tile.left === tile.right;
|
||||||
const isOpenDouble = this.gs.openDouble?.train === key && t === tiles.length - 1;
|
const isOpenDouble = this.gs.openDouble?.train === key && t === tiles.length - 1;
|
||||||
const border = isOpenDouble ? COLORS.danger : (isDouble ? COLORS.gold : COLORS.accent);
|
const border = isOpenDouble ? COLORS.danger : (isDouble ? COLORS.gold : COLORS.accent);
|
||||||
this.paintDomino(g, drawX, y, TRACK_HALF, tile.left, tile.right, isDouble, border);
|
this.paintDomino(g, positions[ti], y, TRACK_HALF, tile.left, tile.right, isDouble, border);
|
||||||
drawX += TRACK_PITCH;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -331,7 +363,10 @@ export default class MexicanTrainGame extends Phaser.Scene {
|
||||||
if (!this.gs.trains[key].marker) continue;
|
if (!this.gs.trains[key].marker) continue;
|
||||||
const y = this.rowY(i);
|
const y = this.rowY(i);
|
||||||
const tiles = this.gs.trains[key].tiles;
|
const tiles = this.gs.trains[key].tiles;
|
||||||
const x = TRACKS_LEFT + TRACK_HALF + Math.min(tiles.length, Math.floor((TRACKS_RIGHT - TRACKS_LEFT) / TRACK_PITCH)) * TRACK_PITCH + 6;
|
const { positions } = this.computeTrackLayout(key);
|
||||||
|
const lastPos = positions.length ? positions[positions.length - 1] : TRACKS_LEFT;
|
||||||
|
const lastHW = tiles.length ? this.tileHalfW(tiles[tiles.length - 1]) : TRACK_HALF;
|
||||||
|
const x = lastPos + lastHW + 6;
|
||||||
// little open-train flag
|
// little open-train flag
|
||||||
g.fillStyle(COLORS.danger, 1);
|
g.fillStyle(COLORS.danger, 1);
|
||||||
g.fillCircle(x, y, 9);
|
g.fillCircle(x, y, 9);
|
||||||
|
|
@ -419,8 +454,8 @@ export default class MexicanTrainGame extends Phaser.Scene {
|
||||||
|
|
||||||
for (let i = 0; i < this.gs.players.length; i++) {
|
for (let i = 0; i < this.gs.players.length; i++) {
|
||||||
const score = scores[i];
|
const score = scores[i];
|
||||||
const x = PORTRAIT_X;
|
const x = PORTRAIT_X + Math.round(PORTRAIT_R * 0.85);
|
||||||
const y = this.rowY(i) - PORTRAIT_R - 20;
|
const y = this.rowY(i) - Math.round(PORTRAIT_R * 0.85);
|
||||||
const borderColor = score > 75 ? COLORS.danger
|
const borderColor = score > 75 ? COLORS.danger
|
||||||
: score > 50 ? 0xe07030
|
: score > 50 ? 0xe07030
|
||||||
: score > 25 ? COLORS.accent
|
: score > 25 ? COLORS.accent
|
||||||
|
|
@ -676,14 +711,14 @@ export default class MexicanTrainGame extends Phaser.Scene {
|
||||||
// ─── Effects ──────────────────────────────────────────────────────────
|
// ─── Effects ──────────────────────────────────────────────────────────
|
||||||
flashTrainEnd(key) {
|
flashTrainEnd(key) {
|
||||||
const rows = this.gs.players.length + 1;
|
const rows = this.gs.players.length + 1;
|
||||||
let i = key === MEXICAN ? rows - 1 : Number(key);
|
const i = key === MEXICAN ? rows - 1 : Number(key);
|
||||||
const y = this.rowY(i);
|
const y = this.rowY(i);
|
||||||
const tiles = this.gs.trains[key].tiles;
|
const tiles = this.gs.trains[key].tiles;
|
||||||
const maxTiles = Math.floor((TRACKS_RIGHT - TRACKS_LEFT) / TRACK_PITCH);
|
if (!tiles.length) return;
|
||||||
const shown = Math.min(tiles.length, maxTiles);
|
const { positions } = this.computeTrackLayout(key);
|
||||||
const x = TRACKS_LEFT + TRACK_HALF + (shown - 1) * TRACK_PITCH;
|
const x = positions[positions.length - 1];
|
||||||
const lastTile = tiles[tiles.length - 1];
|
const lastTile = tiles[tiles.length - 1];
|
||||||
const isLastDouble = lastTile && lastTile.left === lastTile.right;
|
const isLastDouble = lastTile.left === lastTile.right;
|
||||||
const fw = isLastDouble ? TRACK_HALF : TRACK_TILE_W;
|
const fw = isLastDouble ? TRACK_HALF : TRACK_TILE_W;
|
||||||
const fh = isLastDouble ? TRACK_TILE_W : TRACK_HALF;
|
const fh = isLastDouble ? TRACK_TILE_W : TRACK_HALF;
|
||||||
const fx = this.add.graphics().setDepth(DEPTH.tileFx);
|
const fx = this.add.graphics().setDepth(DEPTH.tileFx);
|
||||||
|
|
@ -697,9 +732,12 @@ export default class MexicanTrainGame extends Phaser.Scene {
|
||||||
const i = key === MEXICAN ? rows - 1 : Number(key);
|
const i = key === MEXICAN ? rows - 1 : Number(key);
|
||||||
const y = this.rowY(i);
|
const y = this.rowY(i);
|
||||||
const tiles = this.gs.trains[key].tiles;
|
const tiles = this.gs.trains[key].tiles;
|
||||||
const maxTiles = Math.floor((TRACKS_RIGHT - TRACKS_LEFT) / TRACK_PITCH);
|
if (tiles.length === 0) return { x: TRACKS_LEFT + TRACK_HALF, y };
|
||||||
const newIdx = Math.min(tiles.length, maxTiles - 1);
|
const { positions } = this.computeTrackLayout(key);
|
||||||
return { x: TRACKS_LEFT + TRACK_HALF + newIdx * TRACK_PITCH, y };
|
const lastX = positions[positions.length - 1];
|
||||||
|
const lastHW = this.tileHalfW(tiles[tiles.length - 1]);
|
||||||
|
const nextX = Math.min(lastX + lastHW + 6 + TRACK_HALF, TRACKS_RIGHT - TRACK_HALF);
|
||||||
|
return { x: nextX, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
animateTilePlay(fromX, fromY, toX, toY, tileA, tileB, onComplete) {
|
animateTilePlay(fromX, fromY, toX, toY, tileA, tileB, onComplete) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue