Update client rendering for the real board and 5 stats
boardRender.js: TYPE_STYLE now covers the 9 real tile types instead of the
old 5 (start/finish/choice/money/event) — payday/cash_bonus/action_space/
dice_space/roll_table_ref/inline_table each get their own icon, color-coded
by mechanic (green=income, blue=rolls-against-a-real-table, dashed
light-blue=rolls-against-a-synthesized-placeholder, red=decision point).
The old per-space cash badge is gone (tiles no longer carry a static cash
amount — effects resolve dynamically via tables) and replaced with a die
badge ("D20") wherever a tile involves a roll. Also fixes a real bug the
headless verification run caught: computeLayout assumed every 'choice'-type
space has a populated `choices` array, which none do yet post-rebuild
(`for (const t of space.choices)` on undefined) — now falls back to `next`
like everything else, and the lane fan-out is keyed off "does this space
have more than one outgoing edge" generically rather than off `type`, so it
keeps working whenever choices/stop tiles do get real branches later.
index.html: player list shows all five stats (cash/love/education/wealth/
age) instead of just cash; the log feed shows each tile's actual resolution
description (already human-readable from tileEffects.js) with a small ⚠️
marker on placeholder/invented resolutions, instead of a bare cash delta.
computeLayout's colsPerRow bumped to 20 for the much larger (212-space)
board.
This commit is contained in:
+22
-16
@@ -24,22 +24,27 @@ const NODE_H = 60;
|
||||
const PLAYER_TOKEN_R = 12;
|
||||
|
||||
const TYPE_STYLE = {
|
||||
start: { icon: '🚩', shape: 'circle' },
|
||||
finish: { icon: '🏁', shape: 'circle' },
|
||||
choice: { icon: '🔀', shape: 'diamond' },
|
||||
money: { icon: '💰', shape: 'rect' },
|
||||
stop: { icon: '🛑', shape: 'diamond' },
|
||||
payday: { icon: '💵', shape: 'rect' },
|
||||
cash_bonus: { icon: '💰', shape: 'rect' },
|
||||
action_space: { icon: '🎯', shape: 'rect' },
|
||||
dice_space: { icon: '🎲', shape: 'rect' },
|
||||
roll_table_ref: { icon: '📋', shape: 'rect' },
|
||||
inline_table: { icon: '🎰', shape: 'rect' },
|
||||
event: { icon: '✨', shape: 'rect' },
|
||||
};
|
||||
|
||||
/** Pure layout computation — no DOM. Returns { positions, edges, width, height }. */
|
||||
export function computeLayout(board, colsPerRow = 14) {
|
||||
export function computeLayout(board, colsPerRow = 20) {
|
||||
const ids = Object.keys(board.spaces);
|
||||
const outEdges = new Map(ids.map((id) => [id, []]));
|
||||
const predecessors = new Map(ids.map((id) => [id, []]));
|
||||
|
||||
for (const id of ids) {
|
||||
const space = board.spaces[id];
|
||||
const targets = space.type === 'choice' ? space.choices : (space.next ? [space.next] : []);
|
||||
const targets = space.type === 'choice' && space.choices?.length ? space.choices : (space.next ? [space.next] : []);
|
||||
for (const t of targets) {
|
||||
outEdges.get(id).push(t);
|
||||
predecessors.get(t).push(id);
|
||||
@@ -59,14 +64,15 @@ export function computeLayout(board, colsPerRow = 14) {
|
||||
children.forEach((childId, i) => {
|
||||
col.set(childId, Math.max(col.get(childId) ?? -Infinity, col.get(id) + 1));
|
||||
|
||||
// Choice spaces fan their N branches out symmetrically around the
|
||||
// parent's lane (works for 2-way, 3-way, ... forks alike); anything
|
||||
// else just inherits the parent's lane unchanged. A join (>1
|
||||
// Any space with more than one outgoing edge fans its branches out
|
||||
// symmetrically around the parent's lane (works for 2-way, 3-way, ...
|
||||
// forks alike, whatever type the space is — not just 'choice'); a
|
||||
// single child just inherits the parent's lane unchanged. A join (>1
|
||||
// predecessor) accumulates every incoming contribution and averages
|
||||
// once all of its predecessors have been processed (guaranteed by the
|
||||
// time `remaining` hits 0, since that only happens after every
|
||||
// in-edge is visited).
|
||||
const offset = space.type === 'choice' ? i - (children.length - 1) / 2 : 0;
|
||||
const offset = children.length > 1 ? i - (children.length - 1) / 2 : 0;
|
||||
const contribution = laneSum.get(id) + offset;
|
||||
laneSum.set(childId, (laneSum.get(childId) ?? 0) + contribution);
|
||||
|
||||
@@ -294,8 +300,9 @@ export class BoardView {
|
||||
|
||||
_buildNode(space, pos) {
|
||||
const style = TYPE_STYLE[space.type] ?? TYPE_STYLE.event;
|
||||
const isStart = space.id === this.board.startSpaceId;
|
||||
const node = svgEl('g', {
|
||||
class: `space space-${space.type}`,
|
||||
class: `space space-${space.type}${isStart ? ' space-start-marker' : ''}`,
|
||||
transform: `translate(${pos.x}, ${pos.y})`,
|
||||
'data-space-id': space.id,
|
||||
});
|
||||
@@ -307,7 +314,7 @@ export class BoardView {
|
||||
shapeGroup.appendChild(this._shapeEl(style.shape, 3, 4, 'space-shadow'));
|
||||
shapeGroup.appendChild(this._shapeEl(style.shape, 0, 0, 'space-shape'));
|
||||
shapeGroup.appendChild(this._shapeEl(style.shape, 0, 0, 'space-gloss'));
|
||||
if (space.type === 'choice') {
|
||||
if (space.type === 'choice' || space.type === 'stop') {
|
||||
shapeGroup.appendChild(svgEl('rect', { x: -4, y: NODE_H / 2 + 2, width: 8, height: 22, class: 'choice-post' }));
|
||||
}
|
||||
node.appendChild(shapeGroup);
|
||||
@@ -315,15 +322,14 @@ export class BoardView {
|
||||
const fo = svgEl('foreignObject', { x: -NODE_W / 2, y: -NODE_H / 2, width: NODE_W, height: NODE_H });
|
||||
const div = document.createElement('div');
|
||||
div.className = 'space-label';
|
||||
div.innerHTML = `<span class="space-icon">${style.icon}</span><span class="space-text">${escapeHtml(space.label)}</span>`;
|
||||
const icon = isStart ? '🚩' : style.icon;
|
||||
div.innerHTML = `<span class="space-icon">${icon}</span><span class="space-text">${escapeHtml(space.label)}</span>`;
|
||||
fo.appendChild(div);
|
||||
node.appendChild(fo);
|
||||
|
||||
if (space.cash) {
|
||||
const badge = svgEl('text', {
|
||||
class: `cash-badge ${space.cash > 0 ? 'pos' : 'neg'}`, y: NODE_H / 2 + 17, 'text-anchor': 'middle',
|
||||
});
|
||||
badge.textContent = `${space.cash > 0 ? '+' : ''}${space.cash}`;
|
||||
if (space.die) {
|
||||
const badge = svgEl('text', { class: 'die-badge', y: NODE_H / 2 + 17, 'text-anchor': 'middle' });
|
||||
badge.textContent = space.die;
|
||||
node.appendChild(badge);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user