<?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, 19 Jul 2026 10:28:58 -0400</pubDate>
    <lastBuildDate>Sun, 19 Jul 2026 10:28:58 -0400</lastBuildDate>
    <generator>Jekyll v4.2.0</generator>
    
    
              <item>
                <title>Tiny Object FTW</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>I was watching Antiques Roadshow, as one does, when it came to me that a tiny object might be just the thing for DungeonView. Let’s find out.</p>
</blockquote>
<p>DungeonView needs a lot of work, but the code of concern this morning is this:</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="n">arcade</span><span class="p">.</span><span class="n">View</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="p">...</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Sprite</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Content</span><span class="p">,</span> <span class="n">ContentView</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="n">ContentView</span><span class="p">]]</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="n">Cell</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_sprite_list</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="p">...</span>
</code></pre></div></div>
<p>DungeonView has way too many collections, but this morning we’ll look at these two:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Sprite</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
</code></pre></div></div>
<p>Those are used, variously:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">setup</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">setup_scroller</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">setup_cameras</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">create_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">create_content_lists</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">create_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape_list</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">view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="n">view</span><span class="p">.</span><span class="n">generate_sprites</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">layout</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">on_draw</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">clear</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">scroll_dungeon_camera</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</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">draw_contents</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_passages</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_flood</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">scroller_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">scroller</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">illuminate_cell</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">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
        <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</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="k">for</span> <span class="n">content_view</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">[</span><span class="n">cell</span><span class="p">]:</span>
            <span class="n">content_view</span><span class="p">.</span><span class="n">just_illuminate</span><span class="p">()</span>
</code></pre></div></div>
<p>What’s going on is simpler than this may appear. For drawing efficiency, we keep all the floor cell tile sprites in a SpriteList, which can be drawn all in one go. But when we illuminate a room, we need to set <code class="language-plaintext highlighter-rouge">visible</code> on the individual sprites for illuminated cells, so we need access to the sprite for each particular cell.</p>
<p>So, currently, we have the full list <code class="language-plaintext highlighter-rouge">dungeon_floor_sprites</code> and a dictionary, <code class="language-plaintext highlighter-rouge">cell_sprites</code>, for those two purposes.</p>
<p>The idea this morning is to cover both uses with a single object. Let’s TDD it, just for fun.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestKeyedSpriteList</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_creation</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. Perfect. I think we’ll write very few tests here, but let’s start out in the classic form, just creating the object.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TestKeyedSpriteList</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">test_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">keyed</span> <span class="o">=</span> <span class="n">KeyedSpriteList</span><span class="p">(</span><span class="n">SpriteList</span><span class="p">())</span>
</code></pre></div></div>
<p>Fails because there is no such thing. Make such thing:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">KeyedSpriteList</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">sprite_list</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite_list</span> <span class="o">=</span> <span class="n">sprite_list</span>
</code></pre></div></div>
<p>I didn’t declare the sprite_list type, because for testing purposes, I want to use an ordinary list. That’s also part of why we provide the sprite list to our new object. Another reason is that we might conceivably want to create the actual SpriteList externally, although I think that will be unlikely.</p>
<p>New 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_and_access</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">contents</span> <span class="o">=</span> <span class="nb">list</span><span class="p">()</span>
        <span class="n">keyed</span> <span class="o">=</span> <span class="n">KeyedSpriteList</span><span class="p">(</span><span class="n">contents</span><span class="p">)</span>
        <span class="n">keyed</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="s">'cell_a'</span><span class="p">,</span> <span class="s">'sprite_A'</span><span class="p">)</span>
        <span class="n">keyed</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="s">'cell_b'</span><span class="p">,</span> <span class="s">'sprite_B'</span><span class="p">)</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">contents</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
        <span class="k">assert</span> <span class="s">'sprite_A'</span> <span class="ow">in</span> <span class="n">contents</span>
        <span class="k">assert</span> <span class="s">'sprite_B'</span> <span class="ow">in</span> <span class="n">contents</span>
        <span class="k">assert</span> <span class="n">keyed</span><span class="p">[</span><span class="s">'cell_a'</span><span class="p">]</span> <span class="o">==</span> <span class="s">'sprite_A'</span>
        <span class="k">assert</span> <span class="n">keyed</span><span class="p">[</span><span class="s">'cell_b'</span><span class="p">]</span> <span class="o">==</span> <span class="s">'sprite_B'</span>
</code></pre></div></div>
<p>We expect to add a cell and a sprite, but the object doesn’t care what we add. We expect all the sprites to show up in our provided content list and we expect to be able to access them by name using a subscripting notation.</p>
<p>Test fails, of course. We code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">KeyedSpriteList</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">sprite_list</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite_dict</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite_list</span> <span class="o">=</span> <span class="n">sprite_list</span>
    <span class="k">def</span> <span class="nf">add</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">sprite</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite_dict</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
</code></pre></div></div>
<p>Test still fails but I expect it to fail on the second last assert.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span>       <span class="k">assert</span> <span class="n">keyed</span><span class="p">[</span><span class="s">'cell_a'</span><span class="p">]</span> <span class="o">==</span> <span class="s">'sprite_A'</span>
               <span class="o">^^^^^^^^^^^^^^^</span>
<span class="n">E</span>       <span class="nb">TypeError</span><span class="p">:</span> <span class="s">'KeyedSpriteList'</span> <span class="nb">object</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">subscriptable</span>
</code></pre></div></div>
<p>That’s just right. Make it work:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">KeyedSpritList</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__getitem__</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="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">sprite_dict</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
</code></pre></div></div>
<p>And we are green.</p>
<p>And we are at a nice point to commit this code. Commit: TDDing KeyedSpriteList.</p>
<p>Now let’s move the KeyedSpriteList to the ‘src’ tree: I built it inside the test file, as I often do with h/t to Keith Braithwaite. Done. Commit: <em>move KSL to prod.</em></p>
<h2 id="brief-reflection">Brief Reflection</h2>
<p>We could just bang this thing in: there are only a few places needing to be changed, including, I think, one test that may break along the way.</p>
<p>But let’s pretend that we have lots of places needing to be changed, and do it incrementally, one place at a time, never breaking anything (well, trying never to break anything), rinse repeat.</p>
<p>First, we create the new object in DungeonView’s init.</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="p">...</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">keyed_floor_sprites</span> <span class="o">=</span> <span class="n">KeyedSpriteList</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Sprite</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="p">...</span>
</code></pre></div></div>
<p>No harm done. Commit: <em>converting to use KSL over weeks and weeks.</em></p>
<p>Now we have to get it loaded.</p>
<p>I tried this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">create_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape_list</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">view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="n">view</span><span class="p">.</span><span class="n">generate_sprites</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">layout</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">keyed_floor_sprites</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">sprite</span><span class="p">)</span>
</code></pre></div></div>
<p>But tests fail because we are adding to the same SpriteList twice. We need to give our KSL its own list.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="bp">self</span><span class="p">.</span><span class="n">keyed_floor_sprites</span> <span class="o">=</span> <span class="n">KeyedSpriteList</span><span class="p">(</span><span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">())</span>
</code></pre></div></div>
<p>Tests go green. Code above is OK. Commit: <em>converting to use KSL over weeks and weeks.</em></p>
<p>Weeks later, find a place that can use the new object: draw.</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">on_draw</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">clear</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">scroll_dungeon_camera</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_floor_sprites</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">draw_contents</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_passages</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_flood</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">scroller_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">scroller</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
</code></pre></div></div>
<p>We could fetch the list and draw it but why not let the KSL do the job. Add the method and use it.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">KeyedSpriteList</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">draw</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">sprite_list</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">DungeonView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">on_draw</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">clear</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">scroll_dungeon_camera</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">keyed_floor_sprites</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">draw_contents</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_passages</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_flood</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">scroller_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">scroller</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
</code></pre></div></div>
<p>We do test that one on the screen, and are not surprised when it works. Commit. Is the floor list used anywhere else?
It appears not. Remove all the references.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">create_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">view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="n">view</span><span class="p">.</span><span class="n">generate_sprites</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">layout</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">keyed_floor_sprites</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">sprite</span><span class="p">)</span>
</code></pre></div></div>
<p>We discovered along the way that <code class="language-plaintext highlighter-rouge">create_rooms</code> wasn’t even using the provided list, so we removed the parameter. Commit.</p>
<p>Users of <code class="language-plaintext highlighter-rouge">cell_sprites</code> are of interest now. 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">illuminate_cell</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">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
        <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</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="k">for</span> <span class="n">content_view</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">[</span><span class="n">cell</span><span class="p">]:</span>
            <span class="n">content_view</span><span class="p">.</span><span class="n">just_illuminate</span><span class="p">()</span>
</code></pre></div></div>
<p>We can use KSL here.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">illuminate_cell</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">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">keyed_floor_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
        <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</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="k">for</span> <span class="n">content_view</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">[</span><span class="n">cell</span><span class="p">]:</span>
            <span class="n">content_view</span><span class="p">.</span><span class="n">just_illuminate</span><span class="p">()</span>
</code></pre></div></div>
<p>Commit. That’s the only local reference that was left. A test will fail when we remove the instance variable. It’s referring to <code class="language-plaintext highlighter-rouge">cell_sprites</code> and we make it refer to <code class="language-plaintext highlighter-rouge">keyed_floor_sprites</code> and we’re good.</p>
<p>Six commits, basically one possible change at a time, no breakage. And we have reduced two instance variables down to one, and removed a few lines of code. Modified a few more.</p>
<h2 id="reflection">Reflection</h2>
<p>Was it worth all that to remove what amounts to a single instance variable? I don’t know. It has taken an hour to do the work and write the article. We have taken two objects which could in principle get out of sync and encapsulated them so that they will always stay in sync. We have taken two lines of code that always had to be done together and put them together in one place that everyone else just uses without knowing what’s behind the curtain.</p>
<p>A better question to ask might be “Do you kind of wish you had thought of this object at the moment you created that second structure, whichever one it was?” And that one is easy: I do wish I had, and I’m a bit more dedicated to noticing places where a tiny object can help me.</p>
<p>Part of my purpose in programming, whether for show, like here, or for real, as I used to do in the olden days, is to keep my design as clear and proper as I can. I do not have the ability to figure that out in advance, and if anyone has, I have not met them. I do have the ability to improve things as I become aware of them, and every decent programmer has that ability as well.</p>
<h2 id="summary">Summary</h2>
<p>A tiny object makes the code a bit better. It was easy to write, easy to install, and is cute as a kitten. Well, almost.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sun, 19 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/s/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/s/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>In For a Penny</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>I’ve decided to commit this and refactor. Can always reset if I have to. General rule: try always to move forward.</p>
</blockquote>
<p>We’ll begin with some tests for what we have. What do we have? I don’t know, that was hours ago. Here’s just the DungeonView init, evidence that all is not well:</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="n">arcade</span><span class="p">.</span><span class="n">View</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="k">if</span> <span class="ow">not</span> <span class="n">testing</span><span class="p">:</span>
            <span class="nb">super</span><span class="p">().</span><span class="n">__init__</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="bp">self</span><span class="p">.</span><span class="n">pub_sub</span> <span class="o">=</span> <span class="n">dungeon</span><span class="p">.</span><span class="n">pub_sub</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">subscribe</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">pub_sub</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">setup_assets</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="bp">self</span><span class="p">.</span><span class="n">room_sprite_list</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Content</span><span class="p">,</span> <span class="n">ContentView</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_room</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Room</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="n">ContentView</span><span class="p">]]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="n">ContentView</span><span class="p">]]</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room_views</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Room</span><span class="p">,</span> <span class="n">RoomView</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Sprite</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</span><span class="p">:</span> <span class="nb">set</span><span class="p">[</span><span class="n">Cell</span><span class="p">]</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_sprite_list</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_camera</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_camera_bounds</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">scroller</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">scroller_camera</span> <span class="o">=</span> <span class="bp">None</span>
</code></pre></div></div>
<p>We can be pretty sure that 17 lines of instance variables is about 13 too many. So we’ll keep that in mind. Are there any of those that are no longer referenced? <code class="language-plaintext highlighter-rouge">content_views_by_room</code> seems not to be. Removed, commit.</p>
<p>What about the <code class="language-plaintext highlighter-rouge">room_views</code>? They’re defined but not used. Remove, commit.</p>
<p>These two structures seem to have some redundancy:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <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_sprite_list</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Content</span><span class="p">,</span> <span class="n">ContentView</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">setup</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">setup_scroller</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">setup_cameras</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room_sprite_list</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">create_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">room_sprite_list</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">create_content_lists</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">create_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape_list</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">view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="n">view</span><span class="p">.</span><span class="n">generate_sprites</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">layout</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">room_sprite_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">on_draw</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">clear</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">scroll_dungeon_camera</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">room_sprite_list</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">draw_contents</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_passages</span><span class="p">()</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">draw_flood</span><span class="p">()</span>
        <span class="k">with</span> <span class="bp">self</span><span class="p">.</span><span class="n">scroller_camera</span><span class="p">.</span><span class="n">activate</span><span class="p">():</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">scroller</span><span class="p">.</span><span class="n">draw</span><span class="p">()</span>
</code></pre></div></div>
<p>Can’t we just put them in <code class="language-plaintext highlighter-rouge">cell_sprites</code> and draw the values? No the one is a SpriteList, which we really want to use. Let’s rename the member to <code class="language-plaintext highlighter-rouge">dungeon_floor_sprites</code>. Commit.</p>
<p>I think that’s all the redundancy I can eliminate from here. But I think there are some unused bits elsewhere.</p>
<p>The only caller of <code class="language-plaintext highlighter-rouge">RoomView.illuminate</code> is a test:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_room_view_illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">)</span>
        <span class="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_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="bp">True</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">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="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span><span class="mi">15</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="mi">9</span><span class="p">,</span><span class="mi">15</span><span class="p">)]</span>
        <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="n">layout</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">room_view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
        <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span> <span class="o">=</span> <span class="p">{</span><span class="n">cell</span><span class="p">:</span> <span class="n">FakeSprite</span><span class="p">()</span> <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">cells</span><span class="p">}</span>
        <span class="n">room_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">dungeon_view</span><span class="p">,</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="mi">3</span><span class="p">)</span>
        <span class="n">lit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="mi">13</span><span class="p">)</span>
        <span class="n">lit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">lit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">lit_cell</span> <span class="ow">in</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminated_cells</span>
        <span class="k">assert</span> <span class="n">lit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">True</span>
        <span class="n">unlit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">unlit_cell</span> <span class="ow">in</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminated_cells</span>
        <span class="n">unlit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">unlit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">unlit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Ugly. I think we’ll remove <code class="language-plaintext highlighter-rouge">RoomView.illuminate</code> and let this test be our lead-in to actual testing. Sure enough, the test fails.</p>
<p>The good news is, I have a test that is working and checking the new code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_room_view_illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">)</span>
        <span class="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_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="bp">True</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">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="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span><span class="mi">15</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="mi">9</span><span class="p">,</span><span class="mi">15</span><span class="p">)]</span>
        <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="n">layout</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">add_room</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">ensure_connected</span><span class="p">()</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">make_passages</span><span class="p">()</span>
        <span class="n">layout</span><span class="p">.</span><span class="n">make_borders</span><span class="p">()</span>
        <span class="n">dungeon</span><span class="p">.</span><span class="n">just_set_player_position</span><span class="p">(</span><span class="n">Cell</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">12</span><span class="p">))</span>
        <span class="n">dungeon_view</span><span class="p">.</span><span class="n">create_rooms</span><span class="p">(</span><span class="n">arcade</span><span class="p">.</span><span class="n">SpriteList</span><span class="p">())</span>
        <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminate_around_dot</span><span class="p">()</span>
        <span class="n">lit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="mi">13</span><span class="p">)</span>
        <span class="n">lit</span> <span class="o">=</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">lit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">lit_cell</span> <span class="ow">in</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminated_cells</span>
        <span class="k">assert</span> <span class="n">lit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">True</span>
        <span class="n">unlit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">unlit_cell</span> <span class="ow">in</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminated_cells</span>
        <span class="n">unlit</span> <span class="o">=</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">unlit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">unlit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">False</span>
</code></pre></div></div>
<p>The bad news is that it is capital U Ugly. But it is a place to stand, and from which to do better.</p>
<p>I’m calling this progress. The DungeonView is simpler and it has an actual test that is exercising most of the new hammered-in code. So we can clean up this test, refactor the code, and move toward better.</p>
<p>And that is the name of the game. See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sat, 18 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/t/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/t/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>So Do It</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>OK, let’s do the thing we love: find something to improve, and improve it. Shall we start where we left off? Result: Not as joyful as some days.</p>
</blockquote>
<p>We pared down the RoomView by removing its references to layout, dungeon, and dungeon view, passing them as parameters when they were needed. Pretty much what the received wisdom would suggest and I’ve done it many times and don’t recall ever regretting it. There is a place for encapsulating a bunch of objects that need to be processed together, often using the pattern called Method Object that replaces a method with a small object bound to the info needed to do one thing, but as a rule, long-standing objects that connect to lots of other objects just make things hard to change and hard to test.</p>
<p>One could wonder why I do it so often.</p>
<p>Let’s start by looking at RoomView again and thinking about what we have wrought.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</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</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="n">room</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span> <span class="o">=</span> <span class="n">TextureFinder</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">create_floor</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">shape_list</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">room</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">choose_flooring</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">choose_flooring</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">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">choose_flooring_texture</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
        <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dungeon_view</span><span class="p">,</span> <span class="n">center_cell</span><span class="p">,</span> <span class="n">distance</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">center_cell</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">distance</span><span class="p">:</span>
                <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
                <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
    <span class="k">def</span> <span class="nf">choose_flooring_texture</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">cell</span><span class="p">):</span>
        <span class="n">borders</span><span class="p">:</span> <span class="n">BorderList</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">get_borders</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
        <span class="n">border_type</span> <span class="o">=</span> <span class="n">borders</span><span class="p">.</span><span class="n">border_string</span><span class="p">()</span>
        <span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span><span class="p">.</span><span class="n">full_name</span><span class="p">(</span><span class="n">border_type</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</code></pre></div></div>
<p>As I mentioned yesterday, it’s pretty clear that there are two things going on in this object, creating the sprites for the room’s flooring, and illuminating the sprites upon demand.</p>
<p>Arguably there are two phases to the sprite creation, choosing the texture to be used, and creating the actual sprite.</p>
<p>The two <code class="language-plaintext highlighter-rouge">choose</code> methods should be private, and together. Do that.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">create_floor</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">shape_list</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">room</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_choose_flooring</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">_choose_flooring</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">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_choose_flooring_texture</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
        <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">_choose_flooring_texture</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">cell</span><span class="p">):</span>
        <span class="n">borders</span><span class="p">:</span> <span class="n">BorderList</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">get_borders</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
        <span class="n">border_type</span> <span class="o">=</span> <span class="n">borders</span><span class="p">.</span><span class="n">border_string</span><span class="p">()</span>
        <span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span><span class="p">.</span><span class="n">full_name</span><span class="p">(</span><span class="n">border_type</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dungeon_view</span><span class="p">,</span> <span class="n">center_cell</span><span class="p">,</span> <span class="n">distance</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">center_cell</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">distance</span><span class="p">:</span>
                <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
                <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>Commit.</p>
<p>The question in my mind is what this object really is, since it’s not really a view, and whether it should be separated into two objects, one about choosing flooring and one about illuminating the floor.</p>
<p>It is not a dungeon-level object, because it thinks about textures, which are view-level, not dungeon level.</p>
<p>Illumination, though. The DungeonView knows, for all cells, which are illuminated. Currently the dungeon does not know whether it is illuminated or not, and we have no story that tells us that it needs to know, although it is easy to think of why we’ll need it. We don’t do things because we’re going to need them, we do them when we need them.</p>
<p>As written, <code class="language-plaintext highlighter-rouge">illuminate</code> marks sprites and informs the DungeonView that a cell is illuminated. DungeonView just does this:</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">illuminate</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">illuminated_cells</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
</code></pre></div></div>
<p>Hm, how does the RoomView get told to illuminate, one wonders.</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">illuminate_around_dot</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot</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">player_cell</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">dot</span><span class="p">:</span> <span class="k">return</span> <span class="c1"># crock to allow a test to run
</span>        <span class="n">room</span> <span class="o">=</span> <span class="n">dot</span><span class="p">.</span><span class="n">room</span>
        <span class="n">radius</span> <span class="o">=</span> <span class="mi">1000</span> <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">.</span><span class="n">dot_has</span><span class="p">(</span><span class="s">'a brilliant torch'</span><span class="p">)</span> <span class="k">else</span> <span class="mi">4</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room_views</span><span class="p">[</span><span class="n">room</span><span class="p">].</span><span class="n">illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dot</span><span class="p">,</span> <span class="n">radius</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">content_view</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_room</span><span class="p">[</span><span class="n">room</span><span class="p">]:</span>
            <span class="n">content_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">dot</span><span class="p">,</span> <span class="n">radius</span><span class="p">)</span>
</code></pre></div></div>
<p>While we’re at it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dot</span><span class="p">,</span> <span class="nb">range</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">dot</span><span class="p">.</span><span class="n">manhattan_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="o">&lt;</span> <span class="nb">range</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>Now as things stand, the DungeonView has all the sprites for the floor, all in a big sprite list. And the RoomView maintains a dictionary from cell to sprite, one such dictionary for each room.</p>
<p>I just noticed that RoomView maintains the <code class="language-plaintext highlighter-rouge">sprite_list</code> member but never uses it. Remove it. Two more lines gone from RoomView. Commit.</p>
<h2 id="design-thinking">Design Thinking</h2>
<p>I’m not ready to try this but just thinking …</p>
<p>The DungeonView has access to the Room instances. A Room is, among other responsibilities, a set of cells. If DungeonView maintained the mapping from cell to sprite then illumination of a room could be done in DungeonView and <code class="language-plaintext highlighter-rouge">illuminate</code> removed from the RoomView, removing that responsibility and leaving it as no more than a texture finder for flooring.</p>
<p>I think that will be better. Let’s do it.</p>
<p>I want to sketch some code that is like the code I’ll want for <code class="language-plaintext highlighter-rouge">illuminate_around_dot</code> as shown above. Maybe something like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">illuminate_around_dotX</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot</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">player_cell</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">dot</span><span class="p">:</span> <span class="k">return</span> <span class="c1"># crock to allow a test to run
</span>        <span class="n">room</span> <span class="o">=</span> <span class="n">dot</span><span class="p">.</span><span class="n">room</span>
        <span class="n">radius</span> <span class="o">=</span> <span class="mi">1000</span> <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">.</span><span class="n">dot_has</span><span class="p">(</span><span class="s">'a brilliant torch'</span><span class="p">)</span> <span class="k">else</span> <span class="mi">4</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">room</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">dot</span><span class="p">.</span><span class="n">room</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">radius</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">illuminate_cell</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">illuminate_cell</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">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
        <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</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="k">for</span> <span class="n">content_view</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_content_view</span><span class="p">[</span><span class="n">cell</span><span class="p">]:</span>
            <span class="n">content_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">()</span>
</code></pre></div></div>
<p>For this to work, DungeonView needs two new dictionaries, <code class="language-plaintext highlighter-rouge">cell_sprites</code>, which needs to be a mapping from all cells to their sprites, and <code class="language-plaintext highlighter-rouge">cell_content_view</code>, a mapping from cell to a list of contents (or an empty list if absent).</p>
<p>To get the cell_sprites dictionary, we have to deal with the flooring logic in RoomView, which is presently passed a sprite list and adds to it. Where do we do that?</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">create_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape_list</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">view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">room_views</span><span class="p">[</span><span class="n">room</span><span class="p">]</span> <span class="o">=</span> <span class="n">view</span>
            <span class="n">view</span><span class="p">.</span><span class="n">create_floor</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">layout</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">)</span>
</code></pre></div></div>
<p>I think that what we want here is for the view to act as a generator, producing a cell and a sprite for each cell in the room.</p>
<p>Can we build that internally to RoomView and use existing tests to be sure we’re right?</p>
<p>Seems like a small step. Let’s try it.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">create_floor</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">shape_list</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">room</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_choose_flooring</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">_choose_flooring</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">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_choose_flooring_texture</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
</code></pre></div></div>
<p>I refactor to this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">create_floor</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">shape_list</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">generate_sprites</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">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
            <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">generate_sprites</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="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">room</span><span class="p">:</span>
            <span class="n">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">make_sprite</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
            <span class="k">yield</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span>
    <span class="k">def</span> <span class="nf">_choose_flooring</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">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">):</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">make_sprite</span><span class="p">(</span><span class="n">layout</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">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">make_sprite</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">cell</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_choose_flooring_texture</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">sprite</span>
</code></pre></div></div>
<p>This works. I note that no tests broke while I was typing that in, which tells me we probably need more testing around this. I did test in play and it works.</p>
<p>Now there is a generator that DungeonView can call. If we use that instead of <code class="language-plaintext highlighter-rouge">create_floor</code>, the RoomView will no longer have its local list of sprites. Let’s move that one line up:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">generate_sprites</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="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">room</span><span class="p">:</span>
            <span class="n">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">make_sprite</span><span class="p">(</span><span class="n">layout</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">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
            <span class="k">yield</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span>
</code></pre></div></div>
<p>Now let’s see if we can use the generate to create our cell-to-sprite dictionary …</p>
<p>After more hackery than I am comfortable with, I have things working. We’ll need to consider this a spike and it needs doing over. I will not roll it back yet. I need to learn from it a bit more but here’s what I’ve got.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">create_floor</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">shape_list</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">generate_sprites</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">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
            <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">generate_sprites</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="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">room</span><span class="p">:</span>
            <span class="n">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">make_sprite</span><span class="p">(</span><span class="n">layout</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">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
            <span class="k">yield</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span>
    <span class="k">def</span> <span class="nf">make_sprite</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">cell</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_choose_flooring_texture</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">sprite</span>
</code></pre></div></div>
<p>I extracted the new method <code class="language-plaintext highlighter-rouge">make_sprite</code> and use it in the new <code class="language-plaintext highlighter-rouge">generate_sprites</code> method, which keeps <code class="language-plaintext highlighter-rouge">create_floor</code> working, although now that the rest is working I think it’s no longer called or needed.</p>
<p>In DungeonView there’s more:</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="p">...</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="nb">list</span><span class="p">[</span><span class="n">ContentView</span><span class="p">]]</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Sprite</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="p">...</span>
    <span class="k">def</span> <span class="nf">make_view_and_sprite</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">item</span><span class="p">):</span>
        <span class="n">resources</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">resources</span>
        <span class="n">scale</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">scale</span>
        <span class="n">view</span> <span class="o">=</span> <span class="n">ContentView</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">center_position</span><span class="p">(</span><span class="n">cell_size</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views</span><span class="p">[</span><span class="n">item</span><span class="p">]</span> <span class="o">=</span> <span class="n">view</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_room</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="n">append</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">[</span><span class="n">cell</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_sprite_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</span><span class="p">:</span>
            <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
    <span class="k">def</span> <span class="nf">create_rooms</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">shape_list</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">view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">room</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">room_views</span><span class="p">[</span><span class="n">room</span><span class="p">]</span> <span class="o">=</span> <span class="n">view</span>
            <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="n">view</span><span class="p">.</span><span class="n">generate_sprites</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">layout</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">room_sprite_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">illuminate_around_dot</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">dot</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">player_cell</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">dot</span><span class="p">:</span> <span class="k">return</span> <span class="c1"># crock to allow a test to run
</span>        <span class="n">room</span> <span class="o">=</span> <span class="n">dot</span><span class="p">.</span><span class="n">room</span>
        <span class="n">radius</span> <span class="o">=</span> <span class="mi">1000</span> <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon</span><span class="p">.</span><span class="n">dot_has</span><span class="p">(</span><span class="s">'a brilliant torch'</span><span class="p">)</span> <span class="k">else</span> <span class="mi">4</span>
        <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">room</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">dot</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">radius</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">illuminate_cell</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">illuminate_cell</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">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
        <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</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="k">for</span> <span class="n">content_view</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_cell</span><span class="p">[</span><span class="n">cell</span><span class="p">]:</span>
            <span class="n">content_view</span><span class="p">.</span><span class="n">just_illuminate</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="reflection">Reflection</h2>
<p>The bad news is that having just written about the joy I find in programming, what I’ve just done was not joyful. It was confusing, irritating, and working without a net of tests. That’s why I’m not going to commit it now, and while next time around I’ll probably just revert and do it over.</p>
<p>The good news is that the basic idea of generating the sprites in RoomView and handing them over to DungeonView does work. I think that the <code class="language-plaintext highlighter-rouge">illuminate</code> method in RoomView is no longer used.</p>
<p>The observations include:</p>
<ul>
  <li>It’s just possible that despite its flaws, this is a better structure for things.</li>
  <li><code class="language-plaintext highlighter-rouge">create_rooms</code> doesn’t create rooms and never did. Needs better name, if only <code class="language-plaintext highlighter-rouge">create_sprites</code> or something.</li>
  <li>There are way too many lists and dictionaries in DungeonView. Some of them may already be unused. Others should be obviated.</li>
  <li>There are no tests for any of this, and never have been.</li>
</ul>
<p>The hopeful observation is that as I sit with it for a moment, it’s not really that bad. It might make sense not to throw it away, but instead to write the tests that we should have had right along, and then refactor this code to make it decent.</p>
<p>We’ll see. I’m late to my appointment with an iced Chai. See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Sat, 18 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/u/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/u/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>What I Love ...</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>What I love about programming. A few thoughts on what I do, and why I enjoy it so much.</p>
</blockquote>
<p>Code, I guess, is the thing I’m second-best at, maybe best. I enjoy it. I’ve had similar feelings with a few other activities, sometimes writing, sometimes drawing. I link to think that I’m fairly good at all those things, if you count the people who say “I can’t draw” and then don’t. I’m more the one who said “I can’t draw” and then went ahead and drew anyway.</p>
<p>All those activities, but especially programming, can consume my attention, and suffuse me with a general feeling of joy. Not a big ecstatic kind of rush joy, just an ongoing feeling that this is good. I imagine, but cannot know, that it’s the same kind of feeling a musician gets making music, an artist gets when the painting os going well, … and the feeling in the potter as the vase takes on the shape the potter has in mind.</p>
<p>And in one way, code is “better” than most of those other forms. In all such endeavors, the maker can always do better. In drawing, painting, sculpting, one can generally only do better “next time”.</p>
<p>Apparently, according to the Internet, it was Paul Valéry who originated the notion that a work is never truly finished, only abandoned. The idea has been attributed to many, and I’ve quoted it in these pages a time or two.</p>
<p>But the thing with potting, sculpture, painting, furniture, drawing, most forms, we typically can’t go back and improve the thing, we can only do another thing. With writing, prose or poetic, we can go back and improve the thing, but once it’s published, well, writing a new thing seems better.</p>
<p>What I love about code, about programming, is the feeling of the program changing under my hands. There are many aspects to making a program, as there are to any more classical form of art or craft. I imagine that typically, the maker really enjoys some aspects, and others not so much. If they’re fortunate, they enjoy more aspects. A truly lucky person would enjoy every aspect of their work. I don’t know of anyone like that and I, for one, do not enjoy all the aspects.</p>
<p>I mostly enjoy just two programming aspects. I enjoy starting from zero or near zero and coming up with the solution to some problem. And I enjoy looking at code, seeing how it could be improved, and improving it.</p>
<p>The truly marvelous thing about my code, and almost all the code I’ve ever seen, is that there is plenty of room for improvement. Especially with my code.</p>
<p>I look at my code; I think “that’s horrid, what fool did that?”; I forgive the fool—he probably had other things on his mind; and I joyfully improve what I’ve found.</p>
<p>A thing that I do not enjoy is a long slog. I especially don’t enjoy a long slog with the program broken. So, driven by long experience, and driven by GeePaw Hills “Many More Much Smaller Steps” notion, I find ways to improve the program in tiny steps, working mostly from green to green. The result is that the longest drab slog becomes instead a series of joyfully coming up with little solutions, and joyfully improving code.</p>
<p>And that … is why I do this.</p>
<p>Why do I tell you about it? Well, mostly for similar reasons. I like to try to share my joy, and this is the medium in which I can do it. If someone reads these words, great. If they then try to find ways to increase their own joy, greater still.</p>
<p>Find your joy. There must be a pony in here somewhere.</p>
 ]]> 
                </content:encoded>
                <pubDate>Sat, 18 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/v/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/v/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Un-Wiring</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>Writing a note to ask for ideas, I got an idea. Let’s see what happens with it. Result: I think it turns out nicely.</p>
</blockquote>
<p>I was thinking about this morning’s creation of a little object that made things simpler and was easy to test. And I was looking at RoomView, and a long story test for something that doesn’t seem that tricky. And on Hill’s slack, I though I’d start a convo and get some advice, and I wrote:</p>
<blockquote>
  <p>A RoomView has a method <code class="language-plaintext highlighter-rouge">illuminate</code> that illuminates every cell that it contains. The RoomView is owned by the DungeonView, which has a list of illuminated cells, because when new content is added in game play, the content item needs to be illuminated or not depending on whether the cell it is being placed in is illuminated. The DungeonView knows the dungeon layout, since it runs the map and such. So a RoomView has a DungeonView … <strong><em>ah, there’s yer problem right there</em></strong>. Maybe we should just pass in the things the RoomView needs when it needs them, and then we could more readily test it by passing it fake things to talk to.</p>
</blockquote>
<p>So let’s try to unhook the RoomView from knowing its owners, and in so doing, improve things a bit. Here’s the whole class:</p>
<pre><code class="language-Python">class RoomView:
    def __init__(self, dungeon_view, room):
        self.dungeon_view = dungeon_view
        self.dungeon = dungeon_view.dungeon
        self.layout = self.dungeon.layout
        self.room = room
        self.sprites = []
        self.cell_sprites = dict()
        self.texture_finder = TextureFinder()
    def create_floor(self, shape_list):
        for cell in self.room:
            self.choose_flooring(cell, shape_list)
    def choose_flooring(self, cell, shape_list):
        texture = self.choose_flooring_texture(cell)
        sprite = self.make_adjusted_sprite(cell, texture)
        self.cell_sprites[cell] = sprite
        self.sprites.append(sprite)
        shape_list.append(sprite)
    def illuminate(self, center_cell, distance):
        for cell, sprite in self.cell_sprites.items():
            if cell.manhattan_distance(center_cell) &lt; distance:
                self.dungeon_view.illuminate(cell)
                sprite.visible = True
    def make_adjusted_sprite(self, cell, texture):
        scale = scale_texture(texture)
        sprite = arcade.Sprite(texture, scale=scale)
        sprite.position = cell.center_position(cell_size)
        sprite.visible = False
        return sprite
    def choose_flooring_texture(self, cell):
        borders: BorderList = self.layout.get_borders(cell)
        border_type = borders.border_string()
        name = self.texture_finder.full_name(border_type)
        return arcade.load_texture(name)
</code></pre>
<p>The only user of <code class="language-plaintext highlighter-rouge">layout</code> is the last method (and a test). The only use of Dungeon is to get the layout. There is another use of the DungeonView inn <code class="language-plaintext highlighter-rouge">illuminate</code>. Let’s get rid of all three of those members.</p>
<p>First change signature of <code class="language-plaintext highlighter-rouge">create_floor</code> to require the layout as a parameter.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">create_floor</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">shape_list</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">room</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">choose_flooring</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">)</span>
</code></pre></div></div>
<p>I defaulted the new parameter to <code class="language-plaintext highlighter-rouge">self.layout</code>, and since the caller is DungeonView, it works. Tests are green. We could commit. Let’s do, just because we can. Commit: <em>disconnecting RoomView from the universe.</em></p>
<p>Now change signature of <code class="language-plaintext highlighter-rouge">choose_flooring</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">choose_flooring</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">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">choose_flooring_texture</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
        <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
</code></pre></div></div>
<p>Green. Commit. Now change signature of <code class="language-plaintext highlighter-rouge">choose_flooring_texture</code> and remove the reference to <code class="language-plaintext highlighter-rouge">self.layout</code>.</p>
<p>Green. Commit. Now remove two member variables, <code class="language-plaintext highlighter-rouge">dungeon</code> and <code class="language-plaintext highlighter-rouge">layout</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</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_view</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">dungeon_view</span> <span class="o">=</span> <span class="n">dungeon_view</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room</span> <span class="o">=</span> <span class="n">room</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span> <span class="o">=</span> <span class="n">TextureFinder</span><span class="p">()</span>
</code></pre></div></div>
<p>I expect this to break a test and it does. The fix is to change this line in the test:</p>
<pre><code class="language-puthon">        borders = view.layout.get_borders(c_33)
</code></pre>
<p>To this:</p>
<pre><code class="language-puthon">        borders = layout.get_borders(c_33)
</code></pre>
<p>Green. Commit. Now for illuminate:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">center_cell</span><span class="p">,</span> <span class="n">distance</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">center_cell</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">distance</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
                <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>This wants a dungeon_view parameter. Change signature and remove the self:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dungeon_view</span><span class="p">,</span> <span class="n">center_cell</span><span class="p">,</span> <span class="n">distance</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">center_cell</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">distance</span><span class="p">:</span>
                <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
                <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>A test fails. I am not surprised but hadn’t looked to see if one would. This line:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_room_view_illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="p">,,,</span>
        <span class="n">room_view</span><span class="p">.</span><span class="n">illuminate</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="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="mi">3</span><span class="p">)</span>
</code></pre></div></div>
<p>Needs to be:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_room_view_illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="p">,,,</span>
        <span class="n">room_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">dungeon_view</span><span class="p">,</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="mi">3</span><span class="p">)</span>
</code></pre></div></div>
<p>Green. Commit.</p>
<p>We can remove the saving of the dungeon_view instance variable:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</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_view</span><span class="p">,</span> <span class="n">room</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room</span> <span class="o">=</span> <span class="n">room</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span> <span class="o">=</span> <span class="n">TextureFinder</span><span class="p">()</span>
</code></pre></div></div>
<p>Green. Commit. Change signature on the init:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</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</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="n">room</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span> <span class="o">=</span> <span class="n">TextureFinder</span><span class="p">()</span>
</code></pre></div></div>
<p>Green. Commit.</p>
<h2 id="reflection">Reflection</h2>
<p>We have removed three instance variables from RoomView, which were pointers that wired the class into everything around it. Now it is not wired to any other object, and when it needs information from or to send messages to another object, that object is passed to it on the relevant call. It is notably simpler.</p>
<p>PyCharm highlights this method as static, which is generally a hint of Feature Envy:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">make_adjusted_sprite</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">texture</span><span class="p">):</span>
        <span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">Sprite</span><span class="p">(</span><span class="n">texture</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="n">scale</span><span class="p">)</span>
        <span class="n">sprite</span><span class="p">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">center_position</span><span class="p">(</span><span class="n">cell_size</span><span class="p">)</span>
        <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="k">return</span> <span class="n">sprite</span>
</code></pre></div></div>
<p>I don’t see a place we’d rather have the method. <code class="language-plaintext highlighter-rouge">scale_texture</code> is a method in ‘params.py’. We could move this method there, which would at least get it out of here. Let’s do that. Move it and edit in params:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">params</span><span class="p">.</span><span class="n">py</span>
<span class="k">def</span> <span class="nf">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">):</span>
    <span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
    <span class="n">sprite</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">Sprite</span><span class="p">(</span><span class="n">texture</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="n">scale</span><span class="p">)</span>
    <span class="n">sprite</span><span class="p">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">center_position</span><span class="p">(</span><span class="n">cell_size</span><span class="p">)</span>
    <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="k">return</span> <span class="n">sprite</span>
</code></pre></div></div>
<p>Change the call:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">choose_flooring</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">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">choose_flooring_texture</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
        <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
</code></pre></div></div>
<p>Green. Commit: <em>move make_adjusted_sprite to params.</em></p>
<h2 id="reflection-1">Reflection</h2>
<p>RoomView is now vastly simplified:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</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</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="n">room</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span> <span class="o">=</span> <span class="n">TextureFinder</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">create_floor</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">shape_list</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">room</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">choose_flooring</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">choose_flooring</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">cell</span><span class="p">,</span> <span class="n">shape_list</span><span class="p">):</span>
        <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">choose_flooring_texture</span><span class="p">(</span><span class="n">layout</span><span class="p">,</span> <span class="n">cell</span><span class="p">)</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">params</span><span class="p">.</span><span class="n">make_adjusted_sprite</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span> <span class="o">=</span> <span class="n">sprite</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprites</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
        <span class="n">shape_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">sprite</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">dungeon_view</span><span class="p">,</span> <span class="n">center_cell</span><span class="p">,</span> <span class="n">distance</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">sprite</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">cell</span><span class="p">.</span><span class="n">manhattan_distance</span><span class="p">(</span><span class="n">center_cell</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">distance</span><span class="p">:</span>
                <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminate</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
                <span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
    <span class="k">def</span> <span class="nf">choose_flooring_texture</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">cell</span><span class="p">):</span>
        <span class="n">borders</span><span class="p">:</span> <span class="n">BorderList</span> <span class="o">=</span> <span class="n">layout</span><span class="p">.</span><span class="n">get_borders</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
        <span class="n">border_type</span> <span class="o">=</span> <span class="n">borders</span><span class="p">.</span><span class="n">border_string</span><span class="p">()</span>
        <span class="n">name</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">texture_finder</span><span class="p">.</span><span class="n">full_name</span><span class="p">(</span><span class="n">border_type</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</code></pre></div></div>
<p>Thirty lines, down from 40. No connection to substrate classes. We may or may not simplify its tests, but we certainly could if we chose to.</p>
<p>It’s not perfect, and it’s easier to see some flaws. Most significant issues include:</p>
<ol>
  <li>It’s arguably not a view: it neither draws nor receives input from the model.</li>
  <li>It clearly has two entirely separate responsibilities: it creates flooring for the room’s cells, and it illuminates cells within the room.</li>
</ol>
<p>This makes me suspect that we have, at best, some kind of conglomerate RoomUtilityFunctionCollection, and quite possibly two separate tiny classes, a RoomIlluminator and a FlooringSelector, or something like that.</p>
<p>But for now, we have made notable improvements, so let’s sum up.</p>
<h2 id="summary">Summary</h2>
<p>Here, we observed a class that had accumulated links to the DungeonView, the Dungeon (which it really didn’t need) and to the DungeonLayout. No one deserves that much explicit connectivity. Removing the object’s links and passing in reference objects only as needed reduces coupling substantially, at no cost in additional code, merely changes to calling sequences and subsequent references to parameters instead of instance variables.</p>
<p>It may not be worth it to go back and retrofit the tests, but I’d wager that we can write much simpler tests for RoomView now than we currently have.</p>
<p>I wonder … the Cell now knows how to access the layout, which it does through a Cell class variable. Right now, we don’t make use of that link, but we could. Probably not a good idea: still better to pass it where it’s needed.</p>
<p>Or maybe there are new methods that could be added to Cell that would be helpful. We’ll keep an eye out for that, but we want not to overload Cell with things that don’t really pertain to it. But worth keeping in mind.</p>
<p>Bottom line: a noticeable improvement and very easy to accomplish. Something to think about.</p>
<p>See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Fri, 17 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/w/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/w/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Tiny Objects</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>Received wisdom and personal experience tell me to cover built-in collections. Here’s why.</p>
</blockquote>
<p>Back in the day, my betters would rabbit on about “never” using build-in collections directly, saying that it was “always” better to write a little class holding the collection we wanted. I myself have learned that lesson time and time again. It is my fashion to learn things over and over, because I tend to make the same mistakes over and over.</p>
<p>In favor of the built-in collection, especially in Python, we have a strong argument: laziness. Almost anything you want to do with a collection, Python has a custom-made object or function to do the job. And, as we’ll see in a moment, even the straightforward things seem to invite “just” using a built-in collection. And, as always, the word “just” has a lot hiding behind it.</p>
<p>Let’s get to the case. Yesterday we were working on improving our notation for dealing with textures, so that instead of some horrendous path name, we could just say ‘lever/1.png’ and the right texture would be found. Although I had not know it going in, I found that arcade allows defining a “resource handle”, and we wound up with this code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="s">':resources:images/items/star.png'</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':ron:'</span><span class="o">+</span><span class="n">resource</span><span class="p">)</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">True</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">error</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>The new bit is just the <code class="language-plaintext highlighter-rouge">arcade.load_texture(':ron:'+resource)</code>, because over in DungeonView, we did this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">setup_assets</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">arcade_resources</span> <span class="o">=</span> <span class="s">':resources:images/items/'</span>
        <span class="n">arcade_resources</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">arcade_resources</span><span class="p">)</span>
        <span class="n">my_resources</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span><span class="n">arcade_resources</span><span class="p">)</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span><span class="n">my_resources</span><span class="p">)</span>
</code></pre></div></div>
<p>That defines a “handle” ‘:ron:’ that directs arcade to search those two folder for file names and short paths like <code class="language-plaintext highlighter-rouge">lever/1.png</code>. The result is that all arcades built-in textures, and all of my purchased ones, can be easily found.</p>
<p>So far so good. Our plan included caching the textures. The code above loads the texture every time we use it, so if we put a dozen chests in the dungeon, we’ll read the file a dozen times. Tacky.</p>
<p>So yesterday, while you were away, I did this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':resources:images/items/star.png'</span><span class="p">)</span>
    <span class="n">known_textures</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">arcade</span><span class="p">.</span><span class="n">Texture</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">get_texture</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">get_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">resource</span><span class="p">):</span>
        <span class="k">if</span> <span class="n">resource</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">known_textures</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">known_textures</span><span class="p">[</span><span class="n">resource</span><span class="p">]</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':ron:'</span> <span class="o">+</span> <span class="n">resource</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">known_textures</span><span class="p">[</span><span class="n">resource</span><span class="p">]</span> <span class="o">=</span> <span class="n">texture</span>
        <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">True</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span>
        <span class="k">return</span> <span class="n">texture</span>
</code></pre></div></div>
<p>When we create a new ContentView, we pass it the resource names that it should have, and, looping over <code class="language-plaintext highlighter-rouge">get_texture</code>, we append them to the item’s sprite. In <code class="language-plaintext highlighter-rouge">get_+texture</code>, we just check the class-level dictionary <code class="language-plaintext highlighter-rouge">known_textures</code>, returning what we find if it is there, and otherwise loading it and putting it in the dictionary.</p>
<p>Pretty standard little cache of name-&gt;texture. It took less time to do than it just took to explain it.</p>
<p>So, I don’t know why, I was thinking about what I’d done and some voice said something to me about covering native collections, and I started to answer why this case was so simple that the guideline didn’t apply, when I realized that, well actually, a little object of our might be better. So I decided to find out. So let’s find out.</p>
<p>There are only four methods in ContentView other than <code class="language-plaintext highlighter-rouge">__init__</code>: <code class="language-plaintext highlighter-rouge">get_texture</code>, <code class="language-plaintext highlighter-rouge">illuminate</code>, <code class="language-plaintext highlighter-rouge">remove</code>, and <code class="language-plaintext highlighter-rouge">set_state</code>. The latter three refer to things we do at run time, we light up the object, we animate it, and we can remove it when it is destroyed or given to Dot.</p>
<p>The method <code class="language-plaintext highlighter-rouge">get_texture</code> isn’t like that. And it is as long as the other three methods combined.</p>
<h2 id="the-plan">The Plan</h2>
<p>To resolve this tension, we’re going to create a very small object, TextureCache, and use it. I think we’ll just create it where the current <code class="language-plaintext highlighter-rouge">dict</code> is, in the class variable <code class="language-plaintext highlighter-rouge">known_resources</code>.</p>
<p>I’m not sure how good our tests are for this class, so we’ll start by putting None into the <code class="language-plaintext highlighter-rouge">known_textures</code> just to see if anything breaks. If not, we’ll code something that does.</p>
<p>Two tests fail:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_creation</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">resource</span> <span class="o">=</span> <span class="s">'keyRed.png'</span>
        <span class="n">factory</span> <span class="o">=</span> <span class="n">ContentFactory</span><span class="p">()</span>
        <span class="n">item</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">decor</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'key'</span><span class="p">,</span> <span class="n">resource</span><span class="o">=</span><span class="n">resource</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.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="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
        <span class="n">view</span> <span class="o">=</span> <span class="n">ContentView</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">item</span><span class="p">.</span><span class="n">resources</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">False</span>
    <span class="k">def</span> <span class="nf">test_content_view_uses_ron_handle</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">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">dv</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">testing</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># inits handle
</span>        <span class="n">resource</span> <span class="o">=</span> <span class="s">'Skeleton1.png'</span>
        <span class="n">content</span> <span class="o">=</span> <span class="n">ContentFactory</span><span class="p">().</span><span class="n">decor</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'test'</span><span class="p">,</span> <span class="n">resource</span><span class="o">=</span><span class="n">resource</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
        <span class="n">cv</span> <span class="o">=</span> <span class="n">ContentView</span><span class="p">(</span><span class="n">Cell</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">content</span><span class="p">,</span> <span class="n">content</span><span class="p">.</span><span class="n">resources</span><span class="p">,</span> <span class="n">content</span><span class="p">.</span><span class="n">scale</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">cv</span><span class="p">.</span><span class="n">error</span>
</code></pre></div></div>
<p>That’s good. But I think we’d like to TDD this tiny class anyway, trivial though it is. Why? Because I want to be sure it’s really caching. The code we have now could have a defect in it that caused it never to find anything, and we’d never realize that it was reading a file every time.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TextureSaver</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">pass</span>
<span class="k">class</span> <span class="nc">TestTextureSaver</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">cache</span> <span class="o">=</span> <span class="n">TextureSaver</span><span class="p">()</span>
</code></pre></div></div>
<p>I decided to name it TextureSaver. We’ll discuss why below. Green so far, let’s commit: <em>working on TextureSaver</em>.</p>
<p>I figure this test will do for us:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_saves</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">DungeonView</span><span class="p">.</span><span class="n">setup_assets</span><span class="p">()</span>
        <span class="n">cache</span> <span class="o">=</span> <span class="n">TextureSaver</span><span class="p">()</span>
        <span class="n">r1</span> <span class="o">=</span> <span class="s">'lever/1.png'</span>
        <span class="n">r2</span> <span class="o">=</span> <span class="s">'lever/2.png'</span>
        <span class="n">t1</span> <span class="o">=</span> <span class="n">cache</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">r1</span><span class="p">)</span>
        <span class="n">t2</span> <span class="o">=</span> <span class="n">cache</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">r2</span><span class="p">)</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">cache</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
        <span class="n">t3</span> <span class="o">=</span> <span class="n">cache</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">r1</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">t3</span> <span class="o">==</span> <span class="n">t1</span>
        <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">cache</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</code></pre></div></div>
<p>We’ll need some code:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TextureSaver</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':resources:images/items/star.png'</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">_cache</span><span class="p">:</span><span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">arcade</span><span class="p">.</span><span class="n">Texture</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="k">def</span> <span class="nf">__len__</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">_cache</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">resource_name</span><span class="p">:</span><span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">arcade</span><span class="p">.</span><span class="n">Texture</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">resource_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">_cache</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">resource_name</span><span class="p">]</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':ron:'</span> <span class="o">+</span> <span class="n">resource_name</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">resource_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">texture</span>
        <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">True</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span>
        <span class="k">return</span> <span class="n">texture</span>
</code></pre></div></div>
<p>Test passes. No real surprise, we basically just moved the code over. Commit.</p>
<p>But now in ContentView:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">textures</span> <span class="o">=</span> <span class="n">TextureSaver</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">textures</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>We can remove the error texture, and the get method we used to have. Tests all green. Commit.</p>
<p>So this is  already good, because now ContentView doesn’t know anything about how to cache textures.</p>
<p>Let’s rename that class to TextureProvider. And we might rename it again.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">textures</span> <span class="o">=</span> <span class="n">TextureProvider</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">textures</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>Why are we pulling textures out of that one object and stuffing them into another? Let’s have TextureProvider do that job:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">textures</span> <span class="o">=</span> <span class="n">TextureProvider</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">textures</span><span class="p">.</span><span class="n">load_sprite</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">,</span> <span class="n">resources</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>And tests break, but then:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TextureProvider</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">load_sprite</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">sprite</span><span class="p">,</span> <span class="n">resource_names</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">resource_name</span> <span class="ow">in</span> <span class="n">resource_names</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">resource_name</span><span class="p">)</span>
            <span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
</code></pre></div></div>
<p>Why do we bother to create the sprite and pass it in? Let’s have the provider do that too.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">textures</span> <span class="o">=</span> <span class="n">TextureProvider</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">textures</span><span class="p">.</span><span class="n">load_sprite</span><span class="p">(</span><span class="n">resources</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</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">load_sprite</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">resource_names</span><span class="p">):</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource_name</span> <span class="ow">in</span> <span class="n">resource_names</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">resource_name</span><span class="p">)</span>
            <span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">sprite</span>
</code></pre></div></div>
<p>Commit. Let’s review the whole TextureProvider class:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">TextureProvider</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':resources:images/items/star.png'</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">_cache</span><span class="p">:</span><span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">arcade</span><span class="p">.</span><span class="n">Texture</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="k">def</span> <span class="nf">__len__</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">_cache</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">load_sprite</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">resource_names</span><span class="p">):</span>
        <span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource_name</span> <span class="ow">in</span> <span class="n">resource_names</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">resource_name</span><span class="p">)</span>
            <span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">sprite</span>
    <span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">resource_name</span><span class="p">:</span><span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">arcade</span><span class="p">.</span><span class="n">Texture</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">resource_name</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">_cache</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">resource_name</span><span class="p">]</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':ron:'</span> <span class="o">+</span> <span class="n">resource_name</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">_cache</span><span class="p">[</span><span class="n">resource_name</span><span class="p">]</span> <span class="o">=</span> <span class="n">texture</span>
        <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">True</span>
            <span class="n">texture</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span>
        <span class="k">return</span> <span class="n">texture</span>
</code></pre></div></div>
<p>Now it seems to me that the <code class="language-plaintext highlighter-rouge">get</code> method is essentially private now, as our only user now uses <code class="language-plaintext highlighter-rouge">load_sprite</code>. We may have other users later but this tiny class only has one user other than its tests. So we’ll make it private until we need it, if we ever do. Commit again.</p>
<p>What is the name of this class now? Is it still TextureProvider? Or is it a SpriteLoader? TexturedSpriteMaker? SpritePainter? SpriteTexturator? LordHighTexturizerOfSprites?</p>
<p>I’m not sure. We’ll sit with this name for a while. Let’s assess what we’ve done.</p>
<h2 id="reflection">Reflection</h2>
<p>We have simplified ContentView and reduced its responsibilities. It used to have two classes of behavior, managing sprites at run time, and creating a sprite with multiple textures. It now has just the former group. It is down from 4 concrete methods to 3. It has one fewer class variables. It orders out to get its sprite instead of building one itself. It’s much simpler.</p>
<p>The TextureProvider is a new class, and it is 27 lines long, while we have “only” removed 15 lines from ContentView. We have a net increase in code of about a dozen lines!!!</p>
<p>TextureProvider encapsulates a concept that we didn’t have in the code before, loading a Sprite, which was formerly represented by a loop in one method and another method that did the texture getting bits. So it adds to clarity.</p>
<p>The flow of things is interesting.</p>
<p>At first, we were just covering the cache dictionary with a little object, which was sufficient to move the nasty <code class="language-plaintext highlighter-rouge">get</code> method out of ContentView, where it was clearly out of place. But having done that, it slowly became clear that the new object could be more helpful, first by adding a texture to a sprite for us, and then we saw that it could provide the whole Sprite.</p>
<p>And here is what may be the most important thing: this tiny new object is easy to test. We don’t have to set up layouts and dungeons and whatnot. We just create it and it’s ready to test.</p>
<p>I’ve been whining about the difficulty of testing things in this program. If I hadn’t written every line myself I’d be raging at whatever fool made it so hard to test. This experience of creating a cover for a collection making things easier to understand and much easier to test … this experience that I have had innumerable times over many years … revitalizes my attention to such things. If this high sticks, maybe we’ll look for other places to make things better with tiny objects.</p>
<p>Or maybe I’ll just forget and fall back into old bad habits. It could happen.</p>
<h2 id="summary">Summary</h2>
<p>A tiny object improves the code that uses it, and creates opportunities for adding small bits of capability that makes things better. And it’s easy to test! Yummy!</p>
<p>See you next time!</p>
<h2 id="postscript">Postscript</h2>
<p>I was going to tell you why TextureCache got renamed: There is an arcade class named TextureCache. It’s not the same as what we did today, but I didn’t want o hide the name, in case we ever need the arcade one.</p>
 ]]> 
                </content:encoded>
                <pubDate>Fri, 17 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/x/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/x/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Resources</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>Resource designation is awkward, and I think we’re loading them multiple times. Let’s see what we can do about this. Arcade helps out!</p>
</blockquote>
<p>Here’s the sort of thing we have to put up with:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentFactory</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">lever</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
        <span class="k">def</span> <span class="nf">lever</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">interactor</span><span class="p">):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">=</span> <span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">state</span><span class="o">+</span><span class="mi">1</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">resources</span><span class="p">)</span>
            <span class="n">interactor</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="s">'control'</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">state</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">state</span><span class="p">)</span>
            <span class="n">interactor</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="s">'state_number'</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span> <span class="n">state</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">state</span><span class="p">)</span>
            <span class="k">return</span> <span class="bp">False</span>
        <span class="n">resource1</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/lever/1.png'</span>
        <span class="n">resource2</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/lever/2.png'</span>
        <span class="n">resource3</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/lever/3.png'</span>
        <span class="n">resource4</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/lever/4.png'</span>
        <span class="n">resources</span> <span class="o">=</span> <span class="p">[</span><span class="n">resource1</span><span class="p">,</span> <span class="n">resource2</span><span class="p">,</span> <span class="n">resource3</span><span class="p">,</span> <span class="n">resource4</span><span class="p">]</span>
</code></pre></div></div>
<p>Sometimes I’ve resorted to a temp, only slightly less irritating:</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">resources</span><span class="o">=</span><span class="s">':resources:images/items/'</span>
    <span class="n">my_resources</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
    <span class="n">factory</span> <span class="o">=</span> <span class="n">ContentFactory</span><span class="p">()</span>
    <span class="n">item</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">receivable</span><span class="p">(</span><span class="n">name</span> <span class="o">=</span><span class="s">"a red key"</span><span class="p">,</span> <span class="n">resource</span><span class="o">=</span><span class="n">resources</span> <span class="o">+</span> <span class="s">'keyRed.png'</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.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="mi">29</span><span class="p">,</span> <span class="mi">28</span><span class="p">)</span>
    <span class="n">cell</span><span class="p">.</span><span class="n">add_content</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
    <span class="p">...</span>
</code></pre></div></div>
<p>Now here chez Ron that’s not all that irritating, since I don’t really use a lot of resource strings, but if we were a real team building a real game, there would be ResourceMasters creating and using lots of different files. And even here, if and when I decide to flesh this game out a bit — and very likely I will — it can only get more irritating.</p>
<p>So let’s see what some goals might be for improvement.</p>
<ol>
  <li>Load a given resource only once;</li>
  <li>Provide a short notation for resources,  <br />
perhaps like ‘keyRed’ or ‘/lever/1’;</li>
  <li>Possibly allow or require the file type suffix;</li>
  <li>Ideally, require no changes to existing code;</li>
  <li>Failing that, require only minimal changes such as removing the path bits;</li>
  <li>Remember to deal with the floor tile resources, not just content.</li>
</ol>
<p>We’d better look at the code for processing resources now.</p>
<p>The action will take place in ContentView, where we load all the textures from the provided strings.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="s">':resources:images/items/star.png'</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>There’s yer problem right there! We expect a full path name here, and we unconditionally call <code class="language-plaintext highlighter-rouge">arcade.load_texture</code> on it. We even load the error texture every time.</p>
<p>I’ve taken a quick look at flooring, which uses a thing called TextureFinder and while it could use some improvement, I think it is more likely to be a use of our new capability than a part of it. So we’ll try to meet our goals above with some new code.</p>
<h2 id="design-thinking">Design Thinking</h2>
<p>So. An object that takes a short name such as ‘lever/1.png’, or even ‘lever/1’ if we can see how to do it, and returns a loaded texture by that name if it exists and a standard error texture if it can’t be found. (We don’t allow our code to crash.)</p>
<p>There are two kinds of paths to resources supported by arcade:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">resources</span><span class="o">=</span><span class="s">':resources:images/items/'</span>
    <span class="n">my_resources</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
</code></pre></div></div>
<p>So far, the only way I know to process those colon ones is with <code class="language-plaintext highlighter-rouge">arcade.load_texture</code>. We want to cache the textures. I guess we could do that, keyed on the short name and trying all the paths we know. That might be good. It might also be good to know if arcade will resolve that colon thing into an actual path for us.</p>
<h2 id="rtfm-amazing-what-you-can-find">RTFM! Amazing What You Can Find!</h2>
<p>A bit of searching in the arcade documents offers a method that lists all built-in assets. Let’s try it out.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_path</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">p1</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop'</span>
        <span class="k">assert</span> <span class="n">Path</span><span class="p">(</span><span class="n">p1</span><span class="p">).</span><span class="n">exists</span><span class="p">()</span>
        <span class="n">p1_r</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">p1</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">Path</span><span class="p">(</span><span class="n">p1_r</span><span class="p">).</span><span class="n">exists</span><span class="p">()</span>
        <span class="n">p2</span> <span class="o">=</span> <span class="s">':resources:images/items/'</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">Path</span><span class="p">(</span><span class="n">p2</span><span class="p">).</span><span class="n">exists</span><span class="p">()</span>
        <span class="n">p2_r</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">p2</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">Path</span><span class="p">(</span><span class="n">p2_r</span><span class="p">).</span><span class="n">exists</span><span class="p">()</span>
</code></pre></div></div>
<p>So there is the answer to one question, how we convert a colon path to a real one?</p>
<p>Let’s see that list. It is immense. This test limiting to image files with <code class="language-plaintext highlighter-rouge">key</code> in their name:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_assets</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">assets</span> <span class="o">=</span>  <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">list_built_in_assets</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'key'</span><span class="p">,</span> <span class="n">extensions</span><span class="o">=</span><span class="p">[</span><span class="s">'.png'</span><span class="p">,</span> <span class="s">'.jpg'</span><span class="p">,</span> <span class="s">'.jpeg'</span><span class="p">])</span>
        <span class="n">p_assets</span> <span class="o">=</span> <span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="n">assets</span><span class="p">)</span>
        <span class="k">print</span><span class="p">(</span><span class="n">p_assets</span><span class="p">)</span>
        <span class="k">assert</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Provides 15 lines that look like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/Users/ron/PycharmProjects/dungeon/.venv/lib/python3.12
/site-packages/arcade/resources/assets/images
/items/keyGreen.png
</code></pre></div></div>
<p>I also learn that there is a way to provide one’s own <code class="language-plaintext highlighter-rouge">resource handle</code>. After a bit of messing about this test runs:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_resource_handle</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">base</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
        <span class="n">resolved</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">base</span><span class="p">).</span><span class="n">resolve</span><span class="p">()</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span>
                    <span class="n">resolved</span><span class="p">)</span>
        <span class="n">path</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="s">':ron:lever/1.png'</span><span class="p">)</span>
        <span class="k">assert</span> <span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">==</span> <span class="n">base</span> <span class="o">+</span> <span class="s">'lever/1.png'</span>
</code></pre></div></div>
<p>I think that means that if I were to add the right resource handles, we could use shorter names. That would be nice.</p>
<p>After a bit more reading I find that I don’t have to resolve the path. This works:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_resource_handle</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">base</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span>
                    <span class="n">base</span><span class="p">)</span>
        <span class="n">path</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="s">':ron:lever/1.png'</span><span class="p">)</span>
        <span class="k">assert</span> <span class="nb">str</span><span class="p">(</span><span class="n">path</span><span class="p">)</span> <span class="o">==</span> <span class="n">base</span> <span class="o">+</span> <span class="s">'lever/1.png'</span>
</code></pre></div></div>
<p>I had a test fail that made me think I needed to do that but it must have been due to a typo or something.</p>
<p>I wonder if it will now resolve without providing the handle. No. Apparently <code class="language-plaintext highlighter-rouge">resolve</code> doesn’t try all the handles. Is there some way to do that? We can add multiple directories under one handle, and it will search all of them.</p>
<p>We only really use two paths currently:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">resources</span><span class="o">=</span><span class="s">':resources:images/items/'</span>
    <span class="n">my_resources</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
</code></pre></div></div>
<p>Can’t we make a single resource handle for both those? Let’s try another test.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_combined_handle</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">resources</span> <span class="o">=</span> <span class="s">':resources:images/items/'</span>
        <span class="n">arcade_resources</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">resources</span><span class="p">)</span>
        <span class="n">my_resources</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span><span class="n">arcade_resources</span><span class="p">)</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span><span class="n">my_resources</span><span class="p">)</span>
        <span class="n">lever</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="s">':ron:lever/1.png'</span><span class="p">)</span>
        <span class="n">keyRed</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="s">':ron:keyRed.png'</span><span class="p">)</span>
</code></pre></div></div>
<p>This test fails if it can’t resolve those resources, and it passes. So I’ve created a single handle ‘:ron:’’ that can point to all our resources.</p>
<p>That, alone, resolves one of our goals, a more convenient notation. Let’s change main to assume that it can use ‘:ron:’ and make it work. We’ll start with a test.</p>
<p>My plan is that DungeonView, which it starts up, will register the handle and paths. Then when a ContentView goes for a resource, it should always be able to find it. I’m not sure what it’ll take. We’ll make the test work, learning how along the way.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_content_view_uses_ron_handle</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">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">dv</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">testing</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># inits handle
</span>        <span class="n">resource</span> <span class="o">=</span> <span class="s">':ron:Skeleton1.png'</span>
        <span class="n">content</span> <span class="o">=</span> <span class="n">ContentFactory</span><span class="p">().</span><span class="n">decor</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'test'</span><span class="p">,</span> <span class="n">resource</span><span class="o">=</span><span class="n">resource</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
        <span class="n">cv</span> <span class="o">=</span> <span class="n">ContentView</span><span class="p">(</span><span class="n">Cell</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">content</span><span class="p">,</span> <span class="n">content</span><span class="p">.</span><span class="n">resources</span><span class="p">,</span> <span class="n">content</span><span class="p">.</span><span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>This fails because the handle isn’t defined yet, by DungeonView, which needs to be enhanced:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">setup_assets</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">arcade_resources</span> <span class="o">=</span> <span class="s">':resources:images/items/'</span>
        <span class="n">arcade_resources</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">resolve</span><span class="p">(</span><span class="n">arcade_resources</span><span class="p">)</span>
        <span class="n">my_resources</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/'</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span><span class="n">arcade_resources</span><span class="p">)</span>
        <span class="n">arcade</span><span class="p">.</span><span class="n">resources</span><span class="p">.</span><span class="n">add_resource_handle</span><span class="p">(</span><span class="s">'ron'</span><span class="p">,</span><span class="n">my_resources</span><span class="p">)</span>
</code></pre></div></div>
<p>I call that in the <strong><code class="language-plaintext highlighter-rouge">init</code></strong>. Test passes, which tells us that the ContentView’s <code class="language-plaintext highlighter-rouge">load_texture</code> accepts the handle.</p>
<p>But let’s make it harder. Let’s not mention the handle, so that our users can just name the short path and file that they want.</p>
<p>I am mistaken. ContentView, because we cannot crash, recovers if it cannot find the file specified.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="s">':resources:images/items/star.png'</span>
    <span class="k">def</span> <span class="nf">__</span><span class="sb">`init`</span><span class="n">__</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">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>I could just make this work: I think I know exactly what needs to be done. Let’s add an error flag to CntentView:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="s">':resources:images/items/star.png'</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">True</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>Now my test can check the flag.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_content_view_uses_ron_handle</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">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">dv</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">testing</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="c1"># inits handle
</span>        <span class="n">resource</span> <span class="o">=</span> <span class="s">'Skeleton1.png'</span>
        <span class="n">content</span> <span class="o">=</span> <span class="n">ContentFactory</span><span class="p">().</span><span class="n">decor</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'test'</span><span class="p">,</span> <span class="n">resource</span><span class="o">=</span><span class="n">resource</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
        <span class="n">cv</span> <span class="o">=</span> <span class="n">ContentView</span><span class="p">(</span><span class="n">Cell</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">content</span><span class="p">,</span> <span class="n">content</span><span class="p">.</span><span class="n">resources</span><span class="p">,</span> <span class="n">content</span><span class="p">.</span><span class="n">scale</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">cv</span><span class="p">.</span><span class="n">error</span>
</code></pre></div></div>
<p>Test fails as anticipated. Fix ContentView to add the ‘:ron:’ internally.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':ron:'</span><span class="o">+</span><span class="n">resource</span><span class="p">)</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">True</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span><span class="p">)</span>
</code></pre></div></div>
<p>Now I think the entire game will break until we fix up main. It does:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ValueError</span><span class="p">:</span> <span class="n">Invalid</span> <span class="n">resource</span> <span class="n">handle</span> <span class="s">'ron::resources:images/items/keyRed.png'</span>
</code></pre></div></div>
<p>In main we need to change things like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">appearing</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">appearing</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'a brilliant torch'</span><span class="p">,</span> <span class="n">resource</span><span class="o">=</span><span class="n">my_resources</span> <span class="o">+</span> <span class="s">'torch/1.png'</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
    <span class="n">cell9</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">36</span><span class="p">,</span> <span class="mi">29</span><span class="p">)</span>
    <span class="n">cell9</span><span class="p">.</span><span class="n">add_content</span><span class="p">(</span><span class="n">appearing</span><span class="p">)</span>
</code></pre></div></div>
<p>To this</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">appearing</span> <span class="o">=</span> <span class="n">factory</span><span class="p">.</span><span class="n">appearing</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'a brilliant torch'</span><span class="p">,</span> <span class="n">resource</span><span class="o">=</span><span class="s">'torch/1.png'</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
    <span class="n">cell9</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">36</span><span class="p">,</span> <span class="mi">29</span><span class="p">)</span>
    <span class="n">cell9</span><span class="p">.</span><span class="n">add_content</span><span class="p">(</span><span class="n">appearing</span><span class="p">)</span>
</code></pre></div></div>
<p>And similarly in the ContentFactory. The game works. But there are tests that are using the long form. Let’s make them break:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="s">':resources:images/items/star.png'</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="s">':ron:'</span><span class="o">+</span><span class="n">resource</span><span class="p">)</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">error</span> <span class="o">=</span> <span class="bp">True</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">error</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>I am somewhat surprised. I expected tests to be providing parameters that couldn’t resolve. Find all the users of ContentView and look. There was one referring to the old ‘:resources:’ path. We will probably discover, in the future, that assuming everything can be found under ‘:ron:’ isn’t quite right. If we do discover that, we’ll deal with it. Dealing with it now would be possible but speculative.</p>
<p>I’ll remove the assert from ContentView but retain the error flag. Commit: <em>ContentView assumes resources are in the handle :ron:</em></p>
<p>Let’s sum up.</p>
<h2 id="summary">Summary</h2>
<p>I wasn’t aware of how the :resource: things worked in arcade, but a bit of Reading of The Fine Manual (RTFM) taught me enough so that I could fumble my way to a single resource handle, :ron:, that points to my desktop folders, which are good enough for now, and the built-in assets that we use. So now any short-path file name will search both those folders, my desktop first, and all the file names in the system are now simple file names, or minimally prefixed like ‘lever/1.png’.</p>
<p>We changed DungeonView, as the first and topmost class that cares about images, to define our new handle to arcade. Then ContentView helpfully prefixes incoming resource short-path names with :ron: and resolves and loads the texture.</p>
<p>In our next phase, we’ll build a little cache of some kind so as to load textures only once. And at some future date, we should look at doing something similar for the flooring, which has a lot of reuse of the same tiles over and over.</p>
<p>A little learning, a little code, and how the references to resources are much more convenient. And we’re in a perfect position to cache them so as only to load them once.</p>
<p>Nice. Not bad at all. See you next time!</p>
 ]]> 
                </content:encoded>
                <pubDate>Thu, 16 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/y/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/y/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Sweet Little 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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>Sweet little idea … I have one. Very small but I think it’ll make things just a bit nicer. We’ll see.</p>
</blockquote>
<p>I’ve been improving the code both in the last few articles and sometimes when you weren’t watching. If there has been a theme to the work, it has been to move responsibility for things to the places where they seem to fit better. Something something cohesion. I know—or used to know—all that theory, but these days I follow much simpler practices, removing duplication, observing Feature Envy and moving code to the class we’ve been fondling a bit too much, and so on.</p>
<p>Much of what I’ve done the past few days has added capability to Cell class. I think it’s worth mentioning how that works, since a Cell knows essentially nothing:</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="bp">self</span><span class="p">.</span><span class="n">_xy</span> <span class="o">=</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>A Cell is just a tuple with delusions of grandeur, because it can do many things. Its methods include <code class="language-plaintext highlighter-rouge">add_content, attempt_move, available_neighbors, can_move_to, center_position, contents, creating, distance, has_passage, is_available, is_in_a_room, layout, manhattan_distance, neighbors, offset_by, randomized_neighbors, remove_content, room, vector_diff, x, xy, y</code>.</p>
<p>How can an object containing just x-y coordinates do all that? It’s a bit tricky, but I think not <em>too</em> tricky, considering the power it gives us.</p>
<p>During game execution, all the Cell instances are stored in the DungeonLayout, which includes data structures for associating content with cells, for adding content, for allocating cells, creating rooms and so on. DungeonLayout creates all the Cells that will ever exist during the game . It creates them with Cell(x,y), and stores them in a dictionary indexed by (x,y).</p>
<p>Once that is done, if any code creates Cell(x,y), they get, not a new Cell, but the one that the layout created. 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">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">:</span>
        <span class="p">...</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_create_cells</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="p">...</span>
    <span class="k">def</span> <span class="nf">_create_cells</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="k">with</span> <span class="n">Cell</span><span class="p">.</span><span class="n">creating</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">layout</span> <span class="o">=</span> <span class="bp">None</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="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="bp">self</span><span class="p">.</span><span class="n">cells</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="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="c1"># cell access
</span>
    <span class="k">def</span> <span class="nf">_at_xy</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">xy</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">xy</span><span class="p">,</span> <span class="bp">None</span><span class="p">)</span>
</code></pre></div></div>
<p>Meanwhile, Cell has these methods:</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">layout</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="o">@</span><span class="nb">classmethod</span>
    <span class="o">@</span><span class="n">contextmanager</span>
    <span class="k">def</span> <span class="nf">creating</span><span class="p">(</span><span class="n">cls</span><span class="p">,</span> <span class="n">layout</span><span class="p">):</span>
        <span class="n">Cell</span><span class="p">.</span><span class="n">layout</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="k">yield</span>
        <span class="k">finally</span><span class="p">:</span>
            <span class="n">Cell</span><span class="p">.</span><span class="n">layout</span> <span class="o">=</span> <span class="n">layout</span>
    <span class="k">def</span> <span class="nf">__new__</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">if</span> <span class="n">cls</span><span class="p">.</span><span class="n">layout</span><span class="p">:</span>
            <span class="c1"># noinspection PyProtectedMember
</span>            <span class="k">return</span> <span class="n">cls</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">_at_xy</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="nb">super</span><span class="p">().</span><span class="n">__new__</span><span class="p">(</span><span class="n">cls</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="bp">self</span><span class="p">.</span><span class="n">_xy</span> <span class="o">=</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><code class="language-plaintext highlighter-rouge">creating</code> is what Python calls a context manager. It’s commonly used to open a file and return it for subsequent use, guaranteeing that it’ll be closed when the calling code exists the <code class="language-plaintext highlighter-rouge">with</code> block.</p>
<p>In this case, we clear Cell’s class variable <code class="language-plaintext highlighter-rouge">layout</code> and yield, dropping into the double loop in <code class="language-plaintext highlighter-rouge">_create_cells</code>. When that code creates a Cell, we enter <code class="language-plaintext highlighter-rouge">Cell.__new__</code> and discover that there is no layout, so we create the Cell, and return it. The <code class="language-plaintext highlighter-rouge">super()--new__</code> runs the <code class="language-plaintext highlighter-rouge">__init__</code> and we have a brand new cell, which DungeonLayout duly tucks away.</p>
<p>When that creation loop ends, the context manager code puts the DungeonLayout instance into its class member <code class="language-plaintext highlighter-rouge">layout</code>.</p>
<p>The effect of all that is that after DungeonLayout creates all the Cells, any time we say Cell(x,y), the <code class="language-plaintext highlighter-rouge">__new__</code> code runs, finds <code class="language-plaintext highlighter-rouge">cls.layout</code>, and instead of creating yet another Cell, returns the one that is already in DungeonLayout.</p>
<p>From the viewpoint of a Cell, we now have access to the layout that contains the Cell. Individual cells do not contain a pointer to the layout, but the class does. So inside Cell code, <code class="language-plaintext highlighter-rouge">self.layout</code> will return the current layout. And that’s how we can implement something like this:</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">room</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">get_room</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">is_available</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">room</span> <span class="ow">is</span> <span class="bp">None</span>
    <span class="o">@</span><span class="nb">property</span>
    <span class="k">def</span> <span class="nf">is_in_a_room</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">room</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span>
</code></pre></div></div>
<p>In our code, if we want to know a Cell’s room, or whether it is available, we just ask it. Inside the works, that turns into a lookup inside the layout, which has enough information to answer the question.</p>
<p>The result is much more readable code, since all the <code class="language-plaintext highlighter-rouge">layout.contents_at(cell)</code> kind of thing is expressed as <code class="language-plaintext highlighter-rouge">cell.contents()</code>. And much of what I’ve been doing over the past few days has been moving little methods into Cell,making the code all a bit more clear.</p>
<dl>
  <dt>Isn’t that less efficient?</dt>
  <dd>Yes. It is one method call more costly. Generally speaking, even in Python, which is notoriously slow, adding a single method dispatch is almost always negligible, unless it’s in a very long very tight loop. In practice, I would never hesitate to defer something rather than inline it, often even just for clarity. Here we get not just clarity but a simpler notation.</dd>
</dl>
<h2 id="todays-idea">Today’s Idea</h2>
<p>OK, that was then. What about now? I’m thinking about this method:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">offset_by</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">offset</span><span class="p">):</span>
        <span class="n">x</span><span class="p">,</span><span class="n">y</span> <span class="o">=</span> <span class="n">offset</span>
        <span class="k">return</span> <span class="n">Cell</span><span class="p">(</span><span class="n">x</span><span class="o">+</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="o">+</span><span class="bp">self</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
</code></pre></div></div>
<p>We use this method when we calculate neighbors, and occasional other times. And it’s just fine. Except  that rather than this:</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">neighbors</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">neighbor</span>
                <span class="k">for</span> <span class="n">xy</span> <span class="ow">in</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="k">if</span> <span class="p">(</span><span class="n">neighbor</span> <span class="p">:</span><span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">offset_by</span><span class="p">(</span><span class="n">xy</span><span class="p">))]</span>
</code></pre></div></div>
<p>I’d prefer to write this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">neighbors</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">neighbor</span>
                <span class="k">for</span> <span class="n">xy</span> <span class="ow">in</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="k">if</span> <span class="p">(</span><span class="n">neighbor</span> <span class="p">:</span><span class="o">=</span> <span class="bp">self</span> <span class="o">+</span> <span class="n">xy</span><span class="p">)]</span>
</code></pre></div></div>
<p>No biggie. I just like it better. Let’s make it so:</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">__add__</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="n">x</span><span class="p">,</span><span class="n">y</span> <span class="o">=</span> <span class="n">other</span>
        <span class="k">return</span> <span class="n">Cell</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="p">,</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="p">)</span>
</code></pre></div></div>
<p>Now we can change <code class="language-plaintext highlighter-rouge">offset_by</code> to use <code class="language-plaintext highlighter-rouge">+</code>.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">offset_by</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">offset</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span> <span class="o">+</span> <span class="n">offset</span>
</code></pre></div></div>
<p>Now we can inline <code class="language-plaintext highlighter-rouge">offset_by</code>. PyCharm fails to find two. I do them by hand. Remove the method. Commit.</p>
<p>Scanning Cell, I notice this:</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">vector_diff</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">-</span> <span class="n">target</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">y</span> <span class="o">-</span> <span class="n">target</span><span class="p">.</span><span class="n">y</span>
</code></pre></div></div>
<p>We could implement <code class="language-plaintext highlighter-rouge">__sub__</code> and simplify the users of that method. It’s really only used here:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">manhattan_distance</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
        <span class="n">x</span><span class="p">,</span><span class="n">y</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">vector_diff</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">abs</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">+</span> <span class="nb">abs</span><span class="p">(</span><span class="n">y</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">distance</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">math</span><span class="p">.</span><span class="n">hypot</span><span class="p">(</span><span class="o">*</span><span class="bp">self</span><span class="p">.</span><span class="n">vector_diff</span><span class="p">(</span><span class="n">target</span><span class="p">))</span>
    <span class="k">def</span> <span class="nf">vector_diff</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">target</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">x</span> <span class="o">-</span> <span class="n">target</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">y</span> <span class="o">-</span> <span class="n">target</span><span class="p">.</span><span class="n">y</span>
</code></pre></div></div>
<p>There is a test that uses it as well, though it didn’t really need to. I don’t think we get any mileage out of doing <code class="language-plaintext highlighter-rouge">__sub__</code> and since the dunder methods are at least one layer down in the bag of tricks, I wouldn’t use them without a decent amount of returned value. Here, I don’t see it.</p>
<h2 id="summary">Summary</h2>
<p>Same idea, a named method could be replaced by an arithmetic operator. In both cases, the operator would be used correctly. But in the one case I chose to do it, and in the other, I did not. The difference: adding the <code class="language-plaintext highlighter-rouge">+</code> capability impacted a bit more code and some of it was outside Cell. In the case of <code class="language-plaintext highlighter-rouge">-</code>, all the production code impacted is inside Cell itself, so no real expressive advantage materializes.</p>
<p>Bottom line, for me, a very small change, a small but non-zero code improvement, the program is just a bit easier to work with.</p>
<p>Worth it? If you’re a human, certainly. If you’re not a human (or some other kind of sentient biology-based organism) stop reading my site.</p>
<p>See you next time!</p>
<hr />
<h2 id="postscript">Postscript</h2>
<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">attempt_move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">direction</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_get_connection_in_direction</span><span class="p">(</span><span class="n">direction</span><span class="p">).</span><span class="n">move</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">_get_connection_in_direction</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">direction</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_get_connection_offset</span><span class="p">(</span><span class="n">direction</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">_get_connection_offset</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">offset</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_get_connection</span><span class="p">(</span><span class="bp">self</span> <span class="o">+</span> <span class="n">offset</span><span class="p">)</span>
</code></pre></div></div>
<p>We can inline:</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">direction</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_get_connection_in_direction</span><span class="p">(</span><span class="n">direction</span><span class="p">).</span><span class="n">move</span><span class="p">()</span>
    <span class="k">def</span> <span class="nf">_get_connection_in_direction</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">direction</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_get_connection</span><span class="p">(</span><span class="bp">self</span> <span class="o">+</span> <span class="n">direction</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
</code></pre></div></div>
<p>And inline again:</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">direction</span><span class="p">):</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">_get_connection</span><span class="p">(</span><span class="bp">self</span> <span class="o">+</span> <span class="n">direction</span><span class="p">.</span><span class="n">value</span><span class="p">).</span><span class="n">move</span><span class="p">()</span>
</code></pre></div></div>
<p>Worth it? Maybe. I’m keeping it.</p>
 ]]> 
                </content:encoded>
                <pubDate>Wed, 15 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/r/z/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/r/z/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Futzing</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>I have an on-line meeting that I can pay only small attention to. Herewith, a report on whatever tiny changes I find.</p>
</blockquote>
<p>No plan This is just some time to spare, improving what I see. I’ll start from Dungeon, I think, but look to the views when Dungeon bores me.</p>
<p>Here is a method used only once other than by tests:</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="c1"># no longer in use?
</span>    <span class="k">def</span> <span class="nf">set_player_position_with_interaction</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="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">_interactions_allow_move</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">just_set_player_position</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
</code></pre></div></div>
<p>It has something like seven tests, however. I think we’ll let it be.</p>
<p>There’s this method, which I used once and was proud of:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">maker_flood</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">flood_list</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">flood_list</span> <span class="o">=</span> <span class="n">SpriteList</span><span class="p">()</span>
            <span class="k">return</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">distance</span> <span class="ow">in</span> <span class="p">(</span><span class="n">Flooder</span><span class="p">(</span><span class="n">layout</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">,</span> <span class="n">origin</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">player_cell</span><span class="p">)</span>
                <span class="p">.</span><span class="n">can_traverse</span><span class="p">()</span>
                <span class="p">.</span><span class="n">flood</span><span class="p">()):</span>
            <span class="n">cx</span><span class="p">,</span> <span class="n">cy</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">center_position</span><span class="p">(</span><span class="n">cell_size</span><span class="p">)</span>
            <span class="n">text</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">create_text_sprite</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">distance</span><span class="p">),</span><span class="n">font_size</span><span class="o">=</span><span class="mi">8</span><span class="p">)</span>
            <span class="n">text</span><span class="p">.</span><span class="n">center_x</span> <span class="o">=</span> <span class="n">cx</span>
            <span class="n">text</span><span class="p">.</span><span class="n">center_y</span> <span class="o">=</span> <span class="n">cy</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">flood_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</code></pre></div></div>
<p>It fills the screen with numbers reflecting the distance of the cell from the current player position. It’s not useful just now but if w get into searching trouble, it may come in handy. We’ll preserve it.</p>
<p>This is awkward:</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">contents_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="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">contents</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
</code></pre></div></div>
<p>Dungeon maintains <code class="language-plaintext highlighter-rouge">self.contents</code> as a <code class="language-plaintext highlighter-rouge">dict[Cell, list[Content]]</code>. From a code-reading viewpoint, one would prefer <code class="language-plaintext highlighter-rouge">cell.contents()</code> or <code class="language-plaintext highlighter-rouge">cell.contents</code>. Cells, however, do not know the Dungeon, only the Layout. This may be hinting that contents should be a layout property. I think that would be a bit of an invasive change. But maybe not, if we “just” forwarded.</p>
<p>Let’s see how DungeonLayout would feel about that.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">DungeonLayout</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">max_x</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">max_y</span><span class="o">=</span><span class="mi">10</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">max_x</span> <span class="o">=</span> <span class="n">max_x</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">max_y</span> <span class="o">=</span> <span class="n">max_y</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cells</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">int</span><span class="p">],</span> <span class="n">Cell</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">_create_cells</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">rooms</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">border_map</span><span class="p">:</span><span class="n">BorderMap</span><span class="o">|</span><span class="bp">None</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">passages</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">tuple</span><span class="p">[</span><span class="n">Cell</span><span class="p">,</span> <span class="n">Cell</span><span class="p">],</span> <span class="nb">bool</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">room_map</span><span class="p">:</span> <span class="nb">dict</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="o">=</span> <span class="nb">dict</span><span class="p">()</span>
</code></pre></div></div>
<p>Let’s change Dungeon here:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">contents_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="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">contents_at</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">place_content_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="n">content</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">place_content_at</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">run</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">layout</span><span class="p">.</span><span class="n">run_contents</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">remove_content_from_cell</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="n">containing_cell</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">remove_content_at</span><span class="p">(</span><span class="n">containing_cell</span><span class="p">,</span> <span class="n">content</span><span class="p">)</span>
</code></pre></div></div>
<p>That breaks 12 tests, no surprise there. In DungeonLayout</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="c1"># contents
</span>
    <span class="k">def</span> <span class="nf">contents_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="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">contents</span><span class="p">[</span><span class="n">cell</span><span class="p">]</span>
    <span class="k">def</span> <span class="nf">place_content_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="n">content</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">contents</span><span class="p">[</span><span class="n">cell</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">find_cell_containing_name</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">item_name</span><span class="p">):</span>
        <span class="n">cell</span> <span class="o">=</span> <span class="bp">None</span>
        <span class="k">for</span> <span class="n">candidate</span><span class="p">,</span> <span class="n">items</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">contents</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">for</span> <span class="n">item</span> <span class="ow">in</span> <span class="n">items</span><span class="p">:</span>
                <span class="k">if</span> <span class="n">item</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">item_name</span><span class="p">:</span>
                    <span class="n">cell</span> <span class="o">=</span> <span class="n">candidate</span>
        <span class="k">return</span> <span class="n">cell</span>
</code></pre></div></div>
<p>We are green and the contents dictionary is gone from Dungeon. I find two more methods and they have no tests:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">remove_content_from_cell</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content</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">contents</span><span class="p">[</span><span class="n">cell</span><span class="p">].</span><span class="n">remove</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">run_contents</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pub_sub</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">cell</span><span class="p">,</span> <span class="n">contents</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">contents</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
            <span class="k">for</span> <span class="n">content</span> <span class="ow">in</span> <span class="n">contents</span><span class="p">:</span>
                <span class="n">content</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">pub_sub</span><span class="p">)</span>
</code></pre></div></div>
<p>That should be corrected. And we can do some convenience methods in Cell now. Possibly get rid of some of the forwarding.</p>
<p>This is enough for an afternoon’s idleness. Commit: <em>moving contents handling to Layout. Forwarding in Dungeon is in for now.</em></p>
<h2 id="summary">Summary</h2>
<p>Small steps, small improvements. Done over time, done all in a batch, doesn’t matter. Things improve. This is the way.</p>
<h2 id="postscript">Postscript</h2>
<p>Adding <code class="language-plaintext highlighter-rouge">contents()</code> as a method on Cell lets me remove <code class="language-plaintext highlighter-rouge">Dungeon.contents_at</code> entirely. I think a global replace or two might do the same for the remove method. We’ll leave that for another day.</p>
<p>See you soon!</p>
 ]]> 
                </content:encoded>
                <pubDate>Tue, 14 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/s/a/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/s/a/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
              <item>
                <title>Can&apos;t Sleep</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"}
-->
<!--
https://ronjeffries.com
-->
<!-- CHANGE ME  -->
<p>Hello, loves!</p>
<blockquote>
  <p>Let’s see if I can tire myself out with code. I enjoy crafting code and ain’t no oligarch-feeding resource-consuming job-stealing computer program gonna take that away from me.</p>
</blockquote>
<p>It’s 0030, I can’t sleep, so I’m going to code. We’ll begin with reviewing the responsibilities and connections of the various objects and layers that make up our design. I’ve been finding it hard to think about and work with and want to understand why. I suspect responsibilities in the wrong place, connections that aren’t right, the usual suspects.</p>
<h2 id="time-passes-">Time Passes …</h2>
<p>I annotated some dictionaries to make it more clear to me what’s in them. Then I moved about nine or ten methods out of DungeonLayout, the ones answering questions about cells, such as <code class="language-plaintext highlighter-rouge">layout.is_available(cell)</code> and the like. I provided similar methods and properties on Cell, such as <code class="language-plaintext highlighter-rouge">cell.is_available</code>. Those were all straightforward, although to finish the cleanup, I had to edit all the old versions to use the new versions. In a large system, that part could have been undertaken over time, with the <code class="language-plaintext highlighter-rouge">layout</code> ones deprecated. With everything in one repo and PyCharm at our fingertips, it was easy enough to do each one completely.</p>
<p>The result was to simplify DungeonLayout substantially, while the corresponding capabilities make more sense in Cell, and are usually a bit simpler as well. I’ll spare you the details. I believe that no test ever failed during this cycle. Certainly there was no significant trouble.</p>
<p>I got to bed at 0200 and slept uneventfully until 0830 or something like that.</p>
<h2 id="motivation">Motivation</h2>
<p>Of course I would not claim that I really understand why I do things, but certainly I have been thinking for some time that the DungeonLayout / Dungeon / DungeonView family are too complex and that there are aspects in at least some of those classes that should be pulled over to another of the family, or perhaps into helper classes. So simplifying is on my mind.</p>
<h2 id="this-morning-">This Morning …</h2>
<p>I plan to continue looking for ways to simplify and normalize the design. It’s high time, and there’s no one to say that I can’t.</p>
<p>One concern that I have has to do with Content, because a content item has a very substantial multiple personality. It has a presence in the Layout, in the sense that it is “in” a Cell. It has a presence in the Dungeon, in that Dot interacts with it when she moves, and the content item can even prohibit her from moving. It has a presence in the Views, in that its graphical representation resides in the sprite lists. And there is interaction among these roles.</p>
<p>Take Spikes, for example. They have three operating states, cycling, stopped up, and stopped down. They have two graphical states, up and down. When fully implemented, they will inconvenience Dot when up or running, and may even prohibit her from entering. They are controlled by another content item, which, when Dot bumps it, changes its own visible character, and publishes its state, which the spikes hear and use to change their own state.</p>
<p>Having told that story, I’m motivated to track through some of the code that is involved. Here’s the creation method:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentFactory</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">spikes</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
        <span class="n">resource1</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/trap/1.png'</span>
        <span class="n">resource2</span> <span class="o">=</span> <span class="s">'/Users/ron/Desktop/DungeonTiles/png/objects/trap/2.png'</span>
        <span class="n">resources</span> <span class="o">=</span> <span class="p">[</span><span class="n">resource1</span><span class="p">,</span> <span class="n">resource2</span><span class="p">]</span>
        <span class="n">scale</span> <span class="o">=</span> <span class="mf">0.75</span>
        <span class="n">cases</span> <span class="o">=</span> <span class="p">{</span>
            <span class="mi">0</span><span class="p">:</span> <span class="p">(</span><span class="bp">True</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="p">(</span><span class="bp">False</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
            <span class="mi">2</span><span class="p">:</span> <span class="p">(</span><span class="bp">False</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
            <span class="mi">3</span><span class="p">:</span> <span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
        <span class="p">}</span>
        <span class="n">info</span> <span class="o">=</span> <span class="n">SimpleNamespace</span><span class="p">(</span><span class="n">cycling</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">cases</span><span class="o">=</span><span class="n">cases</span><span class="p">,</span> <span class="n">time</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">cycle</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pub_sub</span><span class="p">,</span> <span class="n">delta_time</span><span class="p">):</span>
            <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="p">.</span><span class="n">info</span><span class="p">.</span><span class="n">cycling</span><span class="p">:</span> <span class="k">return</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">info</span><span class="p">.</span><span class="n">time</span> <span class="o">+=</span> <span class="n">delta_time</span>
            <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">info</span><span class="p">.</span><span class="n">time</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">info</span><span class="p">.</span><span class="n">time</span> <span class="o">=</span> <span class="mi">0</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">=</span> <span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">state</span><span class="o">+</span><span class="mi">1</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">resources</span><span class="p">)</span>
                <span class="n">pub_sub</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="s">'state_number'</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span> <span class="n">state</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">state</span><span class="p">)</span>
        <span class="n">cycle_sub</span> <span class="o">=</span> <span class="n">Subscription</span><span class="p">(</span><span class="n">event</span><span class="o">=</span><span class="s">'on_update'</span><span class="p">,</span> <span class="n">caller_id</span><span class="o">=</span><span class="s">'view'</span><span class="p">,</span> <span class="n">callback</span><span class="o">=</span><span class="n">cycle</span><span class="p">)</span>
        <span class="k">def</span> <span class="nf">control</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">pub_sub</span><span class="p">,</span> <span class="n">state</span><span class="p">):</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="bp">self</span><span class="p">.</span><span class="n">info</span><span class="p">.</span><span class="n">cycling</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">state</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">info</span><span class="p">.</span><span class="n">cases</span><span class="p">[</span><span class="n">state</span><span class="p">]</span>
                <span class="n">pub_sub</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="s">'state_number'</span><span class="p">,</span> <span class="bp">self</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="bp">self</span><span class="p">,</span> <span class="n">state</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">state</span><span class="p">)</span>
            <span class="k">except</span> <span class="nb">KeyError</span><span class="p">:</span>
                <span class="k">return</span>
        <span class="n">control_sub</span> <span class="o">=</span> <span class="n">Subscription</span><span class="p">(</span><span class="n">event</span><span class="o">=</span><span class="s">'control'</span><span class="p">,</span> <span class="n">caller_id</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">callback</span><span class="o">=</span><span class="n">control</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">Content</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">resources</span><span class="o">=</span><span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="n">scale</span><span class="p">,</span>
                       <span class="n">info</span><span class="o">=</span><span class="n">info</span><span class="p">,</span> <span class="n">subs</span><span class="o">=</span><span class="p">[</span><span class="n">cycle_sub</span><span class="p">,</span> <span class="n">control_sub</span><span class="p">],</span>
                       <span class="p">)</span>
</code></pre></div></div>
<p>NOte that it has two event callbacks, <code class="language-plaintext highlighter-rouge">cycle</code> and <code class="language-plaintext highlighter-rouge">control</code>, responding to ‘on_update’ and ‘control’, respectively. Both of those events publishes <code class="language-plaintext highlighter-rouge">state_number</code>. That event is fielded 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="k">def</span> <span class="nf">subscribe_to_state_number</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="k">def</span> <span class="nf">callback</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">pub_sub</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="n">state</span><span class="p">):</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">view_do</span><span class="p">(</span>
                <span class="n">content</span><span class="p">,</span>
                <span class="k">lambda</span> <span class="n">view</span><span class="p">:</span> <span class="n">view</span><span class="p">.</span><span class="n">set_state</span><span class="p">(</span><span class="n">state</span><span class="p">))</span>
        <span class="n">dungeon</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="s">'state_number'</span><span class="p">,</span> <span class="s">''</span><span class="p">,</span> <span class="n">callback</span><span class="p">)</span>
</code></pre></div></div>
<p>That callback is given just the Content instance and its state. It finds the ContentView instance corresponding to that Content, and sends it <code class="language-plaintext highlighter-rouge">set_state</code>:</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">view_do</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">action</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="n">view</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">content_views</span><span class="p">[</span><span class="n">content_item</span><span class="p">]</span>
            <span class="n">action</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="k">except</span> <span class="nb">KeyError</span><span class="p">:</span>
            <span class="k">return</span>
<span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">set_state</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">state_number</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</span><span class="p">(</span><span class="n">state_number</span><span class="p">)</span>
</code></pre></div></div>
<p>When the ContentViews are set up, each one builds a sprite containing all the textures (images) that the sprite can take on:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ContentView</span><span class="p">:</span>
    <span class="n">error_texture</span> <span class="o">=</span> <span class="s">':resources:images/items/star.png'</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">cell</span><span class="p">,</span> <span class="n">content_item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mf">0.5</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">item</span> <span class="o">=</span> <span class="n">content_item</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span> <span class="o">=</span> <span class="n">Sprite</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">resource</span> <span class="ow">in</span> <span class="n">resources</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="n">resource</span><span class="p">)</span>
            <span class="k">except</span> <span class="p">(</span><span class="nb">FileNotFoundError</span><span class="p">,</span> <span class="nb">AttributeError</span><span class="p">):</span>
                <span class="n">texture</span> <span class="o">=</span> <span class="n">arcade</span><span class="p">.</span><span class="n">load_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">error_texture</span><span class="p">)</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">append_texture</span><span class="p">(</span><span class="n">texture</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">set_texture</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">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">False</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">scale</span> <span class="o">=</span> <span class="n">scale_texture</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">textures</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">scale</span><span class="p">)</span>
</code></pre></div></div>
<p>The ContentItems provide the Views with a mapping from the item to the item’s cell and its sprite. The DungeonView maintains a dictionary from Content item to ContentView, from Room to RoomView (q.v), and from Room to a list of ContentView. That last structure is used to illuminate content when we illuminate a section of the Dungeon.</p>
<h2 id="so-we-see-">So We See …</h2>
<p>It gets kind of gnarly. I’m going to just look at random methods that seem odd. Here’s one now:</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">is_lit</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="k">return</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</span>
</code></pre></div></div>
<p>Who’s calling that?</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">make_view_and_sprite</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">item</span><span class="p">):</span>
        <span class="n">resources</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">resources</span>
        <span class="n">scale</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">scale</span>
        <span class="n">view</span> <span class="o">=</span> <span class="n">ContentView</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">center_position</span><span class="p">(</span><span class="n">cell_size</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views</span><span class="p">[</span><span class="n">item</span><span class="p">]</span> <span class="o">=</span> <span class="n">view</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_room</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="n">append</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_sprite_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">)</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">is_lit</span><span class="p">(</span><span class="n">cell</span><span class="p">):</span>
            <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>OK, this is there to make dynamically-added content visible on arrival. Any outside callers?</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">RoomView</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">is_lit</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="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">dungeon_view</span><span class="p">.</span><span class="n">is_lit</span><span class="p">(</span><span class="n">cell</span><span class="p">)</span>
</code></pre></div></div>
<p>Right but who’s asking about that?</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">test_room_view_illuminate</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">layout</span> <span class="o">=</span> <span class="n">DungeonLayout</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">)</span>
        <span class="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_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="bp">True</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">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="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span><span class="mi">15</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="mi">9</span><span class="p">,</span><span class="mi">15</span><span class="p">)]</span>
        <span class="n">room</span> <span class="o">=</span> <span class="n">Room</span><span class="p">(</span><span class="n">cells</span><span class="p">,</span> <span class="n">layout</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">room_view</span> <span class="o">=</span> <span class="n">RoomView</span><span class="p">(</span><span class="n">dungeon_view</span><span class="p">,</span> <span class="n">room</span><span class="p">)</span>
        <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span> <span class="o">=</span> <span class="p">{</span><span class="n">cell</span><span class="p">:</span> <span class="n">FakeSprite</span><span class="p">()</span> <span class="k">for</span> <span class="n">cell</span> <span class="ow">in</span> <span class="n">cells</span><span class="p">}</span>
        <span class="n">room_view</span><span class="p">.</span><span class="n">illuminate_around</span><span class="p">(</span><span class="n">Cell</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">12</span><span class="p">),</span> <span class="mi">3</span><span class="p">)</span>
        <span class="n">lit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">13</span><span class="p">,</span> <span class="mi">13</span><span class="p">)</span>
        <span class="n">lit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">lit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">room_view</span><span class="p">.</span><span class="n">is_lit</span><span class="p">(</span><span class="n">lit_cell</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">lit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">True</span>
        <span class="n">unlit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">room_view</span><span class="p">.</span><span class="n">is_lit</span><span class="p">(</span><span class="n">unlit_cell</span><span class="p">)</span>
        <span class="n">unlit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">unlit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">unlit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Let’s change that test to ask the DungeonView, not the room. Then we can remove that method from Room.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="p">...</span>
        <span class="n">lit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">lit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">is_lit</span><span class="p">(</span><span class="n">lit_cell</span><span class="p">)</span>
        <span class="k">assert</span> <span class="n">lit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">True</span>
        <span class="n">unlit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">is_lit</span><span class="p">(</span><span class="n">unlit_cell</span><span class="p">)</span>
        <span class="n">unlit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">unlit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">unlit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Remove the method. Green. Commit. Now inline those two:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="p">...</span>
        <span class="n">lit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">lit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">lit_cell</span> <span class="ow">in</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminated_cells</span>
        <span class="k">assert</span> <span class="n">lit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">True</span>
        <span class="n">unlit_cell</span> <span class="o">=</span> <span class="n">Cell</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span>
        <span class="k">assert</span> <span class="ow">not</span> <span class="n">unlit_cell</span> <span class="ow">in</span> <span class="n">dungeon_view</span><span class="p">.</span><span class="n">illuminated_cells</span>
        <span class="n">unlit</span> <span class="o">=</span> <span class="n">room_view</span><span class="p">.</span><span class="n">cell_sprites</span><span class="p">[</span><span class="n">unlit_cell</span><span class="p">]</span>
        <span class="k">assert</span> <span class="n">unlit</span><span class="p">.</span><span class="n">visible</span> <span class="o">==</span> <span class="bp">False</span>
</code></pre></div></div>
<p>Now there is only one use of DungeonView <code class="language-plaintext highlighter-rouge">is_lit</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">def</span> <span class="nf">make_view_and_sprite</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">item</span><span class="p">):</span>
        <span class="n">resources</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">resources</span>
        <span class="n">scale</span> <span class="o">=</span> <span class="n">item</span><span class="p">.</span><span class="n">scale</span>
        <span class="n">view</span> <span class="o">=</span> <span class="n">ContentView</span><span class="p">(</span><span class="n">cell</span><span class="p">,</span> <span class="n">item</span><span class="p">,</span> <span class="n">resources</span><span class="p">,</span> <span class="n">scale</span><span class="p">)</span>
        <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">position</span> <span class="o">=</span> <span class="n">cell</span><span class="p">.</span><span class="n">center_position</span><span class="p">(</span><span class="n">cell_size</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views</span><span class="p">[</span><span class="n">item</span><span class="p">]</span> <span class="o">=</span> <span class="n">view</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_views_by_room</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="n">append</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">content_sprite_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">)</span>
        <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">is_lit</span><span class="p">(</span><span class="n">cell</span><span class="p">):</span>
            <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>Inline that and remove the method.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="k">if</span> <span class="n">cell</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">illuminated_cells</span><span class="p">:</span>
            <span class="n">view</span><span class="p">.</span><span class="n">sprite</span><span class="p">.</span><span class="n">visible</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p>Commit.</p>
<p class="inline-title">All that for one tiny method?</p>
<p>Well, yes. And it was two tiny methods and included coupling between two classes. I do like tiny methods like <code class="language-plaintext highlighter-rouge">is_lit</code> that cover slightly obscure things like <code class="language-plaintext highlighter-rouge">cell in self.illuminated_cells</code>. You’ve seen me create them many times. Here, where the class has too many methods and the use is all internal to the class, I prefer the slightly less clear construct with the method gone. I could be wrong, of course.</p>
<h2 id="moving-right-along">Moving Right Along</h2>
<p>I’m just spotting things that look like they’d like to be improved. I note that Dungeon class has forwarders for the entire pub_sub protocol:</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">publish</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">caller_id</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pub_sub</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">caller_id</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">subscribe_all</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">subscriptions</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pub_sub</span><span class="p">.</span><span class="n">subscribe_all</span><span class="p">(</span><span class="n">subscriptions</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">subscribe</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">caller_id</span><span class="p">,</span> <span class="n">callback</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pub_sub</span><span class="p">.</span><span class="n">subscribe</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">caller_id</span><span class="p">,</span> <span class="n">callback</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">subscribe_once</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">caller_id</span><span class="p">,</span> <span class="n">callback</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pub_sub</span><span class="p">.</span><span class="n">subscribe_once</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">caller_id</span><span class="p">,</span> <span class="n">callback</span><span class="p">)</span>
</code></pre></div></div>
<p>It’d be nice not to need those. Who is using them? The commonly-used one is <code class="language-plaintext highlighter-rouge">publish</code>. A raft of calls are made to <code class="language-plaintext highlighter-rouge">Interactor</code>, which implements just <code class="language-plaintext highlighter-rouge">publish</code> for use by content that needs to publish stuff.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Interactor</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">cell</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="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
    <span class="k">def</span> <span class="nf">publish</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">caller_id</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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">publish</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">caller_id</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div></div>
<p>Let’s give the Interactor a PubSub. (It could fetch it from Dungeon, but let’s not make that assumption here.)</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Interactor</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">pub_sub</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">dungeon</span> <span class="o">=</span> <span class="n">dungeon</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pub_sub</span> <span class="o">=</span> <span class="n">pub_sub</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">cell</span> <span class="o">=</span> <span class="n">cell</span>
    <span class="k">def</span> <span class="nf">publish</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">caller_id</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pub_sub</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">caller_id</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div></div>
<p>PyCharm fills in the single call to the constructor. Now there are fewer calls to <code class="language-plaintext highlighter-rouge">publish</code> in Dungeon.</p>
<h2 id="time-passes--1">Time Passes …</h2>
<p>Various tedious tiny changes allow me to remove the PubSub protocol from Dungeon. That simplifies the coupling: things that used to go me -&gt; Dungeon -&gt; PubSub now all have direct access to the PubSub instance.</p>
<p>Again, no tests broke substantially along the way. Just calls that needed to be changed to refer to the available pub_sub.</p>
<p>Dungeon is down to just these methods:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">_announce</span>
<span class="n">_interactions_allow_move</span>
<span class="n">_redo_interactions_in_current_cell</span>
<span class="n">announce_via</span>
<span class="n">contents_at</span>
<span class="n">dot_has</span>
<span class="n">find_cell_containing</span>
<span class="n">find_path_to</span>
<span class="n">inventory</span>
<span class="n">just_set_player_position</span>
<span class="n">maker_flood</span>
<span class="n">max_x</span>
<span class="n">max_y</span>
<span class="n">move_player</span>
<span class="n">place_content_at</span>
<span class="n">populate</span>
<span class="n">receive_content_from_cell</span>
<span class="n">remove_content_from_cell</span>
<span class="n">rooms</span>
<span class="n">run</span>
<span class="n">set_player_position_with_interaction</span>
<span class="n">show_path_to</span>
</code></pre></div></div>
<p>That’s fewer than before by quite a few, but looking at the list, it doesn’t tell much of a story, does it? I can see a few themes, which may suggest new objects:</p>
<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">dot_has</code> and <code class="language-plaintext highlighter-rouge">inventory</code> sounds like a Player class wanting to be born. Maybe with <code class="language-plaintext highlighter-rouge">set_player...</code>, <code class="language-plaintext highlighter-rouge">just_set...</code>, and <code class="language-plaintext highlighter-rouge">move_player</code> as well.</p>
  </li>
  <li>
    <p>Why are we using <code class="language-plaintext highlighter-rouge">max_x</code> and <code class="language-plaintext highlighter-rouge">max_y</code> at all? <code class="language-plaintext highlighter-rouge">main</code> uses them to set screen size, we could do that some other way. They seem to be used in DungeonView scrolling. And DungeonView implements them as well, forwarding to Dungeon. We can do better, I’m sure.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">populate</code> is a placeholder. Currently only positions Dot somewhere. <code class="language-plaintext highlighter-rouge">main</code> overrides the setting anyway, mostly for debugging purposes. There will probably be a separate object for allocating things into the Dungeon. Some day.</p>
  </li>
</ul>
<p>Enough. Two hours of refactoring and typing. Enough.</p>
<h2 id="summary">Summary</h2>
<p>If I had to evaluate the overall code quality here, I’d use a term which was used against a team of mine some years back: a lot of it is mediocre. Some is pretty nice, and the individual concepts aren’t too gnarly, but it just doesn’t present itself as clear and clean.</p>
<p>I blame the media. No, I blame the GOP. No, it’s Chet’s fault.</p>
<p class="pull-quote">These are my principles. <br />
If you don’t like them, I have others.<br />
– Groucho Marx</p>
<p>No, it’s just what happens to code as we evolve it. Quite likely many of the issues could have been spotted earlier and dealt with then. But we see what we see, we made the decisions we make, and when they no longer please us, we make others.</p>
<p>Over ninety minutes at oh-dark-thirty and two hours this morning, I’ve made the code much better, and not with any deep thinking, mostly just pushing a bit of code up or down, occasionally sideways, in the class structure. The Dungeon class is much smaller and more cohesive. DungeonView is improved a bit. Cell is much more powerful, but has remained pretty simple, mostly just properties, attributes, and neighbor generation.
There was some tedium rewiring tests but no actual logic changes.</p>
<p>I think the message is that with small changes, even late in the game, we can make things better.</p>
<p>And, of course, my main message is that I enjoy crafting code and ain’t no oligarch-feeding resource-consuming job-stealing computer program gonna take that away from me.</p>
 ]]> 
                </content:encoded>
                <pubDate>Tue, 14 Jul 2026 00:00:00 -0400</pubDate>
                <link>https://ronjeffries.com/articles/-v026/s/b/</link>
                <guid isPermaLink="true">https://ronjeffries.com/articles/-v026/s/b/</guid>
                <category>python</category>
                <category>arcade</category>
                <category>dungeon</category>
                <category>Practices</category>
                <category>tdd</category>
                <category>refactoring</category>
              </item>
      
  </channel>
</rss>
