<?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>Sun, 12 Apr 2026 11:10:35 -0400</pubDate>
    <lastBuildDate>Sun, 12 Apr 2026 11:10:35 -0400</lastBuildDate>
    <generator>Jekyll v4.2.0</generator>
    
    
              <item>
                <title>Design 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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>I’ve had what seems like a good idea. Let’s find out.</p>
</blockquote>
<p>Our mission, and we do choose to accept it, is to find a cell in the dungeon which is maximally distant from a given point, using manhattan distance within the dungeon, no shortcuts allowed. My very rough plan for this feature has been to do a flood fill starting from the given cell, retain some kind of information that maps cell to distance, and select one of the cells with distance equal to the largest we find. (There could be more than one such cell.) I’ve spent a bit of time thinking of how we could minimize the information kept, keeping track only of the current largest ones. Somehow, an idea came to me that seems much better:</p>
<p>As we generate the cells, which will be in an expanding wave from the given cell, each new cell that we add is one step further away than the cell whose neighbors we are expanding. So, I thought to myself, I thought, “Why not just store the distance in the cell itself?” When we encounter a cell, if it already has a distance, we don’t override that value, keeping always the smallest distance. That’s because as we expand the wave front, we can encounter a cell more than once, having taken a longer path to get there. We want the maximal direct path, not the longest path you could take if you were really bad at dungeons.</p>
<p>This seems to me to be a really straightforward way to build our feature:</p>
<ol>
  <li>Clear the distance value in all cells;</li>
  <li>Build new distance values for all cells, starting from given;</li>
  <li>Scan all cells, collecting the ones with maximal value.</li>
  <li>Select one randomly.</li>
</ol>
<p>I think there is probably a small miracle in item #3, where we find all the maximal ones, but it’s probably a one-liner or something once we figure it out.</p>
<dl>
  <dt>Now let’s over-design a bit.</dt>
  <dd>
    <p>We could just add a new member to Cell, called <code class="language-plaintext highlighter-rouge">distance</code> or something, and use it. And I think we <em>should</em> do that, but it does seem clear that as the game grows, there will be lots of occasions to keep things in cells, such as treasures or traps. And there will be an arbitrary number of these things, and we really won’t want to add an instance variable for each. Instead, we’ll have some kind of list or dictionary.</p>
  </dd>
  <dd>
    <p>So, we think to ourselves, we think “We might as well do a dictionary now, we’re gonna need it”. But then we think, “No, that is not our way. Our way is that we design for what we need now, not for some future possible need.”</p>
  </dd>
  <dd>
    <p>Besides, everyone enjoys seeing me get my comeuppance when I don’t provide some robust big facility and then later need it after all. Except that that never seems to happen: instead, it always seems that I can readily refactor to put in the new capability without a lot of trouble. So let’s do it the simple way this time and see if we get in trouble later.</p>
  </dd>
</dl>
<p>Let’s test drive a capability on Cell to clear, set, and reference a variable we’ll call <code class="language-plaintext highlighter-rouge">path_distance</code>.</p>
<p>This seems almost inane:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_path_distance</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">cell</span><span class="p">.</span><span class="n">path_distance</span> <span class="ow">is</span> <span class="bp">None</span>
        <span class="n">cell</span><span class="p">.</span><span class="n">path_distance</span> <span class="o">=</span> <span class="mi">37</span>
        <span class="k">assert</span> <span class="n">cell</span><span class="p">.</span><span class="n">path_distance</span> <span class="o">==</span> <span class="mi">37</span>
</code></pre></div></div>
<p>I’m not sure if we want a <code class="language-plaintext highlighter-rouge">clear_path_distance</code> or not. <code class="language-plaintext highlighter-rouge">None</code> seems pretty clear. We’ll see what we want when we use it.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Cell</span><span class="p">:</span>
    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">path_distance</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">_path_distance</span>
    <span class="o">@</span><span class="n">path_distance</span><span class="p">.</span><span class="n">setter</span>
    <span class="k">def</span> <span class="nf">path_distance</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_path_distance</span> <span class="o">=</span> <span class="n">value</span>
</code></pre></div></div>
<p>Test passes. Commit: <em>add path_distance to Cell.</em></p>
<p>Now I begin to think about how we want to do the path thing. I think I’ll work it out in a test and then move the code after I know what it is.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_max_path_distance</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="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="nb">map</span> <span class="o">=</span> <span class="s">'''
        11111
        '''</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">add_rooms_from_map</span><span class="p">(</span><span class="nb">map</span><span class="p">)</span>
        <span class="n">given</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</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">in_a_room</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="p">.</span><span class="n">is_in_a_room</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">given</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="n">in_a_room</span><span class="p">):</span>
            <span class="n">cell</span><span class="p">.</span><span class="n">path_distance</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="n">given</span><span class="p">.</span><span class="n">path_distance</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">given</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="n">in_a_room</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">neighbor</span> <span class="ow">in</span> <span class="n">cell</span><span class="p">.</span><span class="n">neighbors_in_rooms</span><span class="p">():</span>
                <span class="k">if</span> <span class="n">neighbor</span><span class="p">.</span><span class="n">path_distance</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
                    <span class="n">neighbor</span><span class="p">.</span><span class="n">path_distance</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">path_distance</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
            <span class="k">assert</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="mi">0</span><span class="p">).</span><span class="n">path_distance</span> <span class="o">==</span> <span class="n">x</span>
</code></pre></div></div>
<p>Just a straight path across the top. Should be good enough, we trust <code class="language-plaintext highlighter-rouge">generate</code> to work.</p>
<p>The test passes. Let’s reflect about what we should really do.</p>
<h2 id="reflection">Reflection</h2>
<p>This code, as we can see, is using <code class="language-plaintext highlighter-rouge">Cell.generate</code>, with the condition <code class="language-plaintext highlighter-rouge">in_a_room</code> to restrict the search to cells that are in the dungeon, not the many unallocated cells. This may be a case for maintaining a spare collection of cells instead of the complete collection we now use, with what amounts to a flag saying that a cell is in use.</p>
<p>A friend on Mastodon suggested that idea, which I claim I had also thought of and decided against. I can’t find their name just now but they were onto something with the idea.</p>
<p>Anyway we’re here now, with the full collection and our lambda to check only room cells and that’s certainly working, for values of “certainly” approaching 1.0.</p>
<p>I think that before we go further with this idea, we need to decide where the fundamental notion resides. My guess is that it will be in Dungeon as a stub calling into DungeonLayout, which owns the cells. It’ll probably be part of <code class="language-plaintext highlighter-rouge">Dungeon.populate()</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot_room</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">dot_cell</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="n">dot_room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">place_player_at</span><span class="p">(</span><span class="n">dot_cell</span><span class="p">)</span>
</code></pre></div></div>
<p>We have not yet addressed the larger issue of generating interesting layouts populated with various creatures and treasures, presumably respectively more deadly and more desirable as daring Dot delves dauntlessly deeper into the depths of the dungeon. However those work, it seems fair to expect that they’ll be sending messages to Dungeon to get most of the work done.</p>
<p>I am about ready to stop for the morning. In fact, I see that it is nearly 11 AM, time to have a bit of breakfast and watch Sunday Morning. We’ll stop here.</p>
<h2 id="summary">Summary</h2>
<p>In just a few minutes, we have open code that (p&gt;0.9) will find the in-dungeon path distance from any point to all others. We should probably create a better test of that, but I really am quite confident. That code seems to have two aspects, the clearing of the variable and setting it. We do need to clear it because we use the absence of a saved value as the indicator that we should record the current distance.</p>
<p>I note an odd thing, and this is not the first time it has happened: we have code that generates the cells and then we do something to the provided cell’s neighbors. I don’t recall where else we do that, but I’m sure that we do. That suggests to me that there may be an opportunity to consolidate that duplication somewhere. Note made.</p>
<p>We should have little or no difficulty moving the code over to Layout and certainly wiring up a connection through Dungeon will be trivial. I just don’t want to do it right now and after all, bacon is important.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sun, 12 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/w/v/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/w/v/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Far Away?</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>We’re working on finding a cell that is maximally distant from a given cell. What are our next steps?</p>
</blockquote>
<p>My work yesterday was somewhat interrupted by local matters, so I have even less recollection than usual about what was done and what should be next. I didn’t even leave a non-running test as a reminder. Fortunately, I did append yesterday’s tests to the test class, so those should give a clue. They included:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_string_layout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">r_0</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]</span>
        <span class="n">r_1</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">)]</span>
        <span class="n">r_2</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="s">'''
            ..0.2
            ..0..
            ..000
            .....
            1....
        '''</span>
    <span class="p">...</span>
    <span class="k">def</span> <span class="nf">test_rooms_from_dictionary</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">dict</span> <span class="o">=</span> <span class="p">{</span> <span class="s">'0'</span><span class="p">:</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="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)],</span>
                 <span class="s">'1'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)],</span>
                 <span class="s">'2'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]}</span>
        <span class="p">...</span>
</code></pre></div></div>
<p>The first test converts our little string drawing to a dictionary from a one-character room name—’0’, ‘1’, ‘2’ in the test—to a list of the x y tuples where that character shows up in the picture. The second test test-drove a method on DungeonLayout, <code class="language-plaintext highlighter-rouge">add_from_dictionary</code>, which converts that dictionary to rooms containing the indicated cells and adds them to the dungeon.</p>
<p>If we can put those two things together, we’ll be able to draw sample dungeons with simple text pictures that can reside in the code, and then use those dungeons to test things. The first thing we’ll test, I’m supposing, will be the new feature we want, finding a cell that is as far away from a given cell as possible, through the rooms and paths that exist in the dungeon. The longest direct walk possible from the given point, in other words.</p>
<p>The string to dictionary code currently lives only in the test and we need to decide how to make it reusable. Let’s take a look at the whole test now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_string_layout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">r_0</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]</span>
        <span class="n">r_1</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">)]</span>
        <span class="n">r_2</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="s">'''
            ..0.2
            ..0..
            ..000
            .....
            1....
        '''</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="n">textwrap</span><span class="p">.</span><span class="n">dedent</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="n">string_map</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="p">[</span> <span class="n">string</span> <span class="k">for</span> <span class="n">string</span> <span class="ow">in</span> <span class="n">strings</span> <span class="k">if</span> <span class="n">string</span><span class="p">]</span>
        <span class="n">room_cells</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">strings</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">char</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
                <span class="k">if</span> <span class="n">char</span> <span class="o">!=</span> <span class="s">'.'</span><span class="p">:</span>
                    <span class="k">if</span> <span class="ow">not</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">room_cells</span><span class="p">:</span>
                        <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
                    <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">].</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">room_cells</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'0'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_0</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'1'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_1</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'2'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_2</span>
</code></pre></div></div>
<p>I don’t think I like that code much, so we’ll see about making it a bit better, and a larger question is where it should reside. I am inclined to think that it should go on DungeonLayout along with the <code class="language-plaintext highlighter-rouge">add_from_dictionary</code> method. If we’re going to have the one, we should have the other.</p>
<p>Let’s do that, moving it as it is, via a cut, wishful thinking, and a bit of pastery. You know what? An extract will help us here:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_string_layout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">r_0</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]</span>
        <span class="n">r_1</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">)]</span>
        <span class="n">r_2</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="s">'''
            ..0.2
            ..0..
            ..000
            .....
            1....
        '''</span>
        <span class="n">room_cells</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">string_map_to_dictionary</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">room_cells</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'0'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_0</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'1'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_1</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'2'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_2</span>
    <span class="k">def</span> <span class="nf">string_map_to_dictionary</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">string_map</span><span class="p">):</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="n">textwrap</span><span class="p">.</span><span class="n">dedent</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="n">string_map</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="p">[</span><span class="n">string</span> <span class="k">for</span> <span class="n">string</span> <span class="ow">in</span> <span class="n">strings</span> <span class="k">if</span> <span class="n">string</span><span class="p">]</span>
        <span class="n">room_cells</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">strings</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">char</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
                <span class="k">if</span> <span class="n">char</span> <span class="o">!=</span> <span class="s">'.'</span><span class="p">:</span>
                    <span class="k">if</span> <span class="ow">not</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">room_cells</span><span class="p">:</span>
                        <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
                    <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">].</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">room_cells</span>
</code></pre></div></div>
<p>Now we have a method which could readily reside on DungeonLayout, so let’s do the wishful thinking part, where we write the code in the test that we wish would work:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">()</span>
    <span class="n">room_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">string_map_to_dictionary</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
</code></pre></div></div>
<p>Test goes red. We just have to move that method. PyCharm can’t help, so I do it the old-fashioned cut and paste way:</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="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">string_map_to_dictionary</span><span class="p">(</span><span class="n">string_map</span><span class="p">):</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="n">textwrap</span><span class="p">.</span><span class="n">dedent</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="n">string_map</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="p">[</span><span class="n">string</span> <span class="k">for</span> <span class="n">string</span> <span class="ow">in</span> <span class="n">strings</span> <span class="k">if</span> <span class="n">string</span><span class="p">]</span>
        <span class="n">room_cells</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">strings</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">char</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
                <span class="k">if</span> <span class="n">char</span> <span class="o">!=</span> <span class="s">'.'</span><span class="p">:</span>
                    <span class="k">if</span> <span class="ow">not</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">room_cells</span><span class="p">:</span>
                        <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
                    <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">].</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">room_cells</span>
</code></pre></div></div>
<p>Tests are green. Commit: <em>add string_map_to_dictionary to DungeonLayout.</em></p>
<p>Curiously, having the code over there in DungeonLayout makes me a bit more comfortable with the fact that I don’t like it much and feel it’s probably not robust. At least it’s in the right place, where we can find it and fix it when we want to, or when we find trouble.</p>
<p>Now we need one more method on DungeonLayout, the one that takes a string map and adds the corresponding rooms, using these two new methods. We can gin that up from our existing tests. I think we’ll leave the two we have and create a new one, since the two test the individual methods nicely.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_add_rooms_from_map</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">r_0</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]</span>
        <span class="n">r_1</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">)]</span>
        <span class="n">r_2</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="s">'''
            ..0.2
            ..0..
            ..000
            .....
            1....
        '''</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">()</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">add_rooms_from_map</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="n">room_2_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">cells_in_room</span><span class="p">(</span><span class="s">'2'</span><span class="p">)</span>
        <span class="n">tuples_2</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_2_cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_2</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_2</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_2</span>
        <span class="n">room_1_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">cells_in_room</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
        <span class="n">tuples_1</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_1_cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_1</span>
</code></pre></div></div>
<p>This takes the top from the first test and the bottom from the second, and assumes the method <code class="language-plaintext highlighter-rouge">add_rooms_from_map</code>, which doesn’t exist yet. It should be easy:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">add_rooms_from_map</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">string_map</span><span class="p">):</span>
        <span class="n">d</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">string_map_to_dictionary</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">add_from_dictionary</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
</code></pre></div></div>
<p>I am somewhat surprised to find that that doesn’t work. Why not?</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">test_paths</span><span class="p">.</span><span class="n">py</span><span class="p">:</span><span class="mi">137</span> <span class="p">(</span><span class="n">TestPaths</span><span class="p">.</span><span class="n">test_add_rooms_from_map</span><span class="p">)</span>
<span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">!=</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
<span class="n">Expected</span> <span class="p">:[(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
<span class="n">Actual</span>   <span class="p">:(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</code></pre></div></div>
<p>Silly me, I gave it the wrong answer. Improve the test:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_add_rooms_from_map</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">r_0</span> <span class="o">=</span> <span class="p">{(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)}</span>
        <span class="n">r_1</span> <span class="o">=</span> <span class="p">{(</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">)}</span>
        <span class="n">r_2</span> <span class="o">=</span> <span class="p">{(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)}</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="s">'''
            ..0.2
            ..0..
            ..000
            .....
            1....
        '''</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">()</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">add_rooms_from_map</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="n">room_0_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">cells_in_room</span><span class="p">(</span><span class="s">'0'</span><span class="p">)</span>
        <span class="n">tuples_2</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_0_cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="nb">set</span><span class="p">(</span><span class="n">tuples_2</span><span class="p">)</span> <span class="o">==</span> <span class="n">r_0</span>
        <span class="n">room_1_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">cells_in_room</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
        <span class="n">tuples_1</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_1_cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="nb">set</span><span class="p">(</span><span class="n">tuples_1</span><span class="p">)</span> <span class="o">==</span> <span class="n">r_1</span>
        <span class="n">room_0_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">cells_in_room</span><span class="p">(</span><span class="s">'0'</span><span class="p">)</span>
        <span class="n">tuples_0</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_0_cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="nb">set</span><span class="p">(</span><span class="n">tuples_0</span><span class="p">)</span> <span class="o">==</span> <span class="n">r_0</span>
</code></pre></div></div>
<p>Now we’re checking each of the rooms and they are correct. Test green the feature works. Commit: <em>add_rooms_from_map</em></p>
<p>I want to see this in the visible map, so I’ll hack <code class="language-plaintext highlighter-rouge">main</code> briefly, and we get this:</p>
<p><img src="https://ronjeffries.com/articles/-v026/w/w/map.png" alt="screen map looking like the string map" title="screen map looking like the string map" /></p>
<p>That’s just right. We even put Dot in one of the rooms. We’ll call it a morning after these few lines of code, and take a well-deserved break.</p>
<dl>
  <dt>In all seriousness</dt>
  <dd>I would generally recommend that even when working for a living, one might take a little break, stretch one’s legs, check out the coffee room, pet the cat, after reaching a milestone or inch-pebble like this one. Kind of bring things back to a pause before starting the next effort. And, if you’re as lucky as I am, you have a few hundred lines of text and some code to talk about, so it is time for a well-deserved iced chai or beverage of your choice.</dd>
</dl>
<h2 id="summary">Summary</h2>
<p>Yesterday we had the desire to turn a text picture into rooms, and we had a test that did part of the job longhand in the test, and a test that had driven the other half of the job over to DungeonLayout. Today, we just moved the longhand code over to the Layout class, and then test-drove a simple method that called the two chunks to get what we wanted.</p>
<p>I can imagine having done the whole thing in one big messy test and one big messy method and then, perhaps, refactoring. But as I wrote yesterday’s first test, it became clear that going all the way from string to tuples to cells to rooms was too big a jump for my feeble brain, so I stopped with the tuples and then processed the tuples.</p>
<p>I kind of have the feeling that working from the tuples may come in handy in the future, since we have a lot of tests that talk about Cell.at this and Cell.at that. Even if not, it is a sensible breakdown of the job of converting the map to rooms, and I expect the map to come in handy quite soon.</p>
<p>I’m not fond of the method names, as they are long and mention “data types”, which is a bit naff, so we may find ourselves coming up with better names as we go forward.</p>
<p>The whole process today would have gone perfectly had I not whiffed the test, plugging in a wrong answer. The code was correct and the test was quite wrong. That should tell me something. Probably that the tests are still too hard to write, and that I was probably trying to go too fast and wasn’t paying attention. Anyway it was just a momentary bump in the road.</p>
<p>We didn’t do as many commits as I’d like to have seen, only a couple yesterday and a couple today. There were probably some save points in there where I could have notched a commit in, but since we didn’t ever need to back up, I got away with it. It’s not a great way to bet, since even with very small steps, sometimes I do have to back up and a long revert is always a pain. So notching every bit of progress with a commit seems to be the best way for me.</p>
<p>Anyway, a nice new feature for our “making app” and we’ll start using it next time as we work on the furthest point idea. See you then!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sat, 11 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/w/w/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/w/w/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Feature -&gt; Making Improvement</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>Sometimes we focus on the application, sometimes on the making of the application. We begin with making.</p>
</blockquote>
<p>My brother Hill refers to the “App” and the “Making App”, that is, the code that is actually part of the application, vs the code that is part of making the application. Today I have a feature in mind, which drives me to improve my tool set.</p>
<p>The feature that I want is the ability to find a cell in the map which is maximally distant from a given cell, by Manhattan measurement. We might extend this to finding a cell which is at least some given distance, but the current plan is to find a maximally distant call.</p>
<p>I have a tentative plan for this, which will involve using our expanding cell search to collect some distance information along the paths searched, details to be figured out soon.</p>
<p>Clearly we’ll want to test-drive this, as it is somewhat tricky and anyway that is the style in which we prefer to work. That leads to a need for some improvement to how we build tests.</p>
<p>Yesterday I drew this little map, to test the <code class="language-plaintext highlighter-rouge">ensure_connection</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>..0.2
..0..
..000
.....
1....
</code></pre></div></div>
<p>It took me three or four tries to correctly convert that picture to the correct cell coordinates to create the rooms. I kept mixing up x and y, and probably made other random errors. Even when we do get it right, reading the test doesn’t tell us what the map looks like:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_connected</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="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">r_0</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="n">at</span><span class="p">(</span><span class="mi">2</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="n">at</span><span class="p">(</span><span class="mi">2</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="n">at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">4</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">r_0</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">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</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">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]))</span>
        <span class="n">suites</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">layout</span><span class="p">.</span><span class="n">is_fully_connected</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">ensure_connected</span><span class="p">()</span>
</code></pre></div></div>
<p>So, to improve our ability to build little maps for testing, I propose to build the ability to turn a multi-line string like the one above into a DungeonLayout. I assume that Python has multi-line strings and some way to take them apart.</p>
<p>Let’s write a test, using the layouts above.</p>
<dl>
  <dt>Note</dt>
  <dd>Another inspection showed me that the code for <code class="language-plaintext highlighter-rouge">test_connected</code> was a valid test but did not reflect the drawing. I corrected the lines above and I’m pretty sure I got it right this time.</dd>
</dl>
<p>After some fiddling, I have this longhand test, producing collections of x,y tuples:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_string_layout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">r_0</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]</span>
        <span class="n">r_1</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">)]</span>
        <span class="n">r_2</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="s">'''
            ..0.2
            ..0..
            ..000
            .....
            1....
        '''</span>
        <span class="n">string_map</span> <span class="o">=</span> <span class="n">textwrap</span><span class="p">.</span><span class="n">dedent</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="n">string_map</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="p">[</span> <span class="n">string</span> <span class="k">for</span> <span class="n">string</span> <span class="ow">in</span> <span class="n">strings</span> <span class="k">if</span> <span class="n">string</span><span class="p">]</span>
        <span class="n">room_cells</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">strings</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">char</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
                <span class="k">if</span> <span class="ow">not</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">room_cells</span><span class="p">:</span>
                    <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
                <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">].</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'0'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_0</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'1'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_1</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'2'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_2</span>
</code></pre></div></div>
<dl>
  <dt>Note</dt>
  <dd>In writing what follows, I realize that the <code class="language-plaintext highlighter-rouge">room_cells</code> dictionary includes too much. We’ll fix that but let me explain what we almost have so far.</dd>
</dl>
<p>The rigmarole with the text dedent removes any equal indentation, the split gives us lines of text, and the list comprehension removes any empty lines. There will be one at the beginning.</p>
<p>Then, what I was <em>trying</em> to do was go through and record the x,y locations of any non-dot cells, but I forgot to skip the dot.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="n">string_map</span> <span class="o">=</span> <span class="n">textwrap</span><span class="p">.</span><span class="n">dedent</span><span class="p">(</span><span class="n">string_map</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="n">string_map</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
        <span class="n">strings</span> <span class="o">=</span> <span class="p">[</span> <span class="n">string</span> <span class="k">for</span> <span class="n">string</span> <span class="ow">in</span> <span class="n">strings</span> <span class="k">if</span> <span class="n">string</span><span class="p">]</span>
        <span class="n">room_cells</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">strings</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">char</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
                <span class="k">if</span> <span class="n">char</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span><span class="s">'.'</span><span class="p">]:</span>
                    <span class="k">if</span> <span class="ow">not</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">room_cells</span><span class="p">:</span>
                        <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
                    <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">].</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">room_cells</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'0'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_0</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'1'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_1</span>
        <span class="k">assert</span> <span class="n">room_cells</span><span class="p">[</span><span class="s">'2'</span><span class="p">]</span> <span class="o">==</span> <span class="n">r_2</span>
</code></pre></div></div>
<p>The test fails 4 != 3 without the new <code class="language-plaintext highlighter-rouge">if</code>. I used <code class="language-plaintext highlighter-rouge">in</code> because I think we might allow other than <code class="language-plaintext highlighter-rouge">.</code>. That’s premature. generalization Change it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">for</span> <span class="n">y</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">strings</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">char</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">char</span> <span class="o">!=</span> <span class="s">'.'</span><span class="p">:</span>
                <span class="k">if</span> <span class="ow">not</span> <span class="n">char</span> <span class="ow">in</span> <span class="n">room_cells</span><span class="p">:</span>
                    <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
                <span class="n">room_cells</span><span class="p">[</span><span class="n">char</span><span class="p">].</span><span class="n">append</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">))</span>
</code></pre></div></div>
<p>I think that what we need is a method on DungeonLayout to do this job and actually create the Room instances.</p>
<p>And bah: we need a test for that as well. We have no decent way to test a whole layout for correctness. Let’s see what might make sense. We have code here that makes a dictionary of tuples, given a specialized string. That has no reason to live anywhere, really.</p>
<p>We do have the class RoomMaker, which is a sort of eclectic collection of ways to make individual rooms of various shapes, round, diamond, cave, and so on. Whatever we’re trying to do might fit there. Or we might just put something in DungeonLayout to accept a dictionary of x,y tuples and make rooms from it.</p>
<p>The hole is getting deeper but I think we might be nearing the bottom. Let’s try a test for <code class="language-plaintext highlighter-rouge">add_from_dictionary</code>.</p>
<p>This seems close, and enough to drive out the method and find out what’s wrong with this idea:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_rooms_from_dictionary</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">dict</span> <span class="o">=</span> <span class="p">{</span> <span class="s">'0'</span><span class="p">:</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="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)],</span>
                 <span class="s">'1'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)],</span>
                 <span class="s">'2'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]}</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</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="n">layout</span><span class="p">.</span><span class="n">add_from_dictionary</span><span class="p">(</span><span class="nb">dict</span><span class="p">)</span>
        <span class="n">room_2</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">layout</span><span class="p">.</span><span class="n">rooms</span> <span class="k">if</span> <span class="n">room</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s">'2'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
        <span class="n">cells</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</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="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="ow">in</span> <span class="n">cells</span>
</code></pre></div></div>
<p>We code this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">add_from_dictionary</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name_to_xy</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">name</span><span class="p">,</span> <span class="n">tuples</span> <span class="ow">in</span> <span class="n">name_to_xy</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="n">cells</span> <span class="o">=</span> <span class="p">[</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span> <span class="ow">in</span> <span class="n">tuples</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="n">cells</span><span class="p">,</span> <span class="n">name</span><span class="p">))</span>
</code></pre></div></div>
<p>And the test is green. Let’s continue making the test a bit more robust:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_rooms_from_dictionary</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">dict</span> <span class="o">=</span> <span class="p">{</span> <span class="s">'0'</span><span class="p">:</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="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)],</span>
                 <span class="s">'1'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)],</span>
                 <span class="s">'2'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]}</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</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="n">layout</span><span class="p">.</span><span class="n">add_from_dictionary</span><span class="p">(</span><span class="nb">dict</span><span class="p">)</span>
        <span class="n">room_2</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">layout</span><span class="p">.</span><span class="n">rooms</span> <span class="k">if</span> <span class="n">room</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s">'2'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
        <span class="n">cells_2</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</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="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="ow">in</span> <span class="n">cells_2</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span> <span class="ow">in</span> <span class="n">cells_2</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="ow">in</span> <span class="n">cells_2</span>
        <span class="n">room_1</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">layout</span><span class="p">.</span><span class="n">rooms</span> <span class="k">if</span> <span class="n">room</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s">'1'</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>
        <span class="n">cells_1</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_1</span><span class="p">.</span><span class="n">cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="ow">in</span> <span class="n">cells_1</span>
</code></pre></div></div>
<p>Green. Commit: <em>working on layout from strings.</em></p>
<p>I think that the room comprehension above could be a method on layout. Change the test to demand it: “wishful thinking”.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_rooms_from_dictionary</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="nb">dict</span> <span class="o">=</span> <span class="p">{</span> <span class="s">'0'</span><span class="p">:</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="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)],</span>
                 <span class="s">'1'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)],</span>
                 <span class="s">'2'</span><span class="p">:</span> <span class="p">[(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)]}</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</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="n">layout</span><span class="p">.</span><span class="n">add_from_dictionary</span><span class="p">(</span><span class="nb">dict</span><span class="p">)</span>
        <span class="n">room_2_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">cells_in_room</span><span class="p">(</span><span class="s">'2'</span><span class="p">)</span>
        <span class="n">tuples_2</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_2_cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_2</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_2</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_2</span>
        <span class="n">room_1_cells</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">cells_in_room</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
        <span class="n">tuples_1</span> <span class="o">=</span> <span class="p">[</span><span class="n">c</span><span class="p">.</span><span class="n">xy</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">room_1_cells</span><span class="p">]</span>
        <span class="k">assert</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="ow">in</span> <span class="n">tuples_1</span>
</code></pre></div></div>
<p>And:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">cells_in_room</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">room_name</span><span class="p">):</span>
        <span class="n">room</span> <span class="o">=</span> <span class="nb">next</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="n">room</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">room_name</span><span class="p">),</span> <span class="bp">None</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">room</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">room</span><span class="p">.</span><span class="n">cells</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="p">[]</span>
</code></pre></div></div>
<p>Green. Commit: <em>adding cells_in_room(name) and add_from_dictionary</em></p>
<p>I need a break. Let’s sum up and continue later.</p>
<h2 id="summary">Summary</h2>
<p>We’re still improving the “making app”. We have two useful new methods on DungeonLayout, <code class="language-plaintext highlighter-rouge">add_from_dictionary</code> and <code class="language-plaintext highlighter-rouge">cells_in_room(name)</code>, both of which are only used in tests. I have a feeling that both those methods will turn out to be useful in production, but even if not, they’re useful already in that they make it easier to write tests and check results. If we were tight on memory, very unlikely these days, we might add a test-only subclass of DungeonLayout to hold them or something like that, but really there is no need for anything that fancy.</p>
<p>Looking forward, I think we will find a place, probably Layout, for our code that converts a string map to rooms, and of course we’ll test that into existence. Then, we can turn our attention to the feature we want, which will be tested by creating a layout that we understand and finding a point of maximal distance from a given point.</p>
<p>So far, it’s going well. There are distractions here at home, so for now, I’m calling a break.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Fri, 10 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/w/x/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/w/x/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Bit of a Defect</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>I think ensure_connected can fail. Let’s prove it, then fix it.</p>
</blockquote>
<p>I need an afternoon distraction, so we’ll try some more code improvement. We’ll start with the path-making code we find lying about.</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">ensure_connected</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">is_fully_connected</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="n">room</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_straight_path_room</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">def</span> <span class="nf">_straight_path_room</span><span class="p">(</span><span class="bp">self</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">_straight_path_cells</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
            <span class="k">return</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="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
    <span class="k">def</span> <span class="nf">_straight_path_cells</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">cells</span> <span class="o">=</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="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="p">.</span><span class="n">extend</span><span class="p">(</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">return</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">cells</span><span class="p">))</span>
</code></pre></div></div>
<p>This code gets a list of all current suites—each suite being a collection of rooms that are directly adjacent—and then creates a path between each pair or suites 0-&gt;1, 1-&gt;2, and so on, collecting all the cells of all those paths into one room, which it adds to the layout.</p>
<p>I suspect that this code can fail to connect the dungeon as it stands, if the rooms are arranged poorly enough. Let’s try to 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_connected</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="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">r_0</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="n">at</span><span class="p">(</span><span class="mi">2</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="n">at</span><span class="p">(</span><span class="mi">2</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="n">at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
               <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">4</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">r_0</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">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</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">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">0</span><span class="p">)]))</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">layout</span><span class="p">.</span><span class="n">is_fully_connected</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">ensure_connected</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">layout</span><span class="p">.</span><span class="n">is_fully_connected</span>
</code></pre></div></div>
<p>The map looks like this, if I’m not mistaken:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>..0.2
..0..
..000
.....
1....
</code></pre></div></div>
<p>So our current code will connect 0-&gt;1 but then be unable to connect 1-&gt;2, leaving two suites, (0,1) and (2).</p>
<p>I expect the test to fail, after three or four attempts to transcribe my picture to coordinates by hand. Now I think that if we loop in <code class="language-plaintext highlighter-rouge">ensure_connected</code> we should pass.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <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="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="n">room</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_straight_path_room</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>Test passes. Commit: <em>test and fix ensure_connected.</em></p>
<p>We’ll call that done.</p>
<h2 id="summary">Summary</h2>
<p>Hard to know what to call that. I thought there was a defect, showed the defect, fixed it. It has been in there for a while, and I’m not sure but I’ve probably mentioned before that it seemed iffy.</p>
<p>Anyway, one more step forward. See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Thu, 09 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/w/y/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/w/y/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Looking Around</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>Having completed the basics of the contents vs layout design, it’s time to look around and see what needs improvement.</p>
</blockquote>
<p>If I were willing to work when tired, or when an article exceeds 500 multi-line lines, we might have done more tidying yesterday, but that is not my way. With a day or two behind us, we probably do a better job of recognizing bumps and dips in the code that would elude us right after a big chunk of coding or refactoring.</p>
<p>I see something already, on the PyCharm tab that is open right in front of me:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot_room</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">dot_cell</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="n">dot_room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="n">dot_cell</span>
    <span class="k">def</span> <span class="nf">place_adventurer_at</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cell</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="n">cell</span>
</code></pre></div></div>
<p>Somewhere in my past there is the idea that the <code class="language-plaintext highlighter-rouge">populate</code> should call the <code class="language-plaintext highlighter-rouge">place</code>, rather than do the member assignment directly. Doing so:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot_room</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">dot_cell</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="n">dot_room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">place_adventurer_at</span><span class="p">(</span><span class="n">dot_cell</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">place_adventurer_at</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cell</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="n">cell</span>
</code></pre></div></div>
<p>That removes the duplication, and helps us ensure that if we ever need to change how placing the adventurer works, there will be one place and only one place to change. I’m not saying that I have reasoned to this practice, and I certainly haven’t proven that it’s better: it’s just built into how I work.</p>
<p>There are other direct references. I’ll fix all four. Here’s one:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">move_adventurer_north</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">place_adventurer_at</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span><span class="p">.</span><span class="n">north</span><span class="p">())</span>
</code></pre></div></div>
<p>I notice the names <code class="language-plaintext highlighter-rouge">dot_room</code> etc. The adventurer is jokingly called Dot in these articles, because she displays as a big dot on the screen. But a programmer discovering those names will be just a bit disrupted by those names.</p>
<p>While we’re thinking about the names, I propose that we rename ‘adventurer’ to ‘player’, which is a bit more common and easier to type, a win-win. Things look like this now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</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">layout</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">layout</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">player_cell</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">rooms</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">layout</span><span class="p">.</span><span class="n">rooms</span>
    <span class="k">def</span> <span class="nf">populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot_room</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">dot_cell</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="n">dot_room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">place_player_at</span><span class="p">(</span><span class="n">dot_cell</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">place_player_at</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cell</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">player_cell</span> <span class="o">=</span> <span class="n">cell</span>
    <span class="k">def</span> <span class="nf">move_adventurer_north</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">place_player_at</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">player_cell</span><span class="p">.</span><span class="n">north</span><span class="p">())</span>
    <span class="p">...</span>
</code></pre></div></div>
<p>Green. Commit: <em>tidying</em>.</p>
<p>We should take a look at DungeonLayout, our new / old class. Immediately we notice that this class seems rather large. We just scan it to make that assessment, before even trying to understand it.</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">__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">rooms</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">suite_sets</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">def</span> <span class="nf">__iter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="nb">iter</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="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">is_fully_connected</span><span class="p">(</span><span class="bp">self</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span> <span class="o">==</span> <span class="nb">len</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="k">def</span> <span class="nf">ensure_connected</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">is_fully_connected</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="n">room</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_straight_path_room</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="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">number_of_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">return</span> <span class="nb">len</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="k">def</span> <span class="nf">add_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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">remove_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="n">room</span><span class="p">.</span><span class="n">return_cells</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="n">remove</span><span class="p">(</span><span class="n">room</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="k">return</span> <span class="p">[</span><span class="n">Suite</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suite_sets</span><span class="p">()]</span>
    <span class="k">def</span> <span class="nf">define_suite_sets</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">suite_sets</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="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="bp">self</span><span class="p">.</span><span class="n">suite_sets</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="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">suite_sets</span>
    <span class="k">def</span> <span class="nf">add_path_room</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">room</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_straight_path_room</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">room</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">def</span> <span class="nf">_straight_path_room</span><span class="p">(</span><span class="bp">self</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">_straight_path_cells</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
            <span class="k">return</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="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
    <span class="k">def</span> <span class="nf">_straight_path_cells</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">cells</span> <span class="o">=</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="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="p">.</span><span class="n">extend</span><span class="p">(</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">return</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">cells</span><span class="p">))</span>
    <span class="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">find_suite</span><span class="p">(</span><span class="n">room</span><span class="p">):</span>
        <span class="n">suite</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
        <span class="n">start</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="mi">0</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">start</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="p">.</span><span class="n">is_in_a_room</span><span class="p">):</span>
            <span class="n">suite</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">cell</span><span class="p">.</span><span class="n">room</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">suite</span>
</code></pre></div></div>
<p>Do we ever use the <code class="language-plaintext highlighter-rouge">suite_sets</code> member? We do, but oddly:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">define_suite_sets</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">suite_sets</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="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="bp">self</span><span class="p">.</span><span class="n">suite_sets</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="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">suite_sets</span>
</code></pre></div></div>
<p>I think that if we used a temp in that method everything would continue to work and we wouldn’t need the instance variable any more.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">define_suite_sets</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suite_sets</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="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">suite_sets</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="k">return</span> <span class="n">suite_sets</span>
</code></pre></div></div>
<p>Green, and <code class="language-plaintext highlighter-rouge">main</code> works. Remove the member. Commit.</p>
<p>I notice <code class="language-plaintext highlighter-rouge">add_path_room</code>, which has no usages. Remove it. Commit.</p>
<dl>
  <dt>Did you notice?</dt>
  <dd>After taking a look at the whole class, I didn’t begin by planning out how to refactor it overall. Instead, I looked for opportunities to make small changes, especially small deletions. After those low-hanging fruit are dealt with, we’ll look for larger changes. When we do that, the problem we face will be smaller and thus easier to resolve.</dd>
</dl>
<p>My next move is going to be to put methods that call each other closer to each other. Things seem a bit random here. First this case:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">define_suite_sets</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">suite_sets</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="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">suite_sets</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="k">return</span> <span class="n">suite_sets</span>
    <span class="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">find_suite</span><span class="p">(</span><span class="n">room</span><span class="p">):</span>
        <span class="n">suite</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
        <span class="n">start</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="mi">0</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">start</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="p">.</span><span class="n">is_in_a_room</span><span class="p">):</span>
            <span class="n">suite</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">cell</span><span class="p">.</span><span class="n">room</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">suite</span>
</code></pre></div></div>
<p>In the second method there, <code class="language-plaintext highlighter-rouge">suite</code> is a set of cells, not a Suite. (What is a Suite, anyway? I think we need to look at that.) Here, we rename …</p>
<p>No, I’m mistaken. <code class="language-plaintext highlighter-rouge">suite</code> is a set of Rooms. I do a bit of renaming and type hinting:</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="k">return</span> <span class="p">[</span><span class="n">Suite</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suite_sets</span><span class="p">()]</span>
    <span class="k">def</span> <span class="nf">define_suite_sets</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="nb">set</span><span class="p">[</span><span class="n">Room</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_rooms</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="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_rooms</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_rooms</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">suites</span>
    <span class="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">find_suite</span><span class="p">(</span><span class="n">room</span><span class="p">)</span> <span class="o">-&gt;</span><span class="nb">set</span><span class="p">[</span><span class="n">Room</span><span class="p">]:</span>
        <span class="n">suite_rooms</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="n">start</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="mi">0</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">start</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="p">.</span><span class="n">is_in_a_room</span><span class="p">):</span>
            <span class="n">suite_rooms</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">cell</span><span class="p">.</span><span class="n">room</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">suite_rooms</span>
</code></pre></div></div>
<p>I remain curious about what a Suite is and what it does. This may all be somewhat moot. But why aren’t we returning a Suite from the bottom method here instead of that last conversion at the top?</p>
<p>I think this code may be a case of backing away slowly. Ah, no, that’s not quite it: there is a test for <code class="language-plaintext highlighter-rouge">define_suite_sets</code> and one for <code class="language-plaintext highlighter-rouge">find_suite</code>. First this one:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_make_suite_lists</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        \
        <span class="c1">#massive setup skipped here
</span>
        <span class="n">suite_sets</span> <span class="o">=</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">define_suite_sets</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">suite_sets</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="k">assert</span> <span class="p">{</span><span class="n">r_0</span><span class="p">,</span> <span class="n">r_1</span><span class="p">,</span> <span class="n">r_3</span><span class="p">}</span> <span class="ow">in</span> <span class="n">suite_sets</span>
        <span class="k">assert</span> <span class="p">{</span><span class="n">r_2</span><span class="p">}</span> <span class="ow">in</span> <span class="n">suite_sets</span>
        <span class="k">assert</span> <span class="p">{</span><span class="n">r_4</span><span class="p">,</span> <span class="n">r_5</span><span class="p">}</span> <span class="ow">in</span> <span class="n">suite_sets</span>
        <span class="n">suites</span> <span class="o">=</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">suites</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
</code></pre></div></div>
<p>We can change this not to use <code class="language-plaintext highlighter-rouge">define_suite_sets</code> by checking the suites after <code class="language-plaintext highlighter-rouge">define_suites</code>. It’s just a bit harder, because we can’t be sure what order they’re in. But that order won’t change so once we get the test right it should keep running.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="n">suites</span> <span class="o">=</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">define_suites</span><span class="p">()</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">suites</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span>
        <span class="k">assert</span> <span class="n">suites</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">room_set</span> <span class="o">==</span> <span class="p">{</span><span class="n">r_0</span><span class="p">,</span> <span class="n">r_1</span><span class="p">,</span> <span class="n">r_3</span><span class="p">}</span>
        <span class="k">assert</span> <span class="n">suites</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">room_set</span> <span class="o">==</span> <span class="p">{</span><span class="n">r_2</span><span class="p">}</span>
        <span class="k">assert</span> <span class="n">suites</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">room_set</span> <span class="o">==</span> <span class="p">{</span><span class="n">r_4</span><span class="p">,</span> <span class="n">r_5</span><span class="p">}</span>
</code></pre></div></div>
<p>Green. Commit: <em>refactoring tests in prep for object refactoring.</em></p>
<p>Now who’s calling <code class="language-plaintext highlighter-rouge">find_suite</code>? Ah, that’s used twice inside DungeonLayout:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">is_fully_connected</span><span class="p">(</span><span class="bp">self</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span> <span class="o">==</span> <span class="nb">len</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="k">def</span> <span class="nf">define_suite_sets</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="nb">set</span><span class="p">[</span><span class="n">Room</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_rooms</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="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_rooms</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_rooms</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">suites</span>
</code></pre></div></div>
<p>I think that if we actually start returning suites from find_suite, <code class="language-plaintext highlighter-rouge">is_fully_connected</code> may fail for want of <code class="language-plaintext highlighter-rouge">len</code> on Suite. Is there a test for that? Yes, there is. So we can proceed.</p>
<p>Looking at this sequence:</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="k">return</span> <span class="p">[</span><span class="n">Suite</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suite_sets</span><span class="p">()]</span>
    <span class="k">def</span> <span class="nf">define_suite_sets</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="nb">set</span><span class="p">[</span><span class="n">Room</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_rooms</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="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_rooms</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_rooms</span><span class="p">]</span>
        <span class="k">return</span> <span class="n">suites</span>
    <span class="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">find_suite</span><span class="p">(</span><span class="n">room</span><span class="p">)</span> <span class="o">-&gt;</span><span class="nb">set</span><span class="p">[</span><span class="n">Room</span><span class="p">]:</span>
        <span class="n">suite_rooms</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="n">start</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="mi">0</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">start</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="p">.</span><span class="n">is_in_a_room</span><span class="p">):</span>
            <span class="n">suite_rooms</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">cell</span><span class="p">.</span><span class="n">room</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">suite_rooms</span>
</code></pre></div></div>
<p>My plan is to return a Suite directly from <code class="language-plaintext highlighter-rouge">find_suite</code>. Makes sense, right? Then we won’t need to do it in <code class="language-plaintext highlighter-rouge">define_suites</code> and I think we rename <code class="language-plaintext highlighter-rouge">define_suite_sets</code> to <code class="language-plaintext highlighter-rouge">define_suites</code>. Or maybe we can inline. Let’s try that now:</p>
<p>First pull out a variable:</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">sets</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">define_suite_sets</span><span class="p">()</span>
        <span class="k">return</span> <span class="p">[</span><span class="n">Suite</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">sets</span><span class="p">]</span>
</code></pre></div></div>
<p>We could commit. Now inline method:</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="nb">set</span><span class="p">[</span><span class="n">Room</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_rooms</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="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_rooms</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_rooms</span><span class="p">]</span>
        <span class="n">sets</span> <span class="o">=</span> <span class="n">suites</span>
        <span class="k">return</span> <span class="p">[</span><span class="n">Suite</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">sets</span><span class="p">]</span>
    <span class="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">find_suite</span><span class="p">(</span><span class="n">room</span><span class="p">)</span> <span class="o">-&gt;</span><span class="nb">set</span><span class="p">[</span><span class="n">Room</span><span class="p">]:</span>
        <span class="n">suite_rooms</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="n">start</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="mi">0</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">start</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="p">.</span><span class="n">is_in_a_room</span><span class="p">):</span>
            <span class="n">suite_rooms</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">cell</span><span class="p">.</span><span class="n">room</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">suite_rooms</span>
</code></pre></div></div>
<p>Now return a Suite from <code class="language-plaintext highlighter-rouge">find_suite</code> and clean up the hints. This will break a test for a moment.</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</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="k">return</span> <span class="n">suites</span>
    <span class="o">@</span><span class="nb">staticmethod</span>
    <span class="k">def</span> <span class="nf">find_suite</span><span class="p">(</span><span class="n">room</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Suite</span><span class="p">:</span>
        <span class="n">suite_rooms</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="n">start</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="mi">0</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">start</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="k">lambda</span> <span class="n">c</span><span class="p">:</span> <span class="n">c</span><span class="p">.</span><span class="n">is_in_a_room</span><span class="p">):</span>
            <span class="n">suite_rooms</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">cell</span><span class="p">.</span><span class="n">room</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">Suite</span><span class="p">(</span><span class="n">suite_rooms</span><span class="p">)</span>
</code></pre></div></div>
<p>Six tests break. I should have committed a save point. Quick look at the tests to see if we can fix things quickly. Ah, we’re in luck:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</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="o">^^^^^^^^^^^^^^^^^</span>
<span class="n">E</span>           <span class="nb">TypeError</span><span class="p">:</span> <span class="n">argument</span> <span class="n">of</span> <span class="nb">type</span> <span class="s">'Suite'</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">iterable</span>
</code></pre></div></div>
<p>We can just fetch the rooms, or make it iterable. I think the former is more conservative.</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</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>Now we still have one test failing. It’s the one I expected:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span>       <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">suite</span><span class="p">)</span> <span class="o">==</span> <span class="nb">len</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="o">^^^^^^^^^^</span>
<span class="n">E</span>       <span class="nb">TypeError</span><span class="p">:</span> <span class="nb">object</span> <span class="n">of</span> <span class="nb">type</span> <span class="s">'Suite'</span> <span class="n">has</span> <span class="n">no</span> <span class="nb">len</span><span class="p">()</span>
</code></pre></div></div>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">is_fully_connected</span><span class="p">(</span><span class="bp">self</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
        <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">suite</span><span class="p">.</span><span class="n">room_set</span><span class="p">)</span> <span class="o">==</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
</code></pre></div></div>
<p>That check is kind of indirect: we are fully connected if the suite containing our first room has the same length as the number of rooms in the layout. Better checks might be:</p>
<ol>
  <li>Is there only one Suite in define_suites?</li>
  <li>Does the Suite containing room one contain all the rooms?</li>
</ol>
<p>Let’s use scheme 1 here.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">is_fully_connected</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="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">suites</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</code></pre></div></div>
<p>Yikes, I haven’t been committing. Commit: <em>refactor define_suites, plus tidying.</em></p>
<p>The remaining “big” thing here is this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <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="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">is_fully_connected</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="n">room</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_straight_path_room</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">def</span> <span class="nf">_straight_path_room</span><span class="p">(</span><span class="bp">self</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">_straight_path_cells</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">cells</span><span class="p">:</span>
            <span class="k">return</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="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
    <span class="k">def</span> <span class="nf">_straight_path_cells</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">cells</span> <span class="o">=</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="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="p">.</span><span class="n">extend</span><span class="p">(</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">return</span> <span class="nb">list</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="n">cells</span><span class="p">))</span>
</code></pre></div></div>
<p>We’ll let that one ride for this morning, as we are approaching the limit of our article size, our patience, and various other personal limits. I want to call out Suite, in relation to the above, since it 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">Suite</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">room_set</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room_set</span> <span class="o">=</span> <span class="n">room_set</span>
    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">cells</span><span class="p">(</span><span class="bp">self</span><span class="p">):</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">room_set</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="n">cells</span><span class="p">:</span>
                <span class="k">yield</span> <span class="n">cell</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="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">cells</span> <span class="o">=</span> <span class="n">source</span><span class="p">.</span><span class="n">find_path</span><span class="p">(</span><span class="n">target</span><span class="p">,</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">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_closest_pair</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">my_best</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="n">his_best</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="n">distance</span> <span class="o">=</span> <span class="mi">1_000_000</span>
        <span class="k">for</span> <span class="n">mine</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">his</span> <span class="ow">in</span> <span class="n">suite</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span>
                <span class="n">dist</span> <span class="o">=</span> <span class="n">mine</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">his</span><span class="p">)</span>
                <span class="k">if</span> <span class="n">dist</span> <span class="o">&lt;</span> <span class="n">distance</span><span class="p">:</span>
                    <span class="n">my_best</span> <span class="o">=</span> <span class="n">mine</span>
                    <span class="n">his_best</span> <span class="o">=</span> <span class="n">his</span>
                    <span class="n">distance</span> <span class="o">=</span> <span class="n">dist</span>
        <span class="k">return</span> <span class="p">[</span><span class="n">my_best</span><span class="p">,</span> <span class="n">his_best</span><span class="p">]</span>
</code></pre></div></div>
<p>There’s something funky going on here, with <code class="language-plaintext highlighter-rouge">find_path</code> and <code class="language-plaintext highlighter-rouge">find_path_cells</code> in Cell, plus some path-finding in Suite and some in DungeonLayout. I suspect that we may be able to find a better division of labor here, ideally something like Layout to Suite to Room to Cell, or perhaps direct from Suite to Cell, with no path-related stuff in Room at all.</p>
<p>All that is for another day.</p>
<h2 id="summary">Summary</h2>
<p>It is no surprise that we found things to improve, after having moved Dungeon and DungeonLayout into place. Some of that was debris left over from our various experiments with ensuring dungeons are connected: remember that we have that interesting trick of tossing in random rooms with high wandering coefficients until everything connects. Clearly that needs to be better integrated. Some kind of path-making scheme with strategies or a selection of approaches or something.</p>
<p>We proceeded without a grand plan, even though the situation might well have seemed like we should have one. Instead, we kind of chip away everything that doesn’t look quite right and almost magically a better-organized set of classes emerges.</p>
<p>One good example of that was pushing the creation of the Suite down to the bottom of the method sequence, to the first method that had the collection needed to create a Suite, instead of collecting the collections and then Suiterizing them. That did require us to refactor a test, but it made the test both shorter and more safe.</p>
<p>The test got the way it was because when we originally built the Suite idea, I didn’t see clearly how to do it, so created a set of rooms first, which is necessary, but I tested that bit, which kind of nailed it in place. We had to undo that test to release the code to be what it really wanted to be.</p>
<p>I missed at least one possible commit, and it nearly bit me when six tests broke, but the fix turned out to be easy. (In fact, I think that PyCharn had highlighted the troublesome line, but I didn’t notice.) But committing Every Single Time we’re green is a good idea, at least here at my place.</p>
<p>So we have made a noticeable improvement to tests and to our Layout, Dungeon, and Suite classes. Very useful 90 minutes, including writing the article.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Thu, 09 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/w/z/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/w/z/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Design Feedback</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>The FGNO OGs<sup id="fnref:o" role="doc-noteref"><a href="#fn:o" class="footnote" rel="footnote">1</a></sup>] gave me some good ideas about my design: thanks, OGs. This morning I’ll start following some of those ideas. I use test doubles!</p>
</blockquote>
<p>Every Tuesday evening for the past few years, has been FGNO, Friday Geeks Night Out<sup id="fnref:c" role="doc-noteref"><a href="#fn:c" class="footnote" rel="footnote">2</a></sup> via Zoom. Last night I asked for some feedback about the design of the dungeon program. OG A challenged me to do a refactoring that I mentioned was probably desirable. Did that, with far more typing errors than I ever seem to have when alone. Worked well. OG B helped with some pygame issues. I suggested that Dungeon should be split into the stable bits, and a DungeonContents new class instance containing the variable parts. Actually made that work.</p>
<p>I think it was OG H who suggested that the view side should do things like the decoding of key strokes, and send messages to the dungeon that make semantic sense to dungeon behavior. We didn’t try that, but it has made it to my list of things to do.</p>
<p>Then OG H, on a bit of a roll, allowed as how he’d do the contents/layout design with the dynamic part of the dungeon holding on to the more static part rather than the way I proposed with the top object holding both static and dynamic contents. We talked about that and I think that I probably agree, so this morning we’re going to work on that.</p>
<p>Let’s get started. Here’s the top of Dungeon now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</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">adventurer_cell</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="c1"># layout starts here
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">rooms</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">suite_sets</span> <span class="o">=</span> <span class="p">[]</span>
</code></pre></div></div>
<p>The arrangement of members and the comment are from FGNO. The last two instance variables are unchanging after the rooms are created. One name that we came up with for the new class was DungeonLayout, thus the comment.</p>
<p>Our target design here is to have a new class, DungeonLayout, that contains the rooms and suites. The class Dungeon will include the dynamic aspects of the game. (OG B-prime commented that this class would often be called Game.)</p>
<p>We would like to do this refactoring in small steps, with plenty of commits along the way. There are quite a few references to Dungeon: 19, according to PyCharm. Eight of those are in <code class="language-plaintext highlighter-rouge">main</code>, and the rest in tests. A quick look tells me that in <code class="language-plaintext highlighter-rouge">main</code>, there is only the import, one real reference, and the rest are type hints. The tests all look similar:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_make_suite_lists</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</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">dungeon</span> <span class="o">=</span> <span class="n">Dungeon</span><span class="p">()</span>
        <span class="n">o_0</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span>
        <span class="n">r_0</span> <span class="o">=</span> <span class="n">RoomMaker</span><span class="p">().</span><span class="n">cave</span><span class="p">(</span><span class="n">number_of_cells</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">origin</span><span class="o">=</span><span class="n">o_0</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s">'0'</span><span class="p">)</span>
        <span class="n">dungeon</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">r_0</span><span class="p">)</span>
        <span class="p">...</span>
</code></pre></div></div>
<p>To a first approximation they’re all creating a Dungeon to put rooms or paths into it. So I think they’ll all want to refer to DungeonLayout when we’re done.</p>
<p>So I suggest the following plan:</p>
<ol>
  <li>Rename Dungeon to DungeonLayout. This will change all the references and keep the tests running. Commit that.</li>
  <li>Then, create a new Dungeon class, via at least one test, and move the <code class="language-plaintext highlighter-rouge">populate</code> and such code to it. Maybe write a test for that.</li>
  <li>Waving hands: deal with DungeonView and its use in <code class="language-plaintext highlighter-rouge">main</code>.</li>
</ol>
<p>I’m pretty sure about that first step, and think that the second is probably the right next thing to do, but we’ll look a bit more deeply at the code before we pick the next move. I’m sure this first one is proper, as it will fix up most of the wiring with a machine refactoring.</p>
<p>Here goes: one rename command. PyCharm even offers to rename the file. Green. Commit: <em>rename Dungeon to DungeonLayout.</em></p>
<p>OK. At this point everything works, including <code class="language-plaintext highlighter-rouge">main</code>. We will work mostly driven by <code class="language-plaintext highlighter-rouge">main</code> and see what we find. <code class="language-plaintext highlighter-rouge">main</code> 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">main</span><span class="p">():</span>
    <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="mi">56</span><span class="p">)</span>
    <span class="c1"># random.seed(234)
</span>    <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">()</span>
    <span class="n">maker</span> <span class="o">=</span> <span class="n">RoomMaker</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">maker</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">maker</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">maker</span><span class="p">)</span>
    <span class="n">number_of_rooms</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">8</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="n">number_of_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">maker</span><span class="p">)</span>
        <span class="n">make_experimental_room</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">maker</span><span class="p">)</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">ensure_connected</span><span class="p">()</span>
    <span class="n">layout</span><span class="p">.</span><span class="n">populate</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">layout</span><span class="p">)</span>
    <span class="n">view</span><span class="p">.</span><span class="n">main_loop</span><span class="p">()</span>
</code></pre></div></div>
<p>That’s nearly good but it should end like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <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="n">dungeon</span><span class="p">.</span><span class="n">populate</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="n">view</span><span class="p">.</span><span class="n">main_loop</span><span class="p">()</span>
</code></pre></div></div>
<p>Ideally, that would be all we need to do in <code class="language-plaintext highlighter-rouge">main</code>, at least for now. We could argue that we shouldn’t have to ensure connected explicitly, and that perhaps the Dungeon should auto-populate. But perhaps not: there are probably all kinds of options to be specified. Anyway, given our current code, that’s about as good as it could get.</p>
<p>Let’s create a test that works somewhat like <code class="language-plaintext highlighter-rouge">main</code>, to drive out some changes.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestDungeon</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_exists</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</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_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</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">room_cell</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="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">dungeon</span> <span class="o">=</span> <span class="n">Dungeon</span><span class="p">(</span><span class="n">layout</span><span class="p">)</span>
</code></pre></div></div>
<p>That’s a lot of wiring but I wanted a legitimate layout. This won’t compile or run because there is no Dungeon class. As is my common practice, I’ll begin by putting it in DungeonLayout’s file.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</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">layout</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">layout</span>
</code></pre></div></div>
<p>Green. We can commit.</p>
<p>Let’s write a new test for <code class="language-plaintext highlighter-rouge">populate</code> and see what we can do.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</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_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</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">room_cell</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="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">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="n">dungeon</span><span class="p">.</span><span class="n">populate</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="bp">None</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">key_lock</span> <span class="ow">is</span> <span class="bp">None</span>
</code></pre></div></div>
<p>These are the current population members in DungeonLayout, so presumably theyu’ll be moved to Dungeon … right now:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</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">layout</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">layout</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">def</span> <span class="nf">populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot_room</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="bp">self</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">dot_cell</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="n">dot_room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="n">dot_cell</span>
</code></pre></div></div>
<p>I expected that to work. It did not. The test is wrong:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
        <span class="n">dungeon</span><span class="p">.</span><span class="n">populate</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="n">room_cell</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">key_lock</span> <span class="ow">is</span> <span class="bp">None</span>
</code></pre></div></div>
<p>Green. Commit: <em>implementing Dungeon class.</em></p>
<h2 id="breathe-reflect-think">Breathe. Reflect. Think.</h2>
<p>Right. We need to make DungeonView work, for <code class="language-plaintext highlighter-rouge">main</code> to work. Most of what needs fixing is here:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">main_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">running</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="n">moving</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="n">background</span> <span class="o">=</span> <span class="s">"gray33"</span>
        <span class="n">color</span> <span class="o">=</span> <span class="s">"darkblue"</span>
        <span class="k">while</span> <span class="n">running</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">pygame</span><span class="p">.</span><span class="n">event</span><span class="p">.</span><span class="n">get</span><span class="p">():</span>
                <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">QUIT</span><span class="p">:</span>
                    <span class="n">running</span> <span class="o">=</span> <span class="bp">False</span>
                <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">KEYDOWN</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">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
                <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">KEYUP</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">handle_keyup</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">screen</span><span class="p">.</span><span class="n">fill</span><span class="p">(</span><span class="n">background</span><span class="p">)</span>
            <span class="c1"># self.draw_grid(color)
</span>            <span class="bp">self</span><span class="p">.</span><span class="n">draw_rooms</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_contents</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">clock</span><span class="p">.</span><span class="n">tick</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
            <span class="n">pygame</span><span class="p">.</span><span class="n">display</span><span class="p">.</span><span class="n">flip</span><span class="p">()</span>
        <span class="n">pygame</span><span class="p">.</span><span class="n">quit</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">draw_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</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">dungeon</span><span class="p">:</span>
            <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">).</span><span class="n">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">screen</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">draw_contents</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span>
        <span class="n">cx</span> <span class="o">=</span> <span class="n">adventurer_cell</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">cell_size</span> <span class="o">+</span> <span class="n">cell_size</span><span class="o">//</span><span class="mi">2</span>
        <span class="n">cy</span> <span class="o">=</span> <span class="n">adventurer_cell</span><span class="p">.</span><span class="n">y</span><span class="o">*</span><span class="n">cell_size</span> <span class="o">+</span> <span class="n">cell_size</span><span class="o">//</span><span class="mi">2</span>
        <span class="n">pygame</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">screen</span><span class="p">,</span> <span class="s">"red"</span><span class="p">,</span> <span class="p">[</span><span class="n">cx</span><span class="p">,</span> <span class="n">cy</span><span class="p">],</span> <span class="n">cell_size</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div></div>
<p>Dungeon does not understand <code class="language-plaintext highlighter-rouge">in</code>, and I don’t think we want it to. I do think it could understand <code class="language-plaintext highlighter-rouge">rooms</code>. So:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">draw_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">):</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">dungeon</span><span class="p">.</span><span class="n">rooms</span><span class="p">:</span>
            <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">).</span><span class="n">draw</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">screen</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Dungeon</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">layout</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">layout</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">rooms</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">layout</span><span class="p">.</span><span class="n">rooms</span>
    <span class="k">def</span> <span class="nf">populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot_room</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">dot_cell</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="n">dot_room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="n">dot_cell</span>
</code></pre></div></div>
<p>I didn’t write a test for that. Let’s extend our existing one:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</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_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</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">room_cell</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="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">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="n">dungeon</span><span class="p">.</span><span class="n">populate</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="n">room_cell</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">key_lock</span> <span class="ow">is</span> <span class="bp">None</span>
        <span class="n">rooms</span> <span class="o">=</span> <span class="n">dungeon</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">1</span>
        <span class="k">assert</span> <span class="n">rooms</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">cells</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="ow">is</span> <span class="n">room_cell</span>
</code></pre></div></div>
<p>A bit grubby but green. Commit: <em>Dungeon provides rooms.</em> (I have not been committing <code class="language-plaintext highlighter-rouge">main</code>, but am committing everything else.)</p>
<p>Let’s see what explodes now when we do run <code class="language-plaintext highlighter-rouge">main</code>. Good news, the map comes up. Then if I try to move Dot:</p>
<p>~~~line 26, in main_loop
    self.dungeon.handle_keydown(event)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: ‘Dungeon’ object has no attribute ‘handle_keydown’</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
## Reflect. Plan.
OG "H" mentioned making the key handlers part of the View and sending semantic messages to the dungeon to do what the keys call for. This is a good time to do that. It'll be a bit more work but not much.
First we'll test in the semantic messages. I start with this to get a known situation:
~~~python
    def test_adventurer_motion(self):
        Cell.create_space(20, 20)
        layout = DungeonLayout()
        c55 = Cell.at(5, 5)
        round = RoomMaker().round(radius=6, origin=c55)
        layout.add_room(round)
        dungeon = Dungeon(layout)
        dungeon.populate()
        dungeon.place_adventurer_at(c55)
        assert dungeon.adventurer_cell is c55
</code></pre></div></div>
<p>We’ll need a way to set the adventurer location anyway. I will get tired of typing “adventurer” pretty soon.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">place_adventurer_at</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cell</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="n">cell</span>
</code></pre></div></div>
<p>Green. Commit a save point.</p>
<p>Extend the test:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="n">dungeon</span><span class="p">.</span><span class="n">move_adventurer_north</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
</code></pre></div></div>
<p>I considered shorter names or passing a string but decided on separate methods at least for now.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">move_adventurer_north</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">adventurer_cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span><span class="p">.</span><span class="n">north</span><span class="p">()</span>
</code></pre></div></div>
<p>We already have the directional moves in Cell:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Cell</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">east</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">attempt_move</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="k">def</span> <span class="nf">north</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">attempt_move</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">south</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">attempt_move</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="k">def</span> <span class="nf">west</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">attempt_move</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">attempt_move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">):</span>
        <span class="n">cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">at_offset</span><span class="p">(</span><span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">)</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">cell</span> <span class="ow">or</span> <span class="n">cell</span><span class="p">.</span><span class="n">is_available</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span>
        <span class="k">return</span> <span class="n">cell</span>
</code></pre></div></div>
<p>Continue with the other three moves:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="n">dungeon</span><span class="p">.</span><span class="n">move_adventurer_north</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
        <span class="n">dungeon</span><span class="p">.</span><span class="n">move_adventurer_east</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="mi">4</span><span class="p">)</span>
        <span class="n">dungeon</span><span class="p">.</span><span class="n">move_adventurer_south</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">dungeon</span><span class="p">.</span><span class="n">move_adventurer_west</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="ow">is</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">)</span>
</code></pre></div></div>
<p>And the corresponding methods of course, just like <code class="language-plaintext highlighter-rouge">north</code>. I’ll spare you. Commit: <em>Dungeon supports semantic move.</em></p>
<h2 id="reflect-plan">Reflect. Plan.</h2>
<p>I think we “just” need to put key handlers somewhere sensible and make them send the new semantic messages. In DungeonView, we have:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonView</span><span class="p">:</span>
    <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">KEYDOWN</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">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
    <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">KEYUP</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">handle_keyup</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
</code></pre></div></div>
<p>It’s up to the view to decode what the keys mean, so let’s send those messages to <code class="language-plaintext highlighter-rouge">self</code> instead of the dungeon, and add a couple of new methods.</p>
<p>Should we write tests for that? Let’s do, because I think I claimed yesterday that I knew how to do it.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestHandlers</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_keydown</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, so it’s hooked up. I pretty much always try that to be sure my setup is correct.</p>
<p>Hm. I guess the view will handle key locking as well. Interesting.</p>
<p>We’ll create a little test dummy to help with this.</p>
<p>Grr. We’ll want a DungeonView that doesn’t start up pygame. So we’ll do a bit of a hack.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonView</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">dungeon</span><span class="p">,</span> <span class="n">testing</span><span class="o">=</span><span class="bp">False</span><span class="p">):</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">if</span> <span class="n">testing</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="p">...</span>
</code></pre></div></div>
<p>I needed two fake objects, though I could probably combine them into one:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">FakeDungeon</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">received</span> <span class="o">=</span> <span class="s">'nothing'</span>
    <span class="k">def</span> <span class="nf">move_adventurer_north</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">received</span> <span class="o">=</span> <span class="s">'north'</span>
    <span class="k">def</span> <span class="nf">move_adventurer_south</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">received</span> <span class="o">=</span> <span class="s">'south'</span>
    <span class="k">def</span> <span class="nf">move_adventurer_east</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">received</span> <span class="o">=</span> <span class="s">'east'</span>
    <span class="k">def</span> <span class="nf">move_adventurer_west</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">received</span> <span class="o">=</span> <span class="s">'west'</span>
<span class="k">class</span> <span class="nc">FakeEvent</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">key</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key</span> <span class="o">=</span> <span class="n">key</span>
<span class="k">class</span> <span class="nc">TestHandlers</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_keydown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">fake</span> <span class="o">=</span> <span class="n">FakeDungeon</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">fake</span><span class="p">,</span> <span class="n">testing</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_UP</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'north'</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_DOWN</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'south'</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_LEFT</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'west'</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_RIGHT</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'east'</span>
</code></pre></div></div>
<p>My test is failing on south. If I comment out everything but south, south works.</p>
<p>Ah. As intended: I”m handling key locking already!</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestHandlers</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_keydown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">fake</span> <span class="o">=</span> <span class="n">FakeDungeon</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">fake</span><span class="p">,</span> <span class="n">testing</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_UP</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'north'</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keyup</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_DOWN</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'south'</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keyup</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_LEFT</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'west'</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keyup</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="n">event</span> <span class="o">=</span> <span class="n">FakeEvent</span><span class="p">(</span><span class="n">pygame</span><span class="p">.</span><span class="n">K_RIGHT</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">fake</span><span class="p">.</span><span class="n">received</span> <span class="o">==</span> <span class="s">'east'</span>
        <span class="n">view</span><span class="p">.</span><span class="n">handle_keyup</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
</code></pre></div></div>
<p>This works, because I moved the full handlers from wherever they were into DungeonView:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">handle_keydown</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span><span class="p">:</span>
            <span class="k">return</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span>
        <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_RIGHT</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">move_adventurer_east</span><span class="p">()</span>
        <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_LEFT</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">move_adventurer_west</span><span class="p">()</span>
        <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_UP</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">move_adventurer_north</span><span class="p">()</span>
        <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_DOWN</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">move_adventurer_south</span><span class="p">()</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">def</span> <span class="nf">handle_keyup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span> <span class="o">==</span> <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">key_lock</span> <span class="o">=</span> <span class="bp">None</span>
</code></pre></div></div>
<p>I think main should work now. Yes, we can move Dot around. Commit including main: <em>DungeonView supports keydown events.</em></p>
<p>Now I’d like to move Dungeon to its own file, but I have concerns about the imports. We’ll try it since we are at a point where we can revert. Good news, everything works. Commit: <em>Dungeon moved to own file.</em></p>
<p>Let’s call it a morning, since the article is getting to be book length.</p>
<h2 id="summary">Summary</h2>
<p>The trick of renaming Dungeon to DungeonLayout was an important first move here, because most of the functionality wanted to move there. After that, everything proceeded in small steps.</p>
<p>It is quite unusual for me to build a test double, as we did here with the FakeEvent and FakeDungeon, but I just wanted to be sure that DungeonView sent the right messages, and setting up an entire Dungeon seemed excessive. The doubles tell me what I want to know in this case.</p>
<p>I am from the Detroit school of TDD, which generally does not use test doubles, as opposed to the London School, which uses them extensively. The London focus is mostly on whether objects are sending the right messages, while Detroit focuses more on whether, sent a message, an object does what we expect. I have not worked extensively in London style, and I have always wondered how you ever find out that the messages you correctly send are handled correctly. I’m sure it works: people I respect use the approach. I just haven’t worked that way enough to see where it bottoms out.</p>
<p>It seems likely that there are methods now that are unused. I tried to get rid of the ones I was aware of, but it would behoove us to do a scan of the affected classes to see what can be removed.</p>
<p>As for keeping the layout under the dungeon contents, it certainly works so far, for the one content item we have, good ol’ Dot. We’ll see how it goes in the future but at a guess it’ll in fact be better. With the contents under the layout, all the operational calls from view would have to bounce through Dungeon to DungeonContents, or we’d have to rip the contents out and handle them separately. This way, there’s only one bounce in Dungeon, the one that fetches <code class="language-plaintext highlighter-rouge">rooms</code>.</p>
<p>I think I’m going to like it. Bit of a hat tip to OG “H”. See you next time!</p>
<hr />
<p><br /></p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:o" role="doc-endnote">
      <p>“Old Geek”, of course. None older than YT<sup id="fnref:y" role="doc-noteref"><a href="#fn:y" class="footnote" rel="footnote">3</a></sup>, of course. <a href="#fnref:o" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:c" role="doc-endnote">
      <p>Days since last calendar error: <strong>[-1]</strong> <a href="#fnref:c" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:y" role="doc-endnote">
      <p>“Yours Truly”, of course. <a href="#fnref:y" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>
 ]]> 
                </content:encoded>
                <pubDate>Wed, 08 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/x/b/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/x/b/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Enter the Dungeon</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>Today, we’ll start populating our dungeon. We’ll start with an intrepid adventurer named Dot.</p>
</blockquote>
<dl>
  <dt>Our story begins …</dt>
  <dd>
    <p>A lone intrepid adventurer named Dot finds herself in a random dungeon. She can move in any of the cardinal directions, except of course that she cannot walk through walls.</p>
  </dd>
</dl>
<p>We’ll guide Dot by typing on the arrow keys, if things go as I intend. Key down and she moves if she can, then the key must go up before she can move again. At this moment, I don’t know what I think about more than one key down at a time, but I think we’d like to ignore all but the first.</p>
<p>Our own first step this morning will be to review and recall how keyboard events work in PyGame. We’ll be modifying our main loop:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonView</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">dungeon</span><span class="p">):</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="n">pygame</span><span class="p">.</span><span class="n">init</span><span class="p">()</span>
        <span class="n">pygame</span><span class="p">.</span><span class="n">display</span><span class="p">.</span><span class="n">set_caption</span><span class="p">(</span><span class="s">"Dungeon"</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">screen</span> <span class="o">=</span> <span class="n">pygame</span><span class="p">.</span><span class="n">display</span><span class="p">.</span><span class="n">set_mode</span><span class="p">(</span>
            <span class="p">(</span><span class="n">screen_width</span><span class="p">,</span> <span class="n">screen_height</span><span class="p">))</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">clock</span> <span class="o">=</span> <span class="n">pygame</span><span class="p">.</span><span class="n">time</span><span class="p">.</span><span class="n">Clock</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">main_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">running</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="n">moving</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="n">background</span> <span class="o">=</span> <span class="s">"gray33"</span>
        <span class="n">color</span> <span class="o">=</span> <span class="s">"darkblue"</span>
        <span class="k">while</span> <span class="n">running</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">pygame</span><span class="p">.</span><span class="n">event</span><span class="p">.</span><span class="n">get</span><span class="p">():</span>
                <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">QUIT</span><span class="p">:</span>
                    <span class="n">running</span> <span class="o">=</span> <span class="bp">False</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">screen</span><span class="p">.</span><span class="n">fill</span><span class="p">(</span><span class="n">background</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_grid</span><span class="p">(</span><span class="n">color</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_paths</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_rooms</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">clock</span><span class="p">.</span><span class="n">tick</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
            <span class="n">pygame</span><span class="p">.</span><span class="n">display</span><span class="p">.</span><span class="n">flip</span><span class="p">()</span>
        <span class="n">pygame</span><span class="p">.</span><span class="n">quit</span><span class="p">()</span>
</code></pre></div></div>
<p>From the look of that, we want to see what other <code class="language-plaintext highlighter-rouge">event.type</code> we might encounter. We find that there are events <code class="language-plaintext highlighter-rouge">KEYDOWN</code> and <code class="language-plaintext highlighter-rouge">KEYUP</code>. The events can return <code class="language-plaintext highlighter-rouge">key</code>, <code class="language-plaintext highlighter-rouge">mod</code>, <code class="language-plaintext highlighter-rouge">unicode</code> and <code class="language-plaintext highlighter-rouge">scancode</code>. There are key names: <code class="language-plaintext highlighter-rouge">K_UP</code>, <code class="language-plaintext highlighter-rouge">K_DOWN</code>, <code class="language-plaintext highlighter-rouge">K_RIGHT</code>, and <code class="language-plaintext highlighter-rouge">K_LEFT</code> among many others.</p>
<p>With a bit of searching on my own site, I find a little patch of code that tells us what we need to know:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">main_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">running</span> <span class="o">=</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">_testing</span>
        <span class="k">while</span> <span class="n">running</span><span class="p">:</span>
            <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">pygame</span><span class="p">.</span><span class="n">event</span><span class="p">.</span><span class="n">get</span><span class="p">():</span>
                <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">KEYDOWN</span><span class="p">:</span>
                    <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span> <span class="o">==</span> <span class="n">K_ESCAPE</span><span class="p">:</span>
                        <span class="n">running</span> <span class="o">=</span> <span class="bp">False</span>
                <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">QUIT</span><span class="p">:</span>
                    <span class="n">running</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="p">...</span>
</code></pre></div></div>
<p>Let’s do just a bit of design thinking here, to get a sense of what we will want in the future, and what we might want to do as our first few steps into the future.</p>
<p>I think that the Dungeon will be the main state-bearing object during game play, at least for now: it knows where all the rooms are. So it will also know where all the stuff is, including Intrepid Adventurer Dot. However, for some reason having to do with a tendency to over-design, or perhaps to follow some good advice I heard somewhere, I’ve already committed to there being “view” objects, specifically DungeonView and RoomView. We’ll have to work with those.</p>
<p>I can think of at least two ways to have it work: we could draw all the paths and rooms and then draw all the contents, including Dot, on top of that. Or we could draw each room or path and at that time, its contents.</p>
<p>I think for now, we’ll just have Dot know her coordinates in the dungeon, so we’ll take the approach of drawing contents last. And we’ll code something directly into the DungeonView, for now.</p>
<p>First step, let’s put Dot somewhere and make her display.</p>
<p>At the end of <code class="language-plaintext highlighter-rouge">main</code>, right before we enter the game loop, we’ll “populate” the dungeon. This will, I guess, be the process of placing all the goodies, including Dot. For now, only Dot.</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="p">...</span>
    <span class="n">path_room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">'path'</span><span class="p">)</span>
    <span class="n">dungeon</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="n">dungeon</span><span class="p">.</span><span class="n">populate</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="n">view</span><span class="p">.</span><span class="n">main_loop</span><span class="p">()</span>
</code></pre></div></div>
<p>Need to write that:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">populate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot_room</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="bp">self</span><span class="p">.</span><span class="n">rooms</span><span class="p">)</span>
        <span class="n">dot_cell</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="n">dot_room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="n">dot_cell</span>
</code></pre></div></div>
<p>Random cell in a random room. Now we need to display something. In DungeonView.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonView</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_paths</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_rooms</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_contents</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">clock</span><span class="p">.</span><span class="n">tick</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
            <span class="n">pygame</span><span class="p">.</span><span class="n">display</span><span class="p">.</span><span class="n">flip</span><span class="p">()</span>
        <span class="n">pygame</span><span class="p">.</span><span class="n">quit</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">draw_contents</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">.</span><span class="n">adventurer_cell</span>
        <span class="n">cx</span> <span class="o">=</span> <span class="n">adventurer_cell</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">cell_size</span> <span class="o">+</span> <span class="n">cell_size</span><span class="o">//</span><span class="mi">2</span>
        <span class="n">cy</span> <span class="o">=</span> <span class="n">adventurer_cell</span><span class="p">.</span><span class="n">y</span><span class="o">*</span><span class="n">cell_size</span> <span class="o">+</span> <span class="n">cell_size</span><span class="o">//</span><span class="mi">2</span>
        <span class="n">pygame</span><span class="p">.</span><span class="n">draw</span><span class="p">.</span><span class="n">circle</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">screen</span><span class="p">,</span> <span class="s">"red"</span><span class="p">,</span> <span class="p">[</span><span class="n">cx</span><span class="p">,</span> <span class="n">cy</span><span class="p">],</span> <span class="n">cell_size</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
</code></pre></div></div>
<p>This isn’t as arcane as it may look, since we use a similar pair of expressions to calculate cell positions. Here we offset by half the size to get the center and draw a red circle of radius half the cell size. And we get this in our current excessively simple dungeon:</p>
<p><img src="https://ronjeffries.com/articles/-v026/x/c/dot.png" alt="jagged path between tiny rooms, showing red dot along path" title="jagged path between tiny rooms, showing red dot along path" /></p>
<p>That’s pretty much exactly what we wanted. Let’s commit a save point.</p>
<p>We’ll divert to create a more interesting dungeon, since this setup was just there to show how our wiggly path logic looks. I’ll spare you the bandwidth of the picture, but that’s done, let’s do the moving code:</p>
<p>For now, let’s just send the key to the dungeon with a <code class="language-plaintext highlighter-rouge">handle_key_up</code> or <code class="language-plaintext highlighter-rouge">handle_key_down</code> method. We’ll sort things out once they’re wired up.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="n">running</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">event</span> <span class="ow">in</span> <span class="n">pygame</span><span class="p">.</span><span class="n">event</span><span class="p">.</span><span class="n">get</span><span class="p">():</span>
        <span class="k">if</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">QUIT</span><span class="p">:</span>
            <span class="n">running</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">KEYDOWN</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">handle_keydown</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
        <span class="k">elif</span> <span class="n">event</span><span class="p">.</span><span class="nb">type</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">KEYUP</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">handle_keyup</span><span class="p">(</span><span class="n">event</span><span class="p">)</span>
</code></pre></div></div>
<p>Then:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Dungeon</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">handle_keydown</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
        <span class="n">key</span> <span class="o">=</span> <span class="n">event</span><span class="p">.</span><span class="n">key</span>
        <span class="k">if</span> <span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_RIGHT</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span><span class="p">.</span><span class="n">east</span><span class="p">()</span>
        <span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_LEFT</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span><span class="p">.</span><span class="n">west</span><span class="p">()</span>
        <span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_UP</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span><span class="p">.</span><span class="n">north</span><span class="p">()</span>
        <span class="k">elif</span> <span class="n">key</span> <span class="o">==</span> <span class="n">pygame</span><span class="p">.</span><span class="n">K_DOWN</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">adventurer_cell</span><span class="p">.</span><span class="n">south</span><span class="p">()</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">east</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">attempt_move</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="k">def</span> <span class="nf">north</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">attempt_move</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">south</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">attempt_move</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="k">def</span> <span class="nf">west</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">attempt_move</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">attempt_move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">at_offset</span><span class="p">(</span><span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">)</span> <span class="ow">or</span> <span class="bp">self</span>
</code></pre></div></div>
<p>This nearly works. It allows Dot to move into available space. <code class="language-plaintext highlighter-rouge">attempt_move</code> needs to be a bit more stringent.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">attempt_move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">):</span>
        <span class="n">cell</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">at_offset</span><span class="p">(</span><span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">)</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">cell</span> <span class="ow">or</span> <span class="n">cell</span><span class="p">.</span><span class="n">is_available</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span>
        <span class="k">return</span> <span class="n">cell</span>
</code></pre></div></div>
<p>Works as intended. We don’t get another key down on a given key until we lift that key, though you can in fact press and hold another arrow key if your fingers will perform. So, no surprise, we’ll need a little interlock to ensure that we don’t allow another down until we see a suitable up. That should be easy enough.</p>
<p>Here’s a video.</p>
<video controls="" autoplay="" loop="" muted="" width="80%" height="auto" vertical-align="middle" src="https://ronjeffries.com/articles/-v026/x/c/dot.mov" type="video/mp4">
</video>
<p>We’ll call it a morning. Commit: Intrepid Adventurer Dot can traverse the dungeon.</p>
<h2 id="summary">Summary</h2>
<p>Clearly, this is little more than a spike, an experiment to see how things might work. We have learned, or re-learned, quite a bit:</p>
<ol>
  <li>We found <code class="language-plaintext highlighter-rouge">KEY-UP</code> and <code class="language-plaintext highlighter-rouge">KEY_DOWN</code> events and access to the keys we care about.</li>
  <li>We have a tentative decision to draw the dungeon contents after drawing everything else.</li>
  <li>We found a decent way to start Dot in a random location. In real dungeons, we will more likely have a defined start, but perhaps not always.</li>
  <li>We can move Intrepid Adventurer Dot and keep her from wandering outside the dungeon.</li>
  <li>And I learned that jagged rooms and paths are a pain to navigate compared to simple rectangular rooms and straight paths, because of the keyboard dancing that is required to switch between repeated single direction moves and zig-zag moves.</li>
</ol>
<p>Certainly we’ll want more objects and more conditions and more everything, including a lot more good design, since what we have is very direct, quite ad-hoc, and not readily able to be generalized. But all that will come, assuming that we press forward with this little game for a while.</p>
<p>A good morning’s work, putting I.A.Dot in the dungeon and on the screen.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Mon, 06 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/x/c/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/x/c/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Back to Code</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>This morning, I think we’ll look at some code. As a start, I have a question about Room. Brief LLM-AI digression.</p>
</blockquote>
<p>The Room class has a member variable <code class="language-plaintext highlighter-rouge">origin</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Room</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">cells</span><span class="p">,</span> <span class="n">origin</span><span class="p">,</span> <span class="n">name</span> <span class="o">=</span> <span class="s">''</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span><span class="nb">list</span><span class="p">[</span><span class="n">Cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">cells</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">origin</span> <span class="o">=</span> <span class="n">origin</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">color</span> <span class="o">=</span> <span class="s">"grey22"</span>
        <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span>
            <span class="n">count</span> <span class="o">=</span> <span class="n">count</span> <span class="o">+</span> <span class="mi">1</span>
            <span class="n">cell</span><span class="p">.</span><span class="n">room</span> <span class="o">=</span> <span class="bp">self</span>
</code></pre></div></div>
<p>Why does it have that instance variable? What use do we make of it? Can we get rid of it somehow?</p>
<p>The only use of <code class="language-plaintext highlighter-rouge">origin</code> is in test that essentially makes sure that we saved it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_build</span><span class="p">(</span><span class="bp">self</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">234</span><span class="p">)</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="mi">56</span><span class="p">)</span>
        <span class="n">size</span> <span class="o">=</span> <span class="mi">100</span>
        <span class="n">origin</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="mi">28</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">cave</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="k">assert</span> <span class="nb">len</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="n">cells</span><span class="p">))</span> <span class="o">==</span> <span class="mi">100</span>
        <span class="k">assert</span> <span class="n">room</span><span class="p">.</span><span class="n">origin</span> <span class="o">==</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="mi">28</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">verify_contiguous</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
</code></pre></div></div>
<p>Now we note that RoomMaker does need the origin, because it builds rooms starting at that provided cell, but since there is no use of it in Room, we don’t need it there. Remove the setting line in Room, and Change Signature.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Room</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">cells</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s">''</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span><span class="nb">list</span><span class="p">[</span><span class="n">Cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">cells</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">color</span> <span class="o">=</span> <span class="s">"grey22"</span>
        <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span>
            <span class="n">count</span> <span class="o">=</span> <span class="n">count</span> <span class="o">+</span> <span class="mi">1</span>
            <span class="n">cell</span><span class="p">.</span><span class="n">room</span> <span class="o">=</span> <span class="bp">self</span>
</code></pre></div></div>
<p>That one test fails, of course. Remove that check, put in a couple of others just for grins.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_build</span><span class="p">(</span><span class="bp">self</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">234</span><span class="p">)</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="mi">56</span><span class="p">)</span>
        <span class="n">size</span> <span class="o">=</span> <span class="mi">100</span>
        <span class="n">origin</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="mi">28</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">cave</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="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">room</span><span class="p">.</span><span class="n">cells</span><span class="p">)</span> <span class="o">==</span> <span class="mi">100</span>
        <span class="k">assert</span> <span class="n">origin</span><span class="p">.</span><span class="n">room</span> <span class="o">==</span> <span class="n">room</span>
        <span class="k">assert</span> <span class="n">origin</span> <span class="ow">in</span> <span class="n">room</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">verify_contiguous</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
</code></pre></div></div>
<p>Green. Commit: <em>remove unneeded origin from Room.</em></p>
<p>Room only has three other real methods, though it has some dunder methods to allow for iteration and a reasonable print. The real methods are:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">return_cells</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span>
            <span class="n">cell</span><span class="p">.</span><span class="n">room</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">def</span> <span class="nf">reclaim_cells_from</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span>
            <span class="n">other</span><span class="p">.</span><span class="n">forget</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="n">room</span> <span class="o">=</span> <span class="bp">self</span>
    <span class="k">def</span> <span class="nf">forget</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">cell</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">.</span><span class="n">remove</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
</code></pre></div></div>
<p>We use all of those methods when we create our round room with a diamond room inside:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">make_diamond_in_round_room</span><span class="p">(</span><span class="n">dungeon</span><span class="p">:</span> <span class="n">Dungeon</span><span class="p">,</span> <span class="n">maker</span><span class="p">:</span> <span class="n">RoomMaker</span><span class="p">):</span>
    <span class="n">origin</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">32</span><span class="p">,</span> <span class="mi">28</span><span class="p">)</span>
    <span class="n">diamond</span> <span class="o">=</span> <span class="n">maker</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="mi">13</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">diamond</span><span class="p">.</span><span class="n">return_cells</span><span class="p">()</span>
    <span class="nb">round</span> <span class="o">=</span> <span class="n">maker</span><span class="p">.</span><span class="nb">round</span><span class="p">(</span><span class="n">radius</span><span class="o">=</span><span class="mi">5</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">diamond</span><span class="p">.</span><span class="n">reclaim_cells_from</span><span class="p">(</span><span class="nb">round</span><span class="p">)</span>
    <span class="n">dungeon</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">diamond</span><span class="p">)</span>
    <span class="n">dungeon</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="nb">round</span><span class="p">)</span>
</code></pre></div></div>
<p>To make a nested room, we create the inner room, return its cells (retaining the cell collection in the inner room), then create the outer room, and then reclaim the inner room’s cells from it, requiring it to forget them.</p>
<p>The Dungeon class also uses <code class="language-plaintext highlighter-rouge">return_cells</code>, when we do <code class="language-plaintext highlighter-rouge">remove_room</code>. We really only do that in <code class="language-plaintext highlighter-rouge">main</code>, as part of trying to connect the rooms by adding lots of large wandering rooms.</p>
<p>In any case, we do use those methods and they seem pretty straightforward, so we’ll let them be.</p>
<h2 id="reflection">Reflection</h2>
<p>There is a lot of ad hoc code here, various experiments that we have done as we have built up our dungeon making ability. We have no real “production” system in place that organizes these various functions and methods into some higher-level more orderly process. We just have a lot of possibly useful, definitely interesting functions in main and methods in RoomMaker, that make various kinds of rooms.</p>
<p>And we’ll need at least one more such thing, because so far, we have no way to make a rectangular room, which, let’s face it, is kind of the canonical minimum standard dungeon room. We haven’t made them because we know how to make them and we have not known how to draw paths and connect rooms and such, so those capabilities have been higher priority. Sooner or later, someone will ask for a rectangular room and we’ll make one. It’ll just take a few minutes, I expect.</p>
<h2 id="what-next">What Next?</h2>
<p>We have the following files and classes in our ‘src’ tree:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>main.py
params.py
class Cell
    class CellSpace
    class PathHelper
class Dungeon
class DungeonView
class Room
class RoomMaker
class RoomView
class Suite
</code></pre></div></div>
<p>I show CellSpace and PathHelper subordinate to Cell, because they are only used in Cell and are presently co-resident in file ‘cell.py’.</p>
<p>The View classes do the drawing for us and may become more capable if we expand the current code to be more like a game. Suite is no more than a collection of one or more rooms that have a border in common, with the ability to find a pair of points, one in each or two suites, such that there is no pair of points closer together than the pair we find. We use those points to find a path between the suites:</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="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">cells</span> <span class="o">=</span> <span class="n">source</span><span class="p">.</span><span class="n">find_path</span><span class="p">(</span><span class="n">target</span><span class="p">,</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">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>That method returns the cells that are on the path but not in the starting rooms: the outside part of the path.</p>
<h2 id="reflection-1">Reflection</h2>
<p>There are issues here. We have multiple ways of finding paths now, with an adjustable randomness factor that is not available here. We will need to sort that capability into a more orderly form. And our current pathfinding code can, in principle, include many cells from the source or target room, because find_path isn’t guaranteed to start on an edge cell. It could build a path that goes right across both rooms. We should probably make all paths include only cells that are not in the source or target rooms, or any of the rooms that they are allowed to traverse.</p>
<p>There will be a similar notion to <code class="language-plaintext highlighter-rouge">find_path</code> deeper in the game, one that we might call <code class="language-plaintext highlighter-rouge">find_route</code>, which could be used by a monster to find its way to the player. That capability would use only existing room or path cells, where what we are doing with <code class="language-plaintext highlighter-rouge">find_path</code> is actually allocating new cells to make up a new path in the dungeon.</p>
<p>I’ve made a note of both of those concerns.</p>
<h2 id="evolving">Evolving</h2>
<p>Here’s a kind of meta-reflection: we are evolving the code here, but we are also evolving our understanding of what the code might be, can be, should be. This is pretty much the exact opposite of the theoretical approach to software design, which is to specify it first, then design it, then code it. I have never, in my six and a half decades of experience, encountered a program that could have been correctly specified at the beginning. I have never seen a design done before coding that held up once the coding began.</p>
<p>Some people would argue that that means we should try harder to do good specs, try harder to do perfect designs. I don’t think so. When we see the thing we’re making, we see what we like about it and what we don’t like. If we’re stuck with a spec, or stuck with a design, we’re stuck, period. So we know with certainty that the spec will change. It follows that the design will change. We cannot get them right at the beginning.</p>
<p>This is the essence of the approach formerly known as “Agile”, before it was diluted and subverted and turned Dark: We start from a minimal spec, a minimal design, and we evolve what we want, how it should be done, and doing it <em>together</em>.</p>
<p>This is not easy. It requires developers who can work across the board from spec to design to code, it requires management or customers who can guide an evolving project, and it requires technical capability that includes solid testing, flexible design, and very strong refactoring capability.</p>
<p>It is not easy. But it can possibly work: the spec then design then code scheme cannot possibly work.</p>
<dl>
  <dt>Digression: LLM-AI</dt>
  <dd>
    <p>I leave it to you to reflect on how LLM-AI style development is supposed to work and whether and where it might work and where it is likely to fail. My first approximation is that it will fail on anything that is interesting and has to work.</p>
  </dd>
</dl>
<h2 id="but-what-next">But What Next?</h2>
<p>Right, sorry. It seems to me that what we have is actually working rather well. I am tempted to add in a simple Player object, represented by a mark on the screen, and let it move around based on keyboard action, navigating the dungeon. That might lead us to the notion of doors and such, or even treasures and monsters. What we have would begin to look like a game.</p>
<p>I think we’ll do that.</p>
<p>As for the code, as things stand, Cell plus CellSpace plus PathHelper amount to almost 250 lines of code, while Suite is 32, Room is 32, Dungeon 67, and even main is only 97. Cell alone is about 165 lines. It seems to me that this is out of balance, a sign that <em>Something Should Be Done</em>.</p>
<p>I do think that the Cell and its associates are pretty solid: it’s just that the size being so different makes me suspect there are classes waiting to be found. We’ll keep this concern in mind and try to notice possibilities or difficulties as they come up. For now, it’s working as intended, so we’ll move on.</p>
<p>So next time, we’ll put a little dude in the dungeon and see what that takes. I suspect doing that will impact not just the View classes but also Dungeon, which currently is not much more than a collection of Rooms. Yes, it knows suites and paths, but I’m not even very sure about those notions and how they fit in. We’ll change them and move them as needed. We are in the business of changing code.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sun, 05 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/x/d/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/x/d/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
                <category>LLM-AI</category>
              </item>
              <item>
                <title>Butlerian Jihad</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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>Ten thousand years before the events of Dune, mankind fought the Butlerian Jihad against the tyranny of machine intelligence. We need a Humans Union.</p>
</blockquote>
<p>I was around in 1966 when Weizenbaum created his DOCTOR script and ELIZA, the chatbot that behaved like a non-directive psychotherapist, with leading questions like “Tell me more about that” and “How did that make you feel”. We had a copy of ELIZA where I worked and it was very weak. If you typed “he gave me cake”, it might say “How did he gave you cake make you feel?”. Its ability to parse and modify what you said to it was very limited. Even so, the thing was fascinating.</p>
<p>I suspect that most people saw ELIZA’s defects quickly, and found its limited reactions to be repetitive and not very revealing. Some people, however, began to describe their emotions quite deeply, as this simple program seemed to be interested. There are stories of people who wouldn’t let others read what they had typed to ELIZA: presumably things they rarely if ever shared with anyone else.</p>
<p class="pull-quote">I have tried LLMs.</p>
<p>Early on in the time of today’s LLM-based chatbots, I tried to get one to help me write some program or other. It would make mistakes, some of them quite stupid, such as suggesting that I use library functions that didn’t exist. When I would tell it “math.hamburger_helper” does not exist, the chat demon was all cooperative:</p>
<p>“You’re absolutely right! Here’s a better solution.”</p>
<p>It never told me that I had my head up, as my friends will often do. It never pushed back, it never refused to try again, and it expressed all its ideas politely and helpfully. It seemed to be making every effort to be my friendly helpful partner, and seemed to be trying to be completely aligned with my leadership.</p>
<p>The word “sycophantic” means to behave in an obsequious way in order to gain advantage. A more modern term with a similar meaning is “sucking up”.</p>
<p>The so-called “AIs” are sucking up. Not because the “AI” wants an advantage: it has no feelings, no desires, no wishes. But its creators want you to use the “AI”: it is the business they are in. It is to their advantage if you use their program more and more. Billions of dollars are at stake and the LLM perpetrators want those dollars.</p>
<p class="pull-quote">Their “AI” must offer value.</p>
<p>To get those dollars, their “AI” has to offer value to you, and they do offer value, in two forms. The answers that the program provides have to be pretty good, and they are in fact pretty good. If you work with the thing, prompting a few times, providing enough context, you’ll probably get pretty good answers.</p>
<p>No one falls in love with “pretty good answers”. Most of us will use stack overflow or reddit, but we don’t believe everything we read there. We have come to expect that most of the advice we’ll get from those places will be pretty good, nearly correct, and probably not quite good enough. We use them, but we aren’t hooked on them.</p>
<p class="pull-quote">The “AI” needs to hook us.</p>
<p>The “AI” is programmed with an aspect that is demonic in its simplicity and power: it forms its answers to <em>appear</em> to be friendly, to <em>appear</em> to be trying to help, to <em>appear</em> to respect our opinion, to <em>appear</em> just short of obviously sucking up. I emphasize “appear” because it’s a program it absolutely cannot be friendly, it cannot be tying to help, it cannot respect us. But it can be programmed to seem that way, and it can be and has been programmed to dial its apparent obsequiousness up and down based on how it is prompted. It allows us to tune its responses to be the kind of responses we like.</p>
<p>The “AI” is programmed to influence us to like it, to enjoy interacting with it. It is programmed to be addictive. It is programmed to hook us.</p>
<p class="pull-quote">Cui bono: Who benefits?</p>
<p>The creators of the demons are working toward immense benefits for their companies and themselves. The companies that “move to AI” foresee great benefit as well, in that they’ll be able to get rid of people and replace them with much less expensive “AI”. And yes, as the user of the “AI”, you do get some benefit. As we’ll discuss below, that benefit is likely very short term.</p>
<p class="pull-quote">“AI” Creators</p>
<p>There are hundreds of billions of dollars waiting for the “AI” creators: revenue from “AI” usage; huge salaries for executives; massive stock market profits, direct and indirect; and more. They are talking about trillion-dollar valuation, and they could be right. Hundreds of billions is clearly in hand.</p>
<p class="pull-quote">User Companies</p>
<p>The company that “moves to AI”, your company? How do they expect to benefit? The easiest and most direct way is that they hope that using “AI” will allow them to get rid of human employees. We already see chatbots replacing on-line help people. We see chatbots making phone solicitations that would formerly have employed people. And, in case you happen to be a technologist, you’ll have noticed that they’re laying off programmers with the pretext that their “AI” can do much of the programming. If your company is trying “AI” and asking you to try it in your job, it should be pretty clear that if it can do half of your job, half of you and your colleagues will be on the street any day now.</p>
<p class="pull-quote">People like you and me</p>
<p>For the “AI” industry to succeed, for the pyramid we’re drawing here to work, the “AI” has to be useful to you, the worker who is asked to use it. In essence, when we use these things, we are training our replacement, and our replacement isn’t even another human being who at least needs a job, it is a computer program whose sole purpose is to put money in the hands of people who are not you and me.</p>
<p>Yes, we’ll see some benefit from using it, but that benefit will be short term, because the long term plan, as it applies to most “AI” human users, is to replace them. To replace you. You.</p>
<p class="pull-quote">Not to mention</p>
<p>There are of course also immense effects of the “AI” on our planet. Data centers consume mass quantities of water and power. Data centers run so hot that their heat can be detected miles away. I get it: it’s hard to think about something that indirect and still a ways out in the future: everyone alive today will be gone before the planet is no longer inhabitable by humans. This impact is probably more important than the individual losing their job, but I’m writing to individuals and whatever floats your boat. Including rising sea levels and the prospect of a nice coastal view from Nevada.</p>
<h2 id="what-can-we-do">What Can We Do?</h2>
<p>Probably nothing we can do will work, but possibly <em>everything</em> we do can help. I don’t want to go over the top here, but I think we are in a battle between the vast majority of people, and a very few, very powerful people, the ultra-rich and the politicians who are at their beck and call, from all parties and all sides.</p>
<p>My friend Hill points out that billionaires, “AI”, politics, wars in Iran, all of this mess is the same thing. In my words, it is all a very complex system of power accruing to a few people, at the expense of almost everyone else. But a complex system isn’t a ball bearing whose surface is all the same, every point of which is the same as every other. It’s more like a ball of many different aspects, held together by the glue of money and power. There are always vulnerable bits sticking out, and every act that hammers away at those, that pulls one of those out of the ball, helps to destabilize the system.</p>
<p>It is possible to derange a complex system. Often what seems like a small impact can destabilize it and bring about substantial change. (More often, the system will jiggle and recover: but the right tweaks will bring about change.)</p>
<p>For the good of the bulk of humanity, we need to re-balance the power and money disparities that drive the system today. Perhaps at a later time, I’ll address things we might do that are not focused on the “AI”, but today is about the “AI”.</p>
<p>Today, we need to begin the Butlerian Jihad against machine intelligence. I have a few ideas, and I invite my readers to suggest more, and to carry the ideas to others. Here are just a few starting thoughts:</p>
<ol>
  <li>Do not ever support “AI” and what it does, in your speech or writing. Be against it, every time.</li>
  <li>Do not use “AI” if you can possibly avoid it. Even those handy uses are harming the world, and are aimed at harming you.</li>
  <li>If you’re forced to use “AI” in your work, and you can’t find other work, find every flaw in it that you can.</li>
  <li>If you can sabotage an “AI” effort with safety to yourself, do so.</li>
</ol>
<p>Finally: organize. I never thought I would recommend that programmers form a union, but I’ve changed my view on that. I think we need a programmer’s union.</p>
<p>No, what we need is a Humans Union. No billionaires allowed. This is a war between good and evil and we need to win it.</p>
 ]]> 
                </content:encoded>
                <pubDate>Sat, 04 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/x/e/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/x/e/</guid>
                <category>LLM-AI</category>
                <category>Social</category>
              </item>
              <item>
                <title>Cleaning .., and an 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"}
-->
<!-- CHANGE ME  -->
<blockquote>
  <p>I did a little off-article cleaning of Cell. We’ll look at that. And I have an idea which may be good, or then again, not. Weirdly, things go better than expected.</p>
</blockquote>
<h2 id="the-issue">The Issue</h2>
<p>The Cell object is a major player in our current design. Rooms and paths are collections of Cells. Cells know what Room they are in. They can produce their neighboring Cells, including some conditions for what kind of neighbor is acceptable. You can get to a cell by arbitrary x-y offset from any other.</p>
<p>The Cells accomplish this by knowing their own x-y coordinates in a CellSpace instance, which is not much more than a big array of NxM Cells. And each Cell knows the CellSpace, so it can access any other Cell via x and y.</p>
<p>There are at least two issues with this design that might be concerning.</p>
<p>One concern is that it is uncommon for an element of a collection to know what collection it is in. Usually a given element can and will be in many collections, so knowing who has it is troublesome.</p>
<p>My view here is that our cells are explicitly and intrinsically arranged in a 2D array, in the same way that vast tracts of land are inherently adjacent to one another. As such, the substrate that holds them together is a part of the meaning of Cell. So I deem this concern to be null and void, and <code class="language-plaintext highlighter-rouge">None</code> for that matter.</p>
<p>A second concern is smaller but irritating. Once our code has a Cell, it can pretty much do anything we need, since we can ask the Cell for neighbors, or to generate a map, or to find a path, and it will happen.</p>
<p>But to get the first Cell, say the one at (4,5), we have to do something like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">space</span> <span class="o">=</span> <span class="n">CellSpace</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
<span class="n">cell</span> <span class="o">=</span> <span class="n">space</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">)</span>
</code></pre></div></div>
<p>Other than in tests of CellSpace itself, if there are any, we never refer to that <code class="language-plaintext highlighter-rouge">space</code> again. And I find that irritating, a forced reference to a class that we would otherwise not have to know about.</p>
<h2 id="the-idea">The Idea</h2>
<p>So I was thinking myself to sleep last night, and I thought: what if we only had to know about Cell? What if Cell’s use of CellSpace, or whatever laces Cells together, were entirely internal to Cell?</p>
<p>We would still need some capabilities relating to the whole collection of Cells. We’d perhaps need to be able to clear all the cells, setting them back to a big empty array of Cells. Or maybe we’d want to just allocate a new big array of Cells.</p>
<p>Or—and I think this is a reach—maybe there could be any number of Cells, any number of rows and columns. Maybe Cells would just exist as needed, and Cells never referenced wouldn’t exist at all.</p>
<p>We have no need for the last bit, as far as I can see. A sufficiently large 2D array can contain all the cells, and a cell already knows whether it is allocated to a Room or available. So we’ll belay that last idea.</p>
<h2 id="refining-the-idea">Refining the Idea</h2>
<p>OK, what if there are class methods on Cell, including at least
<code class="language-plaintext highlighter-rouge">Cell.create_space(max_x,max_y)</code>? Then, instead of creating and tossing a CellSpace, we could perhaps say:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
<span class="n">cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">)</span>
</code></pre></div></div>
<p>There would be a bit of magic in there. <code class="language-plaintext highlighter-rouge">Cell(4,5)</code> looks like an instance creation but in fact we want always to get the same Cell instance when we ask for Cell(4,5). I think we could make this work: Python lets us implement <code class="language-plaintext highlighter-rouge">__new__</code>, and that could do the job, returning the “singleton” Cell(4,5) rather than a new one.</p>
<h2 id="assessing-the-idea">Assessing the Idea</h2>
<p>Well. Is the total value of this idea replacing creation of a CellSpace with an equivalent line of code? Then it’s probably not really worth doing.</p>
<p>However, I think it will be interesting, that we’ll learn a little something, and it might be fun. So we’re going to do it anyway.</p>
<p>Let’s review how cells get created now and see whether we can find a smooth step by step way to do this. I’m sure we could do it all in one go, but it would be nice if the current sequence of creating a space and then asking it for a cell would do the right thing, so that we can remove those at our leisure.</p>
<p>I think we can leave CellSpace alone, but we need to be aware that there are two active methods in it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CellSpace</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">max_x</span><span class="p">,</span> <span class="n">max_y</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">Cell</span><span class="p">]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_array</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">list</span><span class="p">[</span><span class="n">Cell</span><span class="p">]]</span> <span class="o">=</span> <span class="nb">list</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">max_x</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">height</span> <span class="o">=</span> <span class="n">max_y</span>
        <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_x</span><span class="p">):</span>
            <span class="n">col</span> <span class="o">=</span> <span class="p">[]</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">cell_array</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">col</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_y</span><span class="p">):</span>
                <span class="n">cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</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">cells</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
                <span class="n">col</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">at</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Cell</span><span class="o">|</span><span class="bp">None</span><span class="p">:</span>
        <span class="k">if</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="p">.</span><span class="n">width</span> <span class="ow">and</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="p">.</span><span class="n">height</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_array</span><span class="p">[</span><span class="n">x</span><span class="p">][</span><span class="n">y</span><span class="p">]</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
    <span class="k">def</span> <span class="nf">random_available_cell</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">available</span> <span class="o">=</span> <span class="p">[</span><span class="n">cell</span> <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cells</span> <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">is_available</span><span class="p">]</span>
        <span class="k">if</span> <span class="n">available</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">available</span><span class="p">)</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>We note that creating the CellSpace creates all the Cell instances right then.</p>
<p>Every Cell already knows the CellSpace:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Cell</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">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">space</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_x</span> <span class="o">=</span> <span class="n">x</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_y</span> <span class="o">=</span> <span class="n">y</span>
        <span class="k">assert</span> <span class="n">space</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_space</span> <span class="o">=</span> <span class="n">space</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_room</span> <span class="o">=</span> <span class="bp">None</span>
</code></pre></div></div>
<p>I’ve been thinking that we’d allow Cell(4,5) to get the proper existing Cell instance, spoofing the <code class="language-plaintext highlighter-rouge">__new__</code>. Let’s not do that. Instead, let’s give Cell two new class methods, <code class="language-plaintext highlighter-rouge">at</code> and <code class="language-plaintext highlighter-rouge">random_available_cell</code>.</p>
<p>I wrote this little test, line by line:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_class_methods</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</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">cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">)</span>
        <span class="n">random_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">random_available_cell</span><span class="p">()</span>
        <span class="k">assert</span> <span class="n">cell</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
        <span class="k">assert</span> <span class="n">random_cell</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
</code></pre></div></div>
<p>And the code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">class</span> <span class="nc">Cell</span><span class="p">:</span>
    <span class="n">deltas</span> <span class="o">=</span> <span class="p">[(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</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="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">),</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">space</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">create_space</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">max_x</span><span class="p">,</span> <span class="n">max_y</span><span class="p">):</span>
        <span class="kn">from</span> <span class="nn">cell_space</span> <span class="kn">import</span> <span class="n">CellSpace</span>
        <span class="n">cls</span><span class="p">.</span><span class="n">space</span> <span class="o">=</span> <span class="n">CellSpace</span><span class="p">(</span><span class="n">max_x</span><span class="p">,</span> <span class="n">max_y</span><span class="p">)</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">at</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">cls</span><span class="p">.</span><span class="n">space</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="k">def</span> <span class="nf">random_available_cell</span><span class="p">(</span><span class="n">cls</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">cls</span><span class="p">.</span><span class="n">space</span><span class="p">.</span><span class="n">random_available_cell</span><span class="p">()</span>
</code></pre></div></div>
<p>Commit: <em>adding space-oriented class methods.</em></p>
<p>Now I can replace a lot of CellSpace references. Tests look nicer, for example:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_neighbors</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">create_space</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">)</span>
        <span class="n">neighbors</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">neighbors</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">neighbors</span><span class="p">)</span> <span class="o">==</span> <span class="mi">4</span>
        <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="p">[</span><span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="n">Cell</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">)]:</span>
            <span class="k">assert</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">neighbors</span>
</code></pre></div></div>
<p>I am tempted to create a default space if there is none, but we’ll hold off on that.</p>
<p>In looking for things to change, I found this usage, which I had forgotten about:</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">make_a_cave_room</span><span class="p">(</span><span class="n">dungeon</span><span class="p">:</span> <span class="n">Dungeon</span><span class="p">,</span> <span class="n">maker</span><span class="p">:</span> <span class="n">RoomMaker</span><span class="p">,</span> <span class="n">space</span><span class="p">:</span> <span class="n">CellSpace</span><span class="p">):</span>
    <span class="n">origin</span> <span class="o">=</span> <span class="n">space</span><span class="p">.</span><span class="n">random_available_cell</span><span class="p">()</span>
    <span class="n">size</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mi">70</span><span class="p">)</span>
    <span class="n">room</span> <span class="o">=</span> <span class="n">maker</span><span class="p">.</span><span class="n">cave</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">dungeon</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>These functions can do without the space parameter now. Currently none of them are called: we use them when we fiddle with <code class="language-plaintext highlighter-rouge">main</code> to test different things.</p>
<p>Main is quickly fixed up. We’ll call a break at this point.</p>
<h2 id="summary">Summary</h2>
<p>This went more easily than I anticipated, because of the decision not to futz with the constructor but instead implement <code class="language-plaintext highlighter-rouge">at</code> as a class method on Cell. The result was that instead of needing to refactor things, we were able to keep all the existing code working, plus the new code. Right now, some tests are converted and quite a few are not. The conversion isn’t quite automatic but it’s very simple and I seem to be able to multi-cursor most of it within a given file.</p>
<p>If we had done the new-futzing, I think this change would have been quite questionable but as it stands, it was quite straightforward. There is now no need for any class using Cell to know about CellSpace, and we could replace its mechanism with another quite readily.</p>
<p>This turned out better than I expected. A rare and pleasant event!</p>
<p>I’ll commit the works. Maybe next time, move CellSpace into the cell file. Maybe not.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Fri, 03 Apr 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/x/f/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/x/f/</guid>
                <category>python</category>
                <category>pygame</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
      
  </channel>
</rss>
