Flag builder geometry
1. Foundational mental model
Section titled “1. Foundational mental model”Flag Builder (/challenges/flag-builder) names a country and asks the player to rebuild its flag in three steps: pick a layout, paint each region, then choose a symbol and where it goes. The result screen compares the answer with the stored puzzle and scores it out of MAX_SCORE (10).
A flag in this game is a small record, not an image:
export type FlagSymbol = | "none" | "star" | "stars" | "sun" | "crescent-star" | "cross" | "shield" | "leaf" | "bird" | "circle" | "emblem";
export type SymbolPlacement = "center" | "canton" | "hoist" | "none";Each puzzle in FLAG_BUILDER_PUZZLES stores a countryCode, a layout, an ordered colors array (one entry per region), a symbol, an optional symbolVariant, and a symbolPlacement. The same record type drives the live preview and the answer key, so the preview is exactly what gets scored.
canVisitPhase enforces the order: colors needs a layout, and symbol needs a layout plus colorsComplete (every region has a color). Going back is always allowed.
2. Implementation status & known gaps
Section titled “2. Implementation status & known gaps”Flag Builder is live and client-side. getRandomPuzzle picks uniformly from 59 hand-written puzzles with Math.random. For every puzzle, colors.length equals LAYOUT_REGION_COUNT[layout].
Not every layout or symbol is used as an answer. Three layouts (horizontal-top-heavy, saltire, quarters) appear in the picker but no puzzle uses them, so they only ever cost the player points. The bird symbol is drawn by the renderer but no puzzle uses it either.
3. Concrete implementation
Section titled “3. Concrete implementation”The 16 layouts
Section titled “The 16 layouts”buildLayoutSvg(layout, colors, W, H) returns SVG markup for the background. Region order matches the labels from getRegionLabels (for example Field, Canton). Geometry below is for any W × H. The canvas uses 300 × 200.
| Layout | Regions | Geometry | Puzzles |
|---|---|---|---|
solid | 1 | One full rect | 6 |
horizontal-bicolor | 2 | Two bands of | 3 |
horizontal-tricolor | 3 | Three bands of | 15 |
horizontal-tricolor-wide-center | 3 | Bands , , | 4 |
horizontal-top-heavy | 3 | Bands , , | 0 |
vertical-bicolor | 2 | Two bands of | 2 |
vertical-tricolor | 3 | Three bands of | 9 |
nordic-cross | 2 | Arms thick, vertical arm centred at | 4 |
symmetric-cross | 2 | Arms thick, centred | 1 |
diagonal | 2 | Triangle over the second color | 2 |
canton | 2 | Canton rect at the top left | 7 |
saltire | 2 | Two corner-to-corner lines, stroke | 0 |
circle-on-solid | 2 | Circle at , radius | 2 |
triangle-hoist | 2 | Bands color 1, white, color 1, then triangle to | 2 |
chevron | 3 | Two bands of , triangle to | 2 |
quarters | 2 | Four quadrants, colors on the diagonals | 0 |
Two representative cases, verbatim:
case "nordic-cross": { const armW = W * 0.15; const xOff = W * 0.3; return `<rect x="0" y="0" width="${W}" height="${H}" fill="${c(0)}"/><rect x="${xOff - armW / 2}" y="0" width="${armW}" height="${H}" fill="${c(1)}"/><rect x="0" y="${H / 2 - armW / 2}" width="${W}" height="${armW}" fill="${c(1)}"/>`; } case "circle-on-solid": return `<rect x="0" y="0" width="${W}" height="${H}" fill="${c(0)}"/><circle cx="${W * 0.45}" cy="${H / 2}" r="${H * 0.28}" fill="${c(1)}"/>`;An unset region renders in one of FLAG_REGION_PLACEHOLDERS (#3F4D6B, #8B9BB8, #D7DEEA), chosen by region index, so empty regions stay distinguishable in the preview.
Symbols
Section titled “Symbols”buildSymbolSvg places every symbol at an anchor and sizes it from one radius:
function symbolAnchor( placement: string | null, viewWidth: number, viewHeight: number): SymbolAnchor { if (placement === "canton") { return { x: viewWidth * 0.18, y: viewHeight * 0.28 }; } if (placement === "hoist") { return { x: viewWidth * 0.2, y: viewHeight * 0.5 }; } return { x: viewWidth / 2, y: viewHeight / 2 };}The radius is Math.min(viewWidth, viewHeight) * 0.16, which is 32 on the 300×200 canvas. pickSymbolPaint reads the region behind the symbol (region 1 for a canton placement, else region 0). A background with relative luminance above 0.62 gets a near-black symbol. A red-family background (red, dark-red, crimson, maroon) gets gold #FFD54F. Anything else gets off-white #F7F3E8.
buildFlagSvg joins layout, symbol and a 1.5-unit border rect. It skips the symbol when no layout is chosen.
Scoring
Section titled “Scoring” const layoutPoints = answer.layout === puzzle.layout ? 3 : 0;
let colorPoints = 0; if (layoutPoints > 0 && answer.colors.length === puzzle.colors.length) { const correct = answer.colors.filter( (color, index) => color === puzzle.colors[index] ).length; // Scale to 0–4 points colorPoints = Math.round((correct / puzzle.colors.length) * 4); }
let symbolPoints = 0; if (answer.symbol === puzzle.symbol) { symbolPoints = 1; const variantMatches = puzzle.symbolVariant == null || answer.symbolVariant === puzzle.symbolVariant; if (variantMatches) { symbolPoints += 1; } if (answer.symbolPlacement === puzzle.symbolPlacement) { symbolPoints += 1; }The code continues with an else if that awards 3 when both symbols are none. That branch cannot run: two none values are equal, so the first branch already handles them (variant is null, and the UI sets placement to none, giving 1 + 1 + 1 = 3).
handleSymbolConfirm then reports didWin: finalScore.total >= 8 and a guessCount of one for the layout, one per painted region, and one for the symbol. getScoreGrade labels 10 Perfect, 8–9 Excellent, 6–7 Good, 4–5 Fair, and below 4 Try Again.
4. Internal mechanics & mathematics
Section titled “4. Internal mechanics & mathematics”Color rounding
Section titled “Color rounding”With regions and correct, color points are
| 1 | 0 | 4 | ||
| 2 | 0 | 2 | 4 | |
| 3 | 0 | 1 (1.33) | 3 (2.67) | 4 |
On a three-region flag the second correct color is worth 2 points and the first and third are worth 1 each.
What it takes to win
Section titled “What it takes to win”Let , and . Since whenever ,
so the layout is mandatory for a win. With the layout right, a win needs . That allows, for example, all colors plus the right symbol in the wrong place (), or two of three colors plus symbol and placement ( when the puzzle has no variant).
Size of the answer space
Section titled “Size of the answer space”For a puzzle with regions, the player chooses from 16 layouts, color assignments, 11 symbols and up to 3 placements (plus variants where they exist). For a three-band flag and a plain symbol that is
combinations, but the phases split the problem: the layout is judged on its own, then each region is one choice out of 25.
Symbol geometry on the canvas
Section titled “Symbol geometry on the canvas”On the 300×200 canvas the canton rect spans and the canton anchor is , slightly left of and below the rect centre . The symbol radius of 32 fits inside the canton with at least 22 units to spare on the left edge. The hoist anchor sits inside the triangle-hoist triangle, whose tip reaches .
5. Threat model, failure modes & edge cases
Section titled “5. Threat model, failure modes & edge cases”- Answer exposure. The puzzle record is in the client bundle and component state. Flag Builder awards nothing that is verified server-side.
- Unused layouts.
horizontal-top-heavy,saltireandquartersare never correct. A player who picks one always scores at most 3. - Hard-coded white.
triangle-hoistalways draws color 1, white, color 1, then the triangle in color 2. Cuba (blue,red) comes out as a close approximation. The Philippines puzzle also storesblue,red, so its answer renders as blue-white-blue bands with a red triangle, while the real flag is blue over red with a white triangle. A player who paints the storedblue,redscores full color points while the preview looks unlike the real flag. - Layout change after painting.
ensureColorSlotsresets colors only when the region count changes. Switching between two layouts with the same count keeps the painted colors in region order, which may put them in a different place on the flag. - Placement default. Entering the symbol phase sets placement to
centerif none was chosen. A player who picks the right symbol but never touches placement gets the placement point only when the puzzle’s placement iscenter. - Proportions. Every flag is drawn 3:2, so shape is not part of the challenge. Square flags and 1:2 flags look the same as 3:2 flags here.
6. Architectural tradeoffs & non-goals
Section titled “6. Architectural tradeoffs & non-goals”| Decision | Benefit | Cost |
|---|---|---|
| Parametric layouts, not traced flags | Small data, instant preview, exact scoring | 59 of 205 countries covered. Many real flags cannot be expressed. |
| Fixed 3:2 canvas | One geometry per layout, simple tests | Proportion is lost. |
| Colors gated on layout | Prevents partial credit on a wrong structure | Harsh: a near-miss layout scores at most 3. |
| Symbol paint from luminance | Symbols stay visible on any background | Symbol color is picked for contrast, not taken from the real flag, and is not scored. |
| Uniform random puzzle | No backend, no history | Repeats are possible. 15 of 59 puzzles are horizontal tricolors. |
Non-goals: pixel-accurate flags (the rest of the site uses FlagImage for that), free drawing, and scoring symbol color or size.