refactor Catan setup: use deterministic chit placement and randomize player order
- Replace the random hex chit assignment with a fixed spiral-based sequence (`CHIT_SPIRAL` and `CHIT_SEQUENCE`) to ensure consistent board layouts. - Randomize the initial player turn order using a shuffled seat array instead of the static snake pattern. - Update the build costs legend UI to display colored resource swatches in a bottom panel.
This commit is contained in:
parent
19620c10d4
commit
4b6593b074
|
|
@ -39,6 +39,12 @@ export const RESOURCE_BAG = [
|
||||||
// 18 number chits for the 18 non-desert hexes.
|
// 18 number chits for the 18 non-desert hexes.
|
||||||
export const CHIT_BAG = [2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10, 11, 11, 12];
|
export const CHIT_BAG = [2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10, 11, 11, 12];
|
||||||
|
|
||||||
|
// Hex IDs visited in the standard Catan clockwise spiral (outer ring → inner ring → centre).
|
||||||
|
// Rows [3,4,5,4,3] assign IDs left-to-right, top-to-bottom: 0-2, 3-6, 7-11, 12-15, 16-18.
|
||||||
|
export const CHIT_SPIRAL = [0, 1, 2, 6, 11, 15, 18, 17, 16, 12, 7, 3, 4, 5, 10, 14, 13, 8, 9];
|
||||||
|
// Standard chit values A–R placed in spiral order; desert hex is skipped when assigning.
|
||||||
|
export const CHIT_SEQUENCE = [5, 2, 6, 3, 8, 10, 9, 12, 11, 4, 8, 10, 9, 4, 5, 6, 3, 11];
|
||||||
|
|
||||||
// 9 ports: 4 generic 3:1, one 2:1 per resource.
|
// 9 ports: 4 generic 3:1, one 2:1 per resource.
|
||||||
export const PORT_BAG = ['any', 'any', 'any', 'any', 'brick', 'lumber', 'wool', 'grain', 'ore'];
|
export const PORT_BAG = ['any', 'any', 'any', 'any', 'brick', 'lumber', 'wool', 'grain', 'ore'];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,7 @@ export default class CatanGame extends Phaser.Scene {
|
||||||
fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.mutedHex,
|
fontFamily: '"Julius Sans One"', fontSize: '14px', color: COLORS.mutedHex,
|
||||||
}).setOrigin(0, 0.5).setDepth(D.hud);
|
}).setOrigin(0, 0.5).setDepth(D.hud);
|
||||||
|
|
||||||
// cost legend (right, above buttons)
|
// cost legend (bottom bar, right of dice)
|
||||||
this.buildCostLegend();
|
this.buildCostLegend();
|
||||||
|
|
||||||
// action buttons (vertical column, right)
|
// action buttons (vertical column, right)
|
||||||
|
|
@ -273,19 +273,45 @@ export default class CatanGame extends Phaser.Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCostLegend() {
|
buildCostLegend() {
|
||||||
const x = 1600, y = 230;
|
const panelRight = 1900;
|
||||||
|
const panelW = 320;
|
||||||
|
const cx = panelRight - panelW / 2;
|
||||||
|
const bgCy = 980, bgH = 164;
|
||||||
|
|
||||||
const panel = this.add.container(0, 0).setDepth(D.hud);
|
const panel = this.add.container(0, 0).setDepth(D.hud);
|
||||||
panel.add(this.add.rectangle(x, y + 80, 180, 200, 0x000000, 0.3).setStrokeStyle(1, COLORS.accent, 0.5));
|
panel.add(this.add.rectangle(cx, bgCy, panelW, bgH, 0x000000, 0.3).setStrokeStyle(1, COLORS.accent, 0.5));
|
||||||
panel.add(this.add.text(x, y - 4, 'Build Costs', { fontFamily: 'Righteous', fontSize: '16px', color: COLORS.goldHex }).setOrigin(0.5));
|
panel.add(this.add.text(cx, bgCy - bgH / 2 + 7, 'Build Costs', {
|
||||||
const lines = [
|
fontFamily: 'Righteous', fontSize: '20px', color: COLORS.goldHex,
|
||||||
['Road', 'Brick Lumber'],
|
}).setOrigin(0.5, 0));
|
||||||
['Settle', 'Br Lu Wo Gr'],
|
|
||||||
['City', '2 Grain 3 Ore'],
|
const rows = [
|
||||||
['Dev', 'Wool Grain Ore'],
|
{ name: 'Road', resources: ['brick', 'lumber'] },
|
||||||
|
{ name: 'Settlement', resources: ['brick', 'lumber', 'wool', 'grain'] },
|
||||||
|
{ name: 'City', resources: ['grain', 'grain', 'ore', 'ore', 'ore'] },
|
||||||
|
{ name: 'Dev Card', resources: ['wool', 'grain', 'ore'] },
|
||||||
];
|
];
|
||||||
lines.forEach((ln, i) => {
|
|
||||||
panel.add(this.add.text(x - 78, y + 30 + i * 38, ln[0], { fontFamily: '"Julius Sans One"', fontSize: '15px', color: COLORS.textHex }).setOrigin(0, 0.5));
|
const lx = panelRight - panelW + 14;
|
||||||
panel.add(this.add.text(x - 78, y + 48 + i * 38, ln[1], { fontFamily: '"Julius Sans One"', fontSize: '12px', color: COLORS.mutedHex }).setOrigin(0, 0.5));
|
const rx = panelRight - 14;
|
||||||
|
const rowY0 = bgCy - bgH / 2 + 44;
|
||||||
|
const rowStep = (bgH - 44 - 14) / (rows.length - 1);
|
||||||
|
const SW = 16, SH = 16, SG = 4, SR = 3; // swatch w/h/gap/radius
|
||||||
|
|
||||||
|
const g = this.add.graphics();
|
||||||
|
panel.add(g);
|
||||||
|
|
||||||
|
rows.forEach(({ name, resources }, i) => {
|
||||||
|
const ry = rowY0 + i * rowStep;
|
||||||
|
panel.add(this.add.text(lx, ry, name, {
|
||||||
|
fontFamily: '"Julius Sans One"', fontSize: '19px', color: COLORS.textHex,
|
||||||
|
}).setOrigin(0, 0.5));
|
||||||
|
const totalW = resources.length * SW + (resources.length - 1) * SG;
|
||||||
|
let sx = rx - totalW;
|
||||||
|
for (const r of resources) {
|
||||||
|
g.fillStyle(RESOURCE_INFO[r].swatch, 1);
|
||||||
|
g.fillRoundedRect(sx, ry - SH / 2, SW, SH, SR);
|
||||||
|
sx += SW + SG;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NODES, EDGES, HEXES, PORT_SLOTS, edgeBetween,
|
NODES, EDGES, HEXES, PORT_SLOTS, edgeBetween,
|
||||||
RESOURCE_BAG, CHIT_BAG, PORT_BAG, COSTS, DEV_DECK, RESOURCE_TYPES, WIN_VP,
|
RESOURCE_BAG, PORT_BAG, COSTS, DEV_DECK, RESOURCE_TYPES, WIN_VP,
|
||||||
|
CHIT_SPIRAL, CHIT_SEQUENCE,
|
||||||
} from './CatanBoard.js';
|
} from './CatanBoard.js';
|
||||||
|
|
||||||
// ── small utilities ─────────────────────────────────────────────────────────
|
// ── small utilities ─────────────────────────────────────────────────────────
|
||||||
|
|
@ -26,16 +27,6 @@ export function handSize(player) {
|
||||||
return RESOURCE_TYPES.reduce((s, r) => s + player.resources[r], 0);
|
return RESOURCE_TYPES.reduce((s, r) => s + player.resources[r], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hex adjacency (share an edge → share 2 corner nodes).
|
|
||||||
const HEX_NEIGHBORS = HEXES.map(() => []);
|
|
||||||
for (const e of EDGES) {
|
|
||||||
if (e.hexes.length === 2) {
|
|
||||||
const [h1, h2] = e.hexes;
|
|
||||||
if (!HEX_NEIGHBORS[h1].includes(h2)) HEX_NEIGHBORS[h1].push(h2);
|
|
||||||
if (!HEX_NEIGHBORS[h2].includes(h1)) HEX_NEIGHBORS[h2].push(h1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── occupancy queries ────────────────────────────────────────────────────────
|
// ── occupancy queries ────────────────────────────────────────────────────────
|
||||||
export function nodeBuilding(state, nodeId) {
|
export function nodeBuilding(state, nodeId) {
|
||||||
for (const p of state.players) {
|
for (const p of state.players) {
|
||||||
|
|
@ -65,21 +56,12 @@ export function createInitialState(playerCount = 3) {
|
||||||
const desertHex = hexes.find((h) => h.resource === 'desert');
|
const desertHex = hexes.find((h) => h.resource === 'desert');
|
||||||
desertHex.hasRobber = true;
|
desertHex.hasRobber = true;
|
||||||
|
|
||||||
// Number chits onto non-desert hexes, 6/8 never adjacent.
|
// Number chits: walk the standard spiral, skip desert, assign fixed sequence A–R.
|
||||||
const nonDesert = hexes.filter((h) => h.resource !== 'desert');
|
let chitIdx = 0;
|
||||||
for (let attempt = 0; attempt < 500; attempt++) {
|
for (const hexId of CHIT_SPIRAL) {
|
||||||
const chits = shuffle(CHIT_BAG);
|
if (hexes[hexId].resource !== 'desert') {
|
||||||
nonDesert.forEach((h, i) => { h.number = chits[i]; });
|
hexes[hexId].number = CHIT_SEQUENCE[chitIdx++];
|
||||||
let ok = true;
|
|
||||||
for (const h of nonDesert) {
|
|
||||||
if (h.number !== 6 && h.number !== 8) continue;
|
|
||||||
for (const nb of HEX_NEIGHBORS[h.id]) {
|
|
||||||
const other = hexes[nb];
|
|
||||||
if (other.number === 6 || other.number === 8) { ok = false; break; }
|
|
||||||
}
|
}
|
||||||
if (!ok) break;
|
|
||||||
}
|
|
||||||
if (ok) break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port types onto fixed slots.
|
// Port types onto fixed slots.
|
||||||
|
|
@ -109,10 +91,9 @@ export function createInitialState(playerCount = 3) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snake setup order: 0..n-1 then n-1..0.
|
// Snake setup order: randomized forward then reverse.
|
||||||
const order = [];
|
const seats = shuffle([...Array(n).keys()]);
|
||||||
for (let s = 0; s < n; s++) order.push(s);
|
const order = [...seats, ...[...seats].reverse()];
|
||||||
for (let s = n - 1; s >= 0; s--) order.push(s);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
playerCount: n,
|
playerCount: n,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue