<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Javascript on hippotion</title><link>https://blog.hippotion.com/tags/javascript/</link><description>Recent content in Javascript on hippotion</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 04 Jul 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.hippotion.com/tags/javascript/index.xml" rel="self" type="application/rss+xml"/><item><title>🎲 I Built a Browser Game to Learn AI Coding Tools. It Turned Into Something Else.</title><link>https://blog.hippotion.com/posts/dice-and-shrines/</link><pubDate>Fri, 04 Jul 2025 00:00:00 +0000</pubDate><guid>https://blog.hippotion.com/posts/dice-and-shrines/</guid><description>What started as a Claude Code / Codex sandbox became a territory conquest game with five asymmetric guardians, procedurally generated hex maps, and a stats service to balance them. Here&amp;rsquo;s what happened.</description><content:encoded><![CDATA[<h2 id="it-started-as-a-sandbox">It started as a sandbox</h2>
<p>I wanted to get a feel for AI-assisted coding tools — Claude Code, Codex — in a low-stakes environment where breaking things was fine. A browser game seemed like the right vehicle: self-contained, no prod database, no users to disappoint.</p>
<p>I picked a premise I knew was fun: <strong>Dice Wars</strong>. The Flash-era classic. Roll dice to attack adjacent territories, biggest army snowballs. Simple enough that I could focus on the tooling rather than the design. Or so I thought.</p>
<p>Six weeks later I had five asymmetric character classes, a procedural hex map generator with acceptance criteria, a FastAPI telemetry service recording every dice roll, and a stat dashboard I check more than I probably should. The tooling became a background concern. The game took over.</p>
<hr>
<h2 id="how-the-game-works">How the game works</h2>
<p>The rules are genuinely minimal.</p>
<p>You start with a random slice of a procedurally generated map — a patchwork of irregular coloured territories, each one a cluster of hex tiles that reads as a solid blob. You and up to seven opponents begin scattered across it. Objective: own everything.</p>
<p><strong>Attacking</strong> is one click. Select your territory, click an adjacent enemy territory. Both sides roll all their dice and sum. Higher total wins. Attacker wins: you capture the territory, your dice advance in minus one. Defender wins: your attack is repelled, you&rsquo;re reduced to a single die on the attacking territory.</p>
<p><strong>Reinforcements</strong> are the mechanic that makes this a strategy game. At the end of your turn, you receive bonus dice equal to the size of your <strong>largest contiguous group of territories</strong>. Not total territories — the biggest connected blob. Fragmented territory generates almost nothing. A solid connected bloc snowballs.</p>
<p>That one rule creates the entire strategic texture. Grab fast but stay connected. Chokepoints are worth defending at a loss. Cutting an opponent in half collapses their income immediately. The late game turns into tense standoffs until one roll cracks something open.</p>
<p><img alt="An 8-player Epic game in progress — territories changing hands, rankings shifting in the side panel" loading="lazy" src="/posts/dice-and-shrines/midgame.png"></p>
<hr>
<h2 id="the-shrines">The shrines</h2>
<p>Early on I added shrines — special territories marked with a ★. They behave differently from normal territories in a few ways:</p>
<ul>
<li><strong>Higher dice cap</strong>: normal territories max out at 8 dice, shrines at 10</li>
<li><strong>Minimum floor</strong>: a shrine never drops below 2 dice after attacking, win or loss — it can&rsquo;t be stripped bare</li>
<li><strong>Guaranteed reinforcement</strong>: the shrine gets a die first at end of turn, before random distribution</li>
<li><strong>Aura</strong>: each of your own territories adjacent to the shrine gets a +1 guaranteed die (shown with a dim ◆ indicator)</li>
</ul>
<p>The shrine mechanic does something interesting to the risk calculus. Holding a shrine isn&rsquo;t just a territory — it&rsquo;s a node that warps the value of everything adjacent to it. You&rsquo;ll defend an aura territory harder than a territory of equivalent size elsewhere on the map, because losing it means losing the aura bonus. It also means attacking <em>into</em> a shrine aura is expensive: the shrine can&rsquo;t be worn down easily, and the neighboring territories keep refilling.</p>
<p>Shrines turned out to be the moment the math got interesting.</p>
<hr>
<h2 id="five-guardians">Five guardians</h2>
<p>The game has a character select screen. Each player — human or AI — picks a guardian before the map generates. Five options, each with a passive and an active ability that meaningfully change how you play:</p>
<p><strong>Hippo</strong> gets to manually place one reinforcement die each turn before the rest distribute randomly. One die placement doesn&rsquo;t sound like much until you realise you always have a frontline territory that needs it more than anywhere else. High floor, consistent.</p>
<p><strong>Hedgehog</strong> fills weakest territories first during reinforcement. Defensively solid — you never bleed out a border territory to zero while inland territories sit at cap. It doesn&rsquo;t generate more dice, but it wastes fewer.</p>
<p><strong>Fox</strong> banks a stored die every other turn and can spend it as a critical multiplier on an attack. The stored dice accumulate (up to a cap), so the power compounds if you resist using it. Two-hop flanking passive: Fox can attack across two territory hops from border territories, making it very hard to feel safely tucked away from.</p>
<p><strong>Owl</strong> has a passive two-hop attack range like Fox but for all attacks — Owl sees further. The active ability is a Dice Transfer: move dice from one of your territories to an adjacent friendly one. Lets you concentrate force without committing to an attack.</p>
<p><strong>Turtle</strong> gets 2 dice back to a neighboring territory any time it loses a defense. It&rsquo;s the only guardian that turns <em>taking damage</em> into a resource. Hard to snowball, but very hard to finish off.</p>
<p>The AI cycles through the five guardians deterministically, so in a full 8-player game you&rsquo;ll face at least one of each.</p>
<hr>
<h2 id="the-map">The map</h2>
<p>The map generator produces irregular coloured territories from a hex grid. Hexes are the visual scaffolding — what matters in gameplay is the blob they form. Internal edges within a territory are hidden; only the outer border of each cluster is drawn. The result reads like a contested piece of ground rather than a grid.</p>
<p><img alt="A freshly generated map before the first move — eight starting positions across irregular territory blobs" loading="lazy" src="/posts/dice-and-shrines/freshmap.png"></p>
<p>The generator has acceptance criteria. A proposed map is rejected if territory sizes are too uneven — a tiny starting territory with 2 hexes versus a sprawling 15-hex territory produces a wildly unfair game. The stats service actually records generation attempts and acceptance rates per map, so I can see how often the map generator throws away its own work.</p>
<p>The <code>mapId</code> is stamped on every game record, so I can eventually correlate map topology with game outcomes. I haven&rsquo;t done that analysis yet, but the data is there.</p>
<hr>
<h2 id="what-the-tools-actually-felt-like">What the tools actually felt like</h2>
<p>Claude Code handles the kind of work that benefits from holding a lot of context simultaneously: &ldquo;this change to <code>resolveAttack()</code> in <code>game.js</code> needs corresponding updates to the AI logic in <code>ai.js</code> and the render pass in <code>render.js</code>.&rdquo; That&rsquo;s tedious to track manually and exactly the kind of thing where the tool earns its keep.</p>
<p>Codex was more useful for the boilerplate-heavy parts — filling in schema.sql, wiring up FastAPI endpoints, the chart.js setup in the dashboard. Directed generation of code you know exactly how should look.</p>
<p>Neither tool replaces thinking. The guardian ability design, the shrine balance, the question of whether Fox&rsquo;s stored critical is too swingy in epic mode — that&rsquo;s all still just sitting down and working it out. The tools speed up the translation from &ldquo;I know what I want&rdquo; to &ldquo;here is working code.&rdquo; The game design itself doesn&rsquo;t compress.</p>
<hr>
<h2 id="its-live">It&rsquo;s live</h2>
<p>The game runs at <a href="https://dice.hippotion.com">dice.hippotion.com</a>. Single HTML file served from a Kubernetes ConfigMap on my homelab. No accounts, no install. It runs fast — AI turns are instant when you toggle off animations, and a full game can be over in five minutes or stretch to twenty depending on how the map falls.</p>
<p>Stats dashboard at <a href="https://game-stats.hippotion.com">game-stats.hippotion.com</a>. More on that in the next post.</p>
]]></content:encoded></item></channel></rss>