<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Lucas Guzzo]]></title><description><![CDATA[Lucas Guzzo]]></description><link>https://blog.lucasguzzo.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 12:37:47 GMT</lastBuildDate><atom:link href="https://blog.lucasguzzo.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AwRust (part 1)]]></title><description><![CDATA[I’m starting a small project called awrust: a set of lightweight, Docker-first AWS service emulators written in Rust, MIT licensed.
The goal of this first post is to establish a foundation that’s:

easy to run,

easy to debug,

and easy to evolve wit...]]></description><link>https://blog.lucasguzzo.dev/awrust-part-1</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/awrust-part-1</guid><category><![CDATA[s3 emulator]]></category><category><![CDATA[AWS local development]]></category><category><![CDATA[Rust]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Mon, 16 Feb 2026 21:33:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1771234848610/9ab1ef79-1e6b-4944-beb7-3ad776010b02.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’m starting a small project called awrust: a set of lightweight, Docker-first AWS service emulators written in Rust, MIT licensed.</p>
<p>The goal of this first post is to establish a foundation that’s:</p>
<ul>
<li><p>easy to run,</p>
</li>
<li><p>easy to debug,</p>
</li>
<li><p>and easy to evolve without regret.</p>
</li>
</ul>
<p>So today we’ll do exactly one thing: ship a minimal server with a <code>/health</code> endpoint, and make sure it logs requests in a way that won’t annoy future me.</p>
<hr />
<h2 id="heading-the-why">The “why”</h2>
<p>S3 emulation sounds straightforward until you try to make it compatible with real tooling (AWS SDKs, AWS CLI), and then you realize the API surface is huge and full of edge cases. So instead of building “all of S3”, I’m building a small scoped emulator that’s great for local development and CI, and I’m writing down decisions as I go.</p>
<hr />
<h2 id="heading-project-setup-workspace-server">Project setup (workspace + server)</h2>
<p>Right now the workspace has two crates:</p>
<ul>
<li><p><code>awrust-s3-domain</code> — domain logic (empty for now)</p>
</li>
<li><p><code>awrust-s3-server</code> — the HTTP server</p>
</li>
</ul>
<p>At this stage, the server listens on <code>0.0.0.0:4566</code> and serves a single endpoint:</p>
<ul>
<li><code>GET /health</code> → <code>{"status":"ok"}</code></li>
</ul>
<hr />
<h2 id="heading-the-code-first-vertical-slice">The code (first vertical slice)</h2>
<p>Here’s the entire server for this first step:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">use</span> axum::{routing::get, Json, Router};
<span class="hljs-keyword">use</span> serde::Serialize;
<span class="hljs-keyword">use</span> std::net::SocketAddr;
<span class="hljs-keyword">use</span> tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
<span class="hljs-keyword">use</span> tracing::{info, Level};
<span class="hljs-keyword">use</span> tracing_subscriber::{fmt, EnvFilter};

<span class="hljs-meta">#[derive(Serialize)]</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">HealthResponse</span></span> {
    status: &amp;<span class="hljs-symbol">'static</span> <span class="hljs-built_in">str</span>,
}

<span class="hljs-meta">#[tokio::main]</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">main</span></span>() {
    init_tracing();

    <span class="hljs-keyword">let</span> app = Router::new()
        .route(<span class="hljs-string">"/health"</span>, get(health))
        .layer(
            TraceLayer::new_for_http()
                .make_span_with(DefaultMakeSpan::new().level(Level::INFO))
                .on_response(DefaultOnResponse::new().level(Level::INFO)),
        );

    <span class="hljs-keyword">let</span> addr: SocketAddr = <span class="hljs-string">"0.0.0.0:4566"</span>.parse().expect(<span class="hljs-string">"valid listen addr"</span>);
    info!(service = <span class="hljs-string">"s3"</span>, %addr, <span class="hljs-string">"awrust-s3 server starting"</span>);

    <span class="hljs-keyword">let</span> listener = tokio::net::TcpListener::bind(addr).<span class="hljs-keyword">await</span>.expect(<span class="hljs-string">"bind listen addr"</span>);
    axum::serve(listener, app).<span class="hljs-keyword">await</span>.expect(<span class="hljs-string">"server error"</span>);
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">health</span></span>() -&gt; Json&lt;HealthResponse&gt; {
    Json(HealthResponse { status: <span class="hljs-string">"ok"</span> })
}

<span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">init_tracing</span></span>() {
    <span class="hljs-keyword">let</span> filter = EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| EnvFilter::new(Level::INFO.to_string()));

    fmt()
        .json()
        .with_env_filter(filter)
        .with_current_span(<span class="hljs-literal">true</span>)
        .with_span_list(<span class="hljs-literal">true</span>)
        .init();
}
</code></pre>
<hr />
<h2 id="heading-why-these-dependencies">Why these dependencies?</h2>
<p>For now, I’m keeping the stack boring and well-supported:</p>
<ul>
<li><p><strong>Tokio</strong> — the async runtime (most of Rust’s server ecosystem assumes it)</p>
</li>
<li><p><strong>Axum</strong> — minimal HTTP routing on top of Hyper/Tokio</p>
</li>
<li><p><strong>Tracing + tracing-subscriber</strong> — structured logs and request-level context</p>
</li>
<li><p><strong>tower-http TraceLayer</strong> — request logging (method/path/status/latency) with minimal code</p>
</li>
</ul>
<p>I wrote short ADRs documenting these choices, because future changes are easier when “why” is recorded.</p>
<hr />
<h2 id="heading-why-wasnt-it-logging-requests">“Why wasn’t it logging requests?”</h2>
<p>One small gotcha: <code>TraceLayer</code> logs under the <code>tower_http</code> target, and log levels/filters matter.</p>
<p>I set explicit <code>INFO</code> levels for:</p>
<ul>
<li><p>span creation (<code>make_span_with</code>)</p>
</li>
<li><p>response logging (<code>on_response</code>)</p>
</li>
</ul>
<p>That way, hitting <code>/health</code> reliably prints something like:</p>
<pre><code class="lang-rust">{
  <span class="hljs-string">"level"</span>:<span class="hljs-string">"INFO"</span>,
  <span class="hljs-string">"target"</span>:<span class="hljs-string">"tower_http::trace::on_response"</span>,
  <span class="hljs-string">"fields"</span>:{<span class="hljs-string">"message"</span>:<span class="hljs-string">"finished processing request"</span>,<span class="hljs-string">"status"</span>:<span class="hljs-number">200</span>,<span class="hljs-string">"latency"</span>:<span class="hljs-string">"0 ms"</span>},
  <span class="hljs-string">"span"</span>:{<span class="hljs-string">"method"</span>:<span class="hljs-string">"GET"</span>,<span class="hljs-string">"uri"</span>:<span class="hljs-string">"/health"</span>}
}
</code></pre>
<p>This is important early. Debuggability is part of the product.</p>
<hr />
<h2 id="heading-running-it">Running it</h2>
<p>From the server crate:</p>
<pre><code class="lang-bash">cargo run
</code></pre>
<p>Then:</p>
<pre><code class="lang-bash">curl -s localhost:4566/health; <span class="hljs-built_in">echo</span>
</code></pre>
<p>To adjust log levels:</p>
<pre><code class="lang-bash">RUST_LOG=info cargo run
</code></pre>
<p>or to go noisier:</p>
<pre><code class="lang-bash">RUST_LOG=debug cargo run
</code></pre>
<hr />
<h2 id="heading-whats-next-part-2">What’s next (Part 2)</h2>
<p>Now that the server is alive and observable, the next step is the first real “S3-shaped” feature:</p>
<ul>
<li><p>a Store trait</p>
</li>
<li><p>a MemoryStore</p>
</li>
<li><p>and the first endpoints:</p>
<ul>
<li><p><code>PUT /&lt;bucket&gt;</code></p>
</li>
<li><p><code>PUT /&lt;bucket&gt;/&lt;key&gt;</code></p>
</li>
<li><p><code>GET /&lt;bucket&gt;/&lt;key&gt;</code></p>
</li>
</ul>
</li>
</ul>
<p>Once those work, I’ll validate the contract using AWS CLI.</p>
]]></content:encoded></item><item><title><![CDATA[Tiny Colony (part 4)]]></title><description><![CDATA[Up to now, the simulation was politely lying to me.
It looked like a colony sim:

pawns moved

trees got chopped

numbers went up

UI reacted


But under the hood, it was cheating.
There was only one pawn really doing work.
This post is about what ha...]]></description><link>https://blog.lucasguzzo.dev/tiny-colony-scaling</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/tiny-colony-scaling</guid><category><![CDATA[Rust]]></category><category><![CDATA[bevy]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Sun, 08 Feb 2026 00:44:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770509576230/da984555-f287-433b-945e-d883debad72b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Up to now, the simulation was politely lying to me.</p>
<p>It <em>looked</em> like a colony sim:</p>
<ul>
<li><p>pawns moved</p>
</li>
<li><p>trees got chopped</p>
</li>
<li><p>numbers went up</p>
</li>
<li><p>UI reacted</p>
</li>
</ul>
<p>But under the hood, it was cheating.</p>
<p>There was only <strong>one pawn</strong> really doing work.</p>
<p>This post is about what happened when I stopped cheating.</p>
<hr />
<h2 id="heading-from-one-pawn-to-many">From one pawn to many</h2>
<p>The first lie was obvious: <code>if pawn.id == 0 { ... }</code></p>
<p>That single check kept everything simple:</p>
<ul>
<li><p>one job at a time</p>
</li>
<li><p>no conflicts</p>
</li>
<li><p>no coordination problems</p>
</li>
</ul>
<p>Removing it was the first real step toward a <em>simulation</em> instead of a scripted demo.</p>
<p>Once I removed the early <code>break</code> in <code>tick_jobs</code>, every pawn started ticking… and immediately exposed a new class of problems.</p>
<p>That was expected. And, honestly, kind of satisfying.</p>
<hr />
<h2 id="heading-the-everyone-go-chop-the-same-tree-problem">The “everyone go chop the same tree” problem</h2>
<p>As soon as multiple pawns existed, they all did the same thing:</p>
<ul>
<li><p>find the nearest tree</p>
</li>
<li><p>walk to it</p>
</li>
<li><p>start chopping</p>
</li>
</ul>
<p>Which, of course, meant <strong>they all picked the same tree</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770509821106/f811669d-d4c6-413b-9786-0cdb1741c1c0.png" alt class="image--center mx-auto" /></p>
<p>This is the moment where you realize: <em>Pawns are not smart. They need to be coordinated</em></p>
<p>So I introduced a tiny but powerful concept: reservations.</p>
<hr />
<h2 id="heading-reservations-the-smallest-coordination-system-that-works">Reservations: the smallest coordination system that works</h2>
<p>I added a <code>Reservations</code> resource that tracks which world tiles are already claimed.</p>
<p>The rules are straightforward</p>
<ul>
<li><p>idle pawns reserve a tree before walking to it</p>
</li>
<li><p>reserved trees are skipped by other pawns</p>
</li>
<li><p>reservations are released when:</p>
<ul>
<li><p>chopping finishes</p>
</li>
<li><p>the tree disappears</p>
</li>
<li><p>the task aborts</p>
</li>
</ul>
</li>
</ul>
<p><strong>Resource:</strong></p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Resource)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Reservations</span></span> {
    <span class="hljs-keyword">pub</span> reserved_tiles: HashMap&lt;IVec2, Entity&gt;
}
</code></pre>
<p><strong>Claim on idle:</strong></p>
<pre><code class="lang-rust"><span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> <span class="hljs-literal">Some</span>(tree) = find_nearest_tree(map, from, reservations) {
    reservations.reserved_tiles.insert(tree, pawn_entity);
    Task::GoToTree(tree)
} <span class="hljs-keyword">else</span> {
    Task::Idle
}
</code></pre>
<p><strong>Release when done / invalid:</strong></p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> next = progress + <span class="hljs-number">1</span>;
<span class="hljs-keyword">if</span> next &gt;= <span class="hljs-number">10</span> {
    world::set_with_sprite(map, &amp;tile_entities, q_tiles, at.x, at.y, Tile::Ground);
    inv.wood += <span class="hljs-number">1</span>;
    <span class="hljs-keyword">if</span> reservations.reserved_tiles.get(&amp;at) == <span class="hljs-literal">Some</span>(&amp;pawn_entity) {
        reservations.reserved_tiles.remove(&amp;at);
    }
    Task::GoToStockpile
} <span class="hljs-keyword">else</span> {
    Task::Chop { at, progress: next }
}
</code></pre>
<p>No locks. No queues. No AI magic.<br />Just a set of intents.</p>
<p>And suddenly:</p>
<ul>
<li><p>pawns spread out naturally</p>
</li>
<li><p>no more dogpiling</p>
</li>
<li><p>task lifecycles became clearer</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770509954158/260bd9c1-3a4a-413e-8e9d-f28f4ab44208.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-selection-is-not-pawn-0">Selection is not pawn 0</h2>
<p>Another quiet lie in the earlier version:<br />the UI always showed pawn <code>id == 0</code>.</p>
<p>That’s fine, until you have more than one pawn.</p>
<p>So I added a <code>SelectedPawn</code> resource and some very basic click-to-select logic:</p>
<ul>
<li><p>click a pawn → it becomes selected</p>
</li>
<li><p>selected pawn gets a visual highlight</p>
</li>
<li><p>the inspector UI shows <em>that</em> pawn’s state</p>
</li>
</ul>
<p>Nothing fancy, but suddenly the simulation feels interactive instead of hardcoded.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770510048690/22656afc-2760-4d6e-81b2-a4881ed04f46.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-what-if-i-spawn-1000-pawns">“What if I spawn 1000 pawns?”</h2>
<p>This was the fun (and dangerous) thought.</p>
<p>I cranked <code>PAWN_COUNT</code> up to <strong>1000</strong> and immediately learned why most games don’t do this casually.</p>
<p>Spawning pawns with fixed offsets doesn’t scale, so I switched to:</p>
<ul>
<li><p>a spiral search around the stockpile</p>
</li>
<li><p>skipping trees and occupied tiles</p>
</li>
<li><p>validating positions against the world map</p>
</li>
</ul>
<p>This required reordering world initialization so pawn spawning could <em>query the map</em> instead of guessing.</p>
<pre><code class="lang-rust"><span class="hljs-keyword">for</span> p <span class="hljs-keyword">in</span> spiral_positions(stockpile, max_radius) {
    <span class="hljs-keyword">if</span> spawned &gt;= PAWN_COUNT { <span class="hljs-keyword">break</span>; }
    <span class="hljs-keyword">if</span> p.x &lt; <span class="hljs-number">0</span> || p.x &gt;= MAP_W || p.y &lt; <span class="hljs-number">0</span> || p.y &gt;= MAP_H { <span class="hljs-keyword">continue</span>; }
    <span class="hljs-keyword">if</span> world::get(map, p.x, p.y) == Tile::Tree { <span class="hljs-keyword">continue</span>; }
    <span class="hljs-keyword">if</span> occupied.contains(&amp;p) { <span class="hljs-keyword">continue</span>; }
    occupied.insert(p);

    commands.spawn((Pawn { id: spawned <span class="hljs-keyword">as</span> <span class="hljs-built_in">u32</span>, x: p.x, y: p.y }, <span class="hljs-comment">/* sprite */</span>, <span class="hljs-comment">/* task */</span>));
    spawned += <span class="hljs-number">1</span>;
}
</code></pre>
<p>The result:</p>
<ul>
<li><p>pawns spawn cleanly</p>
</li>
<li><p>no overlaps</p>
</li>
<li><p>no trees overwritten</p>
</li>
<li><p>no hardcoded magic numbers</p>
</li>
</ul>
<hr />
<h2 id="heading-the-beautiful-lag">The beautiful lag</h2>
<p>Then came the best part.</p>
<p>With 1000 pawns and a large forest, everything ran fine…<br />until trees started getting sparse.</p>
<p>Frame time exploded.</p>
<p>Why?</p>
<p>Because every pawn was scanning the <strong>entire map</strong> to find the nearest tree.</p>
<p>That’s when the illusion fully shattered. I added a FPS indicator to illustrate the problem.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770510163134/9df45c70-da40-4c52-a6bd-516be5f2012d.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-worldtrees-stop-scanning-the-universe">WorldTrees: stop scanning the universe</h2>
<p>The fix was simple and very unglamorous:</p>
<ul>
<li><p>maintain a <code>WorldTrees</code> index (<code>HashSet&lt;IVec2&gt;</code>)</p>
</li>
<li><p>iterate <em>only existing trees</em></p>
</li>
<li><p>remove trees from the index when chopped</p>
</li>
</ul>
<p><strong>Index:</strong></p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Resource)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">WorldTrees</span></span>(<span class="hljs-keyword">pub</span> HashSet&lt;IVec2&gt;);
</code></pre>
<p><strong>Iterate only trees instead of the full grid:</strong></p>
<pre><code class="lang-rust"><span class="hljs-keyword">for</span> &amp;target <span class="hljs-keyword">in</span> world_trees.<span class="hljs-number">0</span>.iter() {
    <span class="hljs-keyword">let</span> reserved = reservations.reserved_tiles.contains_key(&amp;target);
    <span class="hljs-keyword">if</span> !reserved &amp;&amp; world::get(map, target.x, target.y) == Tile::Tree {
        <span class="hljs-keyword">let</span> dist = (from.x - target.x).abs() + (from.y - target.y).abs();
        <span class="hljs-comment">// keep best...</span>
    }
}
</code></pre>
<p><strong>Remove when chopped:</strong></p>
<pre><code class="lang-rust">world_trees.<span class="hljs-number">0</span>.remove(&amp;at);
</code></pre>
<p>Suddenly:</p>
<ul>
<li><p>late-game performance stabilized</p>
</li>
<li><p>search cost scaled with remaining trees, not map size</p>
</li>
<li><p>the simulation stopped punishing success</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770510308972/f6d1b37b-9bb8-482b-9fe6-02b5e1be5e64.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-whats-next">What’s next</h2>
<p>At this point:</p>
<ul>
<li><p>multiple pawns work concurrently</p>
</li>
<li><p>jobs are coordinated</p>
</li>
<li><p>selection and inspection are real</p>
</li>
<li><p>performance problems are visible and explainable</p>
</li>
</ul>
<p>There’s still a lot that could be done here: collisions, better pathfinding, smarter work distribution.</p>
<p>But for now, I’m pretty happy watching <strong>1000 tiny idiots politely avoid chopping the same tree</strong>.</p>
<p>The code at this stage is available on GitHub under the <code>post-4</code> tag: <a target="_blank" href="https://github.com/GuzzoLM/tiny-colony/tree/post-4/tiny-colony">https://github.com/GuzzoLM/tiny-colony/tree/post-4/tiny-colony</a></p>
]]></content:encoded></item><item><title><![CDATA[Tiny Colony (part 3)]]></title><description><![CDATA[In the previous post, I set up the absolute basics of a tiny colony simulation in Rust using Bevy:a grid, some tiles, and a handful of pawns rendered as little circles.
Everything compiled. Things moved.And… that was about it.
At that point, pawns we...]]></description><link>https://blog.lucasguzzo.dev/tiny-colony-part-3</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/tiny-colony-part-3</guid><category><![CDATA[Rust]]></category><category><![CDATA[bevy]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Wed, 28 Jan 2026 23:13:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769559737067/45ecb5cb-4da7-4c85-b83f-3908644c766e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous post, I set up the absolute basics of a tiny colony simulation in Rust using Bevy:<br />a grid, some tiles, and a handful of pawns rendered as little circles.</p>
<p>Everything compiled. Things moved.<br />And… that was about it.</p>
<p>At that point, pawns were basically <strong>well-behaved pixels</strong>. They could move, but they didn’t really <em>do</em> anything. There was no intent, no persistence, no reason for a pawn to go somewhere other than <em>because I told it to</em>.</p>
<p>This post is about fixing that.</p>
<hr />
<h2 id="heading-the-real-problem-movement-is-not-behavior">The real problem: movement is not behavior</h2>
<p>Initially, I had a system that moved <em>pawn 0</em> toward a hardcoded target. It worked fine, but it immediately felt wrong:</p>
<ul>
<li><p>It didn’t scale beyond one pawn</p>
</li>
<li><p>The logic was brittle</p>
</li>
<li><p>Nothing was inspectable or debuggable</p>
</li>
<li><p>There was no concept of <em>why</em> a pawn was moving</p>
</li>
</ul>
<p>In other words:<br />I was telling pawns <strong>how</strong> to move, not <strong>what</strong> they were trying to achieve.</p>
<p>So I stopped doing that.</p>
<hr />
<h2 id="heading-tasks-making-pawn-intent-explicit">Tasks: making pawn intent explicit</h2>
<p>The first big shift was introducing a <code>Task</code> component.</p>
<p>Instead of directly moving pawns, each pawn now carries an explicit <em>job state</em>. In simplified form, it looks like this:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Component, Debug)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Task</span></span> {
    Idle,
    GoToTree(IVec2),
    Chop { at: IVec2, progress: <span class="hljs-built_in">f32</span> },
    GoToStockpile,
}
</code></pre>
<p>This enum does a lot of heavy lifting.</p>
<p>Instead of “a system that moves pawns”, the simulation now has <strong>pawns with intent</strong>. A pawn isn’t being pushed around, it’s <em>in a state</em>, and systems simply react to that state.</p>
<p>This has a few immediate benefits:</p>
<ul>
<li><p>Behavior becomes inspectable</p>
</li>
<li><p>Refactors stop breaking everything</p>
</li>
<li><p>UI becomes trivial later</p>
</li>
</ul>
<p>At this point, pawns finally stopped being dots and started being… workers.</p>
<hr />
<h3 id="heading-a-quick-aside-why-tasks-feel-so-nice-in-rust">A quick aside: why tasks feel so nice in Rust</h3>
<p>One thing I really liked here is how naturally tasks with different data fit into the model.</p>
<p>In Rust, enums aren’t just labels. Each variant can carry its own data:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Task</span></span> {
    Idle,
    GoToTree(IVec2),
    Chop { at: IVec2, progress: <span class="hljs-built_in">f32</span> },
    GoToStockpile,
}
</code></pre>
<p>Each task only carries <em>exactly</em> what it needs. No optional fields, no “only valid when state == X” comments.</p>
<p>This isn’t completely unique to Rust, but Rust makes it unusually pleasant and safe. In C# or Java this often turns into class hierarchies or nullable fields; in Python it’s easy to represent, but easy to misuse.</p>
<p>Here, the type system does most of the work: adding a new task forces you to handle it everywhere, and impossible states simply can’t exist.</p>
<p>For a simulation where behavior <em>is</em> state, that turned out to be a really good fit.</p>
<hr />
<h2 id="heading-the-world-grows-up-introducing-worldmap">The world grows up: introducing <code>WorldMap</code></h2>
<p>Once pawns had tasks, they needed to reason about the world:</p>
<ul>
<li><p>Is there still a tree at this position?</p>
</li>
<li><p>Did another pawn already chop it?</p>
</li>
<li><p>What happens to the tile when the tree is gone?</p>
</li>
</ul>
<p>To support this, I introduced a <code>WorldMap</code> resource. It owns:</p>
<ul>
<li><p>The tile grid</p>
</li>
<li><p>Tile types (<code>Ground</code>, <code>Tree</code>, <code>Stockpile</code>)</p>
</li>
<li><p>Small helpers to query and mutate tiles</p>
</li>
</ul>
<p>The important design choice here is ownership:</p>
<blockquote>
<p>Pawns don’t own the world.<br />They query it, reason about it, and sometimes modify it — but the world is shared state.</p>
</blockquote>
<p>This cleared up a lot of accidental coupling early on and made the simulation logic much easier to reason about.</p>
<hr />
<h2 id="heading-colony-state-stockpiling-without-pawns-knowing">Colony state: stockpiling without pawns knowing</h2>
<p>Next question: where does the chopped wood go?</p>
<p>Instead of letting pawns “own” resources, I added a <code>Colony</code> resource:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Resource, Default)]</span>
<span class="hljs-keyword">pub</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Colony</span></span> {
    <span class="hljs-keyword">pub</span> wood: <span class="hljs-built_in">u32</span>,
}
</code></pre>
<p>Pawns don’t know how much wood the colony has.<br />They just deliver items into it.</p>
<p>This separation turned out to be surprisingly nice:</p>
<ul>
<li><p>Pawns stay simple</p>
</li>
<li><p>Colony-wide logic stays centralized</p>
</li>
<li><p>UI and future systems have a clean place to read from</p>
</li>
</ul>
<p>It also mirrors how I want the game to feel later: pawns do work, the colony accumulates results.</p>
<hr />
<h2 id="heading-the-job-loop">The job loop</h2>
<p>At this point, the simulation loop stopped being about movement and became about <em>progression</em>.</p>
<p>Very roughly, a pawn now follows this loop:</p>
<pre><code class="lang-rust">Idle
 → GoToTree
 → Chop (with progress)
 → GoToStockpile
 → DropOff
 → Idle
</code></pre>
<p>No single system “knows” this whole flow.</p>
<p>Each task handler only knows:</p>
<ul>
<li><p>What it needs to do right now</p>
</li>
<li><p>When it’s done</p>
</li>
<li><p>What the next task should be</p>
</li>
</ul>
<p>For example, chopping a tree looks roughly like this:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">handle_chop</span></span>(
    pawn: &amp;<span class="hljs-keyword">mut</span> Task,
    colony: &amp;<span class="hljs-keyword">mut</span> Colony,
    world: &amp;<span class="hljs-keyword">mut</span> WorldMap,
) {
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> Task::Chop { at, progress } = pawn {
        *progress += <span class="hljs-number">0.01</span>;

        <span class="hljs-keyword">if</span> *progress &gt;= <span class="hljs-number">1.0</span> {
            world.set_with_sprite(*at, Tile::Ground);
            colony.wood += <span class="hljs-number">1</span>;
            *pawn = Task::GoToStockpile;
        }
    }
}
</code></pre>
<p>No scheduler.<br />No behavior tree.<br />Just “are we done yet?” — and if so, move on.</p>
<p>And yes, watching the first pawn successfully chop a tree felt <em>way</em> better than it should have.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769640883796/c3a18995-36ad-4165-92b5-3912dfd05bdc.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-observability-adding-the-first-ui">Observability: adding the first UI</h2>
<p>At this point, the simulation worked. But I couldn’t <em>see</em> what was going on inside it.</p>
<p>So I added UI early. Not for gameplay, but for observability.</p>
<p>Two simple UI sections:</p>
<h3 id="heading-colony-ui">Colony UI</h3>
<ul>
<li>Current wood count</li>
</ul>
<h3 id="heading-pawn-inspector">Pawn inspector</h3>
<ul>
<li><p>Pawn ID</p>
</li>
<li><p>Grid position</p>
</li>
<li><p>Current task</p>
</li>
<li><p>Task progress (when applicable)</p>
</li>
</ul>
<p>This paid off immediately.</p>
<p>Suddenly I could:</p>
<ul>
<li><p>See task transitions</p>
</li>
<li><p>Verify progress increments</p>
</li>
<li><p>Spot logic bugs instantly</p>
</li>
</ul>
<p>The UI isn’t gameplay yet.<br />It’s a debugging tool that happens to be on screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769640951913/ef836117-7650-450a-86a6-b4de37eae3f4.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-a-small-detour-change-detection">A small detour: change detection</h2>
<p>While wiring up the UI, I ran into something new (for me): change detection.</p>
<p>My first version updated the UI every frame. That works, but it’s noisy, both mentally and architecturally.</p>
<p>Bevy lets you filter queries so systems only run when something actually changed:</p>
<pre><code class="lang-rust">Query&lt;(&amp;Pawn, &amp;Task), Changed&lt;Task&gt;&gt;
</code></pre>
<p>This query only returns pawns whose <code>Task</code> component changed since the last frame. If a pawn is still chopping the same tree, the UI system doesn’t even run for it.</p>
<p>A few clarifications:</p>
<ul>
<li><p>This is not an optimization I needed yet</p>
</li>
<li><p>It’s mostly about intent: “update UI when state changes”</p>
</li>
<li><p>Unexpected UI updates now usually mean unexpected state changes</p>
</li>
</ul>
<p>I like this pattern because it nudges the code toward being <strong>event-driven</strong>, without having to introduce explicit events this early.</p>
<hr />
<h2 id="heading-keeping-visuals-honest-updating-tile-sprites">Keeping visuals honest: updating tile sprites</h2>
<p>One annoying issue showed up quickly:</p>
<ul>
<li><p>A tree would be chopped in the simulation…</p>
</li>
<li><p>…but visually, it stayed a tree forever</p>
</li>
</ul>
<p>To fix this, I added a simple mapping from logical tiles to sprite entities. Tile updates now go through a centralized helper that updates both:</p>
<ul>
<li><p>The world state</p>
</li>
<li><p>The corresponding sprite color</p>
</li>
</ul>
<p>Once this was in place, visuals stopped lying.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769641039685/3261d62b-abc5-4bc1-8932-9e7686db0ec1.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-small-refactors-that-paid-off">Small refactors that paid off</h2>
<p>As the systems grew, I made a few unglamorous but valuable cleanups:</p>
<ul>
<li><p>Centralized tile + sprite updates</p>
</li>
<li><p>Used change detection for UI instead of per-frame updates</p>
</li>
<li><p>Reduced UI marker components into a single enum</p>
</li>
<li><p>Cleaned up system signatures and warnings</p>
</li>
</ul>
<p>None of these are flashy, but together they made the codebase much calmer to work in.</p>
<hr />
<h2 id="heading-where-things-stand-now">Where things stand now</h2>
<p>At this point, the project has crossed an important threshold.</p>
<p>The simulation now:</p>
<ul>
<li><p>Has intentional pawn behavior</p>
</li>
<li><p>Modifies shared world state</p>
</li>
<li><p>Keeps visuals in sync automatically</p>
</li>
<li><p>Exposes internal state via UI</p>
</li>
</ul>
<p>It’s still tiny.<br />It’s still very much a toy.</p>
<p>But it’s no longer just rendering things, it’s <strong>simulating a system</strong>.</p>
<p>In the next steps, I want to explore:</p>
<ul>
<li><p>Multiple pawns competing for work</p>
</li>
<li><p>Job selection instead of hardcoded flows</p>
</li>
<li><p>Basic player interaction and selection</p>
</li>
</ul>
<p>For now though, this feels like a good stopping point.</p>
<p>The dots have learned to work. 🙂</p>
<p>The code at this stage is available on GitHub under the <code>post-</code>3 tag: <a target="_blank" href="https://github.com/GuzzoLM/tiny-colony/tree/post-3/tiny-colony">https://github.com/GuzzoLM/tiny-colony/tree/post-3/tiny-colony</a></p>
]]></content:encoded></item><item><title><![CDATA[Tiny Colony (part 2)]]></title><description><![CDATA[In the previous post, I defined the scope of Tiny Colony: a small, 2D, grid-based colony simulator inspired by games like RimWorld, built mainly as a way to learn Rust and explore Bevy.
This post covers the next step: rendering a grid, spawning pawns...]]></description><link>https://blog.lucasguzzo.dev/tiny-colony-something-moving</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/tiny-colony-something-moving</guid><category><![CDATA[Rust]]></category><category><![CDATA[bevy]]></category><category><![CDATA[Game Development]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Mon, 26 Jan 2026 00:14:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769386386644/d77a41bc-8ee5-4d91-9c93-dd672dce1d5c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous post, I defined the scope of <em>Tiny Colony</em>: a small, 2D, grid-based colony simulator inspired by games like RimWorld, built mainly as a way to learn Rust and explore Bevy.</p>
<p>This post covers the next step: <strong>rendering a grid, spawning pawns, and moving one of them using a fixed simulation tick</strong>.</p>
<hr />
<h2 id="heading-the-world-is-just-a-grid">The world is just a grid</h2>
<p>At its core, the world is a fixed-size grid:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> MAP_W: <span class="hljs-built_in">i32</span> = <span class="hljs-number">64</span>;
<span class="hljs-keyword">pub</span> <span class="hljs-keyword">const</span> MAP_H: <span class="hljs-built_in">i32</span> = <span class="hljs-number">64</span>;
</code></pre>
<p>Tiles are stored as a flat vector:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Clone, Copy)]</span>
<span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Tile</span></span> {
    Ground,
    Tree,
    Stockpile,
}
</code></pre>
<p>There’s no map abstraction yet — just a <code>Vec&lt;Tile&gt;</code> and some helpers to translate between grid coordinates and world coordinates.</p>
<pre><code class="lang-rust"><span class="hljs-function"><span class="hljs-keyword">fn</span> <span class="hljs-title">grid_to_world</span></span>(x: <span class="hljs-built_in">i32</span>, y: <span class="hljs-built_in">i32</span>) -&gt; Vec3 {
    <span class="hljs-keyword">let</span> origin_x = -(MAP_W <span class="hljs-keyword">as</span> <span class="hljs-built_in">f32</span>) * TILE_SIZE * <span class="hljs-number">0.5</span> + TILE_SIZE * <span class="hljs-number">0.5</span>;
    <span class="hljs-keyword">let</span> origin_y = -(MAP_H <span class="hljs-keyword">as</span> <span class="hljs-built_in">f32</span>) * TILE_SIZE * <span class="hljs-number">0.5</span> + TILE_SIZE * <span class="hljs-number">0.5</span>;

    Vec3::new(
        origin_x + x <span class="hljs-keyword">as</span> <span class="hljs-built_in">f32</span> * TILE_SIZE,
        origin_y + y <span class="hljs-keyword">as</span> <span class="hljs-built_in">f32</span> * TILE_SIZE,
        <span class="hljs-number">0.0</span>,
    )
}
</code></pre>
<p>This keeps the simulation grid-centered and avoids camera math early on.</p>
<hr />
<h2 id="heading-rendering-tiles-brute-force-yay">Rendering tiles (brute force, yay!)</h2>
<p>Each tile is rendered as a simple sprite, tinted by type:</p>
<pre><code class="lang-rust">commands.spawn((
    Sprite {
        color,
        custom_size: <span class="hljs-literal">Some</span>(Vec2::splat(TILE_SIZE - TILE_GAP)),
        ..default()
    },
    Transform::from_translation(world_pos),
));
</code></pre>
<p>This means spawning <strong>64 × 64 = 4096 sprites</strong> on startup.</p>
<p>Is that optimal? Probably not.<br />Is it good enough for now? Absolutely.</p>
<p>One of the nice things about Bevy is how easy it is to brute-force something visible without caring about performance too early.</p>
<hr />
<h2 id="heading-pawns-are-data-first-visuals-second">Pawns are data first, visuals second</h2>
<p>A pawn is deliberately boring:</p>
<pre><code class="lang-rust"><span class="hljs-meta">#[derive(Component)]</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Pawn</span></span> {
    id: <span class="hljs-built_in">u32</span>,
    x: <span class="hljs-built_in">i32</span>,
    y: <span class="hljs-built_in">i32</span>,
}
</code></pre>
<p>No animation, no state machine, no behavior tree.</p>
<p>Visually, pawns are just small circles rendered slightly above tiles:</p>
<pre><code class="lang-rust">commands.spawn((
    Pawn { id, x, y },
    Sprite {
        image: circle_image.clone(),
        color: Color::srgb(<span class="hljs-number">0.85</span>, <span class="hljs-number">0.85</span>, <span class="hljs-number">0.95</span>),
        custom_size: <span class="hljs-literal">Some</span>(Vec2::splat(TILE_SIZE - <span class="hljs-number">2.0</span>)),
        ..default()
    },
    Transform::from_translation(world_pos + Vec3::Z),
));
</code></pre>
<p>The circle texture itself is generated in code, not loaded from disk. This keeps iteration fast and avoids asset management entirely at this stage.</p>
<hr />
<h2 id="heading-a-fixed-simulation-tick">A fixed simulation tick</h2>
<p>The most important step so far was introducing a <strong>fixed simulation clock</strong>.</p>
<pre><code class="lang-rust"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Sim</span></span> {
    paused: <span class="hljs-built_in">bool</span>,
    speed: <span class="hljs-built_in">f32</span>,
    tick: Timer,
    target: IVec2,
}
</code></pre>
<p>This separates:</p>
<ul>
<li><p>simulation time (deterministic)</p>
</li>
<li><p>rendering time (best effort)</p>
</li>
</ul>
<p>The tick runs at 10 Hz:</p>
<pre><code class="lang-rust">Timer::from_seconds(<span class="hljs-number">0.10</span>, TimerMode::Repeating)
</code></pre>
<p>And can be paused or sped up independently from framerate.</p>
<hr />
<h2 id="heading-moving-one-pawn-dumbest-way-possible">Moving one pawn (dumbest way possible)</h2>
<p>With the simulation tick in place, I implemented the simplest possible behavior:</p>
<blockquote>
<p>Move pawn <code>id = 0</code> one grid tile per tick toward a fixed target.</p>
</blockquote>
<pre><code class="lang-rust"><span class="hljs-keyword">if</span> pawn.x &lt; target.x {
    pawn.x += <span class="hljs-number">1</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> pawn.x &gt; target.x {
    pawn.x -= <span class="hljs-number">1</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> pawn.y &lt; target.y {
    pawn.y += <span class="hljs-number">1</span>;
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> pawn.y &gt; target.y {
    pawn.y -= <span class="hljs-number">1</span>;
}
</code></pre>
<p>After updating the grid position, the render transform is updated accordingly:</p>
<pre><code class="lang-rust"><span class="hljs-keyword">let</span> pos = grid_to_world(pawn.x, pawn.y);
transform.translation = pos + Vec3::Z;
</code></pre>
<p>No smoothing, no interpolation, no pathfinding.</p>
<p>But once the pawn started moving, the project immediately felt more “alive”.</p>
<hr />
<h2 id="heading-early-lessons">Early lessons</h2>
<p>A few things became clear very quickly:</p>
<ul>
<li><p>Bevy makes it easy to get something visible fast</p>
</li>
<li><p>Rust’s borrow checker shows up early, but usually points to real design improvements</p>
</li>
<li><p>Fixed-tick simulation feels <em>right</em> for this genre</p>
</li>
</ul>
<p>Most importantly: keeping the scope small made it possible to actually finish these steps.</p>
<hr />
<h2 id="heading-current-state">Current state</h2>
<p>At this point, Tiny Colony has:</p>
<ul>
<li><p>a grid-based world</p>
</li>
<li><p>basic terrain</p>
</li>
<li><p>multiple pawns as ECS entities</p>
</li>
<li><p>a fixed simulation loop</p>
</li>
<li><p>pause and speed controls</p>
</li>
<li><p>one pawn moving deterministically</p>
</li>
</ul>
<p>That’s enough of a foundation to start building <em>actual gameplay</em>.</p>
<p>The code at this stage is available on GitHub under the <code>post-1</code> tag: <a target="_blank" href="https://github.com/GuzzoLM/tiny-colony/tree/post-1/tiny-colony">https://github.com/GuzzoLM/tiny-colony/tree/post-1/tiny-colony</a></p>
]]></content:encoded></item><item><title><![CDATA[A Tiny Colony Simulation in Rust (Part 1)]]></title><description><![CDATA[I’ve tried starting this game before.
More than once, actually. A small, fantasy-flavored colony simulation: a few people, a piece of land, some basic production, and the feeling that things slowly start to work on their own. Every time I approached ...]]></description><link>https://blog.lucasguzzo.dev/tiny-colony-scope</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/tiny-colony-scope</guid><category><![CDATA[Rust]]></category><category><![CDATA[bevy]]></category><category><![CDATA[#Game Design]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Sun, 25 Jan 2026 23:45:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769384521026/c3ffd915-c9d7-43b1-abee-f8fa24f4ff1d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’ve tried starting this game before.</p>
<p>More than once, actually. A small, fantasy-flavored colony simulation: a few people, a piece of land, some basic production, and the feeling that things slowly start to work on their own. Every time I approached it with decent intentions and familiar tools (Unity, C#, things I use professionally), and every time it quietly died.</p>
<p>The reasons were always the same: lack of time, increasing scope, and no clear stopping point.</p>
<p>This time, I’m trying something different. I want to keep the project small, treat it as a learning exercise, and, most importantly, actually finish something. I’ve also been wanting to learn Rust for a while, mostly out of curiosity. This project is a way to combine both goals.</p>
<p>This is not a commercial game, and it’s not meant to be a polished tutorial series. Think of it more as a public lab notebook: documenting decisions, constraints, and small steps taken along the way.</p>
<hr />
<h2 id="heading-the-game">The “game”</h2>
<p>At its core, the game is a very small, RimWorld-inspired colony simulation.</p>
<p>There is a fixed grid world. A handful of pawns live in it. Some tiles contain trees, which can be chopped for wood. There is a stockpile. Pawns autonomously go to trees, chop them, and haul the wood back to the stockpile.</p>
<p>That’s the entire gameplay loop.</p>
<p>There are no grand narratives, no diplomacy systems, no deep needs simulation. Just a simple loop that is easy to reason about and, crucially, easy to finish.</p>
<h3 id="heading-in-scope">In scope</h3>
<p>For the first version, the scope is deliberately constrained to:</p>
<ul>
<li><p>a fixed-size grid map</p>
</li>
<li><p>around 10 pawns</p>
</li>
<li><p>trees with a finite amount of wood</p>
</li>
<li><p>hauling wood to a stockpile</p>
</li>
<li><p>a fixed tick-based simulation loop</p>
</li>
<li><p>minimal UI for debug information (wood count, tick rate, pause/speed controls)</p>
</li>
</ul>
<h3 id="heading-explicitly-out-of-scope">Explicitly out of scope</h3>
<p>Equally important is what this version will <em>not</em> include:</p>
<ul>
<li><p>hunger, mood, relationships, or social systems</p>
</li>
<li><p>building placement or construction</p>
</li>
<li><p>diplomacy, factions, combat, or military mechanics</p>
</li>
<li><p>advanced pathfinding or optimization</p>
</li>
<li><p>editor tooling or polished UI</p>
</li>
</ul>
<hr />
<h2 id="heading-why-rust">Why Rust</h2>
<p>I don’t have a professional reason to use Rust here. This is not about picking the “best” language.</p>
<p>I’ve wanted to learn Rust for a while because it sits at an interesting intersection of systems programming, safety, and performance. Simulation-heavy problems are a natural fit for its strengths, and the language forces a level of clarity that I find appealing.</p>
<p>Using Rust here is mostly about curiosity and learning, trying something new outside my usual stack, in a context where correctness and explicit data flow matter.</p>
<hr />
<h2 id="heading-why-bevy">Why Bevy</h2>
<p>For the engine, I chose Bevy.</p>
<p>Bevy is a Rust-native game engine built around an ECS (Entity-Component-System) architecture. That model maps very naturally to simulation-heavy games, where behavior emerges from many small rules applied over large sets of entities.</p>
<p>I also like Bevy’s code-first workflow. There’s no heavy editor dependency, and most of the logic lives directly in Rust code. That makes it easier to reason about what’s actually happening, and easier to keep the project lightweight.</p>
<p>Bevy isn’t the simplest option, and it’s certainly not the most mature engine available. But it strikes a good balance between control and productivity, and it’s a solid excuse to explore ECS-driven design in practice.</p>
<hr />
<h2 id="heading-high-level-architecture">High-level architecture</h2>
<p>At a high level, the project follows a few simple principles:</p>
<ul>
<li><p>the game is built using ECS concepts: entities are data, behavior lives in systems</p>
</li>
<li><p>the simulation runs on a fixed tick rate</p>
</li>
<li><p>rendering is decoupled from simulation logic</p>
</li>
<li><p>most game behavior is data-driven rather than object-oriented</p>
</li>
</ul>
<p>There’s no attempt here to design a reusable framework or an extensible engine. This is a small, purpose-built simulation.</p>
<hr />
<h2 id="heading-constraints-and-rules">Constraints and rules</h2>
<p>To avoid repeating past mistakes, I’m imposing a few rules on myself:</p>
<ul>
<li><p>no expanding scope mid-iteration</p>
</li>
<li><p>one small feature per milestone</p>
</li>
<li><p>refactor only when something actively hurts</p>
</li>
<li><p>once a milestone works, stop and write about it</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Exact Algorithms for the Traveling Salesman Problem: Finding Optimal Solutions]]></title><description><![CDATA[TL;DR
This post explores exact algorithms for solving the Traveling Salesman Problem (TSP), including brute force, dynamic programming (Held-Karp), and branch-and-bound. These methods guarantee the shortest path but come with varying levels of comput...]]></description><link>https://blog.lucasguzzo.dev/tsp-exact-algorithms</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/tsp-exact-algorithms</guid><category><![CDATA[branch and bound]]></category><category><![CDATA[TSP]]></category><category><![CDATA[Dynamic Programming]]></category><category><![CDATA[algorithms]]></category><category><![CDATA[travelling salesman problem]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Tue, 03 Dec 2024 13:53:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732763206384/93780e03-d1bc-4b4d-8942-c5c0eb31b924.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-tldr"><strong>TL;DR</strong></h3>
<p><em>This post explores exact algorithms for solving the Traveling Salesman Problem (TSP), including brute force, dynamic programming (Held-Karp), and branch-and-bound. These methods guarantee the shortest path but come with varying levels of computational expense.</em></p>
<p><em>You can find the complete implementations for all algorithms discussed in this series, as well as a simple benchmark tool for running comparisons, in my</em> <a target="_blank" href="https://github.com/GuzzoLM/TSP">GitHub repository</a>.</p>
<hr />
<p>In the world of optimization, finding the <em>exact</em> solution to the Traveling Salesman Problem (TSP) is like discovering the holy grail of route planning. While heuristic and metaheuristic methods can provide approximate solutions quickly, exact algorithms go a step further—they guarantee the shortest possible path, offering unmatched precision.</p>
<p>But precision comes at a cost. Exact algorithms are computationally expensive, especially as the number of cities grows. Despite this, they remain invaluable tools for small to medium-sized problems and for understanding the theoretical underpinnings of TSP.</p>
<p>In this post, we’ll explore three cornerstone exact methods: <strong>brute force</strong>, <strong>dynamic programming (Held-Karp)</strong>, and <strong>branch-and-bound</strong>. Each method takes a unique approach to tackle the exponential complexity of TSP, and we’ll break down how they work, where they shine, and their limitations. In the end we will compare them and check when it’s advisable to use each one.</p>
<p>Ready to dive into the world of optimal solutions? Let’s get started!</p>
<h3 id="heading-brute-force-the-simplest-approach"><strong>Brute Force: The Simplest Approach</strong></h3>
<p>The brute force method systematically explores all possible routes for the Traveling Salesman Problem (TSP) to guarantee finding the optimal solution. While elegant in its simplicity, it quickly becomes computationally expensive due to factorial growth (O(n-1)!).</p>
<p>To ensure consistency and facilitate comparisons between different algorithms, all solvers in this series implement the same interface:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">TspSolver</span> </span>{
    <span class="hljs-keyword">int</span>[] solve(<span class="hljs-keyword">int</span>[][] distanceMatrix);
    <span class="hljs-function">String <span class="hljs-title">getName</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<p>This interface defines two methods:</p>
<ul>
<li><p><code>solve(int[][] distanceMatrix)</code>: Takes a distance matrix as input and returns the optimal route as an array of city indices.</p>
</li>
<li><p><code>getName()</code>: Returns the name of the algorithm for easy identification.</p>
</li>
</ul>
<p>By adhering to this interface, each solver can be swapped in and out seamlessly, allowing for straightforward comparisons of performance, accuracy, and computational efficiency.</p>
<p>The brute force method is implemented as follows:</p>
<hr />
<h4 id="heading-step-1-preparing-the-list-of-cities"><strong>Step 1: Preparing the List of Cities</strong></h4>
<p>First, we need a list of cities (indices) to generate all possible routes. The city at index 0 is treated as the starting point, so we exclude it from the list of cities to permute:</p>
<pre><code class="lang-java"><span class="hljs-keyword">var</span> cities = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[distanceMatrix.length - <span class="hljs-number">1</span>];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">1</span>; i &lt; distanceMatrix.length; i++) {
    cities[i - <span class="hljs-number">1</span>] = i;
}
</code></pre>
<p>Here:</p>
<ul>
<li><p><code>distanceMatrix.length</code> is the total number of cities.</p>
</li>
<li><p>We populate the <code>cities</code> array with indices from 1 to (n−1), as city 0 is fixed as the starting and ending point.</p>
</li>
</ul>
<hr />
<h4 id="heading-step-2-generating-all-possible-routes"><strong>Step 2: Generating All Possible Routes</strong></h4>
<p>We use recursion to generate all permutations of the cities. The recursive method <code>getRoutes</code> takes the following parameters:</p>
<ul>
<li><p><code>cities</code>: The list of city indices to permute.</p>
</li>
<li><p><code>visited</code>: A boolean array to track which cities have been included in the current route.</p>
</li>
<li><p><code>route</code>: An array to store the current permutation.</p>
</li>
<li><p><code>start</code>: The current depth of recursion.</p>
</li>
</ul>
<p>Here’s how the recursive method works:</p>
<pre><code class="lang-java"><span class="hljs-keyword">private</span> List&lt;<span class="hljs-keyword">int</span>[]&gt; getRoutes(<span class="hljs-keyword">int</span>[] cities, <span class="hljs-keyword">boolean</span>[] visited, <span class="hljs-keyword">int</span>[] route, <span class="hljs-keyword">int</span> start) {
    <span class="hljs-keyword">var</span> routes = <span class="hljs-keyword">new</span> ArrayList&lt;<span class="hljs-keyword">int</span>[]&gt;();

    <span class="hljs-comment">// Base case: if the route is complete, add it to the list</span>
    <span class="hljs-keyword">if</span> (start == cities.length) {
        routes.add(route.clone());
    }

    <span class="hljs-comment">// Recursive case: add each unvisited city to the route</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; cities.length; i++) {
        <span class="hljs-keyword">if</span> (visited[i]) <span class="hljs-keyword">continue</span>;

        <span class="hljs-comment">// Mark the city as visited and add it to the route</span>
        route[start] = cities[i];
        visited[i] = <span class="hljs-keyword">true</span>;

        <span class="hljs-comment">// Recurse to fill the next position in the route</span>
        routes.addAll(getRoutes(cities, visited, route, start + <span class="hljs-number">1</span>));

        <span class="hljs-comment">// Backtrack: unmark the city and reset the route position</span>
        route[start] = <span class="hljs-number">0</span>;
        visited[i] = <span class="hljs-keyword">false</span>;
    }

    <span class="hljs-keyword">return</span> routes;
}
</code></pre>
<hr />
<h4 id="heading-step-3-evaluating-each-route"><strong>Step 3: Evaluating Each Route</strong></h4>
<p>Once all routes are generated, we use the <code>DistanceCalculator</code> class to evaluate each route and find the shortest one.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DistanceCalculator</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">DistanceCalculator</span><span class="hljs-params">()</span> </span>{}

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">calculateTotalDistance</span><span class="hljs-params">(<span class="hljs-keyword">int</span>[] route, <span class="hljs-keyword">int</span>[][] distances)</span> </span>{
        <span class="hljs-keyword">var</span> totalDistance = <span class="hljs-number">0</span>;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; route.length - <span class="hljs-number">1</span>; i++) {
            <span class="hljs-keyword">var</span> city = route[i];
            <span class="hljs-keyword">var</span> nextCity = route[i + <span class="hljs-number">1</span>];
            totalDistance += distances[city][nextCity];
        }

        <span class="hljs-keyword">return</span> totalDistance;
    }
}
</code></pre>
<p>This utility method calculates the total distance for a given route by iterating through the route array and summing the distances between consecutive cities. The final distance is returned.</p>
<p>Here’s how it integrates into the solver.</p>
<pre><code class="lang-java"><span class="hljs-keyword">var</span> bestRoute = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[cities.length + <span class="hljs-number">2</span>];
<span class="hljs-keyword">var</span> currentRoute = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[cities.length + <span class="hljs-number">2</span>];
<span class="hljs-keyword">var</span> shortestDistance = Integer.MAX_VALUE;

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> route : routes) {
    <span class="hljs-comment">// Insert the start and end points (city 0)</span>
    System.arraycopy(route, <span class="hljs-number">0</span>, currentRoute, <span class="hljs-number">1</span>, cities.length);

    <span class="hljs-comment">// Calculate the total distance of the route</span>
    <span class="hljs-keyword">var</span> routeDistance = DistanceCalculator.calculateTotalDistance(currentRoute, distanceMatrix);

    <span class="hljs-comment">// Update the shortest distance and best route if necessary</span>
    <span class="hljs-keyword">if</span> (routeDistance &lt; shortestDistance) {
        shortestDistance = routeDistance;
        bestRoute = currentRoute.clone();
    }
}
</code></pre>
<hr />
<h4 id="heading-example-walkthrough"><strong>Example Walkthrough</strong></h4>
<p>Suppose we have 4 cities with the following distance matrix:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>A</td><td>B</td><td>C</td><td>D</td></tr>
</thead>
<tbody>
<tr>
<td>A</td><td>0</td><td>10</td><td>15</td><td>20</td></tr>
<tr>
<td>B</td><td>10</td><td>0</td><td>35</td><td>25</td></tr>
<tr>
<td>C</td><td>15</td><td>35</td><td>0</td><td>30</td></tr>
<tr>
<td>D</td><td>20</td><td>25</td><td>30</td><td>0</td></tr>
</tbody>
</table>
</div><ol>
<li><p>The recursive method generates all possible routes:</p>
<ul>
<li>[B, C, D], [B, D, C], [C, B, D], etc.</li>
</ul>
</li>
<li><p>Each route is evaluated by inserting city A (0) at the start and end:</p>
<ul>
<li>A → B → C → D → A = 10 + 35 + 30 + 20 = 95.</li>
</ul>
</li>
<li><p>The shortest route and its distance are returned.</p>
</li>
</ol>
<hr />
<h4 id="heading-strengths-and-limitations"><strong>Strengths and Limitations</strong></h4>
<p><strong>Strengths</strong>:</p>
<ul>
<li><p>Guarantees the shortest route (optimal solution).</p>
</li>
<li><p>Straightforward to understand and implement.</p>
</li>
</ul>
<p><strong>Limitations</strong>:</p>
<ul>
<li><p>Computationally expensive (O(n-1)!).</p>
</li>
<li><p>Becomes infeasible for larger datasets.</p>
</li>
</ul>
<h3 id="heading-dynamic-programming-the-held-karp-algorithm">Dynamic Programming: The Held-Karp Algorithm</h3>
<p>Dynamic programming is a powerful technique for solving optimization problems by breaking them down into smaller, overlapping subproblems. Instead of solving each subproblem repeatedly, results are stored and reused, significantly reducing redundant computations.</p>
<p>The <strong>Held-Karp algorithm</strong> applies this principle to the Traveling Salesman Problem (TSP), offering a more efficient alternative to brute force. Although it still has exponential time complexity (O(n²⋅2^n)), it’s far better than the factorial growth of brute force (O(n!)).</p>
<p>The algorithm uses a dynamic programming table to store the shortest paths. Here's how it works:</p>
<p><strong>Define Subproblems</strong>:<br />Let <code>dp[S][i]</code> represent the shortest path to visit all cities in the subset S, ending at city i.</p>
<ul>
<li><p>S: A subset of cities (excluding the starting city).</p>
</li>
<li><p>i: The last city in the path.</p>
</li>
</ul>
<p><strong>Base Case</strong>:<br />For the starting city (city 0):</p>
<p>$$dp[\{0\}][0]=0$$</p><p>This means the cost to start at city 0 is 0.</p>
<p><strong>Recursive Relation</strong>:<br />For each subset S containing city i, calculate:</p>
<p>$$dp[S][i] = \min_{j \in S, j \neq i} \left( dp[S \setminus \{i\}][j] + \text{dist}[j][i] \right)$$</p><p>This means the shortest path to i is the minimum cost of visiting a city j in subset S (excluding i), plus the cost of traveling from j to i.</p>
<p><strong>Final Step</strong>:<br />After filling the table, calculate the shortest path that returns to the starting city:</p>
<p>$$\text{cost} = \min_{i \neq 0} \left( dp[\{1, 2, \dots, n-1\}][i] + \text{dist}[i][0] \right)$$</p><p><strong>Example Walkthrough</strong></p>
<p>Suppose we have the same scenario from the example solved with Brute Force.</p>
<h4 id="heading-step-1-initialization"><strong>Step 1: Initialization</strong></h4>
<p>We start by defining the dynamic programming table dp[S][i]</p>
<p>Initially:</p>
<p>$$dp[\{0\}][0]=0$$</p><p>This means the cost of starting at city A (city 0) is 0.</p>
<p>All other entries in the table are initialized to infinity (∞), as they represent uncomputed or invalid states.</p>
<h4 id="heading-step-2-building-the-table"><strong>Step 2: Building the Table</strong></h4>
<p>We compute the values of dp[S][i] iteratively for all subsets S of increasing size. Let’s break it down:</p>
<p><strong>Subset S={0,1} ending at B (city 1):</strong></p>
<p>$$dp[\{0,1\}][1]=dp[\{0\}][0] + dist[0][1]$$</p><p>Substituting values:</p>
<p>$$dp[\{0,1\}][1]=0+10=10$$</p><p><strong>Subset S={0,2} ending at C (city 2):</strong></p>
<p>$$dp[\{0,2\}][2]=dp[\{0\}][0] + dist[0][2]$$</p><p>Substituting values:</p>
<p>$$dp[\{0,2\}][2]=0 + 15=15$$</p><p><strong>Subset S={0,3} ending at D (city 3):</strong></p>
<p>$$dp[\{0,3\}][3] = dp[\{0\}][0]+dist[0][3]$$</p><p>Substituting values:</p>
<p>$$dp[\{0,3\}][3] = 0 + 20=20$$</p><h4 id="heading-step-3-expanding-subsets"><strong>Step 3: Expanding Subsets</strong></h4>
<p>We now compute for larger subsets, such as S={0,1,2} and S = {0,1,2,3}.</p>
<p><strong>Subset S={0,1,2} ending at C (city 2):</strong></p>
<p>$$dp[\{0,1,2\}][2]=min(dp[\{0,1\}][1]+dist[1][2])$$</p><p>Substituting values:</p>
<p>$$dp[\{0,1,2\}][2] = min(10 + 35) = 45$$</p><p>Applying to the all subsets of this same size, we have:</p>
<ul>
<li><p>dp[{0,1,2}][1]=50</p>
</li>
<li><p>dp[{0,1,2}][2]=45</p>
</li>
<li><p>dp[{0,1,3}][1]=45</p>
</li>
<li><p>dp[{0,1,3}][3]=35</p>
</li>
<li><p>dp[{0,2,3}][2]=50</p>
</li>
<li><p>dp[{0,2,3}][3]=45</p>
</li>
</ul>
<p><strong>Subset S={0,1,2,3} ending at D (city 3):</strong></p>
<p>$$dp[\{0,1,2,3\}][3]=min(dp[\{0,1,2\}][1]+dist[1][3],dp[\{0,1,2\}][2]+dist[2][3])$$</p><p>Substituting values:</p>
<p>$$dp[\{0,1,2,3\}][3]=min(50+35,45+30)=min(85,75)=75$$</p><p>Applying to the all subsets of this same size, we have:</p>
<ul>
<li><p>dp[{0,1,2,3}][1]=70</p>
</li>
<li><p>dp[{0,1,2,3}][2]=65</p>
</li>
<li><p>dp[{0,1,2,3}][3]=75</p>
</li>
</ul>
<h4 id="heading-step-4-final-step"><strong>Step 4: Final Step</strong></h4>
<p>After filling all entries in the table, compute the minimum cost of completing the cycle</p>
<p>$$\text{cost} = \min_{i \neq 0} \left( dp[\{0,1,2,3\}][i] + \text{dist}[i][0] \right)$$</p><p>Substituting values:</p>
<p>$$\text{cost} = \min_{i \neq 0} \left( dp[\{0,1,2,3\}][1] + \text{dist}[1][0],\{0,1,2,3\}][2] + \text{dist}[2][0],\{0,1,2,3\}][3] + \text{dist}[3][0] \right)$$</p><p>$$\text{cost} = \min_{i \neq 0} \left( 70 + 10, 65+15, 75+20 \right)=min(80,80,95)=80$$</p><p><strong>Step 5: Reconstruct the shortest route:</strong></p>
<p>The minimum cost is 80 and it occurs when the route ends either at B (city 1) or C (city 2). Reconstructing the route back from the dynamic programming table, we have the two optimal routes.</p>
<ul>
<li><p>A → C → D→ B → A</p>
</li>
<li><p>A → B → D → C → A</p>
</li>
</ul>
<h4 id="heading-implementation"><strong>Implementation</strong></h4>
<p>Now that we know how this method works, let’s begin implementing our new solver. We’ll start by initializing the needed variables.</p>
<pre><code class="lang-java"><span class="hljs-keyword">var</span> n = distanceMatrix.length; <span class="hljs-comment">// Number of cities</span>
<span class="hljs-keyword">var</span> dp = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[<span class="hljs-number">1</span> &lt;&lt; n][n]; <span class="hljs-comment">// The dynamic programming table</span>
<span class="hljs-keyword">var</span> parent = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[<span class="hljs-number">1</span> &lt;&lt; n][n]; <span class="hljs-comment">// Stores the optimal end city from previous subsets</span>
</code></pre>
<p>You might have noticed we initialized the matrix with size <code>1 &lt;&lt; n</code>. For those not familiarized, <code>1 &lt;&lt; n</code> is a bitwise operation that computes 2^n, representing the total number of subsets of <code>n</code> cities. Here’s how it works:</p>
<ul>
<li><p><strong>Bitmasking</strong>: We use an integer's binary representation to encode subsets of cities. Each bit in the integer corresponds to whether a particular city is included in the subset:</p>
<ul>
<li><p><strong>Bit 0</strong>: Represents city 0 (A).</p>
</li>
<li><p><strong>Bit 1</strong>: Represents city 1 (B).</p>
</li>
<li><p><strong>Bit 2</strong>: Represents city 2 (C), and so on.</p>
</li>
</ul>
</li>
<li><p><strong>Example for n=4</strong>:</p>
<ul>
<li><p>1&lt;&lt;4=16, meaning there are 2^4 = 16 subsets of cities.</p>
</li>
<li><p>Each subset is represented by a binary number:</p>
<ul>
<li><p>0001: Subset {0}, only city A.</p>
</li>
<li><p>0101: Subset {0,2}, cities A and C.</p>
</li>
<li><p>1111: Subset {0,1,2,3}, all cities.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Bitmasking uses bitwise operations to efficiently manipulate and check subsets:</p>
<ol>
<li><p><strong>Checking if a City is in the Subset</strong>:</p>
<ul>
<li><p><code>mask &amp; (1 &lt;&lt; city)</code> checks whether the bit corresponding to <code>city</code> is set to 1 in <code>mask</code>.</p>
</li>
<li><p><strong>Example</strong>: If <code>mask = 5</code> (0101):</p>
<ul>
<li><p><code>mask &amp; (1 &lt;&lt; 2)</code> → 0101 &amp; 0100 = 0100 ≠ 0, so city 2 is in the subset.</p>
</li>
<li><p><code>mask &amp; (1 &lt;&lt; 1)</code> → 0101 &amp; 0010 = 0000 = 0, so city 1 is not in the subset.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Adding a City to the Subset</strong>:</p>
<ul>
<li><p><code>mask | (1 &lt;&lt; city)</code> sets the bit corresponding to <code>city</code> to 1.</p>
</li>
<li><p><strong>Example</strong>: Add city 1 to <code>mask = 5</code> (0101):</p>
<ul>
<li>0101 | 0010 = 0111, so the new subset is {0,1,2}.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Removing a City from the Subset</strong>:</p>
<ul>
<li><p><code>mask ^ (1 &lt;&lt; city)</code> toggles the bit corresponding to <code>city</code>.</p>
</li>
<li><p><strong>Example</strong>: Remove city 2 from <code>mask = 5</code> (0101):</p>
<ul>
<li>0101 ^ 0100 = 0001, so the new subset is {0}.</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>Using bitmasking for representing the subsets gives us efficiency (check, add and remove lements is O(1)) and a compact representation, requiring only O(n) bits for each subset.</p>
<p>Back to our implementation, now we initialize our DP table, setting all entries to infinity. This ensures that only valid transitions update the table. If a state is never computed, it remains at infinity.</p>
<pre><code class="lang-java"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; (<span class="hljs-number">1</span> &lt;&lt; n); i++) {
    Arrays.fill(dp[i], Integer.MAX_VALUE);
}

dp[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;
</code></pre>
<p>We also set our base case, with a cost of 0, as there are no cost associated with starting at the starting city. City A (city 0) is represented by subset 1 (0001), a subset containing only this city.</p>
<p>Now we need to populate the DP table with the corrects costs for each route. We do this by iterating over all subset of cities.</p>
<pre><code class="lang-java"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> mask = <span class="hljs-number">1</span>; mask &lt; (<span class="hljs-number">1</span> &lt;&lt; n); mask++) {
    <span class="hljs-keyword">if</span> ((mask &amp; <span class="hljs-number">1</span>) == <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// Ensure city 0 is always in the subset</span>
</code></pre>
<p>for every subset we to iterate over all its potential final cities. We check if a city is in the subset by doing <code>mask &amp; 1 &lt;&lt; city</code>.</p>
<pre><code class="lang-java"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> city = <span class="hljs-number">1</span>; city &lt; n; city++) {
    <span class="hljs-keyword">if</span> ((mask &amp; <span class="hljs-number">1</span> &lt;&lt; city) == <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// Skip if city is not in the subset</span>
</code></pre>
<p>Now, to calculate the cost of this route, we need to get the current subset without the current city. We do this with <code>mask ^ (1 &lt;&lt; city)</code>. Then we compute its cost plus the distance to current city, fo every possible ending city in that subset. The minimum value will be the cost for the current subset ending in the current city.</p>
<pre><code class="lang-java"><span class="hljs-keyword">int</span> prevMask = mask ^ (<span class="hljs-number">1</span> &lt;&lt; city);

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> lastCity = <span class="hljs-number">0</span>; lastCity &lt; n; lastCity++) {
    <span class="hljs-keyword">if</span> ((prevMask &amp; <span class="hljs-number">1</span> &lt;&lt; lastCity) == <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>; <span class="hljs-comment">// Skip if lastCity is not in prevMask</span>

<span class="hljs-keyword">var</span> distance = dp[prevMask][lastCity] == Integer.MAX_VALUE ?
        Integer.MAX_VALUE :
        dp[prevMask][lastCity] + distanceMatrix[lastCity][city];

<span class="hljs-keyword">if</span> (distance &lt; dp[mask][city]) {
    dp[mask][city] = distance;
    parent[mask][city] = lastCity;
}
</code></pre>
<p>Notice that we are storing the last city used for this route in the parent matrix. We will use that in the future when reconstructing the best route.</p>
<p>To find the best route, we get the subset that includes all cities and calculate its final cost, meaning the cost to end in every possible city plus the distance from that city to city A.</p>
<pre><code class="lang-java"><span class="hljs-keyword">int</span> mask = (<span class="hljs-number">1</span> &lt;&lt; n) - <span class="hljs-number">1</span>; <span class="hljs-comment">// All cities included</span>
<span class="hljs-keyword">int</span> lastCity = <span class="hljs-number">0</span>;
<span class="hljs-keyword">int</span> shortestDistance = Integer.MAX_VALUE;

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">1</span>; i &lt; n; i++) {
    <span class="hljs-keyword">int</span> routeCost = dp[mask][i] + distanceMatrix[i][<span class="hljs-number">0</span>];
    <span class="hljs-keyword">if</span> (routeCost &lt; shortestDistance) {
        shortestDistance = routeCost;
        lastCity = i;
    }
}
</code></pre>
<p>Knowing the best route, and the city where it ends, we can start reconstructing the complete path.</p>
<pre><code class="lang-java"><span class="hljs-keyword">int</span>[] bestRoute = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[n + <span class="hljs-number">1</span>];

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = n - <span class="hljs-number">1</span>; i &gt; <span class="hljs-number">0</span>; i--) {
    bestRoute[i] = lastCity;
    lastCity = parent[mask][lastCity];
    mask ^= (<span class="hljs-number">1</span> &lt;&lt; bestRoute[i]); <span class="hljs-comment">// Remove lastCity from the subset</span>
}
</code></pre>
<p>This is how the final implementation should look like.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HeldKarpSolver</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">TspSolver</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Held-Karp"</span>;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span>[] solve(<span class="hljs-keyword">int</span>[][] distanceMatrix) {
        <span class="hljs-keyword">var</span> n = distanceMatrix.length; <span class="hljs-comment">// Number of cities</span>
        <span class="hljs-keyword">var</span> dp = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[<span class="hljs-number">1</span> &lt;&lt; n][n]; <span class="hljs-comment">// The dynamic programming table</span>
        <span class="hljs-keyword">var</span> parent = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[<span class="hljs-number">1</span> &lt;&lt; n][n]; <span class="hljs-comment">// Stores the optimal end city from previous subsets</span>

        <span class="hljs-comment">// Initialize DP table with infinity</span>
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; (<span class="hljs-number">1</span> &lt;&lt; n); i++) {
            Arrays.fill(dp[i], Integer.MAX_VALUE);
        }

        <span class="hljs-comment">// Base case: city 0 is the starting point and the total distance of this subset is 0.</span>
        dp[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>;

        <span class="hljs-comment">// Iterate over all subsets of cities, where mask is the bitmask of the subset</span>
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> mask = <span class="hljs-number">1</span>; mask &lt; (<span class="hljs-number">1</span> &lt;&lt; n); mask++) {

            <span class="hljs-comment">// Since we always start from city 0, we only consider subsets where it is present.</span>
            <span class="hljs-keyword">if</span> ((mask &amp; <span class="hljs-number">1</span>) == <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>;

            <span class="hljs-comment">// Iterate over all possible final cities from this subset to calculate subset distances</span>
            <span class="hljs-comment">// Since city 0 will never be the final city of the subset, we start fromm city 1</span>
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> city = <span class="hljs-number">1</span>; city &lt; n; city++) {

                <span class="hljs-comment">// Check if city is in subset</span>
                <span class="hljs-keyword">if</span> ((mask &amp; <span class="hljs-number">1</span> &lt;&lt; city) == <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>;

                <span class="hljs-comment">// Gets a subset of the current subset where the current city is not present</span>
                <span class="hljs-keyword">int</span> prevMask = mask ^ (<span class="hljs-number">1</span> &lt;&lt; city);

                <span class="hljs-comment">// Iterate over all possible final cities from previous subset to find the best final city</span>
                <span class="hljs-comment">// Since previous subset could also be just the city 0, we start this iteration from 0</span>
                <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> lastCity = <span class="hljs-number">0</span>; lastCity &lt; n; lastCity++) {
                    <span class="hljs-keyword">if</span> ((prevMask &amp; <span class="hljs-number">1</span> &lt;&lt; lastCity) == <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>;

                    <span class="hljs-keyword">var</span> distance = dp[prevMask][lastCity] == Integer.MAX_VALUE ?
                            Integer.MAX_VALUE :
                            dp[prevMask][lastCity] + distanceMatrix[lastCity][city];

                    <span class="hljs-keyword">if</span> (distance &lt; dp[mask][city]){
                        dp[mask][city] = distance;
                        parent[mask][city] = lastCity;
                    }
                }
            }
        }

        <span class="hljs-comment">// Find the minimum cost of returning to the starting city</span>
        <span class="hljs-keyword">int</span> mask = (<span class="hljs-number">1</span> &lt;&lt; n) - <span class="hljs-number">1</span>;
        <span class="hljs-keyword">int</span> lastCity = <span class="hljs-number">0</span>;
        <span class="hljs-keyword">int</span> shortestDistance = Integer.MAX_VALUE;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">1</span>; i &lt; n; i++) {
            <span class="hljs-keyword">int</span> routeCost = dp[mask][i] + distanceMatrix[i][<span class="hljs-number">0</span>];

            <span class="hljs-keyword">if</span> (routeCost &lt; shortestDistance) {
                shortestDistance = routeCost;
                lastCity = i;
            }
        }

        <span class="hljs-comment">// Reconstruct the path moving backwards</span>
        <span class="hljs-keyword">int</span>[] bestRoute = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[n + <span class="hljs-number">1</span>];
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = n - <span class="hljs-number">1</span>; i &gt; <span class="hljs-number">0</span>; i--) {
            bestRoute[i] = lastCity;
            lastCity = parent[mask][lastCity];

            <span class="hljs-comment">// After adding last city to the route, we get the subset where it is missing</span>
            mask ^= (<span class="hljs-number">1</span> &lt;&lt; bestRoute[i]);
        }

        <span class="hljs-keyword">return</span> bestRoute;
    }
}
</code></pre>
<h4 id="heading-strengths-and-limitations-1"><strong>Strengths and Limitations</strong></h4>
<h4 id="heading-strengths"><strong>Strengths</strong></h4>
<ol>
<li><p><strong>Guarantees Optimality</strong>:<br /> Like brute force, the Held-Karp algorithm ensures the shortest possible route is found. It’s a powerful method for solving small to medium-sized Traveling Salesman Problem (TSP) instances.</p>
</li>
<li><p><strong>Improved Efficiency Over Brute Force</strong>:<br /> While brute force has O(n!) time complexity, Held-Karp reduces this to O(n²⋅2^n), which is significantly faster for larger m.</p>
</li>
<li><p><strong>Scales Better for Medium-Sized Problems</strong>:<br /> Practical for n≤20~30, depending on available memory and computational resources.</p>
</li>
</ol>
<hr />
<h4 id="heading-limitations"><strong>Limitations</strong></h4>
<ol>
<li><p><strong>Exponential Time Complexity</strong>:<br /> Despite being more efficient than brute force, Held-Karp still has exponential growth in runtime due to the 2^n subsets. This makes it impractical for n&gt;30 in most cases.</p>
</li>
<li><p><strong>High Memory Usage</strong>:<br /> The O(n²⋅2^n) space complexity requires storing results for all subsets of cities and possible end cities. Memory usage grows exponentially as n increases.</p>
</li>
<li><p><strong>Implementation Complexity</strong>:<br /> Compared to brute force, Held-Karp is more challenging to implement due to its reliance on bitmasking and dynamic programming.</p>
</li>
</ol>
<h3 id="heading-branch-and-bound-a-smarter-brute-force"><strong>Branch and Bound: A Smarter Brute Force</strong></h3>
<p>The <strong>Branch and Bound (B&amp;B)</strong> method is an optimization algorithm that builds on brute force by significantly reducing the number of paths explored. It does this by:</p>
<ol>
<li><p><strong>Branching</strong>: Systematically dividing the problem into smaller subproblems (branches).</p>
</li>
<li><p><strong>Bounding</strong>: Calculating an optimistic lower bound for each branch to determine if it’s worth exploring further.</p>
</li>
</ol>
<p>If the lower bound of a branch exceeds the current best solution, that branch is pruned (skipped), saving computation time.</p>
<h4 id="heading-how-it-works"><strong>How It Works</strong></h4>
<ol>
<li><p><strong>Initial Setup</strong>:<br /> Start with all cities and consider the initial city (e.g., A) as the root node of a search tree. Each branch represents a potential route to explore.</p>
</li>
<li><p><strong>Branching</strong>:<br /> At each level of the tree, add one city to the current path, generating new branches. Continue branching until a complete tour is formed.</p>
</li>
<li><p><strong>Bounding</strong>:<br /> For each branch, calculate a <strong>lower bound</strong> on the possible total cost of completing the tour. The lower bound is an estimate of the minimum possible cost, considering the current partial path and the best-case scenario for the remaining cities.</p>
</li>
<li><p><strong>Pruning</strong>:<br /> If the lower bound of a branch is greater than or equal to the cost of the current best solution, discard that branch. Otherwise, explore it further.</p>
</li>
<li><p><strong>Complete and Compare</strong>:<br /> When a complete tour is found, compare its cost to the current best solution. Update the best solution if the new tour has a lower cost.</p>
</li>
</ol>
<h4 id="heading-why-its-better-than-brute-force"><strong>Why It’s Better than Brute Force</strong></h4>
<ul>
<li><p><strong>Brute Force</strong> explores all possible routes exhaustively, while Branch and Bound prunes unnecessary branches early.</p>
</li>
<li><p>For nnn cities, brute force evaluates n!n!n! routes, whereas B&amp;B evaluates only a subset of promising routes, reducing computational effort significantly.</p>
</li>
</ul>
<p><strong>Example Walkthrough</strong></p>
<p>Let’s use the same example we used for the other methods.</p>
<ul>
<li><p><strong>Root Node</strong>:<br />  Start at city A (0). The lower bound is calculated based on the minimum outgoing edges for all cities:</p>
<ul>
<li><p>From A: Min(10, 15, 20) = 10.</p>
</li>
<li><p>From B: Min(10, 25, 35) = 10.</p>
</li>
<li><p>From C: Min(15, 30, 35) = 15.</p>
</li>
<li><p>From D: Min(20, 25, 30) = 20.<br />  Total lower bound:</p>
</li>
</ul>
</li>
</ul>
<p>$$\frac{10 + 10 + 15 + 20}{2} = 27.5$$</p><p>Branching begins by selecting the next city to visit.</p>
<ul>
<li><p><strong>First Branch</strong>:<br />  A → B. The new lower bound is calculated based on the remaining cities (C, D). If the lower bound is higher than the current best solution, this branch is pruned.</p>
</li>
<li><p><strong>Continue Branching</strong>:<br />  Evaluate branches like A → B → C and A → B → D, calculating lower bounds for each. Prune branches as necessary.</p>
</li>
<li><p><strong>Completion</strong>:<br />  Once a complete tour is found (e.g., A → B → C → D → A), compare its cost to the current best solution and update if it’s better.</p>
</li>
</ul>
<p><strong>Implementation</strong></p>
<p>Now let’s implement our new Branch &amp; Bound solver. As always, we begin initializing the main variable we’ll be using throught the method.</p>
<pre><code class="lang-java"><span class="hljs-keyword">var</span> route = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[distanceMatrix.length + <span class="hljs-number">1</span>];
<span class="hljs-keyword">var</span> bestRoute = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[distanceMatrix.length + <span class="hljs-number">1</span>];
<span class="hljs-keyword">var</span> visited = <span class="hljs-keyword">new</span> <span class="hljs-keyword">boolean</span>[distanceMatrix.length];
<span class="hljs-keyword">var</span> cities = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[distanceMatrix.length];

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; distanceMatrix.length; i++) {
    cities[i] = i;
}
</code></pre>
<p>Now to thet actuallogic. This method is a modification of the Brute Force methd and you will notice some similarities. The difference relies in the fact that we calculate the route cost while we create the route and compare it with the the best value we’ve got so far. If it’s not smalles than the best value, then we move on to the next iteration.</p>
<pre><code class="lang-java"><span class="hljs-keyword">if</span> (level == cities.length) {
    <span class="hljs-keyword">int</span> totalCost = currentCost + distanceMatrix[currentCity][<span class="hljs-number">0</span>];
    <span class="hljs-keyword">if</span> (totalCost &lt; minCost.get()) {
        minCost.set(totalCost);
        System.arraycopy(currentPath, <span class="hljs-number">0</span>, bestRoute, <span class="hljs-number">0</span>, cities.length);
    }
    <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>We also perform this check earlier in the permutation, using the lower bound technique.</p>
<pre><code class="lang-java"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> nextCity = <span class="hljs-number">0</span>; nextCity &lt; cities.length; nextCity++) {
    <span class="hljs-keyword">if</span> (!visited[nextCity]) {
        <span class="hljs-keyword">var</span> newCost = currentCost + distanceMatrix[currentCity][nextCity];
        <span class="hljs-keyword">var</span> lowerBound = getBound(nextCity, cities, visited, distanceMatrix);

        <span class="hljs-comment">// Prune if the new lower bound is not promising</span>
        <span class="hljs-keyword">if</span> (newCost + lowerBound &lt; minCost.get()) {
            visited[nextCity] = <span class="hljs-keyword">true</span>;
            currentPath[level] = nextCity;
            branchAndBound(nextCity, newCost, level + <span class="hljs-number">1</span>, currentPath, cities, visited, distanceMatrix, minCost, bestRoute);
            visited[nextCity] = <span class="hljs-keyword">false</span>; <span class="hljs-comment">// Backtrack</span>
            currentPath[level] = <span class="hljs-number">0</span>;
        }
    }
}
</code></pre>
<p>The lower bound is calculated with the help of two auxiliar methods.</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">double</span> <span class="hljs-title">getBound</span><span class="hljs-params">(<span class="hljs-keyword">int</span> candidateCity, <span class="hljs-keyword">int</span>[] cities, <span class="hljs-keyword">boolean</span>[] visited, <span class="hljs-keyword">int</span>[][] distanceMatrix)</span> </span>{
    <span class="hljs-keyword">return</span> (<span class="hljs-keyword">double</span>) Arrays.stream(cities)
        .filter(x -&gt; !visited[x])
        .map(x -&gt; getMinEdgeCost(x, candidateCity, visited, distanceMatrix))
        .sum() / <span class="hljs-number">2</span>;
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getMinEdgeCost</span><span class="hljs-params">(<span class="hljs-keyword">int</span> city, <span class="hljs-keyword">int</span> candidateCity, <span class="hljs-keyword">boolean</span>[] visited, <span class="hljs-keyword">int</span>[][] distanceMatrix)</span> </span>{
    <span class="hljs-keyword">var</span> cost = IntStream.range(<span class="hljs-number">0</span>, distanceMatrix.length)
        .filter(i -&gt; i != city &amp;&amp; (!visited[i] || i == <span class="hljs-number">0</span>) &amp;&amp; i != candidateCity)
        .map(x -&gt; distanceMatrix[city][x])
        .min();
    <span class="hljs-keyword">return</span> cost.isPresent() ? cost.getAsInt() : <span class="hljs-number">0</span>;
}
</code></pre>
<p>The finalimplementation should look like this.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BranchAndBoundSolver</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">TspSolver</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"BranchAndBound"</span>;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span>[] solve(<span class="hljs-keyword">int</span>[][] distanceMatrix) {
        <span class="hljs-keyword">var</span> cities = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[distanceMatrix.length];
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; distanceMatrix.length; i++) {
            cities[i] = i;
        }

        <span class="hljs-keyword">var</span> route = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[distanceMatrix.length + <span class="hljs-number">1</span>];
        <span class="hljs-keyword">var</span> bestRoute = <span class="hljs-keyword">new</span> <span class="hljs-keyword">int</span>[distanceMatrix.length + <span class="hljs-number">1</span>];
        <span class="hljs-keyword">var</span> visited = <span class="hljs-keyword">new</span> <span class="hljs-keyword">boolean</span>[distanceMatrix.length];
        visited[<span class="hljs-number">0</span>] = <span class="hljs-keyword">true</span>;

        branchAndBound(
                <span class="hljs-number">0</span>,
                <span class="hljs-number">0</span>,
                <span class="hljs-number">1</span>,
                route,
                cities,
                visited,
                distanceMatrix,
                <span class="hljs-keyword">new</span> AtomicInteger(Integer.MAX_VALUE),
                bestRoute);

        <span class="hljs-keyword">return</span> bestRoute;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">branchAndBound</span><span class="hljs-params">(
            <span class="hljs-keyword">int</span> currentCity,
            <span class="hljs-keyword">int</span> currentCost,
            <span class="hljs-keyword">int</span> level,
            <span class="hljs-keyword">int</span>[] currentPath,
            <span class="hljs-keyword">int</span>[] cities,
            <span class="hljs-keyword">boolean</span>[] visited,
            <span class="hljs-keyword">int</span>[][] distanceMatrix,
            AtomicInteger minCost,
            <span class="hljs-keyword">int</span>[] bestRoute)</span> </span>{

        <span class="hljs-keyword">if</span> (level == cities.length) {
            <span class="hljs-comment">// Complete the cycle back to the starting city</span>
            <span class="hljs-keyword">int</span> totalCost = currentCost + distanceMatrix[currentCity][<span class="hljs-number">0</span>];
            <span class="hljs-keyword">if</span> (totalCost &lt; minCost.get()) {
                minCost.set(totalCost);
                System.arraycopy(currentPath, <span class="hljs-number">0</span>, bestRoute, <span class="hljs-number">0</span>, cities.length);
            }
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> nextCity = <span class="hljs-number">0</span>; nextCity &lt; cities.length; nextCity++) {
            <span class="hljs-keyword">if</span> (!visited[nextCity]) {
                <span class="hljs-keyword">var</span> newCost = currentCost + distanceMatrix[currentCity][nextCity];
                <span class="hljs-keyword">var</span> lowerBound = getBound(nextCity, cities, visited, distanceMatrix);

                <span class="hljs-comment">// Prune if the new lower bound is not promising</span>
                <span class="hljs-keyword">if</span> (newCost + lowerBound &lt; minCost.get()) {
                    visited[nextCity] = <span class="hljs-keyword">true</span>;
                    currentPath[level] = nextCity;
                    branchAndBound(nextCity, newCost, level + <span class="hljs-number">1</span>, currentPath, cities, visited, distanceMatrix, minCost, bestRoute);
                    visited[nextCity] = <span class="hljs-keyword">false</span>; <span class="hljs-comment">// Backtrack</span>
                    currentPath[level] = <span class="hljs-number">0</span>;
                }
            }
        }

    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">double</span> <span class="hljs-title">getBound</span><span class="hljs-params">(<span class="hljs-keyword">int</span> candidateCity, <span class="hljs-keyword">int</span>[] cities, <span class="hljs-keyword">boolean</span>[] visited, <span class="hljs-keyword">int</span>[][] distanceMatrix)</span> </span>{
        <span class="hljs-keyword">return</span> (<span class="hljs-keyword">double</span>) Arrays.stream(cities).filter(x -&gt; !visited[x]).map(x -&gt; getMinEdgeCost(x, candidateCity, visited, distanceMatrix)).sum() / <span class="hljs-number">2</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getMinEdgeCost</span><span class="hljs-params">(<span class="hljs-keyword">int</span> city, <span class="hljs-keyword">int</span> candidateCity, <span class="hljs-keyword">boolean</span>[] visited, <span class="hljs-keyword">int</span>[][] distanceMatrix)</span></span>{
        <span class="hljs-keyword">var</span> cost = IntStream.range(<span class="hljs-number">0</span>, distanceMatrix.length)
                .filter(i -&gt; i != city &amp;&amp; (!visited[i] || i == <span class="hljs-number">0</span>) &amp;&amp; i != candidateCity)
                .map(x -&gt; distanceMatrix[city][x])
                .min();
        <span class="hljs-keyword">return</span> cost.isPresent() ? cost.getAsInt() : <span class="hljs-number">0</span>;
    }
</code></pre>
<p><strong>Strengths of Branch and Bound</strong></p>
<ol>
<li><p><strong>Guaranteed Optimality</strong>:<br /> Like brute force, Branch and Bound finds the exact optimal solution for the Traveling Salesman Problem (TSP), making it a reliable choice when accuracy is critical.</p>
</li>
<li><p><strong>Effective Pruning</strong>:<br /> By leveraging lower bounds to prune unpromising branches, B&amp;B significantly reduces the number of paths explored compared to brute force.</p>
</li>
<li><p><strong>Flexibility in Lower Bound Heuristics</strong>:<br /> The algorithm allows for the use of different lower-bound techniques (e.g., reduced cost matrix, minimum spanning tree) to improve pruning efficiency.</p>
</li>
<li><p><strong>Adaptable for Variants of TSP</strong>:<br /> Branch and Bound can be easily adapted to handle constraints like time windows, precedence relationships, or asymmetric costs.</p>
</li>
<li><p><strong>Systematic Search</strong>:<br /> The algorithm explores solutions in a structured way, ensuring all possibilities are accounted for without unnecessary repetition.</p>
</li>
</ol>
<p><strong>Limitations</strong></p>
<ol>
<li><p><strong>Exponential Time Complexity in the Worst Case</strong>:<br /> While pruning improves performance, the worst-case time complexity remains exponential (O(n!)) if most branches need to be explored.</p>
</li>
<li><p><strong>Dependence on Lower Bound Quality</strong>:<br /> The efficiency of B&amp;B heavily relies on the tightness of the lower bound. Poor lower bounds result in fewer pruned branches and increased runtime.</p>
</li>
<li><p><strong>Memory Usage</strong>:<br /> Recursive implementations require additional memory for storing the search tree, current paths, visited states, and best solutions.</p>
</li>
<li><p><strong>Scalability</strong>:<br /> B&amp;B is impractical for large TSP instances (e.g., n&gt;30) because the number of branches grows exponentially with the number of cities.</p>
</li>
<li><p><strong>Computational Overhead for Lower Bound Calculation</strong>:<br /> Techniques like the <strong>reduced cost matrix</strong> or <strong>minimum spanning tree</strong> add computational overhead to each node evaluation, which may negate pruning gains in smaller problems.</p>
</li>
</ol>
<h3 id="heading-conclusion-comparing-exact-methods-for-the-traveling-salesman-problem">Conclusion: Comparing Exact Methods for the Traveling Salesman Problem</h3>
<p>In this post, we’ve explored three exact methods for solving the Traveling Salesman Problem (TSP): <strong>Brute Force</strong>, <strong>Held-Karp</strong>, and <strong>Branch and Bound</strong>. Each method guarantees an optimal solution but differs significantly in terms of performance, scalability, and practicality. Let’s summarize their strengths, limitations, and real-world applicability.</p>
<hr />
<h4 id="heading-brute-force-the-simplest-approach-1"><strong>Brute Force: The Simplest Approach</strong></h4>
<p>Brute force explores every possible route (n!) to find the optimal solution. While easy to understand and implement, its exponential growth in computational time makes it impractical for more than 10 cities. For small problems, however, brute force may still be a viable option due to its simplicity.</p>
<hr />
<h4 id="heading-held-karp-the-dynamic-programming-powerhouse"><strong>Held-Karp: The Dynamic Programming Powerhouse</strong></h4>
<p>The Held-Karp algorithm improves on brute force with a time complexity of O(n²⋅2^n), reducing redundant calculations by leveraging dynamic programming. This method consistently outperforms both brute force and Branch and Bound for medium and large problem sizes, as evidenced by benchmark results.</p>
<p>From the chart, we see that Held-Karp scales much better than Branch and Bound for problems with more than 10-15 cities. Despite its high memory requirements, Held-Karp is the most practical exact method for problems with up to 20-25 cities.</p>
<hr />
<h4 id="heading-branch-and-bound-smart-pruning-limited-scalability"><strong>Branch and Bound: Smart Pruning, Limited Scalability</strong></h4>
<p>Branch and Bound employs smart pruning to avoid evaluating unpromising routes. While it often outperforms brute force in small to medium-sized problems, its factorial complexity in worst-case scenarios limits its scalability. The effectiveness of this method depends heavily on the quality of the lower-bound heuristic, and as problem size increases, Held-Karp emerges as the better choice for exact solutions.</p>
<hr />
<h4 id="heading-choosing-the-right-method"><strong>Choosing the Right Method</strong></h4>
<p>Based on the benchmarks and theoretical insights, here’s when to use each method:</p>
<ul>
<li><p><strong>Brute Force</strong>: For very small problems (n≤10) where simplicity matters more than performance.</p>
</li>
<li><p><strong>Held-Karp</strong>: For medium to larger problems (n≤25) where memory constraints are manageable.</p>
</li>
<li><p><strong>Branch and Bound</strong>: For small to medium problems (n≤15) where a good lower-bound heuristic is available.</p>
</li>
</ul>
<hr />
<h3 id="heading-final-thoughts"><strong>Final Thoughts</strong></h3>
<p>The chart below illustrates the performance of these methods as the number of cities increases. While Branch and Bound provides significant improvements over brute force for smaller inputs, Held-Karp consistently delivers better performance as problem size grows, solidifying its position as the most efficient exact algorithm for solving TSP.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733233258613/f3c6ca00-851a-4ea3-8f57-d54ba58d983d.png" alt class="image--center mx-auto" /></p>
<p>When the number of cities exceeds 20-25, even Held-Karp becomes computationally expensive. At this point, approximate methods like Genetic Algorithms, Simulated Annealing, or Ant Colony Optimization are better alternatives.</p>
<p>By understanding the trade-offs of each method, you can make informed decisions about which approach to use based on your problem size and computational resources.</p>
]]></content:encoded></item><item><title><![CDATA[What Is the Traveling Salesman Problem and Why Does It Matter?]]></title><description><![CDATA[A Timeless Challenge in Optimization
Imagine a salesperson who must travel to multiple cities, visiting each one exactly once before returning to their starting point. What’s the shortest route they can take? This seemingly simple question has puzzle...]]></description><link>https://blog.lucasguzzo.dev/what-is-the-tsp</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/what-is-the-tsp</guid><category><![CDATA[TSP]]></category><category><![CDATA[optimization]]></category><category><![CDATA[travelling salesman problem]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Thu, 28 Nov 2024 01:32:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732755147780/7cadcc40-abe6-4883-9cf2-b2f59ad6d1b6.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-a-timeless-challenge-in-optimization">A Timeless Challenge in Optimization</h3>
<p>Imagine a salesperson who must travel to multiple cities, visiting each one exactly once before returning to their starting point. What’s the shortest route they can take? This seemingly simple question has puzzled mathematicians, computer scientists, and engineers for decades. Known as the Traveling Salesman Problem (TSP), it is one of the most well-known optimization problems in computer science, and its applications span industries as diverse as logistics, electronics, and bioinformatics.</p>
<p>What makes TSP particularly intriguing is not just its practical importance but also its computational complexity. As the number of cities increases, the possible routes grow exponentially, turning this problem into a classic example of an NP-hard challenge. Despite this, researchers have developed ingenious algorithms and heuristics to find efficient solutions, even for large-scale instances.</p>
<p>In this blog post, we’ll unravel the mysteries of TSP, explore its real-world applications, and take a closer look at the fascinating methods used to tackle this timeless problem. Whether you're a seasoned data scientist, a curious student, or simply someone intrigued by puzzles, the TSP offers valuable insights into the art and science of optimization.</p>
<h3 id="heading-what-is-the-traveling-salesman-problem"><strong>What Is the Traveling Salesman Problem?</strong></h3>
<p>The Traveling Salesman Problem (TSP) is a fundamental optimization challenge with a deceptively simple goal:</p>
<p><em>What is the shortest route that visits a set of cities exactly once and returns to the starting point?</em></p>
<p>While the question is straightforward, solving it is anything but. The number of possible routes grows exponentially as cities are added, making brute-force solutions impractical for all but the smallest cases. TSP exemplifies the complexity of optimization problems and serves as a cornerstone in computer science and mathematics.</p>
<p>Let’s break it down step by step:</p>
<hr />
<h4 id="heading-a-simple-example"><strong>A Simple Example</strong></h4>
<p>Consider a salesperson visiting four cities: A, B, C, and D. They start at city A, visit each city once, and return to A. Below is a table showing the distances between the cities:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>A</td><td>B</td><td>C</td><td>D</td></tr>
</thead>
<tbody>
<tr>
<td><strong>A</strong></td><td>0</td><td>10</td><td>15</td><td>20</td></tr>
<tr>
<td><strong>B</strong></td><td>10</td><td>0</td><td>35</td><td>25</td></tr>
<tr>
<td><strong>C</strong></td><td>15</td><td>35</td><td>0</td><td>30</td></tr>
<tr>
<td><strong>D</strong></td><td>20</td><td>25</td><td>30</td><td>0</td></tr>
</tbody>
</table>
</div><p>The salesperson could take one of the following routes:</p>
<ol>
<li><p>A → B → C → D → A</p>
</li>
<li><p>A → B → D → C → A</p>
</li>
<li><p>A → C → B → D → A</p>
</li>
<li><p>A → C → D → B → A</p>
</li>
<li><p>A → D → B → C → A</p>
</li>
<li><p>A → D → C → B → A</p>
</li>
</ol>
<p>Each route's total distance is calculated. For instance:<br /><strong>A → B → C → D → A = 10 + 35 + 30 + 20 = 95 units</strong></p>
<p>With just four cities, there are 6 routes to evaluate. But as the number of cities grows, the total routes explode factorially. For <strong>n</strong> cities, the possibilities are (n−1)!, making brute-force computation impractical for large problems.</p>
<hr />
<h4 id="heading-the-goal-of-tsp"><strong>The Goal of TSP</strong></h4>
<p>The Traveling Salesman Problem seeks the <em>optimal route</em>—the shortest possible path that visits all cities exactly once and returns to the starting point. Depending on the context, the "shortest" route might optimize other factors like travel time or cost instead of physical distance.</p>
<hr />
<h4 id="heading-types-of-tsp"><strong>Types of TSP</strong></h4>
<p>Depending on the scenario, TSP can take different forms:</p>
<ol>
<li><p><strong>Symmetric TSP:</strong> The distance between two cities is the same in both directions (e.g., traveling from A to B is the same as from B to A).</p>
</li>
<li><p><strong>Asymmetric TSP:</strong> The distance between two cities may differ depending on the direction (e.g., one-way roads or different traffic conditions).</p>
</li>
</ol>
<hr />
<p>The Traveling Salesman Problem is deceptively simple but incredibly profound. Its beauty lies in its universality: from mapping delivery routes to sequencing DNA, the principles behind TSP find applications across a wide range of fields. However, solving it efficiently remains one of the great challenges of computational science.</p>
<h3 id="heading-why-is-the-traveling-salesman-problem-important"><strong>Why Is the Traveling Salesman Problem Important?</strong></h3>
<p>The Traveling Salesman Problem (TSP) is more than a theoretical puzzle. It’s a cornerstone in optimization, with wide-ranging applications and significant influence on computational theory.</p>
<hr />
<h4 id="heading-1-real-world-applications"><strong>1. Real-World Applications</strong></h4>
<p>TSP has practical uses in countless industries where optimizing routes, sequences, or schedules is essential. Here are some notable examples:</p>
<ul>
<li><p><strong>Logistics:</strong> Delivery companies like UPS and Amazon use TSP-like solutions to optimize routes, saving fuel, time, and costs.</p>
</li>
<li><p><strong>Manufacturing:</strong> In PCB production, TSP minimizes the movement of drill heads or lasers, improving efficiency.</p>
</li>
<li><p><strong>Bioinformatics:</strong> Researchers optimize DNA sequencing by arranging fragments to minimize mismatches.</p>
</li>
<li><p><strong>Travel and Scheduling:</strong> TSP helps plan efficient travel itineraries and sports tournament schedules.</p>
</li>
</ul>
<hr />
<h4 id="heading-2-tsp-as-a-gateway-to-complex-problems"><strong>2. TSP as a Gateway to Complex Problems</strong></h4>
<p>TSP principles extend to more complex problems, such as:</p>
<ul>
<li><p><strong>Vehicle Routing Problem (VRP):</strong> Optimizing routes for fleets of vehicles with constraints like time windows or capacity limits.</p>
</li>
<li><p><strong>Job Scheduling:</strong> Sequencing tasks in manufacturing or IT to minimize costs or delays.</p>
</li>
<li><p><strong>Network Design:</strong> Minimizing connection costs in data or telecommunication networks.</p>
</li>
</ul>
<hr />
<h4 id="heading-3-theoretical-significance"><strong>3. Theoretical Significance</strong></h4>
<p>TSP’s study has advanced algorithms and our understanding of computational complexity:</p>
<ul>
<li><p><strong>NP-Hardness:</strong> TSP exemplifies the difficulty of NP-hard problems, where verifying a solution is easy, but finding one is computationally intense.</p>
</li>
<li><p><strong>Algorithm Innovation:</strong> Research has led to breakthroughs in dynamic programming, branch-and-bound techniques, and heuristic methods.</p>
</li>
<li><p><strong>Broader Insights:</strong> Approximation and heuristic methods inspired by TSP are now applied to many real-world challenges.</p>
</li>
</ul>
<hr />
<h4 id="heading-4-broader-implications"><strong>4. Broader Implications</strong></h4>
<p>TSP is important also for what it symbolizes: the balance between simplicity and complexity. It challenges us to find innovative ways to deal with exponential growth and teaches us how to make trade-offs between computational resources and solution quality.</p>
<p>Additionally, TSP serves as a platform for interdisciplinary collaboration. Mathematicians, computer scientists, engineers, and professionals from logistics, biology, and even the arts come together to tackle problems inspired by TSP, fostering innovation across fields.</p>
<h3 id="heading-the-complexity-of-the-traveling-salesman-problem"><strong>The Complexity of the Traveling Salesman Problem</strong></h3>
<p>The TSP is deceptively simple yet incredibly complex. While its premise is easy to understand, the problem’s exponential growth in possibilities makes solving it a formidable challenge.</p>
<hr />
<h4 id="heading-1-exponential-growth-of-possibilities"><strong>1. Exponential Growth of Possibilities</strong></h4>
<h4 id="heading-1-exponential-growth-of-possibilities-1"><strong>1. Exponential Growth of Possibilities</strong></h4>
<p>For nnn cities, the number of possible routes is (n−1)!(n-1)!(n−1)!, growing factorially:</p>
<ul>
<li><p>4 cities: 666 routes</p>
</li>
<li><p>10 cities: 362,880362,880362,880 routes</p>
</li>
<li><p>20 cities: 121,645,100,408,832,000121,645,100,408,832,000121,645,100,408,832,000 routes</p>
</li>
</ul>
<p>This rapid growth makes brute-force solutions impractical, even for moderate-sized problems.</p>
<hr />
<h4 id="heading-2-np-hardness-why-tsp-is-hard-to-solve"><strong>2. NP-Hardness: Why TSP Is Hard to Solve</strong></h4>
<p>TSP belongs to the NP-hard class of problems, where verifying a solution is quick, but finding one is computationally expensive. This means no efficient algorithm is known for solving all TSP instances in polynomial time. TSP also serves as a benchmark problem for understanding computational limits in fields like cryptography and artificial intelligence.</p>
<hr />
<h4 id="heading-3-exact-solutions-vs-approximation"><strong>3. Exact Solutions vs. Approximation</strong></h4>
<p>Given the computational challenges, solving TSP can be approached in two main ways:</p>
<ol>
<li><p><strong>Exact Methods:</strong></p>
<ul>
<li><p>These algorithms guarantee the shortest possible route but are computationally expensive, especially for large datasets. Examples include:</p>
<ul>
<li><p><strong>Brute Force:</strong> Tests every possible route (impractical for large datasets).</p>
</li>
<li><p><strong>Dynamic Programming (Held-Karp Algorithm):</strong> Reduces redundant calculations but still requires exponential time (O(n2⋅2n)O(n^2 \cdot 2^n)).</p>
</li>
<li><p><strong>Branch and Bound:</strong> Systematically eliminates suboptimal solutions but suffers from exponential worst-case performance.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Approximation and Heuristics:</strong></p>
<ul>
<li><p>Approximation algorithms and heuristics trade precision for speed, providing "good enough" solutions in reasonable timeframes. For instance:</p>
<ul>
<li><p><strong>Nearest Neighbor Heuristic:</strong> Starts at a city and repeatedly visits the nearest unvisited city.</p>
</li>
<li><p><strong>Christofides’ Algorithm:</strong> Guarantees a solution within 1.5 times the optimal route for symmetric TSP.</p>
</li>
<li><p><strong>Genetic Algorithms and Simulated Annealing:</strong> Explore large solution spaces using probabilistic methods.</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<hr />
<h4 id="heading-4-computational-challenges-in-practice"><strong>4. Computational Challenges in Practice</strong></h4>
<p>While theoretical solutions exist, real-world TSP problems often introduce additional complexities that make solving them even harder:</p>
<ul>
<li><p><strong>Dynamic Conditions:</strong> In logistics, traffic, weather, or delivery time windows can change during computation.</p>
</li>
<li><p><strong>Asymmetry:</strong> Distances may vary depending on direction (e.g., one-way streets or varying traffic conditions).</p>
</li>
<li><p><strong>Constraints:</strong> Real-world problems like the Vehicle Routing Problem (VRP) add restrictions like vehicle capacities or time limits, complicating the optimization further.</p>
</li>
</ul>
<hr />
<h4 id="heading-5-the-search-for-efficiency-research-and-innovations"><strong>5. The Search for Efficiency: Research and Innovations</strong></h4>
<p>The challenge of solving TSP has spurred decades of research into optimization, resulting in innovative approaches like:</p>
<ul>
<li><p><strong>Cutting-Plane Methods:</strong> Used in integer programming to iteratively refine solutions.</p>
</li>
<li><p><strong>Parallel Computing:</strong> Distributes computations across multiple processors to handle larger datasets.</p>
</li>
<li><p><strong>Quantum Computing:</strong> Promises revolutionary speedups for TSP-like problems using quantum algorithms (e.g., Grover’s algorithm for searching).</p>
</li>
</ul>
<p>Despite these advancements, the exponential nature of TSP ensures that it remains a computational challenge at its core.</p>
<h3 id="heading-approaches-to-solving-the-traveling-salesman-problem"><strong>Approaches to Solving the Traveling Salesman Problem</strong></h3>
<p>Over the years, researchers have developed various approaches to solve the TSP, ranging from exact solutions to approximations and metaheuristics. Each method strikes a balance between precision and computational feasibility, depending on the problem size and constraints.</p>
<h4 id="heading-1-exact-solutions"><strong>1. Exact Solutions</strong></h4>
<p>These approaches guarantee the shortest route but are computationally expensive, making them ideal for smaller datasets or where precision is critical.</p>
<h5 id="heading-brute-force"><strong>Brute Force</strong></h5>
<p>The most straightforward (and impractical) method is to calculate the distance of every possible route and select the shortest. For n cities, this requires checking (n−1)! routes.</p>
<h5 id="heading-dynamic-programming-held-karp-algorithm"><strong>Dynamic Programming: Held-Karp Algorithm</strong></h5>
<p>This algorithm reduces redundant calculations by storing intermediate results. It achieves a time complexity of O(n2⋅2n), which is an improvement over brute force but still exponential.</p>
<h5 id="heading-branch-and-bound"><strong>Branch and Bound</strong></h5>
<p>This technique eliminates large portions of the search space by "bounding" the cost of suboptimal routes.</p>
<hr />
<h4 id="heading-2-approximation-algorithms"><strong>2. Approximation Algorithms</strong></h4>
<p>Approximation algorithms provide near-optimal solutions efficiently, often trading accuracy for speed.</p>
<h5 id="heading-nearest-neighbor-algorithm"><strong>Nearest Neighbor Algorithm</strong></h5>
<p>Start at a random city and repeatedly visit the nearest unvisited city until all are covered. This method is fast but prone to suboptimal routes.</p>
<p><strong>Minimum Spanning Tree (MST) Heuristic</strong></p>
<p>This algorithm constructs a tree that connects all cities with the minimum total edge weight, then derives a TSP route.</p>
<h5 id="heading-christofides-algorithm"><strong>Christofides’ Algorithm</strong></h5>
<p>For symmetric TSP, Christofides’ Algorithm guarantees a solution within 1.5 times the optimal distance.</p>
<hr />
<h4 id="heading-3-metaheuristic-algorithms"><strong>3. Metaheuristic Algorithms</strong></h4>
<p>Metaheuristics intelligently explore the solution space and are effective for large, complex instances.</p>
<h5 id="heading-genetic-algorithms-ga"><strong>Genetic Algorithms (GA)</strong></h5>
<p>Mimic evolution through selection, crossover, and mutation to iteratively improve routes.</p>
<h5 id="heading-simulated-annealing-sa"><strong>Simulated Annealing (SA)</strong></h5>
<p>This algorithm mimics the process of cooling metal to gradually reduce randomness in exploring solutions.</p>
<h5 id="heading-ant-colony-optimization-aco"><strong>Ant Colony Optimization (ACO)</strong></h5>
<p>Inspired by the behavior of ants, ACO uses "pheromone trails" to explore and optimize routes.</p>
<hr />
<h4 id="heading-4-hybrid-methods"><strong>4. Hybrid Methods</strong></h4>
<p>Combining methods often yields the best results. For instance, metaheuristics like ACO can identify good initial solutions, which are then refined using exact methods like dynamic programming.</p>
<hr />
<h4 id="heading-5-quantum-computing"><strong>5. Quantum Computing</strong></h4>
<p>Quantum computing offers promising new methods for TSP. Quantum annealers like D-Wave and quantum algorithms (e.g., Grover’s search) aim to solve optimization problems more efficiently than classical counterparts.</p>
<hr />
<h3 id="heading-conclusion">Conclusion</h3>
<p>The Traveling Salesman Problem is a remarkable challenge that bridges the gap between theory and real-world problem-solving. Its deceptively simple premise has inspired decades of research, leading to advancements in optimization algorithms, computational theory, and practical applications across industries. Whether it’s improving logistics, sequencing DNA, or exploring complex networks, TSP demonstrates the power and creativity of algorithmic thinking.</p>
<p>In the next post, we’ll dive into <strong>exact methods</strong> for solving TSP, exploring how these approaches provide optimal solutions while highlighting their limitations. Stay tuned as we continue unraveling the intricacies of this timeless problem.</p>
]]></content:encoded></item><item><title><![CDATA[Single Responsibility Principle (SRP)]]></title><description><![CDATA[In the ever-evolving landscape of software development, maintaining clean, manageable, and scalable code is paramount. One of the foundational principles that aid in achieving this is the Single Responsibility Principle (SRP). As the first letter in ...]]></description><link>https://blog.lucasguzzo.dev/single-responsibility-principle-srp</link><guid isPermaLink="true">https://blog.lucasguzzo.dev/single-responsibility-principle-srp</guid><category><![CDATA[SOLID principles]]></category><category><![CDATA[SRP]]></category><category><![CDATA[coding standards]]></category><dc:creator><![CDATA[Lucas Guzzo]]></dc:creator><pubDate>Mon, 24 Jun 2024 20:13:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719259979197/6e6dff27-6f85-494d-b715-d7d7d08cdcee.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the ever-evolving landscape of software development, maintaining clean, manageable, and scalable code is paramount. One of the foundational principles that aid in achieving this is the Single Responsibility Principle (SRP). As the first letter in the SOLID acronym, SRP is a cornerstone of object-oriented design, emphasizing the importance of having a single reason for a class to change.</p>
<p>But what exactly does it mean for a class to have only one responsibility? And why is it so crucial in the realm of software engineering? The concept of a "single responsibility" can be somewhat abstract, but it can be broken down into more tangible terms. A responsibility is considered a reason for change. When a class has more than one responsibility, those responsibilities become coupled. A change to one responsibility may affect the other, leading to an increase in the likelihood of bugs and a decrease in the ease of understanding and maintaining the code. For instance, consider a class that handles both the user interface logic and the data processing logic. Changes to the data processing requirements might inadvertently impact the user interface code, leading to unexpected issues. By adhering to SRP, these responsibilities would be separated into different classes, isolating the potential for unintended side effects and making the system more modular.</p>
<p>The benefits of SRP extend beyond just reducing bugs and improving code clarity. It also enhances the ability to reuse classes and components across different parts of an application or even in different projects. When a class has a single responsibility, it is easier to see its purpose and potential applications in other contexts. Moreover, such classes tend to be smaller and more focused, making them easier to unit test. By ensuring that each class has a distinct and clear responsibility, developers can build more robust and flexible systems that are easier to extend and modify over time. This leads to more efficient development processes and a higher overall quality of the software product.</p>
<h2 id="heading-example">Example</h2>
<p>Ok, let’s dive in a practical example to see how SRP should look like.</p>
<h3 id="heading-the-user-management-service">The User Management Service</h3>
<ol>
<li>Without SRP</li>
</ol>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserManager</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CreateUser</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username, <span class="hljs-keyword">string</span> password</span>)</span>
    {
        <span class="hljs-comment">// Validate user input</span>
        <span class="hljs-keyword">if</span> (username == <span class="hljs-literal">null</span> || password == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Username and password cannot be null"</span>);
        }

        <span class="hljs-comment">// Hash the password</span>
        <span class="hljs-keyword">var</span> hashedPassword = HashPassword(password);

        <span class="hljs-comment">// Save user to the database</span>
        SaveUserToDatabase(username, hashedPassword);

        <span class="hljs-comment">// Send a welcome email</span>
        SendWelcomeEmail(username);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> <span class="hljs-title">HashPassword</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> password</span>)</span>
    {
        <span class="hljs-comment">// Hashing logic here</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"hashedPassword"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SaveUserToDatabase</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username, <span class="hljs-keyword">string</span> hashedPassword</span>)</span>
    {
        <span class="hljs-comment">// Database save logic here</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SendWelcomeEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username</span>)</span>
    {
        <span class="hljs-comment">// Email sending logic here</span>
    }
}
</code></pre>
<p>As we can see, the UserManager class is responsible for multiple tasks: validating user input, hashing passwords, saving user data to the database, and sending emails. This makes the class large and complex, which reduces its readability and makes it harder to understand what the class is supposed to do at a glance. Also, since UserManager class handles multiple responsibilities, any change in one responsibility might affect the others. Over time, as more changes are made, the class can become increasingly fragile and harder to maintain. Testing this class might also be a challenge, as one would need to take into accountb all the responsibilities of this class, which increases the complexity of the test.</p>
<ol start="2">
<li>With SRP</li>
</ol>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserValidator</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Validate</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username, <span class="hljs-keyword">string</span> password</span>)</span>
    {
        <span class="hljs-keyword">if</span> (username == <span class="hljs-literal">null</span> || password == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Username and password cannot be null"</span>);
        }
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PasswordHasher</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">HashPassword</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> password</span>)</span>
    {
        <span class="hljs-comment">// Hashing logic here</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"hashedPassword"</span>;
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserRepository</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SaveUser</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username, <span class="hljs-keyword">string</span> hashedPassword</span>)</span>
    {
        <span class="hljs-comment">// Database save logic here</span>
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmailService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SendWelcomeEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username</span>)</span>
    {
        <span class="hljs-comment">// Email sending logic here</span>
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserManager</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> UserValidator _validator;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> PasswordHasher _hasher;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> UserRepository _repository;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> EmailService _emailService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">UserManager</span>(<span class="hljs-params"></span>)</span>
    {
        _validator = <span class="hljs-keyword">new</span> UserValidator();
        _hasher = <span class="hljs-keyword">new</span> PasswordHasher();
        _repository = <span class="hljs-keyword">new</span> UserRepository();
        _emailService = <span class="hljs-keyword">new</span> EmailService();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CreateUser</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username, <span class="hljs-keyword">string</span> password</span>)</span>
    {
        _validator.Validate(username, password);
        <span class="hljs-keyword">var</span> hashedPassword = _hasher.HashPassword(password);
        _repository.SaveUser(username, hashedPassword);
        _emailService.SendWelcomeEmail(username);
    }
}
</code></pre>
<p>Now, this version of the code looks better. Each of the UserManager responsibilities were extracted into separated classes. This makes it easier to understand and maintain each of the classes, as well as test them. Some of the classes, like the EmailServer, now can be reused someqhere else in the application if needed. The UserManager class itself becomes just, well, a manager. It is the one that knows the other classes and knows when to call them.</p>
<h2 id="heading-identifying-srp-violations">Identifying SRP violations</h2>
<p>Ok, we are all now aware that SRP is super great. But how can we identify violations of this priciple in our code? I’ll list some tips that might help.</p>
<ol>
<li><p><strong>Analyze Class Responsibilities</strong> This one is kinda obvious, right? But we have to begin somewhere. Examine each class and list all the functionalities it implements. Sometimes classes can have more than one functionality, if they are closely related to each toher. We also need to evaluate cohesion here. Classes will have multiple methods, each doing something different, and that’s what’s expected of a clas. What we have to look for is lack of cohesion between these methods. A highly cohesive class has methods and attributes that are directly related to each other. Low cohesion, where methods and attributes serve disparate purposes, indicates a violation of SRP.</p>
</li>
<li><p><strong>Check for Multiple Reasons to Change</strong> According to the SRP, a class should have only one reason to change. If you find that modifications to different aspects of your application (e.g., changes in business rules, data format, or user interface) all require changes to the same class, SRP is likely being violated. This indicates that the class has multiple responsibilities.</p>
</li>
<li><p><strong>Monitor Class Size and Complexity</strong> Large classes with many methods and attributes often signal SRP violations. While size alone is not a definitive indicator, complexity usually accompanies size. If a class is doing too much, it becomes harder to understand and maintain. Consider refactoring large classes into smaller, more focused ones.</p>
</li>
<li><p><strong>Consider Testing Challenges</strong> If writing unit tests for a class is difficult, it may be due to SRP violations. Classes with multiple responsibilities require more complex tests that cover all possible interactions and side effects. Simplifying classes by ensuring they adhere to SRP will result in simpler, more focused tests.</p>
</li>
</ol>
<p>By using these strategies, developers can systematically identify and address SRP violations, leading to cleaner, more maintainable codebases.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope this text has helped you to better understand the SIngle Responsibility Pinciple (SRP), one of the fundameltal guidelines in software design. By recognizing and rectifying SRP violations—through analyzing class responsibilities, checking for multiple reasons to change, reviewing method groupings, monitoring class size and complexity, evaluating cohesion, and considering testing challenges—you will be able to significantly improve the quality of your code. Embracing SRP not only simplifies the development process but also enhances the scalability and robustness of software applications, ultimately leading to more efficient and effective coding practices.</p>
]]></content:encoded></item></channel></rss>