benjivers/val Public Public: anyone can read this; the crew can change it.
Files 674c40ddee5c18295fdc6a04cd14d0d0c8bbfce9
| 1 | /** |
| 2 | * The banner: the one moment a round turns, said big, on the view itself. |
| 3 | * |
| 4 | * Somebody sitting down in front of the game for two minutes meets a round |
| 5 | * ending before they meet anything else, and the view used to freeze with no |
| 6 | * word of who took it or why — the answer was three lines below the canvas at |
| 7 | * eighty percent opacity. So for the few seconds a round is over, the match is |
| 8 | * over, or a round is starting, the view carries a title and one line under |
| 9 | * it. A pure function decides the words; the drawing is a band and two texts. |
| 10 | */ |
| 11 | |
| 12 | import { roundOutcome } from "./prompt.js"; |
| 13 | |
| 14 | /** The buy phase is fourteen seconds; the round card shows for its first three. */ |
| 15 | const BUY_TIME = 14; |
| 16 | const CARD_FOR = 3; |
| 17 | const FADE = 0.5; |
| 18 | |
| 19 | const secs = (n) => Math.max(0, Math.ceil(n)); |
| 20 | |
| 21 | /** |
| 22 | * What the banner says right now, or null for nothing. `alpha` is how solid it |
| 23 | * is: a card that leaves by fading is not a card that blinked off. |
| 24 | */ |
| 25 | export function banner(snap) { |
| 26 | if (!snap) return null; |
| 27 | if (snap.phase === "over") |
| 28 | return { |
| 29 | title: snap.result ? `${snap.result}` : "Match over", |
| 30 | sub: `match over · a new match starts in ${secs(snap.clock)}s`, |
| 31 | alpha: 1, |
| 32 | }; |
| 33 | if (snap.phase === "end") { |
| 34 | const outcome = roundOutcome(snap); |
| 35 | const next = `next round in ${secs(snap.clock)}s`; |
| 36 | if (!outcome) return { title: "Round over", sub: next, alpha: 1 }; |
| 37 | return { title: outcome.title, sub: `${outcome.why} · ${next}`, alpha: 1 }; |
| 38 | } |
| 39 | if (snap.phase === "buy" && snap.me) { |
| 40 | const shownFor = BUY_TIME - Number(snap.clock); |
| 41 | if (!(shownFor < CARD_FOR)) return null; |
| 42 | const alpha = Math.max(0, Math.min(1, (CARD_FOR - shownFor) / FADE)); |
| 43 | const side = snap.me.side === "atk" ? "you attack — plant the spike on A or B" : "you defend — hold A and B"; |
| 44 | return { title: `Round ${snap.round}`, sub: side, alpha }; |
| 45 | } |
| 46 | return null; |
| 47 | } |
| 48 | |
| 49 | /** A dark band across the view with the title and the line under it. */ |
| 50 | export function drawBanner(ctx, words, width, height) { |
| 51 | if (!words || words.alpha <= 0.01) return; |
| 52 | const unit = Math.min(width / 900, height / 570); |
| 53 | const mid = width / 2; |
| 54 | const top = height * 0.2; |
| 55 | const band = 92 * unit; |
| 56 | ctx.save(); |
| 57 | ctx.globalAlpha = words.alpha; |
| 58 | ctx.fillStyle = "rgba(8,10,14,0.78)"; |
| 59 | ctx.fillRect(0, top, width, band); |
| 60 | ctx.textAlign = "center"; |
| 61 | ctx.fillStyle = "#f2f3f6"; |
| 62 | ctx.font = `700 ${Math.round(34 * unit)}px system-ui, sans-serif`; |
| 63 | ctx.fillText(words.title, mid, top + 44 * unit); |
| 64 | ctx.fillStyle = "#b9c0cf"; |
| 65 | ctx.font = `${Math.round(17 * unit)}px system-ui, sans-serif`; |
| 66 | ctx.fillText(words.sub, mid, top + 74 * unit); |
| 67 | ctx.restore(); |
| 68 | } |