<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ruslan Osipov - Gamedev</title>
    <description>Recent posts in Gamedev category on Ruslan Osipov.</description>
    <link>https://rosipov.com/</link>
    <atom:link href="https://rosipov.com/blog/categories/gamedev.xml" rel="self" type="application/rss+xml" />
    <pubDate>Mon, 11 May 2026 17:29:47 +0000</pubDate>
    <lastBuildDate>Mon, 11 May 2026 17:29:47 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>Abandoned project showcase: Hikaya</title>
        <description>&lt;p&gt;Roguelikes are once niche, but an increasing mainstream video game genre. The genre is named after 1980 “Rogue” - a procedurally generated dungeon crawler. Rogue and games inspired by it often include simple ASCII graphics, feature procedural generation of the world, and include “permadeath”: the game is over once you die.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/rogue-1980.png&quot; alt=&quot;A screenshot of 1980 &amp;quot;Rogue&amp;quot; video game.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Many games were heavily inspired by it - like Ancient Domains of Mystery and Nethack, or more recent Cataclysm: DDA and Caves of Qud. Modern games bring a lot of fantastic fusion into the genre too with like The Binding of Isaac or Hades. But I digress.&lt;/p&gt;

&lt;p&gt;A few months ago I was struck by a bout of inspiration. I’ve tried countless times before, but never produced a complete video game – this was meant to be the time! I’ve significantly reduced the scope, came up with a plan of action, and started coding!&lt;/p&gt;

&lt;p&gt;I codenamed the game “Hikaya”, which means “a fairy tale” in Tatar - my native language (although I think the word itself has been borrowed from Arabic). I planned to make a fairly straightforward roguelike, without too many bells and whistles.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/hikaya-splash-screen.png&quot; alt=&quot;A screenshot of the main menu of Hikaya.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’ve found a wonderful tutorial on rogueliketutorials.com, which introduces &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libtcod&lt;/code&gt; – a library to simplify the mundane: drawing on screen, handling input, field of view and lighting, pathfinding. I’ve struggled through all of the above before, and knew that getting deep into the mechanical details would slow me down.&lt;/p&gt;

&lt;p&gt;Alas, I didn’t finish the game. I’ve gotten maybe half way there, before my focus slipped away from the project. But not before getting some screenshots and documenting some interesting ideas I had!&lt;/p&gt;

&lt;p&gt;The only contains a dozen dungeon levels, with a short story being told through item descriptions. The player is an adventurer who’s sent by their village to a nearby cave to retrieve the last flame - a placeholder MacGuffin.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/hikaya-inventory.png&quot; alt=&quot;An inventory screen of Hikaya.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Every item and monster posses a fantasy-sounding name, contrasted with a short, but colorful description hinting at a science fiction nature of the objects. Think magic scrolls with touch screens, or injectable health potions.&lt;/p&gt;

&lt;p&gt;Each monster type has a unique behavior - with goblins running away and regrouping, ogres snacking on goblins to restore health, and so on.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/hikaya-goblin.png&quot; alt=&quot;A goblin running away for backup.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As the player descends down the dungeon, they encounter multiple bosses guarding the staircases. The bosses drop unique armor pieces, which tell a story of a group of adventurers descending into the dungeon, but succumbing to traps, greed, and treachery.&lt;/p&gt;

&lt;p&gt;Finally, a dragon guards the last light on the final floor. The game ends with the player becoming a dragon, and a cycle becomes anew.&lt;/p&gt;

&lt;p&gt;I know, I know: edgy, uninspired – but I enjoyed the premise.&lt;/p&gt;

&lt;p&gt;The combat is focused on tactical movement and avoiding damage, and the player unlocks new moves – like kicks, jumps, or faints – as they progress through the dungeon.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/hikaya-stunts.png&quot; alt=&quot;A screenshots of a stunt selector in Hikaya.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I wanted to experiment with the health system. Inspired by FATE tabletop roleplaying system, I attempted to use health pools. The system was meant to keep combat dangerous and entertaining throughout the game by making even the goblins dangerous throughout the game.&lt;/p&gt;

&lt;p&gt;My biggest experiment came from a health system. Inspired by FATE tabletop roleplaying ruleset, the health consists of multiple pools (as opposed to a single bar). The goal of the system is to make combat deadly, and create a sense of danger even for trivial encounters, while still leaving room for error.&lt;/p&gt;

&lt;p&gt;Let me try to explain. For example, a player might have three health pools - for 1, 2, and 3 hit points each. Each time a player takes damage, the smallest pool is used to absorb that damage. For instance, a 2 damage hit voids the 2 hit point pool. A second 2 damage hit clears out the 3 hit point pool. A third 2 damage hit is fatal.&lt;/p&gt;

&lt;p&gt;You can see the voided health pools marked as red on the screenshot below, and the full health pools in green:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/hikaya-damage.png&quot; alt=&quot;A screenshots of a player taking damage in Hikaya.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To complicate thing further, I give players some leeway by slowly draining partially damaged pools over time. For instance, if the player uses their 5 hit point pool to absorb 2 points of damage, I’ll slowly drain that pool over the next three turns. This would let the player take another 2 damage hit (or a few 1 damage hits) “for free” immediately after being hit. You can see those hearts marked as yellow on the screenshot above.&lt;/p&gt;

&lt;p&gt;Needless to say, the system turned out to be very convoluted to explain in game (and on paper too – I don’t think the above is clear enough). I still think it’s a great idea, but it desperately needs better user experience design to make it accessible. In fact, FATE itself got rid of confusing health pools in it’s latest “FATE Condensed” release, simplifying health down to binary hit markers.&lt;/p&gt;

&lt;p&gt;Despite not finishing it, putting together Hikaya was a fun experience. I’ve had a great time working on the prototype: I’ve learned a lot, and maybe I’ll lead my next video game project to completion given everything I’ve learned!&lt;/p&gt;
</description>
        <pubDate>Mon, 24 May 2021 16:00:00 +0000</pubDate>
        <link>https://rosipov.com/blog/abandoned-project-showcase-hikaya/</link>
        <guid isPermaLink="true">https://rosipov.com/blog/abandoned-project-showcase-hikaya/</guid>
        
        
        <category>Gamedev</category>
        
      </item>
    
      <item>
        <title>Mob level distribution</title>
        <description>&lt;p&gt;Distributing mobs in a dungeon based on player’s level (or some dungeon level
difficulty factor) was somewhat straightforward, but I would still like to
document the progress. I needed to place a mob that’s somewhat within the
difficulty level I want, plus minus a few difficulty levels to spice it up.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/roguelike-leveled-mob-distribution.png&quot; alt=&quot;Random mob distribution in roguelike dungeon.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Above you can see three rats, three cats, a dog (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt;, all level 1), a
farmer (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;f&lt;/code&gt;, level 2), and a lonely bandit (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt;, level 3) in a level 1 dungeon.&lt;/p&gt;

&lt;p&gt;Without going straight into measure theory, I generated intervals for each mob
based on the diff of desired level and their level, and then randomly selected a
point within the boundaries. Here’s the abstract code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import bisect
import random


def get_random_element(data, target, chance):
    &quot;&quot;&quot;Get random element from data set skewing towards target.

    Args:
        data   -- A dictionary with keys as elements and values as weights.
                  Duplicates are allowed.
        target -- Target weight, results will be skewed towards target
                  weight.
        chance -- A float 0..1, a factor by which chance of picking adjacent
                  elements decreases (i.e, with chance 0 we will always
                  select target element, with chance 0.5 probability of
                  selecting elements adjacent to target are halved with each
                  step).

    Returns:
        A random key from data with distribution respective of the target
        weight.
    &quot;&quot;&quot;
    intervals = []  # We insert in order, no overlaps.
    next_i = 0
    for element, v in data.iteritems():
        d = max(target, v) - min(target, v)
        size = 100
        while d &amp;gt; 0:  # Decrease chunk size for each step of `d`.
            size *= chance
            d -= 1
        if size == 0:
            continue
        size = int(size)
        intervals.append((next_i, next_i + size, element))
        next_i += size + 1
    fst, _, _ = zip(*intervals)
    rnd = random.randint(0, next_i - 1)
    idx = bisect.bisect(fst, rnd)  # This part is O(log n).
    return intervals[idx - 1][2]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, if I test the above for, say, a 1000000 iterations, with a chance of 0.5
(halving probability of selecting adjacent elements with each step), and 2 as a
target, here’s the distribution I end up with:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;target, chance, iterations = 2, 0.5, 1000000

data = collections.OrderedDict([  # Ordered to make histogram prettier.
    (&apos;A&apos;, 0), (&apos;B-0&apos;, 1), (&apos;B-1&apos;, 1), (&apos;C&apos;, 2), (&apos;D&apos;, 3), (&apos;E&apos;, 4),
    (&apos;F&apos;, 5), (&apos;G&apos;, 6), (&apos;H&apos;, 7), (&apos;I&apos;, 8), (&apos;J&apos;, 9),
])

res = collections.OrderedDict([(k, 0) for k, _ in data.iteritems()])

# This is just a test, so there&apos;s no need to optimize this for now.
for _ in xrange(iterations):
    res[get_random_element(data, target, chance)] += 1

pyplot.bar(
    range(len(res)), res.values(), width=0.7, align=&apos;center&apos;, color=&apos;green&apos;)
pyplot.xticks(range(len(res)), res.keys())
pyplot.title(
    &apos;iterations={}, target={}, chance={}&apos;.format(
        iterations, target, chance))
pyplot.show()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/roguelike-mob-distribution-chance-05.png&quot; alt=&quot;Distribution histogram: 1000000 iterations, 0.5 chance, and 2 as a target.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can see elements B-0 and B-1 having roughly the same distribution, since
they have the same weight.&lt;/p&gt;

&lt;p&gt;Now, if I decrease the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chance&lt;/code&gt;, likelihood of target being selected increases,
while likelihood of surrounding elements being selected decreases:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/roguelike-mob-distribution-chance-033.png&quot; alt=&quot;Distribution histogram: 1000000 iterations, 0.33 chance, and 2 as a target.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I works the opposite way as well, increasing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chance&lt;/code&gt; decreases likelihood
of the target being selected and increases the probability for surrounding
elements.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/roguelike-mob-distribution-chance-09.png&quot; alt=&quot;Distribution histogram: 1000000 iterations, 0.9 chance, and 2 as a target.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For the sake of completeness, it works with 0 chance of surrounding elements being picked:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/roguelike-mob-distribution-chance-0.png&quot; alt=&quot;Distribution histogram: 1000000 iterations, 0 chance, and 2 as a target.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;And an equal chance of picking surrounding elements:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/roguelike-mob-distribution-chance-1.png&quot; alt=&quot;Distribution histogram: 1000000 iterations, 1 chance, and 2 as a target.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After playing around with the configuration in Jupyter Notebook, I cleaned up
the algorithm above and included it into mob placement routine.&lt;/p&gt;
</description>
        <pubDate>Sun, 02 Oct 2016 00:15:12 +0000</pubDate>
        <link>https://rosipov.com/blog/mob-level-distribution/</link>
        <guid isPermaLink="true">https://rosipov.com/blog/mob-level-distribution/</guid>
        
        
        <category>Gamedev</category>
        
        <category>Programming</category>
        
      </item>
    
      <item>
        <title>Spawning evenly distributed mobs</title>
        <description>&lt;p&gt;After generating a few &lt;a href=&quot;https://rosipov.com/blog/randomly-generated-dungeons/&quot;&gt;good looking random dungeons&lt;/a&gt;, I was puzzled with
randomly placing mobs on resulting maps. To make it fair (and fun) for the
player mobs should be evenly distributed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/dungeon-mob-placement.png&quot; alt=&quot;Dungeon with randomly placed mobs.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The obvious idea was to pick coordinates randomly within the rectangular map
boundaries, and then place mobs if they have floor underneath them. But this way
I lose control of the number of mobs and risk having a chance of not placing any
mobs at all. Plus, dungeons with bigger surface area would get more mobs - which
sounds somewhat realistic, but not entirely what I was aiming for.&lt;/p&gt;

&lt;p&gt;I could improve on the above by rerunning enemy placement multiple times and
select the most favorable outcome - but the solution would be rather naive.&lt;/p&gt;

&lt;p&gt;To have control over the number of mobs I decided to place them as I generate
the rooms of the dungeon. There’s a trick one can use to get a random element
with equal probability distribution from a sequence of an unknown size:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import random


def get_random_element(sequence):
    &quot;&quot;&quot;Select a random element from a sequence of an unknown size.&quot;&quot;&quot;
    selected = None
    for k, element in enumerate(sequence):
        if random.randint(0, k) == 0:
            selected = element
    return selected
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With each iteration the chance of the current element to become a selected item
is 1 divided by number of elements seen so far. Indeed, a probability of an
element being selected out of a 4-item sequence:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 * (1 - 1/2) * (1 - 1/3) * (1 - 1/4) = 1/2 * 2/3 * 3/4 = 6/30 = 1/4 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now all I had to do is to modify this to account for multiple mob placement.
Here’s a generalized function above which accounts for selecting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; elements
from the sequence with even distribution.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;import random


def get_random_element(sequence, n):
    &quot;&quot;&quot;Select n random elements from a sequence of an unknown size.&quot;&quot;&quot;
    selected = [None for _ in range(n)]
    for k, element in enumerate(sequence):
        for i in range(n):
            if random.randint(0, k) == 0:
                selected[i] = element
    return selected
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I incorporated logic above into the room generation code, accounted for
duplicates, and ended up with decent distribution results.&lt;/p&gt;

</description>
        <pubDate>Fri, 30 Sep 2016 18:06:16 +0000</pubDate>
        <link>https://rosipov.com/blog/spawning-evenly-distributed-mobs/</link>
        <guid isPermaLink="true">https://rosipov.com/blog/spawning-evenly-distributed-mobs/</guid>
        
        
        <category>Gamedev</category>
        
        <category>Programming</category>
        
      </item>
    
      <item>
        <title>Randomly generated dungeons</title>
        <description>&lt;p&gt;After playing with random dungeon generation for a bit, I seem to be satisfied
with the results:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/random-dungeon-1.png&quot; alt=&quot;A randomly generated ASCII-dungeon.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The above is displayed using ADOM notation - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#&lt;/code&gt; represents a wall, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; is a
floor tile, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; is meant to be a closed door. After fiddling about with a
few random dungeon generation ideas, I settled with the following.&lt;/p&gt;

&lt;h2 id=&quot;the-algorithm&quot;&gt;The algorithm&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Start with a random point on a canvas.&lt;/li&gt;
  &lt;li&gt;Create a room with random width and height. Don’t worry about walls yet.&lt;/li&gt;
  &lt;li&gt;Select a few points on the sides of the room, put those in a stack. Save the
direction in which the potential doors would point.&lt;/li&gt;
  &lt;li&gt;Go through the stack, and try to add a room or a corridor (corridor is just a
room with a width or a height of 1). Higher chance of corridors seems to look
better and results in those wiggly passageways.
    &lt;ol&gt;
      &lt;li&gt;Try to add a room a few dozen times with different random configurations.
If no luck - give up and grab a new item from the stack. If couldn’t
generate a continuation to the corridor - mark it as a loose end, we’ll
clean those up later.&lt;/li&gt;
      &lt;li&gt;If a room was added successfully - add a door where it connects to the
previous room/corridor. Add a floor section if it’s a corridor connecting
to another corridor.&lt;/li&gt;
      &lt;li&gt;At this point one ends up with quite a few interconnected corridors,
merged rooms, and all kinds of fun surroundings (my desired goal).&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Do the above until the stack is empty or a desired number of rooms has been
generated.&lt;/li&gt;
  &lt;li&gt;Clean up the loose ends from step 4.1. Remove loose corridor segments one by
one until intersection with another room/corridor is detected.&lt;/li&gt;
  &lt;li&gt;Add walls around the rooms and corridors, while also cleaning up any extra
doors we may have left behind when creating merged corridors or rooms.
    &lt;ol&gt;
      &lt;li&gt;I simply used slightly modified depth first search starting inside any
room and drawing walls wherever I could find floor/door not connected to
anything.&lt;/li&gt;
      &lt;li&gt;When encountering a door - check if it’s surrounded by walls or doors from
the opposite sides. If not - remove the door and replace it with a floor
tile. If any doors were adjucent to the removed door - requeue the door
check on them.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Perform steps 1-7 a few hundred times, saving the resulting dungeons each
time. Pick the best candidate with the most desired features - like a number
of rooms, breadth, square footage, longest corridors, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A more detailed explanation of the steps is below. For now, here are a few more
dungeons generated using this method:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/random-dungeon-2.png&quot; alt=&quot;A randomly generated ASCII-dungeon.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/random-dungeon-3.png&quot; alt=&quot;A randomly generated ASCII-dungeon.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/posts/random-dungeon-4.png&quot; alt=&quot;A randomly generated ASCII-dungeon.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I think dungeon generation is far more art than science, and I had a lot of fun
tweaking all the different input parameters:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Room size boundaries.&lt;/li&gt;
  &lt;li&gt;Corridor lengths.&lt;/li&gt;
  &lt;li&gt;Frequency of corridor occurrences.&lt;/li&gt;
  &lt;li&gt;Number of exits from the room.&lt;/li&gt;
  &lt;li&gt;Number of room generation attempts.&lt;/li&gt;
  &lt;li&gt;Number of dungeon generation attempts.&lt;/li&gt;
  &lt;li&gt;Final dungeon picking heuristics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last item on the list is the most interesting one - with few hundred dungeons as
an input, picking the right one is rather important. I ended up settling on
using max priority queue with a rough surface area of the dungeon as a key
(it’s more of a breadth, really - how wide and tall it is). Then I’d sift
through some top results and pick the one with the most rooms available. This
results in the most fun-looking map which feels up most of the screen, while
still usually not being too cluttered.&lt;/p&gt;

&lt;p&gt;Here’s a breakdown of a simple scenario:&lt;/p&gt;

&lt;h3 id=&quot;steps-1-and-2&quot;&gt;Steps 1 and 2&lt;/h3&gt;

&lt;p&gt;Pick a random spot on a canvas and generate a room of random size (4 x 3):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;....
....
....
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;step-3&quot;&gt;Step 3&lt;/h3&gt;

&lt;p&gt;Select potential spots for doors, let’s label them 1, 2, 3.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;....2
....
....1
  3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I went for a uniform distribution by unfolding the rectangle and folding it back
in to get a proper coordinate on the perimeter. Now, stack contains coordinates
of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, 2, 3]&lt;/code&gt; (along with the directions in which they are pointing).&lt;/p&gt;

&lt;h3 id=&quot;steps-4-and-5&quot;&gt;Steps 4 and 5&lt;/h3&gt;

&lt;p&gt;Add a room or a corridor to a connector number 3. We’ll be adding the room to
the right of number 3. Let’s assume random sends a corridor of length 5 our way.
We’re happy with the corridor pointing either up, down, or right - so we let the
random decide again: up.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     4
     .
     .
....2.
.... .
....3.
  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We add the end of the corridor to the stack as number 4 (now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, 2, 4]&lt;/code&gt;). We
also mark 4 as a loose end, in case we end up not adding a room to it. Dangling
corridors are never pretty.&lt;/p&gt;

&lt;p&gt;Now, to replace number 3 with a door:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     4
     .
     .
....2.
.... .
....+.
  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Adding another random corridor of length 2 to the point 4, pointing right.
Replace number 4 with a floor segment, since point 4 was the end of another
corridor. Remove point 4 from loose ends, add point 5.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     ...5
     .
     .
....2.
.... .
....+.
  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Take point 5, generate a room of size 3 x 6. 5 becomes a door. Loose ends list
is empty.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     ...+...
     .   ...
     .   ...
....2.   ...
.... .   ...
....+.   ...
  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For simplicity’s sake, let’s assume we don’t want any more exits from this room.
Back to the stack of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[1, 2]&lt;/code&gt;. Point 2 seem to not have much room for growth.
After a few unsuccessful attempts to place a room or a corridor there, we give
up:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     ...+...
     .   ...
     .   ...
.... .   ...
.... .   ...
....+.   ...
  1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now for point 1: we get another corridor of length 3. Point 6 is now added to
the loose ends list.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     ...+...
     .   ...
     .   ...
.... .   ...
.... .   ...
....+.   ...
  +
  .
  .
  .
  6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s assume we run out of space and can’t add anything to the end of 6. We’re
done generating the dungeon. Our stack is empty, and our loose ends contains
coordinates of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;step-6&quot;&gt;Step 6&lt;/h3&gt;

&lt;p&gt;Start with the loose end, and remove items one by one until a tile with multiple
neighbors is encountered:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     ...+...
     .   ...
     .   ...
.... .   ...
.... .   ...
....+.   ...
  X
  X
  X
  X
  X
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Viola:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     ...+...
     .   ...
     .   ...
.... .   ...
.... .   ...
....+.   ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;steps-7-and-8&quot;&gt;Steps 7 and 8&lt;/h3&gt;

&lt;p&gt;There are no rogue doors in this scenario, so all we need to do is add  the
walls:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     #########
     #...+...#
     #.###...#
######.# #...#
#....#.# #...#
#....#.# #...#
#....+.# #...#
######## #####
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All of the steps above should be repeated a few hundred times with different
dungeons, and then the best dungeon should be picked as a final one.&lt;/p&gt;

&lt;p&gt;Did I miss anything? Was cleaning up “loose ends” too much of a hack? What
should have I done differently?&lt;/p&gt;
</description>
        <pubDate>Thu, 29 Sep 2016 23:26:32 +0000</pubDate>
        <link>https://rosipov.com/blog/randomly-generated-dungeons/</link>
        <guid isPermaLink="true">https://rosipov.com/blog/randomly-generated-dungeons/</guid>
        
        
        <category>Gamedev</category>
        
        <category>Programming</category>
        
      </item>
    
  </channel>
</rss>
