98 lines
3.9 KiB
JavaScript
98 lines
3.9 KiB
JavaScript
// Tri-Ominoes — static data and triangular-grid geometry. No Phaser, no state.
|
||
//
|
||
// The board is a triangular grid. Each cell is an equilateral triangle keyed by
|
||
// (r, c). A cell "points up" when (r + c) is even and "points down" otherwise.
|
||
// Up and down triangles interlock to tile the plane.
|
||
//
|
||
// Every cell owns three CORNER VERTICES, listed clockwise (screen coords, y
|
||
// down). Vertices are shared between neighbouring cells, so a vertex's number is
|
||
// shared by every tile that touches it — which is exactly the Tri-Ominoes
|
||
// matching rule (touching corners must agree). We identify a vertex by lattice
|
||
// coords (vr, vx): horizontal line index vr and half-column vx.
|
||
//
|
||
// up (r,c): top=(r, c+1) bottom-right=(r+1, c+2) bottom-left=(r+1, c)
|
||
// down (r,c): top-left=(r,c) top-right=(r, c+2) bottom=(r+1, c+1)
|
||
//
|
||
// Both windings are clockwise, so the three placement rotations of a tile are
|
||
// just cyclic shifts of its corner triple — reflections (which a physical tile
|
||
// can't do) never sneak in.
|
||
|
||
export const VALUE_MAX = 5; // double-five set: corner pips 0..5
|
||
|
||
// All 56 distinct triangles (multisets of 3 values 0..5). The stored triple is
|
||
// sorted ascending and treated as the tile's CLOCKWISE corner order.
|
||
export function buildTileSet() {
|
||
const tiles = [];
|
||
let id = 0;
|
||
for (let a = 0; a <= VALUE_MAX; a++) {
|
||
for (let b = a; b <= VALUE_MAX; b++) {
|
||
for (let c = b; c <= VALUE_MAX; c++) {
|
||
tiles.push({ id: id++, v: [a, b, c] });
|
||
}
|
||
}
|
||
}
|
||
return tiles; // 56 tiles
|
||
}
|
||
|
||
export const tileValue = (t) => t.v[0] + t.v[1] + t.v[2];
|
||
export const isTripleTile = (t) => t.v[0] === t.v[1] && t.v[1] === t.v[2];
|
||
|
||
// Standard hand sizes: 2 players draw 9, 3–4 draw 7.
|
||
export function handSizeFor(n) {
|
||
return n === 2 ? 9 : 7;
|
||
}
|
||
|
||
// ── Geometry ─────────────────────────────────────────────────────────────────
|
||
export function isUp(r, c) {
|
||
return (((r + c) % 2) + 2) % 2 === 0;
|
||
}
|
||
|
||
export const cellKey = (r, c) => `${r},${c}`;
|
||
export const vertKey = (vr, vx) => `${vr},${vx}`;
|
||
|
||
// Three corner vertices in clockwise order. Order matters: a placed tile maps
|
||
// corner i ← tileTriple[(i + rot) % 3].
|
||
export function cellVertices(r, c) {
|
||
if (isUp(r, c)) {
|
||
return [
|
||
[r, c + 1], // top
|
||
[r + 1, c + 2], // bottom-right
|
||
[r + 1, c], // bottom-left
|
||
];
|
||
}
|
||
return [
|
||
[r, c], // top-left
|
||
[r, c + 2], // top-right
|
||
[r + 1, c + 1], // bottom
|
||
];
|
||
}
|
||
|
||
// The three edge-adjacent cells. The slanted-edge neighbours sit in the same
|
||
// row (c±1); the flat-edge neighbour is one row away depending on orientation.
|
||
export function neighborCells(r, c) {
|
||
return isUp(r, c)
|
||
? [[r, c - 1], [r, c + 1], [r + 1, c]]
|
||
: [[r, c - 1], [r, c + 1], [r - 1, c]];
|
||
}
|
||
|
||
// ── Pixel mapping ─────────────────────────────────────────────────────────────
|
||
// A vertex (vr, vx) maps to a board-space pixel. HALF_W is half the triangle
|
||
// base; row height keeps the triangles equilateral.
|
||
export const HALF_W = 46;
|
||
export const ROW_H = Math.round(HALF_W * 2 * 0.8660254); // ≈ 80
|
||
|
||
export function vertPixel(vr, vx) {
|
||
return { x: vx * HALF_W, y: vr * ROW_H };
|
||
}
|
||
|
||
export function cellCentroid(r, c) {
|
||
const vs = cellVertices(r, c);
|
||
let x = 0, y = 0;
|
||
for (const [vr, vx] of vs) { const p = vertPixel(vr, vx); x += p.x; y += p.y; }
|
||
return { x: x / 3, y: y / 3 };
|
||
}
|
||
|
||
// ── Players ─────────────────────────────────────────────────────────────────
|
||
export const PLAYER_COLORS = [0xc8a84b, 0x4a90d9, 0x49a25a, 0xd0473a];
|
||
export const PLAYER_COLOR_HEX = ['#c8a84b', '#4a90d9', '#49a25a', '#d0473a'];
|