From 4594912325a46eaf01e2608ede3e94e67e3d1139 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Tue, 8 Sep 2026 12:04:51 -0400 Subject: [PATCH] feat(minigames): format OPORD Recall grids Arma style as 034-672 --- src/lib/opord-recall.ts | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/lib/opord-recall.ts b/src/lib/opord-recall.ts index bc845c0..7a4ac94 100644 --- a/src/lib/opord-recall.ts +++ b/src/lib/opord-recall.ts @@ -188,7 +188,7 @@ export interface OpordScenario { readonly operationName: string; /** e.g. "TRIDENT" */ readonly objective: string; - /** Eight-digit grid as digits only, e.g. "03467219". */ + /** Six-digit grid as digits only, e.g. "034672". */ readonly grid: string; /** Three-digit degrees, e.g. 47. */ readonly azimuth: number; @@ -217,9 +217,9 @@ export interface OpordScenario { readonly enRouteMin: number; } -/** Formats an 8-digit grid as two 4-digit halves, e.g. "0346 7219". */ +/** Formats a 6-digit grid Arma map style: three digits, hyphen, three digits, e.g. "034-672". */ export function formatOpordGrid(grid: string): string { - return grid.length === 8 ? `${grid.slice(0, 4)} ${grid.slice(4)}` : grid; + return grid.length === 6 ? `${grid.slice(0, 3)}-${grid.slice(3)}` : grid; } /** Buckets a 1-359 azimuth onto the eight winds; 359 and 1 both resolve to NORTH. */ @@ -231,7 +231,7 @@ export function generateOpordScenario(seed: number): OpordScenario { const rng = mulberry32(seed); const operationName = `${pick(rng, OPERATION_ADJECTIVES)} ${pick(rng, OPERATION_NOUNS)}`; const objective = pick(rng, OBJECTIVE_NAMES); - const grid = Array.from({ length: 8 }, () => Math.floor(rng() * 10)).join(""); + const grid = Array.from({ length: 6 }, () => Math.floor(rng() * 10)).join(""); const azimuth = pickInt(rng, 1, 359); const distanceKm = pickInt(rng, 4, 24); const hHourMinutes = pickInt(rng, 0, 23) * 100 + pick(rng, [0, 15, 30, 45]); @@ -327,22 +327,31 @@ export function buildOpordQuestions( CODEWORDS.filter((word) => word !== scenario.codeword && word !== scenario.abortCodeword), ).slice(0, 3); + const gridVariants = [ + scenario.grid.split("").reverse().join(""), + scenario.grid.slice(3) + scenario.grid.slice(0, 3), + scenario.grid + .split("") + .map((digit) => String((Number(digit) + 1) % 10)) + .join(""), + scenario.grid + .split("") + .map((digit) => String((Number(digit) + 2) % 10)) + .join(""), + ]; + // A palindrome or half-repeating grid collapses one variant onto the answer; + // dedupe first so the question always shows four distinct options. + const gridDistractors = gridVariants + .filter((value, index) => value !== scenario.grid && gridVariants.indexOf(value) === index) + .slice(0, 3); + const gridBuilder = () => // The objective grid is the core skill: always asked first. choiceQuestion( "grid", - "What is the 8-digit grid of the objective?", + "What is the 6-digit grid of the objective?", formatOpordGrid(scenario.grid), - [ - formatOpordGrid(scenario.grid.split("").reverse().join("")), - formatOpordGrid(scenario.grid.slice(4) + scenario.grid.slice(0, 4)), - formatOpordGrid( - scenario.grid - .split("") - .map((digit) => String((Number(digit) + 1) % 10)) - .join(""), - ), - ], + gridDistractors.map((value) => formatOpordGrid(value)), rng, );