<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="https://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>ronjeffries.com</title>
    <description>This is RonJeffries.com, the combination of new articles, XProgramming, SameElephant,  and perhaps even some new items never before contemplated.  &lt;br/&gt;Copyright © 1998-forever Ronald E Jeffries
</description>
    <link>https://ronjeffries.com/</link>
    <atom:link href="https://ronjeffries.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Tue, 22 Sep 2026 10:57:46 -0400</pubDate>
    <lastBuildDate>Tue, 22 Sep 2026 10:57:46 -0400</lastBuildDate>
    <generator>Jekyll v4.2.0</generator>
    
    
              <item>
                <title>Bit of a Dilemma</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>I have two items in my queue and I don’t think I can address them in parallel. What shall I do? Contains brief LLM-AI slur. May contain footnotes.</p>
</blockquote>
<p>I enjoy programming in the classic form, where you do it with your own brain and fingers, not using some assemblage of oligarchy-serving book-burning job-stealing planet-destroying computer programs that can’t even enjoy what they’re doing.</p>
<p>In those days we kept an opcode cue card in our pocket protector. It was the style at the time<sup id="fnref:onion" role="doc-noteref"><a href="#fn:onion" class="footnote" rel="footnote">1</a></sup>. Kids these days don’t get it.</p>
<p>And I enjoy writing about it as I do it, which has never been the style as far as I know. The style, if you were going to write at all, was more to work on the code until it was as perfect as it could be, and then write it up as if you were actually so smart that this perfect code just sprang all of a piece from your forehead, like Athena fully formed and fully armed springing from the skull of Zeus. Fortunately, such authors didn’t bother to split open their skull, which may have been the origin of the term “splitting headache”, but I digress.</p>
<p>I like to write as I go, sharing as much of my thought process as I can get down on in words. I do this mostly because it has become a habit, but I tell myself that I do it in case some reader somewhere might someday get a glimmer of an idea from what I think / say / do.</p>
<p>But there is a programming problem, an interesting one, that I want to address. But I can’t write about it as Ron Jeffries, for reasons<sup id="fnref:r" role="doc-noteref"><a href="#fn:r" class="footnote" rel="footnote">2</a></sup>. So working the new problem will interfere with my standard morning of sitting down with some random on-going program, currently the dungeon, and coding something up and writing about it, sharing it with the six people who are known to read these articles, g*d knows why<sup id="fnref:g" role="doc-noteref"><a href="#fn:g" class="footnote" rel="footnote">3</a></sup>.</p>
<p>I’m not sure how to balance the dungeon and the other problem, how to manage the writing bit, even how to switch mental context between two very different problems, two different languages, and three different editors<sup id="fnref:ed" role="doc-noteref"><a href="#fn:ed" class="footnote" rel="footnote">4</a></sup>.</p>
<p>I don’t think switching between AM and PM will do it for me, though I’ll try that first. Maybe a couple of days on one and a couple on the other? That should really foul up the keystroke memory and whether to type a dot or a colon<sup id="fnref:clue" role="doc-noteref"><a href="#fn:clue" class="footnote" rel="footnote">5</a></sup>.</p>
<p>I’ll figure out something. My mornings entertain me, and I hope they somewhat entertain a few other folks.</p>
<p>Oh, and if in fact you do read these and you do enjoy them, please drop me an email or a toot. If the count drops to zero I might stop. Might<sup id="fnref:pw" role="doc-noteref"><a href="#fn:pw" class="footnote" rel="footnote">6</a></sup>.</p>
<hr />
<p><br /></p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:onion" role="doc-endnote">
      <p>The onion thing was earlier. Not sorry I missed it. <a href="#fnref:onion" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:r" role="doc-endnote">
      <p>As has long been suspected, ‘Ron Jeffries’ is just one of my identities<sup id="fnref:w" role="doc-noteref"><a href="#fn:w" class="footnote" rel="footnote">7</a></sup>. I have a secret identity<sup id="fnref:s" role="doc-noteref"><a href="#fn:s" class="footnote" rel="footnote">8</a></sup>, kind of like Spiderman or that dude with the cave<sup id="fnref:bat" role="doc-noteref"><a href="#fn:bat" class="footnote" rel="footnote">9</a></sup>. <a href="#fnref:r" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:g" role="doc-endnote">
      <p>People sometimes wonder why I generally type g*d. It’s an affectation. cf. YHWH. In my case I just don’t want any possibly supernatural beings paying much attention to me. No sense asking for trouble. <a href="#fnref:g" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ed" role="doc-endnote">
      <p>Sublime Text, PyCharm, VS Code. <a href="#fnref:ed" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:clue" role="doc-endnote">
      <p>That’s a clue. There is another. <a href="#fnref:clue" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:pw" role="doc-endnote">
      <p>Probably won’t. <a href="#fnref:pw" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:w" role="doc-endnote">
      <p>No, I am not in witness protection<sup id="fnref:cr" role="doc-noteref"><a href="#fn:cr" class="footnote" rel="footnote">10</a></sup>. <a href="#fnref:w" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:s" role="doc-endnote">
      <p>And I want it to stay that way, so if you think you know<sup id="fnref:k" role="doc-noteref"><a href="#fn:k" class="footnote" rel="footnote">11</a></sup>, please contact me privately<sup id="fnref:a" role="doc-noteref"><a href="#fn:a" class="footnote" rel="footnote">12</a></sup>. <a href="#fnref:s" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:bat" role="doc-endnote">
      <p>That dude is b*tsh*t crazy. If I had his money I could do better with it, you can be sure of that. <a href="#fnref:bat" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cr" role="doc-endnote">
      <p>I’m not even a criminal<sup id="fnref:f" role="doc-noteref"><a href="#fn:f" class="footnote" rel="footnote">13</a></sup>. <a href="#fnref:cr" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:k" role="doc-endnote">
      <p>No, it’s not <a href="https://ronjeffries.com/categories/kate-oneal/" target="_blank">Kate Oneal</a>. She’s real, at least in my mind<sup id="fnref:m" role="doc-noteref"><a href="#fn:m" class="footnote" rel="footnote">14</a></sup>. <a href="#fnref:k" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:a" role="doc-endnote">
      <p>Anyway, you’re wrong, that’s not me. <a href="#fnref:a" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:f" role="doc-endnote">
      <p>OK, sometimes maybe I used to drive too fast. Misdemeanor<sup id="fnref:rd" role="doc-noteref"><a href="#fn:rd" class="footnote" rel="footnote">15</a></sup> at worst. <a href="#fnref:f" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:m" role="doc-endnote">
      <p>If in fact he is in his right mind. <a href="#fnref:m" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:rd" role="doc-endnote">
      <p>OK, one time in my youth a ticked-off off-duty police officer did write me up for willful reckless driving but the fact that I made all the lights all the way downtown made it clear that I wasn’t even driving over the speed limit. Helps to be a math major. <a href="#fnref:rd" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
 ]]> 
                </content:encoded>
                <pubDate>Tue, 22 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/o/w/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/o/w/</guid>
                <category>LLM-AI</category>
                <category>python</category>
                <category>Lua Language</category>
                <category>Kate Oneal</category>
              </item>
              <item>
                <title>&apos;AI&apos;, etc.</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>I propose to blather a bit this morning, starting with some ‘AI’-related ideas and then going I know not where.</p>
</blockquote>
<h3 id="triggers">Triggers</h3>
<p>Jessica Kerr—jessitron—recently published an article that caught my attention: <a href="https://jessitron.com/2026/08/30/who-are-we-now/">Who are we Now</a>. Jessica is unquestionably a better programmer than I am, thinks very deeply about important matters, and has been digging deeply into the use of current ‘AI’ agents. You should read that article, before you read this and perhaps instead. Here is a quote that I want to call out:</p>
<blockquote>
  <p>Do I think AI is bad for us? Sure. <br />
Does that mean I shouldn’t use it? No.</p>
</blockquote>
<p>Another of my betters, Kent Beck, has also been working with the ‘AI’ systems and in a recent article closed with something that struck me:</p>
<blockquote>
  <p>The overall outcome for me is (finally) positive: <br />
        Less useless code <br />
       More confidence in the behavior of what remains</p>
</blockquote>
<p>Kent has coined the term “genie” in referring to whatever ‘AI’ thing he’s using. I am not comfortable with that. If one has read enough about genies, it might be OK, because the genie in literature is capricious and when it grants you a wish, that wish almost always bites you where you’d prefer not to be bitten. I think a better term would have been “demon”, but I’m neither his boss nor his editor. I’m just some guy, y’know?</p>
<h3 id="more-good-than-harm">More Good than Harm</h3>
<p>We live in a world that is incredibly complex, and that owes us nothing. Nature just is, and from the viewpoint of some random human, Jess or Kent or Ron, there is very little that we can really control. To some degree, we can control what we do, and sometimes we can influence what others do but generally not control them, and we might possibly have some influence on the overall system that makes up the world.</p>
<p>While someone might choose to sacrifice themselves, in some way, for something they value highly, I don’t think it’s my place to say that that person over there should sacrifice themselves for something that I care about. We all get to make those decisions for ourselves.</p>
<p>And it’s important to note that we can’t do much good for anyone if we’re dead, or if we live in a cave and do not engage with the world. And when we do engage with the world, we must inevitably do some harm. We eat something that would otherwise have been alive, we step on an ant that would otherwise have brought a delicious crumb home to the nest, we accidentally start a turf war between the Jets and the Sharks. You know how it goes: once you’re a Jet you’re a Jet all the way.</p>
<p>Thoughtful people, “good” people, I would say, try to do as little harm as possible, and try to do more good than harm, in some kind of balancing sense. And that has to start at home: we cannot do any good at all if we harm ourselves too much.</p>
<h3 id="ai-harms">‘AI’ Harms</h3>
<p>The current ‘AI’ thing does harms at many levels. Consumes vast resources, pumps money to the oligarchy, destroys rare books, costs ordinary people their jobs, and so on. In my personal view, the ‘AI’ subsystem is a net negative for humanity and for the world. Might it someday turn positive? Almost anything is possible but if it were to happen it would require a major change in the system of the world.</p>
<p>I think that Jessica and Kent think we’re stuck with it. And, as it happens, I, too, think we are stuck with it. We cannot put the <s>genie</s> demon back in the <s>bottle</s> box.</p>
<h3 id="ai-responses">‘AI’ Responses</h3>
<p>Whatever our overall thinking about ‘AI’, we have to choose how we, personally, will respond, how we will act within the world system as it is, containing among other evils, this ‘AI’ thing. There are innumerably many ways to respond:</p>
<dl>
  <dt>Fight the System</dt>
  <dd>There are those among us with the courage and energy to fight the system, to try to change it into a better system. Within that notion there are many ways to fight, ranging from writing letters to the editor to running for office to burning down the courthouse. Some of those actions work better than others, and it often seems that none of them work very well.</dd>
  <dt>Withdraw</dt>
  <dd>We can withdraw as much as possible from the system, ranging from stopping using Amazon to moving to a subsistence farm with no gas and electric, just a couple of pigs and a flock of raggedy chickens.</dd>
  <dt>Live Our Best Life</dt>
  <dd>I think everyone is really doing this one, making choices that seem to them to result in the best life for themselves, and for those they care about.</dd>
</dl>
<p>My best life, relative to ‘AI’, is to try to use it not at all, or failing that, as little as possible. I can make that choice because I am retired and do not need a job where I’d have to be adept with ‘AI’. The decision does cost me something in terms of what I want, which is somewhat like what I believe Jessica and Kent want.</p>
<p>Jessica and Kent, and many others I know, have made the decision to learn about the ‘AI’ systems, and to learn about them rather deeply, and to share what they have learned. I am absolutely confident that they do that to help others to live well in a world with ‘AI’ in it: helping others is on the mind of most of us who write articles, give talks, and so on. We enjoy being useful. (We may also enjoy the “fame”, and, yes, there is money in it, but with few exceptions, the people I know in my community are not just in it for the bread. They want to help people: they get off on helping people.)</p>
<p>Their best life includes learning about ‘AI’, and sharing what they know. I think that’s a very valid choice.</p>
<p>Other folx, some of whom I know and plenty whom I do not, are using ‘AI’ because their current job requires it, or because they see the handwriting on the wall and know that they’re going to need to be adept with it very soon. They’re doing it to stay employed, to feed themselves and their families, and to be able to do whatever other good they do in the world. Or, I suppose, evil, if any of them are evil henchmen or henchwomen. Henchpersons? Henchones? Never mind.</p>
<p>My own choice, not to participate in ‘AI’, means that I am giving up the possibility of helping people use it well. That is a choice that I make consciously and in the strong belief that it is right for me.</p>
<p>The point is, we all choose how to live our best life, in the midst of the whole glob that is the world, including the ‘AI’ sub-glob, and that is as it should be.</p>
<h3 id="acknowledge-both-sides">Acknowledge Both Sides</h3>
<p>I pulled out those quotes up above. Those are the first time I’ve noticed either of those individuals acknowledging the darker side of ‘AI’ use, which includes not working very well and not being much fun. I’m not saying they haven’t expressed that before, only that this is the first time I’ve noticed.</p>
<p>I do wish that they, and everyone using ‘AI’ would more often acknowledge that side of things, because while we can almost certainly not put the demon back in the box, we might be able to mitigate some of its drawbacks if we are able to keep them in mind and work on them. We might be able to make it less harmful.</p>
<h2 id="live-your-best-life">Live Your Best Life</h2>
<p>I hope that you will pay continued attention to living your best life, minimizing harm and maximizing benefit to the world, as you see fit. Proceed thoughtfully!</p>
 ]]> 
                </content:encoded>
                <pubDate>Fri, 18 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/o/x/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/o/x/</guid>
                <category>LLM-AI</category>
                <category>Social</category>
              </item>
              <item>
                <title>Refactoring = Learning</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME more changes xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>While you were away, I did a little refactoring. Let’s see what it shows us. I think we’ve done some good here.</p>
</blockquote>
<p>I’m still working on paths between sections, and still doing most of the work in the tests. Mostly. Yesterday’s afternoon session, unpublished, made a change to the Passages object.</p>
<p>Here’s what our active test looked like when last we met:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
    <span class="n">path_cells</span> <span class="o">=</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">suite_2</span><span class="p">)</span>
    <span class="c1"># now path_cells[0] is in one suite, and
</span>    <span class="c1"># path_cells[-1] in the other, and
</span>    <span class="c1"># path_cells[1:-1] are the cells we need for
</span>    <span class="c1"># the new path room.
</span>
    <span class="n">room_2</span> <span class="o">=</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">room</span>
    <span class="k">assert</span> <span class="n">room_2</span> <span class="ow">in</span> <span class="n">suite_2</span><span class="p">.</span><span class="n">room_set</span>
    <span class="n">room_1</span> <span class="o">=</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">room</span>
    <span class="k">assert</span> <span class="n">room_1</span> <span class="ow">in</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">room_set</span>
    <span class="n">path_room_1_2</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">path_room_1_2</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">make_passages</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
    <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">])</span>
    <span class="k">yield</span>
</code></pre></div></div>
<p>And now …</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
    <span class="n">path_cells</span> <span class="o">=</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">suite_2</span><span class="p">)</span>
    <span class="c1"># now path_cells[0] is in one suite, and
</span>    <span class="c1"># path_cells[-1] in the other, and
</span>    <span class="c1"># path_cells[1:-1] are the cells we need for
</span>    <span class="c1"># the new path room.
</span>
    <span class="n">room_2</span> <span class="o">=</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">room</span>
    <span class="k">assert</span> <span class="n">room_2</span> <span class="ow">in</span> <span class="n">suite_2</span><span class="p">.</span><span class="n">room_set</span>
    <span class="n">room_1</span> <span class="o">=</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">room</span>
    <span class="k">assert</span> <span class="n">room_1</span> <span class="ow">in</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">room_set</span>
    <span class="n">path_room_1_2</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">path_room_1_2</span><span class="p">)</span>
    <span class="c1"># layout.make_passages()
</span>    <span class="n">layout</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">((</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">((</span><span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">]))</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">isolate</span><span class="p">(</span><span class="n">path_room_1_2</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
    <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">])</span>
    <span class="k">yield</span>
</code></pre></div></div>
<p>The difference is there near the end. Instead of using <code class="language-plaintext highlighter-rouge">make_passages</code>, we use the extended path—the path between rooms that includes the starting and ending cells, in the rooms we’re connecting. We know we want the first two cells in that path to be a passage to the one room, and the last two to be a passage to the other room. So we add those two paths directly. The <code class="language-plaintext highlighter-rouge">unconditionally</code> method already existed.</p>
<p>THen we call <code class="language-plaintext highlighter-rouge">layout.passages.isolate</code>, which is a new method that makes the final path impermeable. That required some new code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Passages</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_connections</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Cell</span><span class="p">]]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room_connections</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="n">Room</span><span class="p">,</span> <span class="n">Room</span><span class="p">]]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">isolated</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="n">Room</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">isolate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">room</span><span class="p">:</span> <span class="n">Room</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">isolated</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
</code></pre></div></div>
<p>And we changed this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">create_passage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">r1</span><span class="p">:</span> <span class="n">Room</span><span class="p">,</span> <span class="n">r2</span><span class="p">:</span> <span class="n">Room</span><span class="p">,</span> <span class="n">func</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">r1</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">isolated</span> <span class="ow">or</span> <span class="n">r2</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">isolated</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">False</span>
        <span class="k">elif</span> <span class="bp">self</span><span class="p">.</span><span class="n">rooms_are_connected</span><span class="p">(</span><span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">):</span>
            <span class="k">return</span> <span class="bp">False</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">passage</span> <span class="o">=</span> <span class="n">func</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">(</span><span class="n">passage</span><span class="p">)</span>
            <span class="k">return</span> <span class="bp">True</span>
</code></pre></div></div>
<p>It’s just the first two lines that were added. If either of the two rooms to be connected is isolated, we return False without creating a passage. If not, then as before we don’t connect them if they are already connected, and if they aren’t connected, we call the provided function and connect them.</p>
<p class="pull-quote">Your point, sir?</p>
<p>The point of writing the code yesterday, if I recall, was that calling <code class="language-plaintext highlighter-rouge">make_passages</code> is kind of a big deal, with a lot of magic going on, and in the case in hand, we know more precisely what needs to be done, so basically I wanted to work out just what we needed and do that.</p>
<p>So the operations we need to connect two sections are now in the test and, cleaned up, they go like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
    <span class="n">path_cells</span> <span class="o">=</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">suite_2</span><span class="p">)</span>
    <span class="n">path_room_1_2</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">path_room_1_2</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">((</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">((</span><span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">]))</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">isolate</span><span class="p">(</span><span class="n">path_room_1_2</span><span class="p">)</span>
</code></pre></div></div>
<p>That sure looks like code that belongs in DungeonLayout, doesn’t it?</p>
<p>I’d like to move it over there, and I’d like not to lose the value of this test, which is looking at some details that will be hidden in the method we extract. We can’t be sure that the code will find the same path every time. If there is more than one pair of “closest” cells, the starting and ending points of the path are random. Maybe if we seed the random number generator?</p>
<p>I think we’ll replicate this test except for calling our new method and see what we can see. And we <em>will</em> seed the RNG and use the same seed in both tests. Here’s the <code class="language-plaintext highlighter-rouge">steps</code> method in the new test:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="n">random</span><span class="p">.</span><span class="n">seed</span><span class="p">(</span><span class="mi">14159</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_1</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_2</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
            <span class="n">path_cells</span> <span class="o">=</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">suite_2</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">add_isolated_path</span><span class="p">(</span><span class="n">suite_1</span><span class="p">,</span> <span class="n">suite_2</span><span class="p">)</span>
            <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
            <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">])</span>
            <span class="c1"># note: test relies on our path_cells being
</span>            <span class="c1"># same as the path found in `add_isolated_path`
</span>            <span class="c1"># currently Suite.find_closest_pair is
</span>            <span class="c1"># deterministic, so this works.
</span>            <span class="k">yield</span>
</code></pre></div></div>
<p>To allow for some checking, we privately call <code class="language-plaintext highlighter-rouge">find_extended_path</code>, which will return the same path that the <code class="language-plaintext highlighter-rouge">add_isolated_path</code> method uses, so we can check the ends to find anything we want to know.</p>
<p>The new method is as one would expect:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">add_isolated_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">s1</span><span class="p">:</span> <span class="n">Suite</span><span class="p">,</span> <span class="n">s2</span><span class="p">:</span> <span class="n">Suite</span><span class="p">):</span>
        <span class="n">x_path</span> <span class="o">=</span> <span class="n">s1</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
        <span class="n">path_room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">x_path</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="s">'isolated path'</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">path_room</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">((</span><span class="n">x_path</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">x_path</span><span class="p">[</span><span class="mi">1</span><span class="p">]))</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">((</span><span class="n">x_path</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">x_path</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">]))</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">isolate</span><span class="p">(</span><span class="n">path_room</span><span class="p">)</span>
</code></pre></div></div>
<p>Let’s commit this: <em>layout can create isolated path between suites.</em></p>
<p>Now I want to try this new scheme in an actual dungeon build. That’s going to be a bit messy, and it’ll probably give us some ideas for improvement. I’ll just edit the existing dungeon build.</p>
<p>Somewhat tedious but it goes like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">yield_table_steps</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">bottom_left</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">suite_bl</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">))</span>
    <span class="k">yield</span>
    <span class="n">top_left</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
    <span class="n">make_a_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">suite_tl</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span> <span class="o">-</span> <span class="n">previous_rooms</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">clear_subregions</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_isolated_path</span><span class="p">(</span><span class="n">suite_bl</span><span class="p">,</span> <span class="n">suite_tl</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">top_right</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">suite_tr</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span> <span class="o">-</span> <span class="n">previous_rooms</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">clear_subregions</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_isolated_path</span><span class="p">(</span><span class="n">suite_tl</span><span class="p">,</span> <span class="n">suite_tr</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">bottom_right</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">suite_br</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span> <span class="o">-</span> <span class="n">previous_rooms</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">right_side</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_isolated_path</span><span class="p">(</span><span class="n">suite_tr</span><span class="p">,</span> <span class="n">suite_br</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">clear_subregions</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
    <span class="n">make_diamond_in_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">bottom_right_plus_center</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">suite_boss</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span> <span class="o">-</span> <span class="n">previous_rooms</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">clear_subregions</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_isolated_path</span><span class="p">(</span><span class="n">suite_br</span><span class="p">,</span> <span class="n">suite_boss</span><span class="p">)</span>
    <span class="n">populate</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">add_content</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">)</span>
    <span class="k">yield</span>
</code></pre></div></div>
<p>There’s a pattern there, where we save the previous rooms, create our new suite from all the rooms, and we’ll look at that next time to see how to make the job easier, but as written we get a map that looks good to me:</p>
<p><img src="https://ronjeffries.com/articles/-v026/o/y/map.png" alt="map showing proper paths between sections" title="map showing proper paths between sections" /></p>
<p>Between that and our tests, I think we’re happy enough. Commit the main. Let’s sum up.</p>
<h2 id="summary">Summary</h2>
<p>We’ve seen a nice flow of events here. We started with some simple tests, built up to a pretty complex one, then expanded the internals of the test to see that a new method on DungeonLayout could do the job, then created a new test calling that method, wrote the method, bada bing bada boom.</p>
<p>Things went as smoothly as they ever do, and the result, the <code class="language-plaintext highlighter-rouge">add_isolated_path</code> facility seems to be just what we need.</p>
<p>The pattern in the main program, where we save all the existing rooms, build some stuff, make a suite of it, and use that suite later, seems worthy of packaging somewhere, probably as a function to use in building.</p>
<p>I suspect that we should begin to abstract building a bit, since (if this were a real game) there would be many dungeon layouts to be done and there should be some way of organizing them. Since we’re here to do things that interest me, we’ll do that if it seems interesting.</p>
<p>So. A productive afternoon and morning. Now to make a nice iced chai and find something to read. See you soon!</p>
 ]]> 
                </content:encoded>
                <pubDate>Thu, 17 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/o/y/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/o/y/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Paths Between Suites?</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>If things go as I expect, we’ll start working on the paths between sections of the dungeon. It could happen. It begins to happen.</p>
</blockquote>
<p>My rough plan for the inter-section paths is to build suites for each end of the path, and then so call a layout method to draw the path. That method may not currently exist, but we know that the code to do the job is in there somewhere. Then, we’ll figure out a way to mark the path “impermeable”, that is, arrange things so that if a room tries to connect with it, it’ll be rejected. Frequent readers may recall that we have a structure that prevents rooms connecting more than once. I think we’ll see about extending that capability to prevent connecting even once.</p>
<p>For all these things, we’ll find out when we get there. And I plan to continue to work with tests to get this done. It always goes better and it went particularly nicely yesterday, so why not today?</p>
<p>Here’s our latest test on this topic:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_two_sections_to_suites</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">diamond</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
                <span class="n">room</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">).</span><span class="n">diamond</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="n">size</span><span class="p">,</span> <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">)</span>
                <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">make_diamond_section</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="n">Any</span><span class="p">]:</span>
            <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
            <span class="n">all_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span> <span class="o">-</span> <span class="n">previous_rooms</span>
            <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
            <span class="k">return</span> <span class="n">new_rooms</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_1</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_2</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="k">yield</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">(),</span> <span class="n">layout</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">()</span>
</code></pre></div></div>
<p>A fanatic would have me write a new test to work on the path idea, but the test would look like this one and we’d have to move the helper functions around. We’re only going to work with <code class="language-plaintext highlighter-rouge">steps</code> for a while, so let’s sketch this in:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_1</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_2</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="n">path_room</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">make_path</span><span class="p">(</span><span class="n">suite_1</span><span class="p">,</span> <span class="n">suite_2</span><span class="p">)</span>
            <span class="k">assert</span> <span class="n">path_room</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
            <span class="k">yield</span>
</code></pre></div></div>
<p>There is no <code class="language-plaintext highlighter-rouge">make_path</code>, and I didn’t know what to assert about it. That’s OK, we’re just sketching. Now let’s see how paths are made. I think things start with <code class="language-plaintext highlighter-rouge">finish_map</code> in the layout:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">finish_map</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ensure_connected</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">make_passages</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">ensure_connected</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">while</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">is_fully_connected</span><span class="p">()</span> <span class="ow">and</span> <span class="n">count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
            <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_make_path_rooms</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">_make_path_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suites</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="c1"># self.check_intersection(suites)  # debug method
</span>        <span class="c1"># for s1, s2 in zip(suites, suites[1:] + suites[:1]):
</span>        <span class="k">for</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">suites</span><span class="p">,</span> <span class="n">suites</span><span class="p">[</span><span class="mi">1</span><span class="p">:]):</span>
            <span class="n">cells</span> <span class="o">=</span> <span class="n">s1</span><span class="p">.</span><span class="n">find_path_cells</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
                <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="s">'path'</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="c1"># cells can be empty if no path is possible
</span>            <span class="c1"># because we only accept source room,
</span>            <span class="c1"># target room, or available cells.
</span></code></pre></div></div>
<p>I’m not surprised to find those last four lines, which do pretty much exactly what we need. Extract the method and return the room, I think. I try this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">_make_path_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suites</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="c1"># self.check_intersection(suites)  # debug method
</span>        <span class="c1"># for s1, s2 in zip(suites, suites[1:] + suites[:1]):
</span>        <span class="k">for</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">suites</span><span class="p">,</span> <span class="n">suites</span><span class="p">[</span><span class="mi">1</span><span class="p">:]):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">make_path</span><span class="p">(</span><span class="n">s1</span><span class="p">,</span> <span class="n">s2</span><span class="p">)</span>
            <span class="c1"># cells can be empty if no path is possible
</span>            <span class="c1"># because we only accept source room,
</span>            <span class="c1"># target room, or available cells.
</span>
    <span class="k">def</span> <span class="nf">make_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">s1</span><span class="p">:</span> <span class="n">Suite</span><span class="p">,</span> <span class="n">s2</span><span class="p">:</span> <span class="n">Suite</span><span class="p">):</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="n">s1</span><span class="p">.</span><span class="n">find_path_cells</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
            <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="s">'path'</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">room</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
</code></pre></div></div>
<p>I should have moved those comment lines down. Do it now. A test is failing. I suppose it must be the current one. Ah. We need to clear the subregions before we look for the path. Subregions prohibit finding cells outside themselves.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="p">...</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
            <span class="n">path_room</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">make_path</span><span class="p">(</span><span class="n">suite_1</span><span class="p">,</span> <span class="n">suite_2</span><span class="p">)</span>
            <span class="k">assert</span> <span class="n">path_room</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
            <span class="k">yield</span>
</code></pre></div></div>
<p>Test passes. Life is good. Commit: <em>working on inter-section paths.</em></p>
<p>Well, life is semi-good. We need now to create the passages between this new room and the rooms in the sections to which it connects … and we do not know that. We may have made a misstep.</p>
<p>I think we’d have done well to look at <code class="language-plaintext highlighter-rouge">find_path_cells</code> in Suite. Let’s do so now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Suite</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">find_path_cells</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">suite</span><span class="p">:</span> <span class="n">Suite</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">Cell</span><span class="p">]:</span>
        <span class="n">source</span><span class="p">,</span> <span class="n">target</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">find_closest_pair</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span>
        <span class="n">rooms</span> <span class="o">=</span> <span class="p">[</span><span class="n">source</span><span class="p">.</span><span class="n">room</span><span class="p">,</span> <span class="n">target</span><span class="p">.</span><span class="n">room</span><span class="p">]</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">find_path</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">rooms</span><span class="p">)</span>
        <span class="n">on_path</span> <span class="o">=</span> <span class="n">cells</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">on_path</span>
</code></pre></div></div>
<p>Ah, yes. In this method, we find the closest two cells that might connect the suites. Then the <code class="language-plaintext highlighter-rouge">find_path</code> will return a path that <em>includes</em> those two cells. Those two cells are exactly the ones that our code wants to know about. I think we’re prefer to work with this information. We’ll change the test to call a new method in Suite, which we’ll extract first:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">find_path_cells</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">suite</span><span class="p">:</span> <span class="n">Suite</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="n">Cell</span><span class="p">]:</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span>
        <span class="n">on_path</span> <span class="o">=</span> <span class="n">cells</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">on_path</span>
    <span class="k">def</span> <span class="nf">find_extended_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">suite</span><span class="p">:</span> <span class="n">Suite</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Any</span><span class="p">:</span>
        <span class="n">source</span><span class="p">,</span> <span class="n">target</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">find_closest_pair</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span>
        <span class="n">rooms</span> <span class="o">=</span> <span class="p">[</span><span class="n">source</span><span class="p">.</span><span class="n">room</span><span class="p">,</span> <span class="n">target</span><span class="p">.</span><span class="n">room</span><span class="p">]</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">find_path</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">rooms</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">cells</span>
</code></pre></div></div>
<p>Now we’ll code inside our test for a while, which will make a bit of a mess, but enable us to see what we need and figure out where it goes.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
    <span class="n">path_cells</span> <span class="o">=</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">suite_2</span><span class="p">)</span>
    <span class="k">assert</span> <span class="n">path_cells</span>
    <span class="c1"># now path_cells[0] is in one suite, and
</span>    <span class="c1"># path_cells[-1] in the other, and
</span>    <span class="c1"># path_cells[1:-1] are the cells we need for
</span>    <span class="c1"># the new path room.
</span>    <span class="k">yield</span>
</code></pre></div></div>
<p>The test is green. I wrote some notes as comments, since I needed to say those things here and want to remember them there. Excuse me a moment while I go over to Layout and remove the extra stuff from that method we extracted. I’ll leave it extracted, that was a righteous move in any case.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">_make_path_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suites</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="c1"># self.check_intersection(suites)  # debug method
</span>        <span class="c1"># for s1, s2 in zip(suites, suites[1:] + suites[:1]):
</span>        <span class="k">for</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">suites</span><span class="p">,</span> <span class="n">suites</span><span class="p">[</span><span class="mi">1</span><span class="p">:]):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">make_path</span><span class="p">(</span><span class="n">s1</span><span class="p">,</span> <span class="n">s2</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">make_path</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">s1</span><span class="p">:</span> <span class="n">Suite</span><span class="p">,</span> <span class="n">s2</span><span class="p">:</span> <span class="n">Suite</span><span class="p">):</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="n">s1</span><span class="p">.</span><span class="n">find_path_cells</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
            <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="s">'path'</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="c1"># cells can be empty if no path is possible
</span>            <span class="c1"># because we only accept source room,
</span>            <span class="c1"># target room, or available cells.
</span></code></pre></div></div>
<p>Commit. We can always commit with green tests, and as we are kind of feeling our way, I’d prefer to commit often, as we inch forward, sort of like a ratchet jack stepping up one notch at a time.</p>
<h2 id="lets-reflect">Let’s Reflect</h2>
<p>As things stand right now, we have a list of cells in our dungeon-building steps, not as high level an object as we might want to have. But it is an interesting one. It’s a list of cells. Its end cells are in rooms already, one from the first section, one from the second. The cells between those are in no room at present, but they are exactly the path cells we want in our new room, which we would like to have be immune to other rooms connecting.</p>
<p>From the viewpoint of the dungeon creator, this “object” is important. Somewhere along it we’ll want to place the “problem” that Dot has to solve to move to the next section. We could put that problem anywhere along the object, including in the two cells that are in the existing rooms.</p>
<p>At this moment, I don’t know what we need or how we’ll use it, but it seems clear that we will want to remember all this. It seems that we could remember it in the layout, or we could  remember it right here in the build steps. I think it’s premature to put it into the layout. Let’s continue to build and see what happens.</p>
<p>Let’s do some asserts on what came back, and make a room of it. Then we’ll see if we can make it impermeable. I wonder how we could test that. Here we go for the room connecting part:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
    <span class="n">path_cells</span> <span class="o">=</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">find_extended_path</span><span class="p">(</span><span class="n">suite_2</span><span class="p">)</span>
    <span class="c1"># now path_cells[0] is in one suite, and
</span>    <span class="c1"># path_cells[-1] in the other, and
</span>    <span class="c1"># path_cells[1:-1] are the cells we need for
</span>    <span class="c1"># the new path room.
</span>
    <span class="n">room_2</span> <span class="o">=</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">room</span>
    <span class="k">assert</span> <span class="n">room_2</span> <span class="ow">in</span> <span class="n">suite_2</span><span class="p">.</span><span class="n">room_set</span>
    <span class="n">room_1</span> <span class="o">=</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">room</span>
    <span class="k">assert</span> <span class="n">room_1</span> <span class="ow">in</span> <span class="n">suite_1</span><span class="p">.</span><span class="n">room_set</span>
    <span class="n">path_room_1_2</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">path_room_1_2</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">make_passages</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
    <span class="k">assert</span> <span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">has_passage</span><span class="p">(</span><span class="n">path_cells</span><span class="p">[</span><span class="o">-</span><span class="mi">2</span><span class="p">])</span>
    <span class="k">yield</span>
</code></pre></div></div>
<p>This test passes. I think it could fail if our two Suites turned out to be adjacent. Let me write a fake test for that.</p>
<p>Should we mark it to skip, or let it fail, or what? For now I’ll just leave this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_two_adjacent_sections</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">pass</span>
</code></pre></div></div>
<p>We’ve done just about an hour of work and writing. Let’s have a break.</p>
<h2 id="summary-so-far-">Summary So Far …</h2>
<p>Our first step to connecting the section Suites turned out not to be in the right direction. We just belayed that one and took a different course, which seems to be working out so far. Given that the sections are not touching one another, we can draw a path between them, including the cells to be connected in the closest points between the suites. The inner cells of that path are the ones we want in our room connecting the cells. The outer two cells are the ones we connect to.</p>
<p>So we have all the facts we need to do our work, though we’re probably doing too much detailed work in the <code class="language-plaintext highlighter-rouge">steps</code>. When we have all the steps figured out, it should become more clear how to package them up for proper use.</p>
<p>It occurs to me that some decades back I’d have been terrified that we’re building up these things but don’t have any real idea how they’ll be used to allocate things in the dungeon, and so on and so on. I’ve come to be confident that while I will surely make a bit of a mess, I’m very unlikely to make a mess that can’t be readily cleaned up in small steps.</p>
<p>One might argue, yeah, but if you make 100 small steps out and then 100 small steps in, maybe you could have just made 100 small steps directly to what you needed, if you had just figured it out in advance. Or maybe only 50.</p>
<p>If I were more perfect than I am, maybe. But my experience with careful overall design is that to get it right takes a very long time, and then one has to build pretty much all of it before anything works, and then at the far end the design isn’t all that close to what you really needed anyway.</p>
<p>In short: my experience is that doing a lot of design before starting rarely, if ever, measures up to the dream of Design It Build The Design All Is Well.</p>
<p>But you do you, if you’re still allowed to do you. And if you’re not, well, it saddens me and I hope you’ll soon find a better situation.</p>
<p>We’ll just keep pushing the ball forward, bit by bit, in whatever direction feels right. We’ll see what happens, but it has worked well so far. I think that will continue.</p>
<p>And I’ll see you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Wed, 16 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/o/z/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/o/z/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Using YieldStepper</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>Today, unless I miss my guess, which frequently happens, we’ll see some of the strength of the YieldStepper notion. Tests FTW.</p>
</blockquote>
<p>Our dungeons look much like we intend, with separate room sections connected by single paths between them:</p>
<p><img src="https://ronjeffries.com/articles/-v026/p/a/ok.png" alt="map with five connected sections" title="map with five connected sections" /></p>
<p>However, we are working to add more capability for our Dungeon Design Team (I have the hat here somewhere). The purpose of those single paths is to allow us to bar entry to the next section until Dot has solved the problems of the current section, including whatever task opens the gateway to the next section. Furthermore, those paths need to have impermeable walls. Normally, in our dungeon building scheme, if two rooms share a wall, the builder code will place a passage in that wall, permitting, well, passage between them. This makes the dungeon much more interesting, with loops in the paths one can follow. But for these inter-section paths we cannot allow such incursions, because they would create shortcuts through which Dot might pass without solving all the puzzles we pose.</p>
<p>I have rough plans for all this. I think that if it were done “right”, it would go something like this:</p>
<ol>
  <li>Isolate the code that connects two Suites with a path;</li>
  <li>During the build, identify each section as a Suite;</li>
  <li>Use the Suite-connection code to produce a path room;</li>
  <li>Mark the path room as impermeable.</li>
</ol>
<p>As all this goes on, the build should record whatever information it needs for door-building, section-loading, whatever.</p>
<p>As things stand, our dungeon build is a function. If need be, it could be a method of a class, and quite likely it always should be. We’ll find out as we proceed.</p>
<p>If we were wise, which assumes facts not in evidence, we would do all this with tests. We’ll give ourselves the best possible chance by beginning with a test. Let’s try … write a build function that builds some rooms and makes a suite of them.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestAdvancedBuild</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Fails. We have a new test class! Woot! So far so good. Now I’ll sketch a little build function, then see what it takes to make it work.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_minimal_build</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="k">yield</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">(),</span> <span class="n">layout</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">()</span>
</code></pre></div></div>
<p>This runs green. With the <code class="language-plaintext highlighter-rouge">yield</code> replaced by <code class="language-plaintext highlighter-rouge">pass</code>, we get this error:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">run_to_completion</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="o">&gt;</span>       <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">:</span>
<span class="n">E</span>       <span class="nb">TypeError</span><span class="p">:</span> <span class="s">'NoneType'</span> <span class="nb">object</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">iterable</span>
</code></pre></div></div>
<p>Ah, right. There has to be at least one <code class="language-plaintext highlighter-rouge">yield</code>, or Python doesn’t recognize <code class="language-plaintext highlighter-rouge">step</code> as iterable. Not an issue in our code.</p>
<p>Here’s a more meaningful test:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_section_to_suite</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">diamond</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
                <span class="n">room</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">).</span><span class="n">diamond</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="n">size</span><span class="p">,</span> <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">)</span>
                <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
            <span class="n">rooms</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">rooms</span>
            <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
            <span class="n">suite</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">rooms</span><span class="p">)</span>
            <span class="k">yield</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">(),</span> <span class="n">layout</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">()</span>
</code></pre></div></div>
<p>We build two small diamond rooms in the lower left quadrant. We connect them with <code class="language-plaintext highlighter-rouge">finish_map</code> after which there will be two rooms if they happened to be adjacent, and three if a path was needed to connect them. We grab the rooms and make a Suite.</p>
<p>So far so good. Let’s think about this.</p>
<h2 id="reflection">Reflection</h2>
<p>There are at least two issues that strike me. First, this method <code class="language-plaintext highlighter-rouge">step</code> is already pretty complicated. But we have an example of a function <code class="language-plaintext highlighter-rouge">diamond</code>, that can move some complexity out. So we’ll keep an eye on complexity but it doesn’t seem unsolvable.</p>
<p>Second, while it was easy to get the rooms for the first Suite, since it needs all the rooms we have, getting the next Suite’s rooms seems a bit more tricky. But can’t we just grab all the rooms and subtract the ones we just grabbed? Let’s try extending this test. No. Let’s write a new one, and try to make it better.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_two_sections_to_suites</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">diamond</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
                <span class="n">room</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">).</span><span class="n">diamond</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="n">size</span><span class="p">,</span> <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">)</span>
                <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
            <span class="n">rooms_1</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
            <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">rooms_1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">rooms_1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
            <span class="n">suite_1</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">rooms_1</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
            <span class="n">rooms_2</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span> <span class="o">-</span> <span class="n">rooms_1</span>
            <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">rooms_2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">rooms_2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
            <span class="n">suite_2</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">rooms_2</span><span class="p">)</span>
            <span class="k">yield</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">(),</span> <span class="n">layout</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">()</span>
</code></pre></div></div>
<p>We converted the <code class="language-plaintext highlighter-rouge">rooms</code> from <code class="language-plaintext highlighter-rouge">layout</code> to a <code class="language-plaintext highlighter-rouge">set</code> and subtracted. Nice enough. Won’t work quite like that for three, though.</p>
<p>I have an idea how to make those two patches of code alike enough to be used for all four of our corners. First this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
        <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
        <span class="n">all_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span> <span class="o">-</span> <span class="n">previous_rooms</span>
        <span class="n">previous_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="n">suite_1</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
        <span class="n">all_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span> <span class="o">-</span> <span class="n">previous_rooms</span>
        <span class="n">previous_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="n">suite_2</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
        <span class="k">yield</span>
</code></pre></div></div>
<p>That’s nearly good: we could just about extract those two bits as a method. But I think this will go better:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
        <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
        <span class="n">all_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span> <span class="o">-</span> <span class="n">previous_rooms</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="n">suite_1</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
        <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
        <span class="n">all_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span> <span class="o">-</span> <span class="n">previous_rooms</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="n">suite_2</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
        <span class="k">yield</span>
</code></pre></div></div>
<p>The previous version was accumulating previous_rooms, but we have no need to do that, since the layout does that as a matter of course.</p>
<p>Now we should be able to extract a function <code class="language-plaintext highlighter-rouge">make_diamond_section</code>. PyCharm does exactly what we had in mind:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_two_sections_to_suites</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">diamond</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
                <span class="n">room</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">).</span><span class="n">diamond</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="n">size</span><span class="p">,</span> <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">)</span>
                <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">make_diamond_section</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">set</span><span class="p">[</span><span class="n">Any</span><span class="p">]:</span>
            <span class="n">previous_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">diamond</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
            <span class="n">all_rooms</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">all_rooms</span> <span class="o">-</span> <span class="n">previous_rooms</span>
            <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span> <span class="ow">or</span> <span class="nb">len</span><span class="p">(</span><span class="n">new_rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
            <span class="k">return</span> <span class="n">new_rooms</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_1</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
            <span class="n">new_rooms</span> <span class="o">=</span> <span class="n">make_diamond_section</span><span class="p">()</span>
            <span class="n">suite_2</span> <span class="o">=</span> <span class="n">Suite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">new_rooms</span><span class="p">)</span>
            <span class="k">yield</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">(),</span> <span class="n">layout</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">()</span>
</code></pre></div></div>
<p>The test continues to run green. Maybe we should move the assertion down into <code class="language-plaintext highlighter-rouge">steps</code>? No real need, this is just a test and example of how we might really do things.</p>
<p>I’ve only been at this for 80 minutes, but this is a super time to break and sum up. Commit: <em>new tests demonstrating use of YieldStepper.</em></p>
<h2 id="summary">Summary</h2>
<p>We see above that with YieldStepper we can put any code we want into the build steps. With the previous BuildStepper, we were much more constrained. So YieldStepper is showing its strengths already. Chuffed, I am, veritably happy as a dog with two tails.</p>
<p>I’m also pleased that I decided to do this in tests. I am often too lazy to do it, and when I do begin with tests, it usually goes better and never goes worse than trying to just jam the code in. It’s a shame that my habits aren’t better than they are. I do know better. And sometimes, like today, I actually do better.</p>
<p>It was much easier to build things up in small chunks, and easy to identify opportunities to remove duplication, which, as all here know, is my primary way of improving code. Doing something twice is a sign that there is a <em>thing</em> that can be isolated and just used instead of copied.</p>
<p>I want to call out the steps toward making the two sections more similar. I learned that trick around a quarter-century ago, when Kent Beck, working on the big screen, made two very different bits of code look alike, and then, when they looked alike, extracted them into a method. All looking on were amazed. Those were the days when we were all like gods, striding across the code, making it better. Or something.</p>
<p>But the point remains: the similarity was already evident, but by moving things around, and noticing that the previous values we needed were actually just the current set or rooms, we enabled the extraction that makes the code above more clear and rather nicely reusable. I’m sure we can extend this morning’s approach to making Suite instances for each section of our dungeon.</p>
<p>We’re still left with breaking out a Suite-to-Suite path maker, and making that path impermeable. But when we do that, and we will do that, assuming that I last that long, we’ll have the data we need in the build and we’ll just have a new method on <code class="language-plaintext highlighter-rouge">layout</code> that does the inter-section paths.</p>
<p>Unless I miss my guess, which is always a possibility. That’s what makes real programming fun.</p>
<p>And I’ll offer this bet: if you are currently condemned to using LLM-AI to build code, I’ll bet I’m having more fun than you are.</p>
<p>See you next time for more fun!</p>
 ]]> 
                </content:encoded>
                <pubDate>Tue, 15 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/p/a/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/p/a/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>The Fun Part</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>I think this morning we get to do the fun part with our new YieldStepper. Ants??</p>
</blockquote>
<p>But first, we need to put in the view-handling part. When stepping the build, we need to update the view on every step. And in the BuildStepper, there’s some special code at the end of things, but maybe we’ll do that differently. To begin, however, I think we need to stick closely to the BuildStepper, which goes like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">BuildStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">table</span><span class="p">,</span> <span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span> <span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">table</span> <span class="o">=</span> <span class="n">table</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">index</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">layout</span> <span class="o">=</span> <span class="n">layout</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span> <span class="o">=</span> <span class="n">dungeon</span>
    <span class="k">def</span> <span class="nf">run_to_completion</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">while</span> <span class="bp">self</span><span class="p">.</span><span class="n">_more_to_do</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_do_one_step</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_update_view</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">_more_to_do</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_do_one_step</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_update_view</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_more_to_do</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">_update_view</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">view</span><span class="p">:</span>
            <span class="n">sub</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">subregion</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
            <span class="n">view</span><span class="p">.</span><span class="n">update_KSL</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">subregion</span> <span class="o">=</span> <span class="n">sub</span>
    <span class="k">def</span> <span class="nf">_more_to_do</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">index</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">table</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">_do_one_step</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">table</span><span class="p">[</span><span class="bp">self</span><span class="p">.</span><span class="n">index</span><span class="p">](</span><span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">index</span> <span class="o">+=</span> <span class="mi">1</span>
</code></pre></div></div>
<p>The YieldStepper, so far, looks like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">YieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">steps</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">steps</span> <span class="o">=</span> <span class="n">steps</span>
    <span class="k">def</span> <span class="nf">run_to_completion</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">:</span>
            <span class="k">pass</span>
    <span class="k">def</span> <span class="nf">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="nb">next</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">)</span>
            <span class="k">return</span> <span class="bp">True</span>
        <span class="k">except</span> <span class="nb">StopIteration</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">False</span>
</code></pre></div></div>
<p>I think that we may find that we can pass the view as an optional construction parameter, but for now let’s stick to the existing style. Since this code is driving a view, I’m not going to TDD it, as tests of views are difficult, slow, and anyway I don’t want to.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">run_to_completion</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">:</span>
            <span class="k">pass</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_update_view</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="nb">next</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_update_view</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
            <span class="k">return</span> <span class="bp">True</span>
        <span class="k">except</span> <span class="nb">StopIteration</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">False</span>
    <span class="k">def</span> <span class="nf">_update_view</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">view</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">sub</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">subregion</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">clear_subregions</span><span class="p">()</span>
            <span class="n">view</span><span class="p">.</span><span class="n">update_KSL</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">subregion</span> <span class="o">=</span> <span class="n">sub</span>
</code></pre></div></div>
<p>PyCharm points out that we don’t have a layout to talk to. Let’s add that as an optional parameter as well, this time to the constructor.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">YieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">steps</span><span class="p">,</span> <span class="n">layout</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">steps</span> <span class="o">=</span> <span class="n">steps</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">layout</span> <span class="o">=</span> <span class="n">layout</span>
</code></pre></div></div>
<p>PyCharm is happy, the tests are happy, and I want a save point for what’s next. Commit: <em>adding layout and view to YieldStepper.</em></p>
<p>So far, all this code is in the test file. We’ll leave it there but now we have to change ‘main’ substantially, to use the new stepper.</p>
<p>To minimize work and change, I use the old methods to draw some rooms:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">yield_table_steps</span><span class="p">(</span><span class="n">layout</span><span class="p">):</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
    <span class="k">yield</span>
</code></pre></div></div>
<p>And I bash main like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
    <span class="n">dungeon</span> <span class="o">=</span> <span class="n">Dungeon</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="c1"># stepper = make_build_table(layout, dungeon)
</span>    <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">yield_table_steps</span><span class="p">(),</span> <span class="n">layout</span><span class="p">)</span>
    <span class="n">screen_multiplier</span> <span class="o">=</span> <span class="mi">16</span>
    <span class="n">screen_width</span> <span class="o">=</span> <span class="n">screen_multiplier</span><span class="o">*</span><span class="n">dungeon</span><span class="p">.</span><span class="n">max_x</span>
    <span class="n">screen_height</span> <span class="o">=</span> <span class="n">screen_multiplier</span><span class="o">*</span><span class="n">dungeon</span><span class="p">.</span><span class="n">max_y</span>
    <span class="n">arcade</span><span class="p">.</span><span class="n">Window</span><span class="p">(</span><span class="n">screen_width</span> <span class="o">+</span> <span class="mi">8</span><span class="o">*</span><span class="n">screen_multiplier</span><span class="p">,</span> <span class="n">screen_height</span><span class="p">,</span> <span class="s">'Caveat Emptor'</span><span class="p">)</span>
    <span class="n">view</span> <span class="o">=</span> <span class="n">DungeonView</span><span class="p">(</span><span class="n">dungeon</span><span class="p">)</span>
    <span class="c1"># for single stepping comment out next TWO lines
</span>    <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">(</span><span class="n">view</span><span class="p">)</span> <span class="c1">#
</span>    <span class="n">dungeon</span><span class="p">.</span><span class="n">run</span><span class="p">()</span>
    <span class="c1"># for single stepping comment out previous TWO lines
</span>    <span class="n">view</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">stepper</span><span class="p">)</span>
</code></pre></div></div>
<p>This is enough to find out why it doesn’t work. Forgot layout:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">yield_table_steps</span><span class="p">(</span><span class="n">layout</span><span class="p">),</span> <span class="n">layout</span><span class="p">)</span>
</code></pre></div></div>
<p>There were squiggles, I just didn’t notice them. Now when I run it, the game comes up with an empty no rooms visible dungeon, but the starting message scrolls.</p>
<p>We’ll drop in some prints to see where we’re going.</p>
<p>My second run shows a dungeon that looks as I expect. What happened the first time, I’m rather sure, is that the few rooms I created just didn’t show up in the full view, and I never zoomed out.</p>
<p>Let me try the single stepping. When I comment out the two magic lines that turn on the stepper, I notice that one of them is <code class="language-plaintext highlighter-rouge">dungeon(run)</code>, which I think was somehow left out of YieldStepper. But we should be able to see things happening step by step anyway.</p>
<p>Stepping works, and the dungeon does run. Ah. I forgot, we built that into the KeyPress code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">elif</span> <span class="n">symbol</span> <span class="o">==</span> <span class="n">arcade</span><span class="p">.</span><span class="n">key</span><span class="p">.</span><span class="n">D</span><span class="p">:</span>
    <span class="n">more</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">view</span><span class="p">)</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">more</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">called_run</span><span class="p">:</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">.</span><span class="n">run</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">called_run</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>I think it’s actually working! I expected at least some difficulty with the untested bits but we seem to have found an acorn. Excuse me for a moment while I set up the whole build process from BuildStepper, in the YieldStepper.</p>
<p>I’ll spare you the whole thing, but it goes like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">yield_table_steps</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">bottom_left</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_cave_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">top_left</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="n">make_a_round_room</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
    <span class="p">...</span>
    <span class="n">populate</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="n">add_content</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">)</span>
    <span class="k">yield</span>
</code></pre></div></div>
<p>We see already what is nice about this scheme: we can put in as many or as few <code class="language-plaintext highlighter-rouge">yield</code> statements as we want. So I elected to draw each section’s rooms in one go, then the connecting paths between them. Let’s have a movie of what happens now.</p>
<video controls="" autoplay="" loop="" muted="" width="80%" height="auto" vertical-align="middle" src="https://ronjeffries.com/articles/-v026/p/b/yield.mov" type="video/mp4">
</video>
<p>That is very much exactly what is supposed to happen. With the YieldStepper, we can build up a dungeon definition as above, with no <code class="language-plaintext highlighter-rouge">yield</code> statements, and then, if we want to observe some part of the build, we can insert just the ones we want. And, of course, it’s now easier to insert print statements where we may want them, and, for those who like to run the debugger, we could set a breakpoint right in the middle of the build.</p>
<p>The changes to ‘main’ are destructive to the BuildStepper style, and this is working, so I’ll clean out the BuildStepper bits and commit this: <em>converted to YieldStepper</em>.</p>
<h2 id="summary">Summary</h2>
<p>So this has gone just as I had thought (and hoped) that it would. The <code class="language-plaintext highlighter-rouge">yield</code> approach makes defining a dungeon simpler and more flexible. We can use existing functions, as in the example above, or we could write out more detailed code right in line. (Of course we would soon refactor it back out into useful functions, right? Right?)</p>
<p>We can move <code class="language-plaintext highlighter-rouge">yield</code> statements around, adding and subtracting them so that we can run chunks that we don’t want to observe, then insert lots of <code class="language-plaintext highlighter-rouge">yield</code>s around code that we do want to observe.</p>
<p>I think that practicing using <code class="language-plaintext highlighter-rouge">yield</code> with those tests probably made things go more smoothly, and I’m sure that TDDing the basics of YieldStepper was helpful, as it left the stepper in a condition very close to its final form, so that adding the view-related bits was unlikely to break anything.</p>
<p>It went smoothly, the stepping capability is far more flexible and convenient, and best of all let’s face it, doing it with <code class="language-plaintext highlighter-rouge">yield</code> is quite nifty. Kind of like real programming.</p>
<p>I commented to my fellow Slackers the other day:</p>
<blockquote>
  <p>One thing that I don’t always like about small steps is that one rarely gets the pleasure of solving a big complex problem. Of course the art lies in never solving big complex problems, instead turning them into small simple problems. But still, one remembers those few times when one solved the whole shebang in one massive mental effort. If that ever really happened. But seriously, when you’re in the flow of solving something with small steps, one just sees a bit, carves it off, solves it, carves another. Everything is like “oh wow that’s hard”, snip snip snip, “oh wow that was easy”. None of that god-like feeling. I kind of miss it, though my god-like days are probably long in the past anyway.</p>
</blockquote>
<p>The amazing thing for me about the small step approach is that the whole solution never really comes into my mind as a “whole”. Instead, the code gets more and more capable, and often nicer and nicer, and it can end up in a very good place, and yet all I really did was make tiny things better. I’m moving in a useful direction, and that does require some decent intuition about what would be better.</p>
<p class="pull-quote">You were promised ants.</p>
<p>But I feel like an ant. I go around carrying stuff from place to place and then one day I look up and we have this amazing system around us. But the ant could “know” that there are lots of ants making the system happen. If ants knew things. But here chez Ron, there’s just me. And still, things happen.</p>
<p>This particular step was a bit more than just carrying crumbs from place to place: I had a vision of what the YieldStepper could be, and actually built it with that in mind. Obviously no big deal, it only took a couple of hours.</p>
<p>But it has a different feeling than bit-by-bit code improvement. It’s like I called my shot, three rails, eight ball in the corner, and made it.</p>
<p>I don’t know. It feels good whenever I make progress, but today’s is just a bit more tasty than some days. Not a full on Callooh Callay, but at least a bit of a Yippee!</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sun, 13 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/p/b/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/p/b/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Very Nice Idea</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>Better yet, it will involve actual programming! Let’s improve the Making App.</p>
</blockquote>
<p>My brother GeePaw Hill refers to the Making App and the Shipping App as aspects of a software product. The Shipping App is, of course, the bits you ship to the users. The Making App, in contrast, refers to the things that we build into the product that help us to build the product. I have a very nice idea for the Making side of things, and it promises to be both useful and fun to build.</p>
<p>We’ve been working off and on on one of our Making requirements, around the BuildStepper, which lets a developer step through the creation steps that build up the dungeon, so as to see the effect of each step. It’s aimed at one of the teams in our large dungeon product development, the individuals who create layouts and populate them with denizens and curiosa. Of course, there’s only me here. We are using this imaginary dungeon product as a basis for setting up and solving programming issues that arise in real software development, as done by humans.</p>
<p>The BuildStepper is simple and somewhat useful. It is given a list of functions that called in order, and the functions do useful things as part of building up the dungeon. A stepper list looks like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">steps</span> <span class="o">=</span>  <span class="p">[</span>
        <span class="n">bottom_left</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="p">...</span>
</code></pre></div></div>
<p>And the functions are passed the layout and the dungeon, and look like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">bottom_left</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">make_a_diamond_room</span><span class="p">(</span><span class="n">layout</span><span class="p">:</span> <span class="n">DungeonLayout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
        <span class="n">room</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">).</span><span class="n">diamond</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">([</span><span class="mi">25</span><span class="p">,</span> <span class="mi">41</span><span class="p">]),</span> <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">finish</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
</code></pre></div></div>
<p>The Stepper has two modes. It can just call all the functions in sequence, all in one go, or it can call them one at a time under keyboard control. The all in one go is for playing the game, and the step by step is for observing the dungeon building process as we configure it.</p>
<p>For anything we we want to do in building up the dungeon, we “just” have to create a new function and put its name in the list. This works as we intended, and I’m sure we felt that it was just a first step toward what we’d ultimately need, but whether we did or not, the current stepper idea has Serious Drawbacks, including but not limited to:</p>
<ol>
  <li>Even for one-liners like <code class="language-plaintext highlighter-rouge">bottom_left</code> and <code class="language-plaintext highlighter-rouge">finish</code>, we have to write a function so that the stepper can call it;</li>
  <li>There is no provision for printing any kind of ongoing info, other than creating yet another function to print a message;</li>
  <li>There’s no good way to do conditional builds;</li>
  <li>There’s no good way to access information and tuck it away for use in building the dungeon, unless we store info in globals and build yet more functions to save and access it.</li>
</ol>
<p>So far, in our frequent use of the stepper while working on the path making / subregion idea, I’ve built up a general dislike for how it works. If some poor devils were condemned to working in the dungeon building up things with Stepper, I’m sure I could hear their moans and screams all the way up here in my programming aerie. Anyway, my own irritation with it must have hit some kind of subconscious peak, because</p>
<h2 id="i-have-an-idea">I Have an Idea</h2>
<p>The idea has a delightful techie name: coroutines! And Python has a keyword that is just what we need: <code class="language-plaintext highlighter-rouge">yield</code>. Let me summarize the ideas for those who need a refresher. Of course we’ll be into the details shortly. Coroutines are used for “cooperative multitasking”. In our use here we’ll be writing a function that can pause in the middle to return a result, and then the next time it’s called, pick up where it left off to provide the next result. The <code class="language-plaintext highlighter-rouge">yield</code> keyword is like <code class="language-plaintext highlighter-rouge">return</code>, except that on the next call, the function will pick up after the <code class="language-plaintext highlighter-rouge">yield</code> instead of at the top.</p>
<p>Python provides <code class="language-plaintext highlighter-rouge">yield</code> as part of the notion of “generators”, functions that return a series of result without building the series into a list or other structure, instead just, well, generating values one at a time. And in our use, we’ll use this capability to provide single stepping for our dungeon builders.</p>
<p>For a conceptual example, let’s consider a sequence like the build steps we showed above. If we were not stepping, we might just write a long function that called various other functions:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">build_some_stuff</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
            <span class="n">room</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">).</span><span class="n">diamond</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">([</span><span class="mi">25</span><span class="p">,</span> <span class="mi">41</span><span class="p">]),</span> <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
</code></pre></div></div>
<p>We would presumably refactor that long function for clarity and convenience, but the point is that it would be written and organized to the liking of the dungeon builder, and relatively unconstrained in what it could do.</p>
<p>But we can’t step a function like that, executing just one bit at a time. Unless we wrote it this way:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">build_some_stuff</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
            <span class="n">room</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">).</span><span class="n">diamond</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">([</span><span class="mi">25</span><span class="p">,</span> <span class="mi">41</span><span class="p">]),</span> <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">)</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="k">yield</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">finish_map</span><span class="p">()</span>
    <span class="k">yield</span>
</code></pre></div></div>
<p>Putting in those <code class="language-plaintext highlighter-rouge">yield</code> statements turns the function into a Python generator, which means we might call it like this instead of just calling it directly:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">build_some_stuff</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="k">pass</span>
</code></pre></div></div>
<p>That loop would just keep calling <code class="language-plaintext highlighter-rouge">build_some_stuff</code> until it stopped yielding things. Since it’s yielding <code class="language-plaintext highlighter-rouge">None</code> we have no reason to hold on to them.</p>
<p>But the cool thing is this: When we want to <em>step</em> the build, our stepping code can just call <code class="language-plaintext highlighter-rouge">next</code> on the generator, and it will execute from where it left off down to the next yield.</p>
<p>Here are a couple of tests showing the two ways of calling the same generator:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestGenerator</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">three_things</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">yield</span> <span class="mi">1</span>
        <span class="k">yield</span> <span class="mi">2</span>
        <span class="k">yield</span> <span class="mi">3</span>
    <span class="k">def</span> <span class="nf">test_three_things</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">thing</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">three_things</span><span class="p">()):</span>
            <span class="k">assert</span> <span class="n">thing</span> <span class="o">==</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
    <span class="k">def</span> <span class="nf">test_three_things_separately</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">things</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">three_things</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">next</span><span class="p">(</span><span class="n">things</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
        <span class="k">assert</span> <span class="nb">next</span><span class="p">(</span><span class="n">things</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
        <span class="k">assert</span> <span class="nb">next</span><span class="p">(</span><span class="n">things</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
</code></pre></div></div>
<p>I wrote a bunch of other little tests, putting yields into functions called by other yields and such, which may come in handy later. We’ll look at those if we need them. But this one is germane to the real idea:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">make_room</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">layout</span><span class="p">):</span>
        <span class="n">room</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="k">if</span> <span class="n">origin</span> <span class="p">:</span><span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">unused_cell_or_none</span><span class="p">():</span>
            <span class="n">room</span> <span class="o">=</span> <span class="p">(</span><span class="n">RoomMaker</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
                    <span class="p">.</span><span class="n">diamond</span><span class="p">(</span>
                        <span class="n">number_of_cells</span><span class="o">=</span><span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">([</span><span class="mi">25</span><span class="p">,</span> <span class="mi">41</span><span class="p">]),</span>
                        <span class="n">origin</span><span class="o">=</span><span class="n">origin</span><span class="p">))</span>
            <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
        <span class="k">yield</span> <span class="n">room</span>
    <span class="k">def</span> <span class="nf">test_cannot_just_call_use_next</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
        <span class="c1"># must say next here (or loop)
</span>        <span class="n">room</span> <span class="o">=</span> <span class="nb">next</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">make_room</span><span class="p">(</span><span class="n">layout</span><span class="p">))</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">layout</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
        <span class="k">assert</span> <span class="n">room</span> <span class="ow">in</span> <span class="n">layout</span><span class="p">.</span><span class="n">rooms</span>
</code></pre></div></div>
<p>Here we have a very small bit of code that makes a very small dungeon, and actually yields the room, just so we can see what it does. That said, my present plan is that we won’t yield anything, unless we get some clever idea as we use the new stepper.</p>
<p>OK, more than enough chatter, let’s get to it. I think we should just TDD up a new kind of BuildStepper and plug it in. It’ll be tiny.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestYieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_hook</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">assert</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Perfect. One test fails. Replace it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestYieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_create</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">stepper</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
</code></pre></div></div>
<p>And, of course …</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">YieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">pass</span>
</code></pre></div></div>
<p>You may be thinking “Why doesn’t he just test something real instead of doing these tiny stupid things”. And yes, I could, and yes, I often do. But I find that starting with these baby steps is easy, requires no particular thinking, and provides a basis where thinking can take place. So now we’ll write tests that are little users of YieldStepper, similar to the real ones that we’ll create Real Soon Now.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_run_stepper</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="k">nonlocal</span> <span class="nb">sum</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">2</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">3</span>
            <span class="k">yield</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">6</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">run_to_completion</code> is the method on the existing BuildStepper that just executes whatever’s left in the stepper to be done. In the new class it looks like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">YieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">steps</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">steps</span> <span class="o">=</span> <span class="n">steps</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">run_to_completion</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">:</span>
            <span class="k">pass</span>
</code></pre></div></div>
<p>Test passes. Let’s commit: <em>working on YieldStepper</em>. Now let’s drive out single-stepping:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="k">nonlocal</span> <span class="nb">sum</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">2</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">3</span>
            <span class="k">yield</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">1</span>
</code></pre></div></div>
<p>This is enough to fail, although I plan to extend it. <code class="language-plaintext highlighter-rouge">maker_single_step</code> is the method we use on the existing stepper, of course, so we’re replicating it here.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">next</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">)</span>
</code></pre></div></div>
<p>Not terribly difficult. We do have an issue, however, which we’ll talk about in a moment. Let’s extend the test first:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="k">nonlocal</span> <span class="nb">sum</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">2</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">3</span>
            <span class="k">yield</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">)</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">1</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">6</span>
</code></pre></div></div>
<p>Green. Commit.</p>
<p>The issue is this: In the current stepper, we have this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">run_to_completion</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">while</span> <span class="bp">self</span><span class="p">.</span><span class="n">_more_to_do</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_do_one_step</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_update_view</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">view</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">_more_to_do</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_do_one_step</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_update_view</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_more_to_do</span><span class="p">()</span>
</code></pre></div></div>
<p>I’m not concerned about passing the the <code class="language-plaintext highlighter-rouge">view</code>: that’s trivial. I am concerned about <code class="language-plaintext highlighter-rouge">_more_to_do</code>. If anyone is using that feature, we have a problem, which is that generators cannot answer whether there is more to do, they can only try. When a generator is called one more time than it has values, it raises a StopIteration exception. In our new <code class="language-plaintext highlighter-rouge">run_to_completion</code> we just iterate until done, and the <code class="language-plaintext highlighter-rouge">for</code> eats the StopIteration and stops. But in <code class="language-plaintext highlighter-rouge">maker_single_step</code> we can’t really replicate the return of True if there is more to do. We can, however, swallow the exception and return something at that point.</p>
<p>In our code for the ‘D’ operation (D for maker single step, obviously), we have this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="k">elif</span> <span class="n">symbol</span> <span class="o">==</span> <span class="n">arcade</span><span class="p">.</span><span class="n">key</span><span class="p">.</span><span class="n">D</span><span class="p">:</span>
            <span class="n">more</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">view</span><span class="p">)</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="n">more</span> <span class="ow">and</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">called_run</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">.</span><span class="n">run</span><span class="p">()</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">called_run</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>Basically that’s protection against calling <code class="language-plaintext highlighter-rouge">run</code> more than once, but calling it once.</p>
<p>Let’s try this. We’ll require one D past the last yield and do the <code class="language-plaintext highlighter-rouge">run</code> then. We’ll return <code class="language-plaintext highlighter-rouge">True</code> from <code class="language-plaintext highlighter-rouge">maker_single_step</code> if we did a command, and <code class="language-plaintext highlighter-rouge">False</code> if we’re off the end.</p>
<p>Write a test for that.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_maker_single_step_return</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">():</span>
            <span class="k">nonlocal</span> <span class="nb">sum</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">2</span>
            <span class="k">yield</span>
            <span class="nb">sum</span> <span class="o">+=</span> <span class="mi">3</span>
            <span class="k">yield</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">)</span>
        <span class="n">ok</span> <span class="o">=</span> <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">ok</span> <span class="ow">is</span> <span class="bp">True</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">1</span>
        <span class="n">ok</span> <span class="o">=</span> <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">ok</span> <span class="ow">is</span> <span class="bp">True</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="n">ok</span> <span class="o">=</span> <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">ok</span> <span class="ow">is</span> <span class="bp">True</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">6</span>
        <span class="n">ok</span> <span class="o">=</span> <span class="n">stepper</span><span class="p">.</span><span class="n">maker_single_step</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">ok</span> <span class="ow">is</span> <span class="bp">False</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">6</span>
</code></pre></div></div>
<p>And:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">maker_single_step</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="nb">next</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">steps</span><span class="p">)</span>
            <span class="k">return</span> <span class="bp">True</span>
        <span class="k">except</span> <span class="nb">StopIteration</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Green. Commit.</p>
<h2 id="reflection">Reflection</h2>
<p>This is an excellent opportunity to reflect, and, quite likely, to call a break in the proceedings, since the object is done (except for the View parameter, which is still trivial) needing only to be put into production and fed a sequence for its debut and final testing.</p>
<p>The existing BuildStepper is 33 lines long and is not as capable as this one is, while also being harder to use. The new one is presently 14 lines and will add fewer than ten when we add in the view updating which it does in fact need to do. The real proof of the idea, of course, will come when we create a sequence and actually use it to build a dungeon, testing it all in one go, and step by step (slowly I turned …).</p>
<p>I have one semi-large concern. It’s only one line, how large could it be, but it is this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">YieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">steps</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">steps</span> <span class="o">=</span> <span class="n">steps</span><span class="p">()</span>
</code></pre></div></div>
<p>YieldStepper calls the steps generator. What will happen if we need a parameterized generator? Let’s try to test that before the break.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_parameterized</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">def</span> <span class="nf">steps</span><span class="p">(</span><span class="n">count</span><span class="p">):</span>
            <span class="k">nonlocal</span> <span class="nb">sum</span>
            <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">count</span><span class="p">):</span>
                <span class="nb">sum</span> <span class="o">+=</span> <span class="p">(</span><span class="n">c</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
                <span class="k">yield</span>
        <span class="n">stepper</span> <span class="o">=</span> <span class="n">YieldStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">(</span><span class="mi">3</span><span class="p">))</span>
        <span class="n">stepper</span><span class="p">.</span><span class="n">run_to_completion</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">sum</span> <span class="o">==</span> <span class="mi">6</span>
</code></pre></div></div>
<p>This fails trying to call the already called generator:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">E</span>       <span class="nb">TypeError</span><span class="p">:</span> <span class="s">'generator'</span> <span class="nb">object</span> <span class="ow">is</span> <span class="ow">not</span> <span class="nb">callable</span>
</code></pre></div></div>
<p>OK, new rule, pass in a called generator. Or we could get fancy and call it if it needs calling? No let’s not.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">YieldStepper</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">steps</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">steps</span> <span class="o">=</span> <span class="n">steps</span>
</code></pre></div></div>
<p>Commit that. Let’s sum up and break.</p>
<h2 id="summary">Summary</h2>
<p>This is going nicely. The yield works perfectly, and I’m sure we’ll be able to put as many yields in as we wish, keep track of things inside the build, and so on.</p>
<p>It’s tempting to push on and finish up, just need to put in the view and its update, which we can copy from BuildStepper, and then create an example. But I’ve been coding and writing for a couple of hours, so taking a break seems prudent. See you later!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sat, 12 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/p/c/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/p/c/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>What If ...</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>If we make the path-maker sensitive to active regions, would that solve our issues? It might, but we solve them without that. Most peculiar.</p>
</blockquote>
<dl>
  <dt>Note</dt>
  <dd>Not much code is written below, but then I remove it all and discover that I don’t need it.</dd>
</dl>
<p>The issue we need to fix is that once we have populated three corners of the dungeon, we will have correctly connected those three: the first two have no choice but to connect, and two and three will connect correctly because #2 is closer to #3 than #1 is. But when we go to connect up corner #4, it is a toss-up whether #1 is closer to #4, or #3 is. #2 is right out.</p>
<p>My first idea, as mentioned in the blurb, is to make the path making sensitive to region. If, before connecting #3 and #4, we set a right-side region, and only select rooms that fall in that region, it should do the right thing. Let’s see how we might do that:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">finish_map</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ensure_connected</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">make_passages</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">ensure_connected</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">while</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">is_fully_connected</span><span class="p">()</span> <span class="ow">and</span> <span class="n">count</span> <span class="o">&lt;</span> <span class="mi">10</span><span class="p">:</span>
            <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_make_path_rooms</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">_make_path_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suites</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="c1"># self.check_intersection(suites)  # debug method
</span>        <span class="c1"># for s1, s2 in zip(suites, suites[1:] + suites[:1]):
</span>        <span class="k">for</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">suites</span><span class="p">,</span> <span class="n">suites</span><span class="p">[</span><span class="mi">1</span><span class="p">:]):</span>
            <span class="n">cells</span> <span class="o">=</span> <span class="n">s1</span><span class="p">.</span><span class="n">find_path_cells</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
                <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="s">'path'</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
</code></pre></div></div>
<p>The commented-out <code class="language-plaintext highlighter-rouge">for</code> is the one that links back to front, always resulting in a loop among the rooms connected. We don’t want that all the time, so I’ve just turned it off for now at least.</p>
<p>In the code above we add rooms that connect separate “suites” together. We do not define the actual passages until later, in <code class="language-plaintext highlighter-rouge">make_passages</code>. I am hopeful that we can leave <code class="language-plaintext highlighter-rouge">make_passages</code> alone, at least for now.</p>
<p>If <code class="language-plaintext highlighter-rouge">define_suites</code> only found the right suites, I think we’d likely be OK. So …</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">define_suites</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suites</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Suite</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="n">unexplored</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">.</span><span class="n">copy</span><span class="p">()</span>
        <span class="k">while</span> <span class="n">unexplored</span><span class="p">:</span>
            <span class="n">suite</span><span class="p">:</span> <span class="n">Suite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">find_suite</span><span class="p">(</span><span class="n">unexplored</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
            <span class="n">suites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span>
            <span class="n">unexplored</span> <span class="o">=</span> <span class="p">[</span><span class="n">room</span> <span class="k">for</span> <span class="n">room</span> <span class="ow">in</span> <span class="n">unexplored</span> <span class="k">if</span> <span class="n">room</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">suite</span><span class="p">.</span><span class="n">room_set</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">suites</span>
</code></pre></div></div>
<p>If we only considered the current region as we set up unexplored, we should get the right result. Let’s try Wishful Thinking:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">define_suites</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suites</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Suite</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="n">unexplored</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">rooms_in_subregion</span><span class="p">()</span>
        <span class="k">while</span> <span class="n">unexplored</span><span class="p">:</span>
            <span class="n">suite</span><span class="p">:</span> <span class="n">Suite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">find_suite</span><span class="p">(</span><span class="n">unexplored</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
            <span class="n">suites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span>
            <span class="n">unexplored</span> <span class="o">=</span> <span class="p">[</span><span class="n">room</span> <span class="k">for</span> <span class="n">room</span> <span class="ow">in</span> <span class="n">unexplored</span> <span class="k">if</span> <span class="n">room</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">suite</span><span class="p">.</span><span class="n">room_set</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">suites</span>
</code></pre></div></div>
<p>Now we just have to write that method:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">rooms_in_subregion</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="p">[</span><span class="n">room</span> <span class="k">for</span> <span class="n">room</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">rooms</span> <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">subregion</span><span class="p">.</span><span class="n">contains_room</span><span class="p">(</span><span class="n">room</span><span class="p">)]</span>
</code></pre></div></div>
<p>So far so good. Now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SubRegion</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">contains_room</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">room</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">room</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">cell</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">:</span>
                <span class="k">return</span> <span class="bp">False</span>
        <span class="k">return</span> <span class="bp">True</span>
</code></pre></div></div>
<p>That looks right. Ask me where my tests are, but not until we try this.</p>
<h2 id="lost-track">Lost Track</h2>
<p>Somewhere between that last sentence and this morning, I was just fiddling, and finally realized that my subregions weren’t quite right, especially at the end. Even with all the Wishful Thinking code removed, and the right sequence of subregion setup, we’re getting maps that are what we intend, such as this one:</p>
<p><img src="https://ronjeffries.com/articles/-v026/p/d/ok.png" alt="five section map with paths circling from bottom left ending at central room" title="five section map with paths circling from bottom left ending at central room" /></p>
<p>That’s really what I was trying to do, and the glitches I was seeing had to do with improperly setting up the subregions, making the code fail to discover space for the paths needed.</p>
<p>Running a few more random dungeons, I do get this map:</p>
<p><img src="https://ronjeffries.com/articles/-v026/p/d/not.png" alt="map with path from 3 to 4 and 4 to central merged" title="map with path from 3 to 4 and 4 to central merged" /></p>
<p>I’m not sure whether to consider that a defect or not. If we put the door to the central boss room at the entrance to the room, this would be perfectly valid. Even if we put the door at the beginning of the stub path from the vertical hallway, it would be valid, and I’m rather sure that’s where the stub path goes.</p>
<p>Fiddling with the subregions generated some maps that weren’t acceptable but also this very fine one:</p>
<p><img src="https://ronjeffries.com/articles/-v026/p/d/wow.png" alt="very intricate and attractive map" title="very intricate and attractive map" /></p>
<p>I think we’re pleased with our results here. Setting the subregions back to the ones that created the first two maps above, we’ll commit the changes. As it turns out, after some reasoning errors on my part, all we seem to need is careful selection of subregions and stepper flow. The stepper looks like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">make_build_table</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">steps</span> <span class="o">=</span>  <span class="p">[</span>
        <span class="n">bottom_left</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">top_left</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">top_right</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">bottom_right</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">right_side</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">make_diamond_in_round_room</span><span class="p">,</span>
        <span class="n">bottom_right_plus_center</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">populate</span><span class="p">,</span>
        <span class="n">add_content</span><span class="p">,</span>
    <span class="p">]</span>
    <span class="k">return</span> <span class="n">BuildStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">,</span> <span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">)</span>
</code></pre></div></div>
<p>Let’s sum up.</p>
<h2 id="summary">Summary</h2>
<p>The trick to getting two sections to connect is to make the subregion, draw the section, <code class="language-plaintext highlighter-rouge">finish</code> to make sure it is connected, then clear the subregions and <code class="language-plaintext highlighter-rouge">finish</code> again, which will connect everything that isn’t connected. You can exert sufficient control what connects by setting the subregions properly.</p>
<p>With my product manager hat on (a very jaunty fedora, I might add) this scheme seems to be doing what I asked for, so far, and I suspect that we’re going to want better control over the layout. Discussing this with the lead developer (he doesn’t wear a hat at all and his hair is a mess) we agree to wait and see, and I’m comfortable that we’ll be able to improve the situation should we need to.</p>
<p>In the actual world of ronjeffries.com, we’re just here to set ourselves interesting improvements to the program that tend to break the design, so as to discover that we can improve it without massive rewriting delays, which have, in my development lifetime, killed more than one project that didn’t deserve to die.</p>
<p>What is interesting about this problem, which is to create a dungeon in sections that could be separately populated with problems and solutions, is that while I did try a lot of ideas to get this to happen, so far most of the work has been in configuring subregions that will cause path creation to do what we want.</p>
<p>We do <em>still</em> have one or two things to do, and some new ideas have come to mind:</p>
<ol>
  <li>
    <p>We need to provide some way to control “doors”, or whatever kind of obstacles the dungeon designer wants to place between sections. This will require more feedback between the build sequence and the designer, some kind of trace of where key locations are, perhaps.</p>
  </li>
  <li>
    <p>We probably want to create our inter-section paths to be impermeable, so that they don’t get openings other than at the ends, which can lead to shortcuts that the designer did not contemplate.</p>
  </li>
  <li>
    <p>We might want to provide a more robust stepper “language” that gives more control to the dungeon designer.</p>
  </li>
  <li>
    <p>The Suite object came into view a couple of times during this exercise, and it is possible that we’d benefit by making it more explicit and capable. For example, if a subregion’s rooms automatically formed a Suite, we might have better control over how paths are created.</p>
  </li>
  <li>
    <p>The current paths draw are quite straight. We have the ability to make them more wandering, and might want to use that, or even enhance it.</p>
  </li>
  <li>
    <p>… We’ll run out of interest in this program long before we run out of ideas. I fully intend to be working on something different by 2027, assuming that I last that long.</p>
  </li>
</ol>
<h2 id="summary-summary">Summary Summary</h2>
<p>Small changes leading from where we are to where we need to be seem always to be possible. Of course there are exceptions. I might not be able to figure out a small step transition from Python on the Mac to COBOL on a mainframe. Oh, wait, we already figured out how to go from COBOL on the mainframe to Smalltalk on a SparcStation in small steps. Maybe something similar … but no one wants a dungeon in COBOL anyway …</p>
<p>Small steps, my friends, seem always to be possible. They seem to be easier to make, less prone to errors, and more flexible with respect to interweaving other features.</p>
<p>Fascinating. See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Thu, 10 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/p/d/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/p/d/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Better Pathing</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>If we draw suite paths in the right order, it should give us the layout we want with no extra work. If this works, it’s a lovely idea. Herein we eschew pompous prolixity.</p>
</blockquote>
<p>The new PassageCollection object, which I am inclined to rename to Passages, is now in place. It ensures that once a passage between two rooms has been formed, that passage is preserved even if we’re nominally computing new passages. We did that because when stepping the build for the developer, we want to see the passages as we go, and the passage-computing code would make new decisions about where things should go.</p>
<p>Arguably, we coulda shoulda changed the passage-making in some fundamental way so as to specify somehow just exactly what it should do. That seemed daunting, and once we created the Passages object (see what a nice name that is, gonna change it right now … done) as I was saying, once we created the Passages object, it was very simple to have it keep track of which room pairs already have passages and just ignore any attempts to add passages to them. We even did it with a callback, so that user code need not waste time preparing a path that is inevitably going to be rejected.</p>
<p>All very nice, and quite simple. The task before us now is that given groups of rooms that we have created in subregions of the map, we want to connect those groups with paths that will control the major flow through the dungeon: first this area, then this next area, and so on.</p>
<p>These “major” paths (just long thin rooms really) need to be different from most rooms. Our general rule is that if two rooms share a border, we’ll build a passage across that border. This rule tends to keep the dungeon connected, and seems to make interesting layouts with loops and different ways to get places. But for these inter-region paths, we want that rule changed. We do not want a surprise entry into the side of one of these paths if it happens to pass by some other room. If we were to allow that, there would be a “leak” in the dungeon, allowing the player to bypass some section without solving the problems in that section.</p>
<p class="pull-quote">I believe, though I could be wrong, that this is going to turn out to be quite easy, and rather elegant. We’ll see, probably next time.<sup id="fnref:n" role="doc-noteref"><a href="#fn:n" class="footnote" rel="footnote">1</a></sup></p>
<p>So we’ll need to do something special when we connect up our sections, to ensure that the path we build is not permeable with respect to surprise adjacencies<sup id="fnref:p" role="doc-noteref"><a href="#fn:p" class="footnote" rel="footnote">2</a></sup>.</p>
<p>Before we get there, however, we need to get the paths laid in, and I have an idea about that as well. There are two methods that work together to keep the dungeon connected:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">finish_map</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">ensure_connected</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">make_passages</span><span class="p">()</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ensure_connected</code> looks at the existing rooms and while they are not all connected, adds path rooms between connected “suites”. It just makes paths that are adjacent to a pair of suites, and then <code class="language-plaintext highlighter-rouge">make_passages</code> connects things up. It’s in <code class="language-plaintext highlighter-rouge">make_passages</code> that the extra entries into the sides of a path are formed.</p>
<p>So. Imagine that we want to create a dungeon with four sections in the four corners, and a boss room, round with a diamond center, in the middle of the dungeon. (That’s what the current build does, you may recall.)</p>
<p>It seems to me that if we were to build the center bit last, we could build section 1, connect it up, build section 2, connect it up, then connect 1 and 2, then build 3, connect it up then connect (1,2) and 3, and so on, it would just work. Once 1 and 2 are connected, and 3 is internally connected, it looks like two suites to the <code class="language-plaintext highlighter-rouge">finish_map</code> and it should just hook them together. So we should be able to get the topology we want by just deciding where to place the sections and placing them in the order we want them connected.</p>
<p>So I propose that we begin by changing the build to do that. Here’s what it looks like now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">make_build_table</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">steps</span> <span class="o">=</span>  <span class="p">[</span>
        <span class="n">make_diamond_in_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">bottom_left</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">top_left</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">bottom_right</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">top_right</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">populate</span><span class="p">,</span>
        <span class="n">add_content</span><span class="p">,</span>
    <span class="p">]</span>
    <span class="k">return</span> <span class="n">BuildStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">,</span> <span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">)</span>
</code></pre></div></div>
<p>Let’s reorder things to go bottom left, top left, top right, bottom right, center, and add in some additional connections. (I think we’ll initially get too many, but we know how to fix that if we do.)</p>
<p>New stepper:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">make_build_table</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">steps</span> <span class="o">=</span>  <span class="p">[</span>
        <span class="n">bottom_left</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">top_left</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">top_right</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_diamond_room</span><span class="p">,</span>
        <span class="n">make_a_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">bottom_right</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">make_a_cave_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">clear_subregions</span><span class="p">,</span>
        <span class="n">make_diamond_in_round_room</span><span class="p">,</span>
        <span class="n">finish</span><span class="p">,</span>
        <span class="n">populate</span><span class="p">,</span>
        <span class="n">add_content</span><span class="p">,</span>
    <span class="p">]</span>
    <span class="k">return</span> <span class="n">BuildStepper</span><span class="p">(</span><span class="n">steps</span><span class="p">,</span> <span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">)</span>
</code></pre></div></div>
<p>Note the <code class="language-plaintext highlighter-rouge">clear_subregions</code> <code class="language-plaintext highlighter-rouge">finish</code> combinations. Those are supposedly going to directly connect around the square. Let’s see what it really does.</p>
<p><img src="https://ronjeffries.com/articles/-v026/p/e/first.png" alt="map with double paths between sections, central bit not connected where we would wish" title="map with double paths between sections, central bit not connected where we would wish" /></p>
<p>So that’s not bad. We have those double paths, the extra paths I was referring to. I think I almost like those. I was imagining that they’d probably be more separated but since we try to find the closest cells between suites to draw our paths, it makes sense that they’ll tend to be side by side.</p>
<p>The reason they are drawn at all is that we draw paths all the way around the suite set from 1 to 2, 2 to 3, n-1 to n, n back to 1. We can suppress that last drawing if we care to.</p>
<p>The other issue is that we wanted the path to the boss room to come from bottom right and it does not. We can fix that with a better subregion for the last bit.</p>
<p>In principle, what we really want, I suspect, is for subregions to be more than mere rectangles, but let’s defer that concern for now. If we do that final finish in a subregion containing just bottom right and the area for the boss room it should do just what we want.</p>
<p>As drawn, it appears to me that the boss room overlaps the top rectangle. Let’s examine the rectangles as defined and see what we need to do:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">main</span><span class="p">.</span><span class="n">py</span>
<span class="k">def</span> <span class="nf">bottom_left</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">17</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">23</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">top_left</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">17</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">bottom_right</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">37</span><span class="p">,</span> <span class="mi">55</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">23</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">top_right</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">37</span><span class="p">,</span> <span class="mi">55</span><span class="p">,</span> <span class="mi">33</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
</code></pre></div></div>
<p>I’ll adjust those to make a little room, and add one more subregion, to include bottom right plus the central area:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">main</span><span class="p">.</span><span class="n">py</span>
<span class="k">def</span> <span class="nf">bottom_left</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">top_left</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">bottom_right</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">top_right</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="mi">55</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">bottom_right_plus_center</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">dungeon</span><span class="p">):</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">subregion_LRBT</span><span class="p">(</span><span class="mi">21</span><span class="p">,</span><span class="mi">55</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">34</span><span class="p">)</span>
</code></pre></div></div>
<p>Also I made the change that doesn’t make a path that circles back:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">_make_path_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suites</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="c1"># self.check_intersection(suites)  # debug method
</span>        <span class="c1"># for s1, s2 in zip(suites, suites[1:] + suites[:1]):
</span>        <span class="k">for</span> <span class="n">s1</span><span class="p">,</span> <span class="n">s2</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">suites</span><span class="p">,</span> <span class="n">suites</span><span class="p">[</span><span class="mi">1</span><span class="p">:]):</span>
            <span class="n">cells</span> <span class="o">=</span> <span class="n">s1</span><span class="p">.</span><span class="n">find_path_cells</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
                <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="s">'path'</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
</code></pre></div></div>
<p>It’s that <code class="language-plaintext highlighter-rouge">+ suites[:1]</code> that makes us circle back to the first suite. Those changes give us this picture:</p>
<p><img src="https://ronjeffries.com/articles/-v026/p/e/2.png" alt="map with single paths between sections boss room connected at lower right almost as intended" title="map with single paths between sections boss room connected at lower right almost as intended" /></p>
<p>That is almost as intended. Woot! Note the tiny loop where the boss room connects to the lower right. I think that is our adjacent paths inter-penetrating and it’s the thing that I think we can fix elegantly … next time.</p>
<p>I want to make it more interesting. Let’s add some rooms.</p>
<p><img src="https://ronjeffries.com/articles/-v026/p/e/3.png" alt="map with more rooms still not connected quite as intended" title="map with more rooms still not connected quite as intended" /></p>
<p>Hm. Now there is a connection lower left to lower right that I do not expect, and no connection from upper right to lower left. I think I know why that’s happening: the path-finding isn’t sensitive to the available regions, and the bottom two are closer together than the right side two.</p>
<p>This is a surprise to me and will have to be dealt with. My brilliant idea seems to be slightly flawed. Let’s hope it’s just slightly. If we can make passage-making sensitive to regions, the idea holds water. If not … I’ll need another idea, but I want this one to work. We’ll see.</p>
<p>So that’s fun. In addition to fixing this up, we still have one thing to do, having to do with the double paths. We may need one or two more sessions to get this done. Interesting …</p>
<hr />
<p><br /></p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:n" role="doc-endnote">
      <p>When I started this morning, I thought we’d get to this. I decided at this point lay in paths on the fly, which is also a rather nice idea if I do say so myself. <a href="#fnref:n" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:p" role="doc-endnote">
      <p>I generally prefer direct and simple speech. But sometimes phrases like “permeable with respect to surprise adjacencies” just come out of my fingers, and I do enjoy them. I hope that you see them, as I do, as fun play, not some attempt to seem all academic with pompous prolixity, which as a matter of policy we would generally eschew. <a href="#fnref:p" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
 ]]> 
                </content:encoded>
                <pubDate>Thu, 10 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/p/e/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/p/e/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Upon Reflection</title>
                <description></description>
                <content:encoded>
                  <![CDATA[ <!--
<video controls autoplay loop muted
    width="80%" height="auto" vertical-align="middle"
    src="piston.mov" type="video/mp4">
</video>
-->
<!--
{: target="_blank"}
Tout le monde déteste l’IA.
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME xxxx  -->
<p>Hello, loves!</p>
<blockquote>
  <p>I think PassageCollection should have the protection against duplication built in. We have a lambda, how nice.</p>
</blockquote>
<p>As things stand, the user of the PassageCollection is responsible for ensuring that there’s only one passage per room. Since that’s the default behavior we want, let’s build it into the object. Let’s have <code class="language-plaintext highlighter-rouge">add_passage</code> return True if the passage was added, and False if it was not.</p>
<p>Creating a passage to add is costly, so let’s see if we can pass in a lambda that is only called if needed. Let’s enhance our test.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_create_passage</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
        <span class="n">room_1</span> <span class="o">=</span> <span class="n">Room</span><span class="p">([</span><span class="n">Cell</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">)])</span>
        <span class="n">room_2</span> <span class="o">=</span> <span class="n">Room</span><span class="p">([</span><span class="n">Cell</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">)])</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room_1</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room_2</span><span class="p">)</span>
        <span class="n">passages</span> <span class="o">=</span> <span class="n">PassageCollection</span><span class="p">()</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="k">lambda</span><span class="p">:</span> <span class="p">(</span><span class="n">Cell</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">))</span>
        <span class="n">done</span> <span class="o">=</span> <span class="n">passages</span><span class="p">.</span><span class="n">create_passage</span><span class="p">(</span><span class="n">room_1</span><span class="p">,</span> <span class="n">room_2</span><span class="p">,</span> <span class="n">cells</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">done</span> <span class="o">==</span> <span class="bp">True</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">passages</span><span class="p">.</span><span class="n">cell_connections</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">passages</span><span class="p">.</span><span class="n">room_connections</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="k">lambda</span><span class="p">:</span> <span class="p">(</span><span class="n">Cell</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">22</span><span class="p">),</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">))</span>
        <span class="n">done</span> <span class="o">=</span> <span class="n">passages</span><span class="p">.</span><span class="n">create_passage</span><span class="p">(</span><span class="n">room_1</span><span class="p">,</span> <span class="n">room_2</span><span class="p">,</span> <span class="n">cells</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">done</span> <span class="o">==</span> <span class="bp">False</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">passages</span><span class="p">.</span><span class="n">cell_connections</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">passages</span><span class="p">.</span><span class="n">room_connections</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</code></pre></div></div>
<p>And this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">PassageCollection</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">create_passage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">r1</span><span class="p">:</span> <span class="n">Room</span><span class="p">,</span> <span class="n">r2</span><span class="p">:</span> <span class="n">Room</span><span class="p">,</span> <span class="n">func</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">rooms_are_connected</span><span class="p">(</span><span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">):</span>
            <span class="k">return</span> <span class="bp">False</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">passage</span> <span class="o">=</span> <span class="n">func</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">add_passage</span><span class="p">(</span><span class="n">passage</span><span class="p">)</span>
            <span class="k">return</span> <span class="bp">True</span>
</code></pre></div></div>
<p>I expect that no one will look at the return except for the tests.</p>
<p>Now we can change the production code from this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">make_passages</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">r1</span><span class="p">,</span> <span class="n">r2</span> <span class="ow">in</span> <span class="n">itertools</span><span class="p">.</span><span class="n">combinations</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">rooms_are_connected</span><span class="p">(</span><span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">):</span>
                <span class="n">passage</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">random_passage</span><span class="p">(</span><span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">add_passage</span><span class="p">(</span><span class="n">passage</span><span class="p">)</span>
</code></pre></div></div>
<p>To this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">make_passages</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">r1</span><span class="p">,</span> <span class="n">r2</span> <span class="ow">in</span> <span class="n">itertools</span><span class="p">.</span><span class="n">combinations</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">passages</span><span class="p">.</span><span class="n">create_passage</span><span class="p">(</span><span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="bp">self</span><span class="p">.</span><span class="n">random_passage</span><span class="p">(</span><span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">))</span>
</code></pre></div></div>
<p>Much nicer. Let’s rename the add method now.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">create_passage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">r1</span><span class="p">:</span> <span class="n">Room</span><span class="p">,</span> <span class="n">r2</span><span class="p">:</span> <span class="n">Room</span><span class="p">,</span> <span class="n">func</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">rooms_are_connected</span><span class="p">(</span><span class="n">r1</span><span class="p">,</span> <span class="n">r2</span><span class="p">):</span>
            <span class="k">return</span> <span class="bp">False</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="n">passage</span> <span class="o">=</span> <span class="n">func</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">add_passage_unconditionally</span><span class="p">(</span><span class="n">passage</span><span class="p">)</span>
            <span class="k">return</span> <span class="bp">True</span>
    <span class="k">def</span> <span class="nf">add_passage_unconditionally</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">passage</span><span class="p">:</span><span class="nb">tuple</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Cell</span><span class="p">]):</span>
        <span class="k">if</span> <span class="n">passage</span><span class="p">:</span>
            <span class="n">c1</span><span class="p">,</span> <span class="n">c2</span> <span class="o">=</span> <span class="n">passage</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">cell_connections</span><span class="p">.</span><span class="n">add</span><span class="p">((</span><span class="n">c1</span><span class="p">,</span> <span class="n">c2</span><span class="p">))</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">cell_connections</span><span class="p">.</span><span class="n">add</span><span class="p">((</span><span class="n">c2</span><span class="p">,</span> <span class="n">c1</span><span class="p">))</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">room_connections</span><span class="p">.</span><span class="n">add</span><span class="p">((</span><span class="n">c1</span><span class="p">.</span><span class="n">room</span><span class="p">,</span> <span class="n">c2</span><span class="p">.</span><span class="n">room</span><span class="p">))</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">room_connections</span><span class="p">.</span><span class="n">add</span><span class="p">((</span><span class="n">c2</span><span class="p">.</span><span class="n">room</span><span class="p">,</span> <span class="n">c1</span><span class="p">.</span><span class="n">room</span><span class="p">))</span>
</code></pre></div></div>
<p>That looks good. Commit: <em>PassageCollection create_passage calls creation lambda only if a passage is not already present for the provided rooms.</em></p>
<h2 id="summary">Summary</h2>
<p>I almost invariably find that after a break of a half day or day, some improvement ideas come up for something I’ve just done. I find it both useful and pleasant to pay attention to the ideas and usually find something worth doing.</p>
<p>Here, we have moved a bit of complexity inside the PassageCollection, and our use of it is a bit simpler, although we do now have a lambda to call, but as that is quite common in the dungeon, I think it’s fair to think of it as a well-understood technique.</p>
<p>And there is no one here to say me nay. See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Wed, 09 Sep 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/p/f/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/p/f/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
      
  </channel>
</rss>
