<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Generative GameDev]]></title><description><![CDATA[An Oxford PhD explores the intersection of Generative AI and Game Development with Unity.]]></description><link>https://gamedev.blog</link><image><url>https://substackcdn.com/image/fetch/$s_!VwmD!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F824a5a9f-304f-4d83-8043-4ef8f0bee872_1024x1024.png</url><title>Generative GameDev</title><link>https://gamedev.blog</link></image><generator>Substack</generator><lastBuildDate>Sat, 11 Apr 2026 08:05:07 GMT</lastBuildDate><atom:link href="https://gamedev.blog/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Stefan Webb]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[genaigamedev@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[genaigamedev@substack.com]]></itunes:email><itunes:name><![CDATA[Stefan Webb]]></itunes:name></itunes:owner><itunes:author><![CDATA[Stefan Webb]]></itunes:author><googleplay:owner><![CDATA[genaigamedev@substack.com]]></googleplay:owner><googleplay:email><![CDATA[genaigamedev@substack.com]]></googleplay:email><googleplay:author><![CDATA[Stefan Webb]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[How is a Mondrian painting like a dungeon map?]]></title><description><![CDATA[Abstract art and dungeon layout: an odd couple]]></description><link>https://gamedev.blog/p/how-is-a-mondrian-painting-like-a</link><guid isPermaLink="false">https://gamedev.blog/p/how-is-a-mondrian-painting-like-a</guid><dc:creator><![CDATA[Stefan Webb]]></dc:creator><pubDate>Thu, 09 Oct 2025 05:32:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nOn3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F61bebde9-68fc-4fba-b81e-96e6426736a4_1169x1166.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/jpeg&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/61bebde9-68fc-4fba-b81e-96e6426736a4_1169x1166.jpeg&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c6b6f417-83ed-4f3a-9841-5ddceb8a6093_640x480.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5599745f-3bfb-4959-89d7-248a034b191b_300x300.png&quot;}],&quot;caption&quot;:&quot;Mondrian painting, binary space partition, and using the partition to lay out rooms and corridors&quot;,&quot;alt&quot;:&quot;Mondrian painting, binary space partition, and using the partition to lay out rooms and corridors&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b72ce656-7c9f-4b00-a0d5-537b2da0d2ac_1456x474.png&quot;}},&quot;isEditorNode&quot;:true}"></div><blockquote><p><strong>First, I wanted to give a big shout out to my first two subscribers: </strong><code>empirefrizz</code><strong> and </strong><code>ishaanksheth</code><strong>. Appreciate your support!</strong> </p></blockquote><p>In the early 20th Century, Piet Mondrian, a Dutch artist, was quietly revolutionizing the theory and practice of abstract visual art. While he created art in different styles, he is perhaps best known for his grid-based paintings (see above). These paintings comprise disjoint variegated squares and rectangles filling the entire canvas.</p><p>So, how does that relate to dungeon maps? A dungeon layout is a 2D map on a discrete grid indicating which tiles are to be placed in each cell making up the dungeon. In constructing the layout, we need to divide the space into non-overlapping portions, for example, to place rooms and corridors. If we were to divide our space using axis-aligned cuts we would produce a figure (see above) very much like a Mondrian painting.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Binary space partitions</h2><p>In this article, we&#8217;ll look at a simple algorithm to create Mondrian-like &#8220;paintings&#8221;. Another name for this technique is <em>binary space partitioning</em>. You may have heard of this method in the context of creating an efficient search structure for points in space - an important problem in computer graphics - it&#8217;s the same idea.</p><p>The algorithm works like so:</p><ol><li><p>Begin with a single cell of the desired final layout size.</p></li><li><p>Divide the cell along its longest axis, uniformly between <code>[min_width, width - min_midth]</code> or <code>[min_height, height - min_height]</code>, whichever the case may be.</p></li><li><p>Pick a cell at random, and divide it as in Step 2 if its area is greater than <code>min_area</code>.</p></li><li><p>Repeat Step 3 until there are no dividable cells left.</p></li></ol><p>Here&#8217;s an illustration of binary space partitioning in action:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;007276e1-9306-4f9c-bada-d90748840e52&quot;,&quot;duration&quot;:null}"></div><p>We have design freedom to choose which cell and axis to split at each step, as well as the terminating condition. The parameters are the initial width, height, minimum cell area, minimum cell width and height, and, as always, the random seed.</p><h2>Implementation</h2><p>Let&#8217;s begin our implementation by defining the data structures for our binary space partition. We represent the partition with a binary tree. The leaves of the tree represent the final partitions of the space, and their ancestor nodes represent intermediate splitting steps:</p><pre><code>@dataclass
class Rect:
    x: int = 0
    y: int = 0
    width: int = 0
    height: int = 0

@dataclass
class Node:
    index: int = 0
    rect: Rect = field(default_factory=Rect)
    axis: Optional[int] = None
    split: Optional[int] = None
    left: Optional[&#8221;Node&#8221;] = None
    right: Optional[&#8221;Node&#8221;]= None</code></pre><p>Each node contains a rectangle describing its area within the layout, references to its left and right child, and we assign it an index for visualization purposes later on. The axis and split fields are optional, but make some parts of the implementation easier.</p><p>A binary space partition, then, is represented by a single root node, and, for convenience, we maintain a set of leaf nodes:</p><pre><code>class BinarySpacePartition:
    root: Node
    leaves: Set[Node]</code></pre><p>The classes initializer sets up the root node and begins splitting:</p><pre><code>def __init__(self,
             width: int = 100,
             height: int = 100, 
             min_width: int = 10,
             min_height: int = 10, 
             min_area: int = 250,
             seed: Optional[int]=None):

    if seed is not None:
        random.seed(seed)
    
    self.root = Node(index=0)
    self.root.rect = Rect(0, 0, width, height)
    self.axis = 0
    self.count_nodes = 1
    self.leaves = [self.root]

    self._split(min_width, min_height, min_area)</code></pre><p>Here is the meat of the code, the splitting algorithm:</p><pre><code>def _split(self, min_width: int, min_height: int, min_area: int):
    leaves_todo = list(self.leaves)
    
    while len(leaves_todo):
        candidate = random.choice(leaves_todo)
        leaves_todo.remove(candidate)

        # Work out whether we need to split this node
        r = candidate.rect
        if (min_area &lt; r.width * r.height):
            # Split maximum dimension
            if r.width &gt; r.height:
                candidate.axis = 0
            else:
                candidate.axis = 1

        if candidate.axis == 0:
            candidate.split = random.randrange( \
                min(min_width, r.width - min_width), \
                max(r.width - min_width, min_width) + 1)
            
            candidate.left = Node( \
                index=candidate.index, \
                rect=Rect(r.x, r.y, candidate.split, r.height))
            
            candidate.right = Node( \
                index=len(self.leaves), \
                rect=Rect(r.x + candidate.split, r.y, \
                          r.width - candidate.split, r.height))

        else:
            candidate.split = random.randrange( \
                min(min_height, r.height - min_height), \
                max(r.height - min_height, min_height) + 1)
            
            candidate.left = Node(index=candidate.index, \
                rect=Rect(r.x, r.y, r.width, candidate.split))
            
            candidate.right = Node( \
                index=len(self.leaves), \
                rect=Rect(r.x, r.y + candidate.split, 
                          r.width, r.height - candidate.split))

        # Update leaves
        self.leaves.remove(candidate)
        self.leaves.extend([candidate.left, candidate.right])
        leaves_todo.extend([candidate.left, candidate.right])</code></pre><p>An animation of this algorithm was given in the previous section. The end result is a layout like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s5Y1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s5Y1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 424w, https://substackcdn.com/image/fetch/$s_!s5Y1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 848w, https://substackcdn.com/image/fetch/$s_!s5Y1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 1272w, https://substackcdn.com/image/fetch/$s_!s5Y1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s5Y1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png" width="640" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3206,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/175684239?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s5Y1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 424w, https://substackcdn.com/image/fetch/$s_!s5Y1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 848w, https://substackcdn.com/image/fetch/$s_!s5Y1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 1272w, https://substackcdn.com/image/fetch/$s_!s5Y1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc58e6439-a897-41c0-8140-0e1bbcbb0c5e_640x480.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Discussion</h2><p>In implementing the algorithm in Python from scratch, I discovered some gotchas that seem obvious after the fact.</p><p>In my first implementation attempt, I performed the splits in a depth-first manner. This produces similar output to the final method I decided upon but doesn&#8217;t nearly look as good for visualization. Also, choosing which cell to split at random allows additional termination conditions, such as: &#8220;conclude when there are <em>x</em> cells&#8221; (and the layout will be balanced).</p><p>For the termination condition, I began with a rule that terminated when neither axis of a cell could be split so that the width or height would be greater than some minimum threshold. However, this resulted in too many square-like cells rather than a diversity of aspect ratios.</p><p>Some alternatives I tried for choosing which axis to split were:</p><ul><li><p>choose the axis at random; and.</p></li><li><p>alternate the axis to split from parent to child.</p></li></ul><p>These produced reasonable results, although contained many cells that were too long in one axis. Better results, in my (subjective) opinion, achieve a sweet spot for the distribution of cell aspect ratios. We don&#8217;t want them all to be square-like, but neither do we want them to be long and skinny.</p><p><em>These observations drove home the message that procedural generation is part science, part art. We have to implement the methods with an understanding of the constraints that make the output believable, interesting, and fun.</em></p><h2>What&#8217;s next?</h2><p>One method to use a binary space partition to construct a dungeon layout, is to place a room in each cell. The room can be smaller than the cell and offset randomly from the cell boundary. Then we can simply join adjacent cells in some way:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gwIr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gwIr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 424w, https://substackcdn.com/image/fetch/$s_!gwIr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 848w, https://substackcdn.com/image/fetch/$s_!gwIr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 1272w, https://substackcdn.com/image/fetch/$s_!gwIr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gwIr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png" width="300" height="300" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:300,&quot;width&quot;:300,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1859,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/175684239?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gwIr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 424w, https://substackcdn.com/image/fetch/$s_!gwIr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 848w, https://substackcdn.com/image/fetch/$s_!gwIr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 1272w, https://substackcdn.com/image/fetch/$s_!gwIr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62da3751-ca80-43a4-9f39-770cee3c96a6_300x300.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Being able to construct a binary space partition, however, is a very general purpose tool for dividing the layout space and can be combined effectively with other methods.</p><p>In my next post, we&#8217;ll continue our discussion of traditional PCG methods for dungeon building with another method that achieves a disjoint partition but without aligning the splits to the axes.</p><h2>Resources</h2><ul><li><p>Full Python implementation (to be posted shortly): <a href="https://github.com/stefanwebb/generative-gamedev">https://github.com/stefanwebb/generative-gamedev</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The curious case of the LeetCode problem that was useful in the real world]]></title><description><![CDATA[Who knew all that coding interview prep would actually be useful?]]></description><link>https://gamedev.blog/p/the-curious-case-of-the-leetcode</link><guid isPermaLink="false">https://gamedev.blog/p/the-curious-case-of-the-leetcode</guid><dc:creator><![CDATA[Stefan Webb]]></dc:creator><pubDate>Tue, 30 Sep 2025 02:37:10 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/f45c2f91-177d-477d-b76c-73d8fccff33a_640x480.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;587cdac6-05db-48d3-9895-8c3b04fafe0a&quot;,&quot;duration&quot;:null}"></div><p><em>Something very weird happened today with which I am only just starting to come to terms.</em> A coding challenge question from LeetCode (which I have received in real interviews) turned out to be useful and in fact was just what I needed for the next step in my procedural content generation system. Who knew that all that time spent practicing dynamic programming and graph traversal would come in handy at some point?</p><p>If you recall, in the previous article we coded up a simple system, called the <em>cellular automaton</em>, for generating &#8220;organic-looking&#8221; maps, such as for a rocky cavern or an archipelago. We left off the discussion having produced output like this,</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Vz-S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Vz-S!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 424w, https://substackcdn.com/image/fetch/$s_!Vz-S!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 848w, https://substackcdn.com/image/fetch/$s_!Vz-S!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 1272w, https://substackcdn.com/image/fetch/$s_!Vz-S!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Vz-S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png" width="389" height="389" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/652afe28-1d37-45a1-9735-a7777105ddba_389x389.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:389,&quot;width&quot;:389,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Vz-S!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 424w, https://substackcdn.com/image/fetch/$s_!Vz-S!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 848w, https://substackcdn.com/image/fetch/$s_!Vz-S!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 1272w, https://substackcdn.com/image/fetch/$s_!Vz-S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F652afe28-1d37-45a1-9735-a7777105ddba_389x389.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>where the white space represents an open cave floor or, alternatively, the land mass of an island, and the black space represents solid rock or ocean.</p><p>To go from this to something useful in an actual rogue-like dungeon crawler, however, we need to apply additional constraints. In this article and the subsequent ones, we&#8217;ll look at algorithms for:</p><ul><li><p>removing smaller rooms;</p></li><li><p>combining larger rooms; and,</p></li><li><p>tracing an outline of the resulting single connected space.</p></li></ul><p>The dungeon map will &#8220;make sense&#8221; in that the player will be able to get from anywhere to anywhere else and we&#8217;ll know where to place the walls. Optionally, we&#8217;ll also implement the functionality to remove small enclosed areas of rock. I think they add interest to the map, but it&#8217;s nice to have the option.</p><h2>&#9973;&#65039; Navigating the islands</h2><p>The challenging initial step to applying our constraints is to determine the connected components, or &#8220;islands&#8221;, in our map. But wait, I&#8217;ve seen this before as a LeetCode problem! One solution to calculate the connected components is as follow:</p><ol><li><p>Initialize an array to store whether each cell has been visited by the algorithm.</p></li><li><p>Loop over map cells.</p></li><li><p>When we encounter a cell we haven&#8217;t visited, we start a breadth- (or depth-) first search for connected cells, that is, neighboring cells that have the same value.</p></li></ol><p>After the algorithm has terminated, we have an array of &#8220;islands,&#8221; their cell locations and values.</p><p>Here is the gist of the algorithm in Python, using the same <code>CellularAutomaton</code> class as the previous article:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lzZV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lzZV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 424w, https://substackcdn.com/image/fetch/$s_!lzZV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 848w, https://substackcdn.com/image/fetch/$s_!lzZV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 1272w, https://substackcdn.com/image/fetch/$s_!lzZV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lzZV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png" width="1456" height="1896" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1896,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1156999,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/174735329?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lzZV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 424w, https://substackcdn.com/image/fetch/$s_!lzZV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 848w, https://substackcdn.com/image/fetch/$s_!lzZV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 1272w, https://substackcdn.com/image/fetch/$s_!lzZV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe979945d-7324-4955-94d6-28c56d4c13cc_3200x4168.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This code could use some tidying up and making object-oriented, but I&#8217;ll leave that for another day.</p><p>You can see a visualization in the video at the begining of the article.</p><h2>&#8594; What&#8217;s Next?</h2><p>In the next article, we&#8217;ll use this information on the connected components to remove smaller components and join larger ones. Also, we&#8217;ll determine the boundary of the single large room so we know where to place wall (or coast) tiles in Unity.</p><h2>Resources</h2><ul><li><p><a href="https://leetcode.com/problems/number-of-islands/description/">Number of Islands - LeetCode</a></p></li><li><p><a href="https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/description/">Number of Connected Components in an Undirected Graph - LeetCode</a></p></li><li><p>Johnson et al., 2010. <em><a href="http://julian.togelius.com/Johnson2010Cellular.pdf">Cellular automata for real-time generation of infinite cave levels.</a> PCGames, 2010.</em></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Organic-looking dungeons with Cellular Automata]]></title><description><![CDATA[Why a cavern is like a Petri dish...]]></description><link>https://gamedev.blog/p/organic-looking-dungeons-with-cellular</link><guid isPermaLink="false">https://gamedev.blog/p/organic-looking-dungeons-with-cellular</guid><dc:creator><![CDATA[Stefan Webb]]></dc:creator><pubDate>Sat, 27 Sep 2025 04:48:31 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4963d1f9-332c-4df2-842d-1746b14f3a02_389x389.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Some dungeons are made of rooms with stone walls, while others are more naturally shaped rocky caverns. Naturally, procedurally generating these variegated geometries require different methods. We&#8217;ll be tackling organically shaped caverns in this article with a simple but ingenious method called the <em>cellular automaton </em>(Johnson et al., 2010).</p><p>Here is the core of the idea: imagine you have a square petri dish divided into tiny squares or cells. Each cell contains a bacterium and can be in either a &#8220;dead&#8221; or &#8220;alive&#8221; state. Initially, the cells are in either state at random, independently of each other. </p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>We divide time into discrete steps and a simple rule determines the state of a cell at the next step:</p><ul><li><p>If a cell is alive but it has has 5 or less neighbors who are alive, it dies.</p></li><li><p>If a cell is dead and it has 3 or more living neighbors, it becomes alive.</p></li><li><p>Otherwise, the cell remains in its current state.</p></li></ul><p>What we mean by &#8220;neighbors&#8221; is at our discretion. We&#8217;ll define it as the eight immediately surrounding cells. Also, I&#8217;ve chosen 4 for the &#8220;survival threshold&#8221; and 5 for the &#8220;birth threshold&#8221;, but again this is a parameter we can set.</p><h2>&#129299; Let&#8217;s code it up!</h2><p>We&#8217;ll encapsulate our cellular automaton in a Python class. It needs to store the construction parameters, the grid of cell states, as well as an additional buffer for calculating the next time step:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8bqd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8bqd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 424w, https://substackcdn.com/image/fetch/$s_!8bqd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 848w, https://substackcdn.com/image/fetch/$s_!8bqd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 1272w, https://substackcdn.com/image/fetch/$s_!8bqd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8bqd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png" width="1456" height="317" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:317,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:142667,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/174667927?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8bqd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 424w, https://substackcdn.com/image/fetch/$s_!8bqd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 848w, https://substackcdn.com/image/fetch/$s_!8bqd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 1272w, https://substackcdn.com/image/fetch/$s_!8bqd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd7c2e315-87a4-43cc-a099-c14c31521b34_3200x696.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>When an object of this class is instantiated, we need to allocate the cell arrays and initialize them. The initial state has independent random values with the flip of a coin:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!q7cC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!q7cC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 424w, https://substackcdn.com/image/fetch/$s_!q7cC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 848w, https://substackcdn.com/image/fetch/$s_!q7cC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!q7cC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!q7cC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png" width="1456" height="593" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:593,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:353489,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/174667927?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!q7cC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 424w, https://substackcdn.com/image/fetch/$s_!q7cC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 848w, https://substackcdn.com/image/fetch/$s_!q7cC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 1272w, https://substackcdn.com/image/fetch/$s_!q7cC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc75181ce-da48-472c-8321-b113dbe3ff5f_3200x1304.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To count the number of neighboring cells that are alive, we take a slice of the array, and use a simple trick to treat out-of-bounds neighbors as alive:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lfmQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lfmQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 424w, https://substackcdn.com/image/fetch/$s_!lfmQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 848w, https://substackcdn.com/image/fetch/$s_!lfmQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 1272w, https://substackcdn.com/image/fetch/$s_!lfmQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lfmQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png" width="1456" height="317" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:317,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:211350,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/174667927?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lfmQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 424w, https://substackcdn.com/image/fetch/$s_!lfmQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 848w, https://substackcdn.com/image/fetch/$s_!lfmQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 1272w, https://substackcdn.com/image/fetch/$s_!lfmQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67179afe-f0ae-4fd1-989c-ce21f8615b46_3200x696.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The following method simulates a single time step of the automaton using the rule we previously described. Notice how the values of the next time step only depend on the previous time step, or rather, we update all of the cell values asynchronously:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RCvU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RCvU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 424w, https://substackcdn.com/image/fetch/$s_!RCvU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 848w, https://substackcdn.com/image/fetch/$s_!RCvU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 1272w, https://substackcdn.com/image/fetch/$s_!RCvU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RCvU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png" width="1456" height="948" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:948,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:523408,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/174667927?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!RCvU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 424w, https://substackcdn.com/image/fetch/$s_!RCvU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 848w, https://substackcdn.com/image/fetch/$s_!RCvU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 1272w, https://substackcdn.com/image/fetch/$s_!RCvU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e452ee5-2514-4308-98ee-7f60d0b1e894_3200x2084.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Finally, we just need some methods to visualize our &#8220;dungeon&#8221;:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2GMS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2GMS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 424w, https://substackcdn.com/image/fetch/$s_!2GMS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 848w, https://substackcdn.com/image/fetch/$s_!2GMS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 1272w, https://substackcdn.com/image/fetch/$s_!2GMS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2GMS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png" width="1456" height="553" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:553,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:317552,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/174667927?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2GMS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 424w, https://substackcdn.com/image/fetch/$s_!2GMS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 848w, https://substackcdn.com/image/fetch/$s_!2GMS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 1272w, https://substackcdn.com/image/fetch/$s_!2GMS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bf6472b-f417-4c31-984a-36dfca426852_3200x1216.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>&#129488; Results</h2><p>Here is some output over 12 time steps:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;44f05b84-a7fc-4e53-aaf4-929512f67b09&quot;,&quot;duration&quot;:null}"></div><p>and the final frame:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9Ne-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9Ne-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 424w, https://substackcdn.com/image/fetch/$s_!9Ne-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 848w, https://substackcdn.com/image/fetch/$s_!9Ne-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 1272w, https://substackcdn.com/image/fetch/$s_!9Ne-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9Ne-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png" width="389" height="389" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:389,&quot;width&quot;:389,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3978,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://gamedev.blog/i/174667927?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9Ne-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 424w, https://substackcdn.com/image/fetch/$s_!9Ne-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 848w, https://substackcdn.com/image/fetch/$s_!9Ne-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 1272w, https://substackcdn.com/image/fetch/$s_!9Ne-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F864f2194-d6a6-438f-b2e7-48697fa06233_389x389.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The black regions represent rock walls and the white, open space. You should try playing around with the parameters to understand their effect on the cavern geometry. </p><p>By changing the random seed you can generate a practically infinite number of dungeons. And by fixing the seed, you can, in effect, store a dungeon in 64-bits!</p><h2>&#8594; What&#8217;s next?</h2><p>Our cavernous dungeon could use some heuristic improvements over the plain automaton output. There are several disconnected &#8220;rooms&#8221;, including some that are too small to be practical. A simple post-processing step numbers the &#8220;islands,&#8221; removing those smaller than a given threshold and joining the larger ones to form a single large cavern.</p><p>But, we&#8217;ll have to leave that for another day! Hope to see you in the next article &#129312;</p><h2>Resources</h2><ul><li><p>My Python implementation (to follow shortly&#8230;)</p></li><li><p>Johnson et al., 2010. <em><a href="http://julian.togelius.com/Johnson2010Cellular.pdf">Cellular automata for real-time generation of infinite cave levels.</a> PCGames, 2010.</em></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How do you (procedurally) build a dungeon?]]></title><description><![CDATA[It's shockingly simple, actually.]]></description><link>https://gamedev.blog/p/how-do-you-procedurally-build-a-dungeon</link><guid isPermaLink="false">https://gamedev.blog/p/how-do-you-procedurally-build-a-dungeon</guid><dc:creator><![CDATA[Stefan Webb]]></dc:creator><pubDate>Wed, 24 Sep 2025 04:55:01 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/eab805f3-9454-4152-8662-9b1079d83b2e_1195x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b07fef94-cf5a-4167-a345-ec095cfee508_320x200.png&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/924afdb5-1822-459f-b3b3-a07376d283c5_640x480.png&quot;}],&quot;caption&quot;:&quot;Two classic games using procedural generation: Civilization (1991) and Diablo (1997)&quot;,&quot;alt&quot;:&quot;Screenshots of computer games Civilization and Diablo.&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b4396a46-2ea8-408e-9c8b-aec870c3422b_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>A dungeon for role-playing games can be conjured out of the &#230;ther with just a source of randomness and a few clever algorithms. But how? And why would we want a computer to design our dungeons - doesn&#8217;t that take out the fun?</p><p>First, let&#8217;s address the <em>why</em>. As a game designer, it is immensely advantageous to have game experiences that vary each time you play. It becomes difficult, after all, to empathize with your imagined player if you&#8217;ve experienced each level <em>ad infinitum</em>. But replayability is not just a boon for the designer, it&#8217;s also one for the player. A game stays fresh much longer when the content varies each time - think of the dungeons of Diablo, or the world maps of Civilization.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>So, what are our options for procedurally building a dungeon? Here are three families of methods commonly used: &#8220;digging&#8221; agents, cellular automata, and binary space partitioning:</p><ul><li><p><strong>&#8220;Digging&#8221; agents</strong> construct a dungeon one room at a time, connecting each nearby to one of the previously constructed rooms. I like to think of this method as being like a digger that successively carves out open space underground.</p></li><li><p><strong>Cellular automata </strong>are systems that can &#8220;evolve&#8221; a dungeon&#8217;s geometry where an element&#8217;s value at the next time is a simple function of the value of its neighbors. Cellular automata can produce interesting and varied organic-looking geometry.</p></li><li><p><strong>Binary space partitioning </strong>is an algorithm that recursively splits the map area along an axis just like a Mondrian painting. We can then place rooms in each partition of the space.</p></li></ul><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c452c74b-926d-4f49-8b3f-7a72cd12d783_1402x1408.png&quot;},{&quot;type&quot;:&quot;image/jpeg&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a5faaa5f-268a-49a9-be88-28f2a1f281cf_225x225.jpeg&quot;},{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2c32249a-341d-4568-b03a-5c503f71afde_300x300.png&quot;}],&quot;caption&quot;:&quot;Three different methods for generating dungeon geometry&quot;,&quot;alt&quot;:&quot;Pictures of a dungeon constructed room-by-room, by a cellular automata, and by binary space partitioning.&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4adf29e8-d33a-4934-bdcf-4a4f16780c92_1456x474.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>As with other types of procedurally generated content, our output has constraints. A dungeon must presumably be fully connected, have a diversity of rooms and environments, and direct the player towards a goal. Some of these constraints relate to the geometry of the dungeon, and others relate more to the items and creatures we place in them and how the user can interact with the environment.</p><h2>What&#8217;s next?</h2><p>We&#8217;ve touched upon a handful of &#8220;traditional&#8221; algorithms for constructing dungeons, yet we&#8217;ve barely scratched the surface. In future articles, I intend to implement these dungeon construction methods and more in Python and C# and integrate them into a dungeon system I&#8217;ve been building in an online course: <a href="https://www.udemy.com/course/unity-procedural-generation">&#8220;Unity Procedural Generation: Build Infinite Game Levels&#8221;</a>.</p><p>We discussed how to generate a dungeon, but not what makes a dungeon fun or challenging. And is this even something we can quantify? I think this topic will intersect with interesting graph theory and graph algorithms. If we can quantify a dungeon &#8220;goodness&#8221;, then we can apply a wealth of search methods to find a good one.</p><p>Finally, I think it&#8217;s interesting to revisit these older methods in the age of Generative AI. Can we teach our utility-function-for-dungeon-funness to a foundation model? And can we use foundation models to control dungeon generation via natural language descriptions and images?</p><p><em>Stay tuned, and hope to see you in the next article!</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Is AI-generated game content actually a new thing?]]></title><description><![CDATA[Spoiler alert: No! But why not?]]></description><link>https://gamedev.blog/p/is-ai-generated-game-content-actually</link><guid isPermaLink="false">https://gamedev.blog/p/is-ai-generated-game-content-actually</guid><dc:creator><![CDATA[Stefan Webb]]></dc:creator><pubDate>Wed, 17 Sep 2025 05:16:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/85bc5335-1a26-46ff-a6be-2393078858ab_1195x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Is AI-generated game content actually something new? Not really&#8212;at least not if you zoom out a bit. Let&#8217;s drop the &#8220;AI&#8221; for a second. Games have been using procedurally generated content since the very beginning of the industry.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ex5I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ex5I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 424w, https://substackcdn.com/image/fetch/$s_!Ex5I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 848w, https://substackcdn.com/image/fetch/$s_!Ex5I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 1272w, https://substackcdn.com/image/fetch/$s_!Ex5I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ex5I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png" width="600" height="391.73553719008265" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:316,&quot;width&quot;:484,&quot;resizeWidth&quot;:600,&quot;bytes&quot;:1703,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://genaigamedev.substack.com/i/173822360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ex5I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 424w, https://substackcdn.com/image/fetch/$s_!Ex5I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 848w, https://substackcdn.com/image/fetch/$s_!Ex5I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 1272w, https://substackcdn.com/image/fetch/$s_!Ex5I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3536abdc-ff8e-4e76-b9d8-a6037d457e18_484x316.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Take <em>Rogue</em> from 1980, one of the first dungeon crawlers. Instead of hand-built maps, every dungeon you play through is created by an algorithm that mixes in randomness. Room sizes, layouts, hallways, monster placements, and treasure drops all get shuffled each run. That&#8217;s what kept the game fresh.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Procedural generation originally solved two problems:</p><ol><li><p><strong>Replayability</strong> &#8211; Every run felt new, so players didn&#8217;t get bored as quickly.</p></li><li><p><strong>Memory limits</strong> &#8211; Hardware in the &#8217;80s and &#8217;90s couldn&#8217;t store massive worlds. Generating them on the fly let games feel huge without needing gigabytes of space. For example, <em>The Elder Scrolls II: Daggerfall</em> (1996) gave players an open world about the size of Great Britain, all packed into just a few hundred megabytes.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UplP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UplP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 424w, https://substackcdn.com/image/fetch/$s_!UplP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 848w, https://substackcdn.com/image/fetch/$s_!UplP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 1272w, https://substackcdn.com/image/fetch/$s_!UplP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UplP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png" width="600" height="375" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:200,&quot;width&quot;:320,&quot;resizeWidth&quot;:600,&quot;bytes&quot;:25665,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://genaigamedev.substack.com/i/173822360?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UplP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 424w, https://substackcdn.com/image/fetch/$s_!UplP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 848w, https://substackcdn.com/image/fetch/$s_!UplP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 1272w, https://substackcdn.com/image/fetch/$s_!UplP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e6a132-4928-41ec-a257-8a6081b095f9_320x200.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>And it goes back even further. Tabletop RPGs like <em>Dungeons &amp; Dragons</em> relied on procedural generation too. Rolling dice determined stats, monster encounters, and treasure locations. D&amp;D actually inspired early PC games like <em>Rogue</em>.</p><p>So what&#8217;s different about today&#8217;s AI-generated content? Traditional procedural methods follow hard-coded rules and algorithms. Modern AI, on the other hand, uses machine learning&#8212;foundation models like LLMs or vision-language models that were trained on massive datasets. Instead of just &#8220;rolling the dice,&#8221; we&#8217;re prompting systems that have learned patterns from the real world.</p><p>That doesn&#8217;t mean AI generation is a brand-new invention&#8212;it&#8217;s more of an evolution. We&#8217;ve had procedural generation in games for nearly 50 years. The new twist is that machine learning replaces rigid algorithms with models that can generalize and adapt. To me, prompting an LLM is just another tool in the game dev toolbox&#8212;albeit a super powerful one. And the most exciting part is seeing how developers combine traditional procedural tricks with AI to create something altogether new!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://gamedev.blog/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Generative GameDev! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Articulation]]></title><description><![CDATA[A Short Primer on Character Animation (Part 1)]]></description><link>https://gamedev.blog/p/articulation</link><guid isPermaLink="false">https://gamedev.blog/p/articulation</guid><dc:creator><![CDATA[Stefan Webb]]></dc:creator><pubDate>Sun, 14 Sep 2025 22:37:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0T-f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0T-f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0T-f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 424w, https://substackcdn.com/image/fetch/$s_!0T-f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 848w, https://substackcdn.com/image/fetch/$s_!0T-f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 1272w, https://substackcdn.com/image/fetch/$s_!0T-f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0T-f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png" width="1195" height="800" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:1195,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:179182,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://genaigamedev.substack.com/i/173616976?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0T-f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 424w, https://substackcdn.com/image/fetch/$s_!0T-f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 848w, https://substackcdn.com/image/fetch/$s_!0T-f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 1272w, https://substackcdn.com/image/fetch/$s_!0T-f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F533f73ed-4414-424d-bf59-cef122e5e81b_1195x800.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Picture a Tomb Raider where Lara Croft just floats stiffly through the ruins instead of vaulting, rolling, and leaping across them. Or a Quake where enemies drift aimlessly, instead of prowling and lunging at you. Animation transforms these static worlds into living ones. After music, I&#8217;d argue it&#8217;s the most emotionally charged element in games. So the question is&#8212;<em>how do we create animation that truly brings characters to life?</em></p><p>In this series, I&#8217;ll walk through the fundamentals of character animation using Unity, Blender, and a sprinkle of Python. A bit of background: I come from an AI research background and now work as a Developer Advocate in the Generative AI space, creating content to inspire others to build with AI. Recently (well, actually, four months ago!), I decided to merge that professional expertise with a personal passion&#8212;using Generative AI for game development. My starting point? Human motion synthesis&#8212;using AI to generate animations for human characters.</p><p>While my foundation in Generative AI was strong, I quickly realized I needed to deepen my understanding of digital animation to make sense of the research papers and code I was exploring. That led me down a rabbit hole of textbooks, Unity forum threads, and scattered documentation. I found YouTube tutorials helpful for quick tips, but they rarely dove into the core concepts behind animation. That gap inspired me to create this short series of blog posts and videos, <em>A Short Primer on Character Animation</em>, to cover not just the &#8220;how&#8221; but also the &#8220;why&#8221; of bringing characters to life.&#8221;</p><h2>Articulation: Pinnochio in Digital Form</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XUc5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XUc5!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 424w, https://substackcdn.com/image/fetch/$s_!XUc5!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 848w, https://substackcdn.com/image/fetch/$s_!XUc5!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 1272w, https://substackcdn.com/image/fetch/$s_!XUc5!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XUc5!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif" width="600" height="249" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:249,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;image&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="image" title="image" srcset="https://substackcdn.com/image/fetch/$s_!XUc5!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 424w, https://substackcdn.com/image/fetch/$s_!XUc5!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 848w, https://substackcdn.com/image/fetch/$s_!XUc5!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 1272w, https://substackcdn.com/image/fetch/$s_!XUc5!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e33f92b-4fb6-4014-8eb3-53c282916f00_600x249.gif 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Look again at the puppet in the thumbnail image. Its rigid segments are linked at <em>joints</em>, each one responding to the pull or release of strings from the puppeteer&#8217;s control bar. With deft movements, the puppeteer can make it walk, bow, or wave&#8212;every motion of one joint subtly influencing the next through their connections.</p><p>This familiar approach to bringing figures to life is called <em>articulation</em>. It works for humans, animals, or even a tree swaying in the wind. In this model, rigid bodies&#8212;known as <em>bones</em>&#8212;are joined together to form a <em>skeleton</em>, or, <em>armature</em>. The word &#8220;articulation&#8221; comes from the Latin for &#8220;joint,&#8221; reflecting the way we rotate and position these bones at their joints to create a <em>pose</em>. When we adjust these orientations, we say the character is articulated.</p><p>For example, a common model for human characters contains around 21 bones connected at 20 joints (see the skeletal animation above). I say this is a <em>model</em> since the real human body contains more than 200 bones and 600 muscles, so what we have here is a useful simplification of reality, i.e., a model. A real human would have over 200 degrees of freedom whereas our model has . Still, it is a totally adequate representation for human animation excepting hands, feet, and faces.</p><h2>What's next?</h2><p>So, that's the basics of articulated animation: bones, joints, skeletons, and poses. In the next article, we&#8217;ll dive deeper into the technical aspects of articulated motion and animate a character with Unity.</p>]]></content:encoded></item></channel></rss>