faience / ui /scoring.js
RemiFabre's picture
Deploy browser player — run4/ckpt-037888 elo 2361.0
5d90ebb
Raw
History Blame Contribute Delete
5.94 kB
/* Scoring, shown on the table instead of over it.
*
* Two panels, both inline: the round tally (what each wall tile earned, what the
* floor cost) and the final reckoning (the end-game bonuses, broken down). They
* sit in a two-column grid under the boards, left column = left board, so each
* player's numbers stay under their own wall and the position behind them is
* still fully readable.
*
* Both take the server's own report objects — no number here is re-derived.
*/
import { COLORS, node, tileEl } from "./dom.js";
function miniTile(color) {
const t = tileEl(color, "mini");
return t;
}
function playerCard(label, kids) {
const card = node("div", "score-card");
card.appendChild(node("h3", "score-card-who", label));
kids.forEach((k) => card.appendChild(k));
return card;
}
function line(text) {
return node("p", "score-line", text);
}
/** One player's round: the tiles that reached the wall, and the floor's bill. */
function roundCard(report, seat, label) {
const p = report.players[seat];
const kids = [];
const delta = node(
"p",
"score-delta" + (p.delta < 0 ? " down" : p.delta > 0 ? " up" : ""),
(p.delta >= 0 ? "+" + p.delta : String(p.delta)) + " → " + p.score_after
);
kids.push(delta);
if (p.tiles.length) {
const placed = node("div", "score-tiles");
p.tiles.forEach((t) => {
const box = node("div", "score-tile");
box.appendChild(miniTile(t.color));
box.appendChild(node("span", "pts", "+" + t.points));
box.appendChild(node("span", "where", "r" + (t.row + 1) + "·c" + (t.col + 1)));
box.title =
t.color_name +
" → row " +
(t.row + 1) +
", column " +
(t.col + 1) +
" (run of " +
t.h_run +
" across, " +
t.v_run +
" down)";
placed.appendChild(box);
});
kids.push(placed);
kids.push(
line(
"Wall: +" +
p.tiling_points +
" from " +
p.tiles.length +
" tile" +
(p.tiles.length > 1 ? "s" : "")
)
);
} else {
kids.push(line("No pattern line was full. Nothing reached the wall."));
}
const floorBits = [];
if (p.floor.tiles.length) {
floorBits.push(p.floor.tiles.length + " tile" + (p.floor.tiles.length > 1 ? "s" : ""));
}
if (p.floor.marker) floorBits.push("the marker");
kids.push(
line(
floorBits.length
? "Floor: " + floorBits.join(" and ") + " → " + p.floor.penalty
: "Floor: clean."
)
);
if (p.carried_rows.length) {
kids.push(
line(
"Carried over: " +
p.carried_rows
.map((r) => r.count + " " + COLORS[r.color] + " in row " + (r.row + 1))
.join(", ")
)
);
}
return playerCard(label, kids);
}
/**
* Draw the round tally into `host`.
* `sides` is `[[seat, label], [seat, label]]`, left column first.
*/
export function renderRoundPanel(host, report, sides) {
host.innerHTML = "";
host.classList.add("scoring");
host.hidden = false;
const head = node("div", "scoring-head");
head.appendChild(node("h2", "scoring-title", "Round " + (report.round + 1) + " scored"));
if (!report.game_over) {
const yours = sides[0][0] === report.next_first_player;
const who = yours ? sides[0][1] + " start" : sides[1][1] + " starts";
head.appendChild(node("span", "scoring-note", who + " the next round"));
}
host.appendChild(head);
const grid = node("div", "scoring-grid");
sides.forEach(([seat, label]) => grid.appendChild(roundCard(report, seat, label)));
host.appendChild(grid);
}
/** One player's end-game bonuses, itemised. */
function bonusCard(final, seat, label) {
const b = final.bonuses[seat];
const kids = [];
const total = node("p", "score-delta big", String(b.final_score));
if (final.winner === seat) total.classList.add("won");
kids.push(total);
const rows = [
["On the board", b.score_before_bonus, ""],
["Full rows ×2", "+" + b.row_points, b.rows + " row" + (b.rows === 1 ? "" : "s")],
["Full columns ×7", "+" + b.col_points, b.cols + " column" + (b.cols === 1 ? "" : "s")],
[
"All five of a colour ×10",
"+" + b.color_points,
b.colors + " colour" + (b.colors === 1 ? "" : "s"),
],
["Bonus", "+" + b.total, ""],
];
const table = node("dl", "bonus-list");
rows.forEach(([name, value, note], i) => {
const dt = node("dt", null, name);
const dd = node("dd", null, String(value));
if (i === rows.length - 1) {
dt.classList.add("sum");
dd.classList.add("sum");
}
if (note) dd.title = note;
table.append(dt, dd);
});
kids.push(table);
return playerCard(label, kids);
}
/** Draw the final reckoning into `host` — the board above stays readable. */
export function renderFinalPanel(host, final, sides) {
host.innerHTML = "";
host.classList.add("scoring", "final");
host.hidden = false;
const head = node("div", "scoring-head");
head.appendChild(node("h2", "scoring-title", final.headline));
head.appendChild(
node(
"span",
"scoring-note",
"Final score " +
final.scores[sides[0][0]] +
"–" +
final.scores[sides[1][0]] +
" after " +
final.rounds_played +
" rounds"
)
);
host.appendChild(head);
const grid = node("div", "scoring-grid");
sides.forEach(([seat, label]) => grid.appendChild(bonusCard(final, seat, label)));
host.appendChild(grid);
if (final.winner === null) {
host.appendChild(line("Level on score and on completed rows. The rulebook calls that a draw."));
}
if (final.exhausted) {
host.appendChild(line("The bag and the lid ran dry, so the game stopped early."));
}
}
/** Hide whichever panel is showing (a new deal, or the next round starting). */
export function clearScoring(host) {
host.innerHTML = "";
host.hidden = true;
host.classList.remove("final");
}