1092 lines
69 KiB
HTML
1092 lines
69 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" style="color-scheme: dark;"><head>
|
||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||
<title>
|
||
Part 3 - Generating a dungeon · Roguelike Tutorials
|
||
</title>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="color-scheme" content="light dark">
|
||
|
||
|
||
|
||
|
||
<meta name="description" content="Note: This part of the tutorial relies on TCOD version 11.14 or higher. You might need to upgrade the library (and your requirements.txt file, if you’re using one).
|
||
Remember how we created a wall in the last part? We won’t need that anymore. Additionally, our dungeon generator will start by filling the entire map with “wall” tiles and “carving” out rooms, so we can modify our GameMap class to fill in walls instead of floors.">
|
||
<meta name="keywords" content="">
|
||
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:title" content="Part 3 - Generating a dungeon">
|
||
<meta name="twitter:description" content="Note: This part of the tutorial relies on TCOD version 11.14 or higher. You might need to upgrade the library (and your requirements.txt file, if you’re using one).
|
||
Remember how we created a wall in the last part? We won’t need that anymore. Additionally, our dungeon generator will start by filling the entire map with “wall” tiles and “carving” out rooms, so we can modify our GameMap class to fill in walls instead of floors.">
|
||
|
||
<meta property="og:title" content="Part 3 - Generating a dungeon">
|
||
<meta property="og:description" content="Note: This part of the tutorial relies on TCOD version 11.14 or higher. You might need to upgrade the library (and your requirements.txt file, if you’re using one).
|
||
Remember how we created a wall in the last part? We won’t need that anymore. Additionally, our dungeon generator will start by filling the entire map with “wall” tiles and “carving” out rooms, so we can modify our GameMap class to fill in walls instead of floors.">
|
||
<meta property="og:type" content="article">
|
||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-3/"><meta property="article:section" content="tutorials">
|
||
<meta property="article:published_time" content="2020-06-23T00:00:00+00:00">
|
||
<meta property="article:modified_time" content="2020-06-23T00:00:00+00:00">
|
||
|
||
|
||
|
||
|
||
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-3/">
|
||
|
||
|
||
<link rel="preload" href="https://rogueliketutorials.com/fonts/forkawesome-webfont.woff2?v=1.2.0" as="font" type="font/woff2" crossorigin="">
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
|
||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
|
||
|
||
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||
|
||
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
|
||
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
|
||
|
||
|
||
|
||
|
||
<meta name="generator" content="Hugo 0.110.0">
|
||
|
||
|
||
|
||
|
||
|
||
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<body class="colorscheme-dark vsc-initialized">
|
||
|
||
<div class="float-container">
|
||
<a id="dark-mode-toggle" class="colorscheme-toggle">
|
||
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
|
||
</a>
|
||
</div>
|
||
|
||
|
||
<main class="wrapper">
|
||
<nav class="navigation">
|
||
<section class="container">
|
||
<a class="navigation-title" href="https://rogueliketutorials.com/">
|
||
Roguelike Tutorials
|
||
</a>
|
||
|
||
<input type="checkbox" id="menu-toggle">
|
||
<label class="menu-button float-right" for="menu-toggle">
|
||
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
|
||
</label>
|
||
<ul class="navigation-list">
|
||
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
|
||
</li>
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
|
||
</li>
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
|
||
</li>
|
||
|
||
<li class="navigation-item">
|
||
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
|
||
</li>
|
||
|
||
|
||
|
||
</ul>
|
||
|
||
</section>
|
||
</nav>
|
||
|
||
|
||
<div class="content">
|
||
|
||
<section class="container page">
|
||
<article>
|
||
<header>
|
||
<h1 class="title">
|
||
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-3/">
|
||
Part 3 - Generating a dungeon
|
||
</a>
|
||
</h1>
|
||
</header>
|
||
|
||
<p><em>Note: This part of the tutorial relies on TCOD version 11.14
|
||
or higher. You might need to upgrade the library (and your
|
||
requirements.txt file, if you’re using one).</em></p>
|
||
<p>Remember how we created a wall in the last part? We won’t need that
|
||
anymore. Additionally, our dungeon generator will start by filling the
|
||
entire map with “wall” tiles and “carving” out rooms, so we can modify
|
||
our <code>GameMap</code> class to fill in walls instead of floors.</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class GameMap:
|
||
</span></span><span style="display:flex;"><span> def __init__(self, width: int, height: int):
|
||
</span></span><span style="display:flex;"><span> self.width, self.height = width, height
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.tiles = np.full((width, height), fill_value=tile_types.floor, order="F")
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.tiles[30:33, 22] = tile_types.wall
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>class GameMap:
|
||
def __init__(self, width: int, height: int):
|
||
self.width, self.height = width, height
|
||
<span class="crossed-out-text">self.tiles = np.full((width, height), fill_value=tile_types.floor, order="F")</span>
|
||
<span class="new-text">self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")</span>
|
||
|
||
<span class="crossed-out-text">self.tiles[30:33, 22] = tile_types.wall</span>
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Now, on to our dungeon generator.</p>
|
||
<p>The original version of this tutorial put all of the dungeon generation in the <code>GameMap</code>
|
||
class. In fact, this was my plan for this tutorial as well. But, as
|
||
HexDecimal (author of the TCOD library) pointed out in a pull request,
|
||
that’s not very extensible. It puts a lot of code in <code>GameMap</code>
|
||
where it doesn’t necessarily belong, and the class will grow to huge
|
||
proportions if you ever decide to add an alternate dungeon generator.</p>
|
||
<p>The better approach is to put our new code in a separate file, and utilize <code>GameMap</code> there. Let’s create a new file, called <code>procgen.py</code>, which will house our procedural generator.</p>
|
||
<p>Let’s start by creating a class which we’ll use to create our rooms. We can call it <code>RectangularRoom</code>:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> Tuple
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">RectangularRoom</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, x: int, y: int, width: int, height: int):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>x1 <span style="color:#f92672">=</span> x
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>y1 <span style="color:#f92672">=</span> y
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>x2 <span style="color:#f92672">=</span> x <span style="color:#f92672">+</span> width
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>y2 <span style="color:#f92672">=</span> y <span style="color:#f92672">+</span> height
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">center</span>(self) <span style="color:#f92672">-></span> Tuple[int, int]:
|
||
</span></span><span style="display:flex;"><span> center_x <span style="color:#f92672">=</span> int((self<span style="color:#f92672">.</span>x1 <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>x2) <span style="color:#f92672">/</span> <span style="color:#ae81ff">2</span>)
|
||
</span></span><span style="display:flex;"><span> center_y <span style="color:#f92672">=</span> int((self<span style="color:#f92672">.</span>y1 <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>y2) <span style="color:#f92672">/</span> <span style="color:#ae81ff">2</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> center_x, center_y
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">inner</span>(self) <span style="color:#f92672">-></span> Tuple[slice, slice]:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Return the inner area of this room as a 2D array index."""</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> slice(self<span style="color:#f92672">.</span>x1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, self<span style="color:#f92672">.</span>x2), slice(self<span style="color:#f92672">.</span>y1 <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, self<span style="color:#f92672">.</span>y2)
|
||
</span></span></code></pre></div><p>The <code>__init__</code> function
|
||
takes the x and y coordinates of the top left corner, and computes the
|
||
bottom right corner based on the w and h parameters (width and height).</p>
|
||
<p><code>center</code> is a “property”, which essentially acts like a read-only variable for our <code>RectangularRoom</code> class. It describes the “x” and “y” coordinates of the center of a room. It will be useful later on.</p>
|
||
<p>The <code>inner</code> property returns two “slices”, which represent
|
||
the inner portion of our room. This is the part that we’ll be “digging
|
||
out” for our room in our dungeon generator. It gives us an easy way to
|
||
get the area to carve out, which we’ll demonstrate soon.</p>
|
||
<p>We’ll be adding more to this class shortly, but to get us started, that’s all we need.</p>
|
||
<p>What’s with the + 1 on <code>self.x1</code> and <code>self.y1</code>?
|
||
Think about what we’re saying when we tell our program that we want a
|
||
room at coordinates (1, 1) that goes to (6, 6). You might assume that
|
||
would carve out a room like this one (remember that lists are 0-indexed,
|
||
so (0, 0) is a wall in this case):</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span> 0 1 2 3 4 5 6 7
|
||
</span></span><span style="display:flex;"><span>0 # # # # # # # #
|
||
</span></span><span style="display:flex;"><span>1 # . . . . . . #
|
||
</span></span><span style="display:flex;"><span>2 # . . . . . . #
|
||
</span></span><span style="display:flex;"><span>3 # . . . . . . #
|
||
</span></span><span style="display:flex;"><span>4 # . . . . . . #
|
||
</span></span><span style="display:flex;"><span>5 # . . . . . . #
|
||
</span></span><span style="display:flex;"><span>6 # . . . . . . #
|
||
</span></span><span style="display:flex;"><span>7 # # # # # # # #
|
||
</span></span></code></pre></div><p>That’s all fine and good, but what
|
||
happens if we put a room right next to it? Let’s say this room starts at
|
||
(7, 1) and goes to (9, 6)</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span> 0 1 2 3 4 5 6 7 8 9 10
|
||
</span></span><span style="display:flex;"><span>0 # # # # # # # # # # #
|
||
</span></span><span style="display:flex;"><span>1 # . . . . . . . . . #
|
||
</span></span><span style="display:flex;"><span>2 # . . . . . . . . . #
|
||
</span></span><span style="display:flex;"><span>3 # . . . . . . . . . #
|
||
</span></span><span style="display:flex;"><span>4 # . . . . . . . . . #
|
||
</span></span><span style="display:flex;"><span>5 # . . . . . . . . . #
|
||
</span></span><span style="display:flex;"><span>6 # . . . . . . . . . #
|
||
</span></span><span style="display:flex;"><span>7 # # # # # # # # # # #
|
||
</span></span></code></pre></div><p>There’s no wall separating the two!
|
||
That means that if two rooms are one right next to the other, then there
|
||
won’t be a wall between them! So long story short, our function needs
|
||
to take the walls into account when digging out a room. So if we have a
|
||
rectangle with coordinates x1 = 1, x2 = 6, y1 = 1, and y2 = 6, then the
|
||
room should actually look like this:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span> 0 1 2 3 4 5 6 7
|
||
</span></span><span style="display:flex;"><span>0 # # # # # # # #
|
||
</span></span><span style="display:flex;"><span>1 # # # # # # # #
|
||
</span></span><span style="display:flex;"><span>2 # # . . . . # #
|
||
</span></span><span style="display:flex;"><span>3 # # . . . . # #
|
||
</span></span><span style="display:flex;"><span>4 # # . . . . # #
|
||
</span></span><span style="display:flex;"><span>5 # # . . . . # #
|
||
</span></span><span style="display:flex;"><span>6 # # # # # # # #
|
||
</span></span><span style="display:flex;"><span>7 # # # # # # # #
|
||
</span></span></code></pre></div><p>This ensures that we’ll always have
|
||
at least a one tile wide wall between our rooms, unless we choose to
|
||
create overlapping rooms. In order to accomplish this, we add + 1 to x1
|
||
and y1.</p>
|
||
<p>Before we dive into a truly procedurally generated dungeon, let’s
|
||
begin with a simple map that consists of two rooms, connected by a
|
||
tunnel. We can create a new function to create our dungeon, intuitively
|
||
named <code>generate_dungeon</code>, which will return a <code>GameMap</code>. As arguments, it will take the needed width and the height to create the <code>GameMap</code>, and it will utilize the <code>RectangularRoom</code> class to create the needed rooms. Here’s what that looks like:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>from typing import Tuple
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from game_map import GameMap
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import tile_types
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>class RectangularRoom:
|
||
</span></span><span style="display:flex;"><span> def __init__(self, x: int, y: int, width: int, height: int):
|
||
</span></span><span style="display:flex;"><span> self.x1 = x
|
||
</span></span><span style="display:flex;"><span> self.y1 = y
|
||
</span></span><span style="display:flex;"><span> self.x2 = x + width
|
||
</span></span><span style="display:flex;"><span> self.y2 = y + height
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> @property
|
||
</span></span><span style="display:flex;"><span> def inner(self) -> Tuple[slice, slice]:
|
||
</span></span><span style="display:flex;"><span> """Return the inner area of this room as a 2D array index."""
|
||
</span></span><span style="display:flex;"><span> return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+def generate_dungeon(map_width, map_height) -> GameMap:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon = GameMap(map_width, map_height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_1 = RectangularRoom(x=20, y=15, width=10, height=15)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_2 = RectangularRoom(x=35, y=15, width=10, height=15)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon.tiles[room_1.inner] = tile_types.floor
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon.tiles[room_2.inner] = tile_types.floor
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return dungeon
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>from typing import Tuple
|
||
|
||
<span class="new-text">from game_map import GameMap
|
||
import tile_types</span>
|
||
|
||
|
||
class RectangularRoom:
|
||
def __init__(self, x: int, y: int, width: int, height: int):
|
||
self.x1 = x
|
||
self.y1 = y
|
||
self.x2 = x + width
|
||
self.y2 = y + height
|
||
|
||
@property
|
||
def inner(self) -> Tuple[slice, slice]:
|
||
"""Return the inner area of this room as a 2D array index."""
|
||
return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2)
|
||
|
||
|
||
<span class="new-text">def generate_dungeon(map_width, map_height) -> GameMap:
|
||
dungeon = GameMap(map_width, map_height)
|
||
|
||
room_1 = RectangularRoom(x=20, y=15, width=10, height=15)
|
||
room_2 = RectangularRoom(x=35, y=15, width=10, height=15)
|
||
|
||
dungeon.tiles[room_1.inner] = tile_types.floor
|
||
dungeon.tiles[room_2.inner] = tile_types.floor
|
||
|
||
return dungeon</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Now we can modify <code>main.py</code> to utilize our now <code>generate_dungeon</code> function.</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>#!/usr/bin/env python3
|
||
</span></span><span style="display:flex;"><span>import tcod
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>from engine import Engine
|
||
</span></span><span style="display:flex;"><span>from entity import Entity
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from game_map import GameMap
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>from input_handlers import EventHandler
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from procgen import generate_dungeon
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>def main() -> None:
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> entities = {npc, player}
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- game_map = GameMap(map_width, map_height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ game_map = generate_dungeon(map_width, map_height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> engine = Engine(entities=entities, event_handler=event_handler, game_map=game_map, player=player)
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>#!/usr/bin/env python3
|
||
import tcod
|
||
|
||
from engine import Engine
|
||
from entity import Entity
|
||
<span class="crossed-out-text">from game_map import GameMap</span>
|
||
from input_handlers import EventHandler
|
||
<span class="new-text">from procgen import generate_dungeon</span>
|
||
|
||
|
||
def main() -> None:
|
||
...
|
||
|
||
entities = {npc, player}
|
||
|
||
<span class="crossed-out-text">game_map = GameMap(map_width, map_height)</span>
|
||
<span class="new-text">game_map = generate_dungeon(map_width, map_height)</span>
|
||
|
||
engine = Engine(entities=entities, event_handler=event_handler, game_map=game_map, player=player)
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Now is a good time to run your code and make sure everything works as
|
||
expected. The changes we’ve made puts two sample rooms on the map, with
|
||
our player in one of them (our poor NPC is stuck in a wall though).</p>
|
||
<p><img src="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-3-two-rooms.png" alt="Part 3 - Two Rooms"></p>
|
||
<p>I’m sure you’ve noticed already, but the rooms are not connected.
|
||
What’s the use of creating a dungeon if we’re stuck in one room? Not to
|
||
worry, let’s write some code to generate tunnels from one room to
|
||
another. Add the following function to <code>procgen.py</code>:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#a6e22e">+import random
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span><span style="color:#f92672">-from typing import Tuple
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+from typing import Iterator, Tuple
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import tcod
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>from game_map import GameMap
|
||
</span></span><span style="display:flex;"><span>import tile_types
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>...
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span> return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+def tunnel_between(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ start: Tuple[int, int], end: Tuple[int, int]
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+) -> Iterator[Tuple[int, int]]:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """Return an L-shaped tunnel between these two points."""
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x1, y1 = start
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x2, y2 = end
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if random.random() < 0.5: # 50% chance.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Move horizontally, then vertically.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ corner_x, corner_y = x2, y1
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Move vertically, then horizontally.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ corner_x, corner_y = x1, y2
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Generate the coordinates for this tunnel.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ for x, y in tcod.los.bresenham((x1, y1), (corner_x, corner_y)).tolist():
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ yield x, y
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ for x, y in tcod.los.bresenham((corner_x, corner_y), (x2, y2)).tolist():
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ yield x, y
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>def generate_dungeon(map_width, map_height) -> GameMap:
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre><span class="new-text">import random</span>
|
||
<span class="crossed-out-text">from typing import Tuple</span>
|
||
<span class="new-text">from typing import Iterator, Tuple</span>
|
||
|
||
<span class="new-text">import tcod</span>
|
||
|
||
from game_map import GameMap
|
||
import tile_types
|
||
|
||
...
|
||
|
||
...
|
||
return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2)
|
||
|
||
|
||
<span class="new-text">def tunnel_between(
|
||
start: Tuple[int, int], end: Tuple[int, int]
|
||
) -> Iterator[Tuple[int, int]]:
|
||
"""Return an L-shaped tunnel between these two points."""
|
||
x1, y1 = start
|
||
x2, y2 = end
|
||
if random.random() < 0.5: # 50% chance.
|
||
# Move horizontally, then vertically.
|
||
corner_x, corner_y = x2, y1
|
||
else:
|
||
# Move vertically, then horizontally.
|
||
corner_x, corner_y = x1, y2
|
||
|
||
# Generate the coordinates for this tunnel.
|
||
for x, y in tcod.los.bresenham((x1, y1), (corner_x, corner_y)).tolist():
|
||
yield x, y
|
||
for x, y in tcod.los.bresenham((corner_x, corner_y), (x2, y2)).tolist():
|
||
yield x, y</span>
|
||
|
||
|
||
def generate_dungeon(map_width, map_height) -> GameMap:
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Let’s dive into this method.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">tunnel_between</span>(
|
||
</span></span><span style="display:flex;"><span> start: Tuple[int, int], end: Tuple[int, int]
|
||
</span></span><span style="display:flex;"><span>) <span style="color:#f92672">-></span> Iterator[Tuple[int, int]]:
|
||
</span></span></code></pre></div><p>The function takes two arguments,
|
||
both Tuples consisting of two integers. It should return an Iterator of a
|
||
Tuple of two ints. All the Tuples will be “x” and “y” coordinates on
|
||
the map.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#e6db74">"""Return an L-shaped tunnel between these two points."""</span>
|
||
</span></span><span style="display:flex;"><span> x1, y1 <span style="color:#f92672">=</span> start
|
||
</span></span><span style="display:flex;"><span> x2, y2 <span style="color:#f92672">=</span> end
|
||
</span></span></code></pre></div><p>We grab the coordinates out of the Tuples. Simple enough.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> random<span style="color:#f92672">.</span>random() <span style="color:#f92672"><</span> <span style="color:#ae81ff">0.5</span>: <span style="color:#75715e"># 50% chance.</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Move horizontally, then vertically.</span>
|
||
</span></span><span style="display:flex;"><span> corner_x, corner_y <span style="color:#f92672">=</span> x2, y1
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Move vertically, then horizontally.</span>
|
||
</span></span><span style="display:flex;"><span> corner_x, corner_y <span style="color:#f92672">=</span> x1, y2
|
||
</span></span></code></pre></div><p>We’re randomly picking between two
|
||
options: Moving horizontally, then vertically, or the opposite. Based on
|
||
what’s chosen, we’ll set the <code>corner_x</code> and <code>corner_y</code> values to different points.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#75715e"># Generate the coordinates for this tunnel.</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> x, y <span style="color:#f92672">in</span> tcod<span style="color:#f92672">.</span>los<span style="color:#f92672">.</span>bresenham((x1, y1), (corner_x, corner_y))<span style="color:#f92672">.</span>tolist():
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">yield</span> x, y
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> x, y <span style="color:#f92672">in</span> tcod<span style="color:#f92672">.</span>los<span style="color:#f92672">.</span>bresenham((corner_x, corner_y), (x2, y2))<span style="color:#f92672">.</span>tolist():
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">yield</span> x, y
|
||
</span></span></code></pre></div><p>This part is where the “magic” happens.</p>
|
||
<p>tcod includes a function in its line-of-sight module to draw <a href="https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm">Bresenham lines</a>.
|
||
While we’re not working with line-of-sight in this case, the function
|
||
still proves useful to get a line from one point to another. In this
|
||
case, we get one line, then another, to create an “L” shaped tunnel. <code>.tolist()</code> converts the points in the line into, as you might have already guessed, a list.</p>
|
||
<p>What’s with the <code>yield</code> lines though? <a href="https://docs.python.org/3.5/reference/expressions.html#yield-expressions">Yield expressions</a>
|
||
are an interesting part of Python, which allows you to return a
|
||
“generator”. Essentially, rather than returning the values and exiting
|
||
the function altogether, we return the values, but keep the local state.
|
||
This allows the function to pick up where it left off when called
|
||
again, instead of starting from the beginning, as most functions do.</p>
|
||
<p>Why is this helpful? In the next section, we’re going to iterate the <code>x</code> and <code>y</code> values that we receive from the <code>tunnel_between</code> function to dig out our tunnel.</p>
|
||
<p>Let’s put this code to use by drawing a tunnel between our two rooms.</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span> dungeon.tiles[room_2.inner] = tile_types.floor
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ for x, y in tunnel_between(room_2.center, room_1.center):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon.tiles[x, y] = tile_types.floor
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> return dungeon
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre> ...
|
||
dungeon.tiles[room_2.inner] = tile_types.floor
|
||
|
||
<span class="new-text">for x, y in tunnel_between(room_2.center, room_1.center):
|
||
dungeon.tiles[x, y] = tile_types.floor</span>
|
||
|
||
return dungeon</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Run the project, and you’ll see a horizontal tunnel that connects the two rooms. It’s starting to come together!</p>
|
||
<p><img src="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-3-two-rooms-connected.png" alt="Part 3 - Two Rooms"></p>
|
||
<p>Now that we’ve demonstrated to ourselves that our room and tunnel
|
||
functions work as intended, it’s time to move on to an actual dungeon
|
||
generation algorithm. Ours will be fairly simple; we’ll place rooms one
|
||
at a time, making sure they don’t overlap, and connect them with
|
||
tunnels.</p>
|
||
<p>We’ll want a method that tells us if our room is intersecting with another room. Enter the following into the <code>RectangularRoom</code> class:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#a6e22e">+from __future__ import annotations
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>import random
|
||
</span></span><span style="display:flex;"><span>from typing import Iterator, Tuple
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>import tcod
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>from game_map import GameMap
|
||
</span></span><span style="display:flex;"><span>import tile_types
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>class RectangularRoom:
|
||
</span></span><span style="display:flex;"><span> def __init__(self, x: int, y: int, width: int, height: int):
|
||
</span></span><span style="display:flex;"><span> self.x1 = x
|
||
</span></span><span style="display:flex;"><span> self.y1 = y
|
||
</span></span><span style="display:flex;"><span> self.x2 = x + width
|
||
</span></span><span style="display:flex;"><span> self.y2 = y + height
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> @property
|
||
</span></span><span style="display:flex;"><span> def center(self) -> Tuple[int, int]:
|
||
</span></span><span style="display:flex;"><span> center_x = int((self.x1 + self.x2) / 2)
|
||
</span></span><span style="display:flex;"><span> center_y = int((self.y1 + self.y2) / 2)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> return center_x, center_y
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> @property
|
||
</span></span><span style="display:flex;"><span> def inner(self) -> Tuple[slice, slice]:
|
||
</span></span><span style="display:flex;"><span> """Return the inner area of this room as a 2D array index."""
|
||
</span></span><span style="display:flex;"><span> return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def intersects(self, other: RectangularRoom) -> bool:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """Return True if this room overlaps with another RectangularRoom."""
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return (
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.x1 <= other.x2
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ and self.x2 >= other.x1
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ and self.y1 <= other.y2
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ and self.y2 >= other.y1
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>def tunnel_between(
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre><span class="new-text">from __future__ import annotations</span>
|
||
|
||
import random
|
||
from typing import Iterator, Tuple
|
||
|
||
import tcod
|
||
|
||
from game_map import GameMap
|
||
import tile_types
|
||
|
||
|
||
class RectangularRoom:
|
||
def __init__(self, x: int, y: int, width: int, height: int):
|
||
self.x1 = x
|
||
self.y1 = y
|
||
self.x2 = x + width
|
||
self.y2 = y + height
|
||
|
||
@property
|
||
def center(self) -> Tuple[int, int]:
|
||
center_x = int((self.x1 + self.x2) / 2)
|
||
center_y = int((self.y1 + self.y2) / 2)
|
||
|
||
return center_x, center_y
|
||
|
||
@property
|
||
def inner(self) -> Tuple[slice, slice]:
|
||
"""Return the inner area of this room as a 2D array index."""
|
||
return slice(self.x1 + 1, self.x2), slice(self.y1 + 1, self.y2)
|
||
|
||
<span class="new-text">def intersects(self, other: RectangularRoom) -> bool:
|
||
"""Return True if this room overlaps with another RectangularRoom."""
|
||
return (
|
||
self.x1 <= other.x2
|
||
and self.x2 >= other.x1
|
||
and self.y1 <= other.y2
|
||
and self.y2 >= other.y1
|
||
)</span>
|
||
|
||
|
||
def tunnel_between(
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p><code>intersects</code> checks if the room and another room (<code>other</code> in the arguments) intersect or not. It returns <code>True</code> if the do, <code>False</code> if they don’t. We’ll use this to determine if two rooms are overlapping or not.</p>
|
||
<p>We’re going to need a few variables to set the maximum and minimum
|
||
size of the rooms, along with the maximum number of rooms one floor can
|
||
have. Add the following to <code>main.py</code>:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span> map_height = 45
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_max_size = 10
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_min_size = 6
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_rooms = 30
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> tileset = tcod.tileset.load_tilesheet(
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre> ...
|
||
map_height = 45
|
||
|
||
<span class="new-text">room_max_size = 10
|
||
room_min_size = 6
|
||
max_rooms = 30</span>
|
||
|
||
tileset = tcod.tileset.load_tilesheet(
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>At long last, it’s time to modify <code>generate_dungeon</code> to, well, generate our dungeon! You can completely remove our old implementation and replace it with the following:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>from __future__ import annotations
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>import random
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from typing import Iterator, Tuple
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+from typing import Iterator, List, Tuple, TYPE_CHECKING
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>from game_map import GameMap
|
||
</span></span><span style="display:flex;"><span>import tile_types
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+if TYPE_CHECKING:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from entity import Entity
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>...
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">-def generate_dungeon(map_width, map_height) -> GameMap:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- dungeon = GameMap(map_width, map_height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- room_1 = RectangularRoom(x=20, y=15, width=10, height=15)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- room_2 = RectangularRoom(x=35, y=15, width=10, height=15)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- dungeon.tiles[room_1.inner] = tile_types.floor
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- dungeon.tiles[room_2.inner] = tile_types.floor
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- create_horizontal_tunnel(dungeon, 25, 40, 23)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- return dungeon
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+def generate_dungeon(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_rooms: int,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_min_size: int,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_max_size: int,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_width: int,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_height: int,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player: Entity,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+) -> GameMap:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """Generate a new dungeon map."""
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon = GameMap(map_width, map_height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ rooms: List[RectangularRoom] = []
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ for r in range(max_rooms):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_width = random.randint(room_min_size, room_max_size)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_height = random.randint(room_min_size, room_max_size)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x = random.randint(0, dungeon.width - room_width - 1)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y = random.randint(0, dungeon.height - room_height - 1)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # "RectangularRoom" class makes rectangles easier to work with
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ new_room = RectangularRoom(x, y, room_width, room_height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Run through the other rooms and see if they intersect with this one.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if any(new_room.intersects(other_room) for other_room in rooms):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ continue # This room intersects, so go to the next attempt.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # If there are no intersections then the room is valid.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Dig out this rooms inner area.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon.tiles[new_room.inner] = tile_types.floor
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if len(rooms) == 0:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # The first room, where the player starts.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.x, player.y = new_room.center
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else: # All rooms after the first.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Dig out a tunnel between this room and the previous one.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ for x, y in tunnel_between(rooms[-1].center, new_room.center):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon.tiles[x, y] = tile_types.floor
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Finally, append the new room to the list.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ rooms.append(new_room)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return dungeon
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>from __future__ import annotations
|
||
|
||
import random
|
||
<span class="crossed-out-text">from typing import Iterator, Tuple</span>
|
||
<span class="new-text">from typing import Iterator, List, Tuple, TYPE_CHECKING</span>
|
||
|
||
from game_map import GameMap
|
||
import tile_types
|
||
|
||
|
||
<span class="new-text">if TYPE_CHECKING:
|
||
from entity import Entity</span>
|
||
|
||
...
|
||
|
||
<span class="crossed-out-text">def generate_dungeon(map_width, map_height) -> GameMap:</span>
|
||
<span class="crossed-out-text">dungeon = GameMap(map_width, map_height)</span>
|
||
|
||
<span class="crossed-out-text">room_1 = RectangularRoom(x=20, y=15, width=10, height=15)</span>
|
||
<span class="crossed-out-text">room_2 = RectangularRoom(x=35, y=15, width=10, height=15)</span>
|
||
|
||
<span class="crossed-out-text">dungeon.tiles[room_1.inner] = tile_types.floor</span>
|
||
<span class="crossed-out-text">dungeon.tiles[room_2.inner] = tile_types.floor</span>
|
||
|
||
<span class="crossed-out-text">create_horizontal_tunnel(dungeon, 25, 40, 23)</span>
|
||
|
||
<span class="crossed-out-text">return dungeon</span>
|
||
|
||
|
||
<span class="new-text">def generate_dungeon(
|
||
max_rooms: int,
|
||
room_min_size: int,
|
||
room_max_size: int,
|
||
map_width: int,
|
||
map_height: int,
|
||
player: Entity,
|
||
) -> GameMap:
|
||
"""Generate a new dungeon map."""
|
||
dungeon = GameMap(map_width, map_height)
|
||
|
||
rooms: List[RectangularRoom] = []
|
||
|
||
for r in range(max_rooms):
|
||
room_width = random.randint(room_min_size, room_max_size)
|
||
room_height = random.randint(room_min_size, room_max_size)
|
||
|
||
x = random.randint(0, dungeon.width - room_width - 1)
|
||
y = random.randint(0, dungeon.height - room_height - 1)
|
||
|
||
# "RectangularRoom" class makes rectangles easier to work with
|
||
new_room = RectangularRoom(x, y, room_width, room_height)
|
||
|
||
# Run through the other rooms and see if they intersect with this one.
|
||
if any(new_room.intersects(other_room) for other_room in rooms):
|
||
continue # This room intersects, so go to the next attempt.
|
||
# If there are no intersections then the room is valid.
|
||
|
||
# Dig out this rooms inner area.
|
||
dungeon.tiles[new_room.inner] = tile_types.floor
|
||
|
||
if len(rooms) == 0:
|
||
# The first room, where the player starts.
|
||
player.x, player.y = new_room.center
|
||
else: # All rooms after the first.
|
||
# Dig out a tunnel between this room and the previous one.
|
||
for x, y in tunnel_between(rooms[-1].center, new_room.center):
|
||
dungeon.tiles[x, y] = tile_types.floor
|
||
|
||
# Finally, append the new room to the list.
|
||
rooms.append(new_room)
|
||
|
||
return dungeon</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>That’s quite a lengthy function! Let’s break it down and figure out what’s doing what.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">generate_dungeon</span>(
|
||
</span></span><span style="display:flex;"><span> max_rooms: int,
|
||
</span></span><span style="display:flex;"><span> room_min_size: int,
|
||
</span></span><span style="display:flex;"><span> room_max_size: int,
|
||
</span></span><span style="display:flex;"><span> map_width: int,
|
||
</span></span><span style="display:flex;"><span> map_height: int,
|
||
</span></span><span style="display:flex;"><span> player: Entity,
|
||
</span></span><span style="display:flex;"><span>) <span style="color:#f92672">-></span> GameMap:
|
||
</span></span></code></pre></div><p>This is the function definition itself. We pass several arguments to it.</p>
|
||
<ul>
|
||
<li><code>max_rooms</code>: The maximum number of rooms allowed in the dungeon. We’ll use this to control our iteration.</li>
|
||
<li><code>room_min_size</code>: The minimum size of one room.</li>
|
||
<li><code>room_max_size</code>: The maximum size of one room. We’ll pick a random size between this and <code>room_min_size</code> for both the width and the height of one room to carve out.</li>
|
||
<li><code>map_width</code> and <code>map_height</code>: The width and height of the <code>GameMap</code> to create. This is no different than it was before.</li>
|
||
<li><code>player</code>: The player Entity. We need this to know where to place the player.</li>
|
||
</ul>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#e6db74">"""Generate a new dungeon map."""</span>
|
||
</span></span><span style="display:flex;"><span> dungeon <span style="color:#f92672">=</span> GameMap(map_width, map_height)
|
||
</span></span></code></pre></div><p>This isn’t anything new, we’re just creating the initial <code>GameMap</code>.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> rooms: List[RectangularRoom] <span style="color:#f92672">=</span> []
|
||
</span></span></code></pre></div><p>We’ll keep a running list of all the rooms.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> r <span style="color:#f92672">in</span> range(max_rooms):
|
||
</span></span></code></pre></div><p>We iterate from 0 to <code>max_rooms</code>
|
||
- 1. Our algorithm may or may not place a room depending on if it
|
||
intersects with another, so we won’t know how many rooms we’re going to
|
||
end up with. But at least we’ll know that number can’t exceed a certain
|
||
amount.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> room_width <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(room_min_size, room_max_size)
|
||
</span></span><span style="display:flex;"><span> room_height <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(room_min_size, room_max_size)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> x <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(<span style="color:#ae81ff">0</span>, dungeon<span style="color:#f92672">.</span>width <span style="color:#f92672">-</span> room_width <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>)
|
||
</span></span><span style="display:flex;"><span> y <span style="color:#f92672">=</span> random<span style="color:#f92672">.</span>randint(<span style="color:#ae81ff">0</span>, dungeon<span style="color:#f92672">.</span>height <span style="color:#f92672">-</span> room_height <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># "RectangularRoom" class makes rectangles easier to work with</span>
|
||
</span></span><span style="display:flex;"><span> new_room <span style="color:#f92672">=</span> RectangularRoom(x, y, room_width, room_height)
|
||
</span></span></code></pre></div><p>Here, we use the given minimum and maximum room sizes to set the room’s width and height. We then get a random pair of <code>x</code> and <code>y</code> coordinates to try and place the room down. The coordinates must be between 0 and the map’s width and heights.</p>
|
||
<p>We use these variables to then create an instance of our <code>RectangularRoom</code>.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#75715e"># Run through the other rooms and see if they intersect with this one.</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> any(new_room<span style="color:#f92672">.</span>intersects(other_room) <span style="color:#66d9ef">for</span> other_room <span style="color:#f92672">in</span> rooms):
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span> <span style="color:#75715e"># This room intersects, so go to the next attempt.</span>
|
||
</span></span></code></pre></div><p>So what happens if a room <em>does</em> intersect with another? In that case, we can just toss it out, by using <code>continue</code>
|
||
to skip the rest of the loop. Obviously there are more elegant ways of
|
||
dealing with a collision, but for our simplistic algorithm, we’ll just
|
||
pretend like it didn’t happen and try the next one.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#75715e"># If there are no intersections then the room is valid.</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Dig out this rooms inner area.</span>
|
||
</span></span><span style="display:flex;"><span> dungeon<span style="color:#f92672">.</span>tiles[new_room<span style="color:#f92672">.</span>inner] <span style="color:#f92672">=</span> tile_types<span style="color:#f92672">.</span>floor
|
||
</span></span></code></pre></div><p>Here, we “dig” the room out. This is similar to what we were doing before to dig out the two connected rooms.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> len(rooms) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># The first room, where the player starts.</span>
|
||
</span></span><span style="display:flex;"><span> player<span style="color:#f92672">.</span>x, player<span style="color:#f92672">.</span>y <span style="color:#f92672">=</span> new_room<span style="color:#f92672">.</span>center
|
||
</span></span></code></pre></div><p>We put our player down in the center of the first room we created. If this room isn’t the first, we move on to the <code>else</code> statement:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>: <span style="color:#75715e"># All rooms after the first.</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Dig out a tunnel between this room and the previous one.</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> x, y <span style="color:#f92672">in</span> tunnel_between(rooms[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>center, new_room<span style="color:#f92672">.</span>center):
|
||
</span></span><span style="display:flex;"><span> dungeon<span style="color:#f92672">.</span>tiles[x, y] <span style="color:#f92672">=</span> tile_types<span style="color:#f92672">.</span>floor
|
||
</span></span></code></pre></div><p>This is similar to how we dug the tunnel before, except this time, we’re using a negative index with <code>rooms</code> to grab the previous room, and connecting the new room to it.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#75715e"># Finally, append the new room to the list.</span>
|
||
</span></span><span style="display:flex;"><span> rooms<span style="color:#f92672">.</span>append(new_room)
|
||
</span></span></code></pre></div><p>Regardless if it’s the first room or not, we want to append it to the list, so the next iteration can use it.</p>
|
||
<p>So that’s our <code>generate_dungeon</code> function, but we’re not quite finished yet. We need to modify the call we make to this function in <code>main.py</code>:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span> entities = {npc, player}
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- game_map = generate_dungeon(map_width, map_height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ game_map = generate_dungeon(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_rooms=max_rooms,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_min_size=room_min_size,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_max_size=room_max_size,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_width=map_width,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_height=map_height,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player=player
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> engine = Engine(entities=entities, event_handler=event_handler, game_map=game_map, player=player)
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre> ...
|
||
entities = {npc, player}
|
||
|
||
<span class="crossed-out-text">game_map = generate_dungeon(map_width, map_height)</span>
|
||
<span class="new-text">game_map = generate_dungeon(
|
||
max_rooms=max_rooms,
|
||
room_min_size=room_min_size,
|
||
room_max_size=room_max_size,
|
||
map_width=map_width,
|
||
map_height=map_height,
|
||
player=player
|
||
)</span>
|
||
|
||
engine = Engine(entities=entities, event_handler=event_handler, game_map=game_map, player=player)
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>And that’s it! There’s our functioning, albeit basic, dungeon
|
||
generation algorithm. Run the project now and you should be placed in a
|
||
procedurally generated dungeon! Note that our NPC isn’t being placed
|
||
intelligently here, so it may or may not be stuck in a wall.</p>
|
||
<p><img src="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-3-dungeon.png" alt="Part 3 - Generated Dungeon"></p>
|
||
<p><em>Note: Your dungeon will look different from this one, so don’t worry if it doesn’t match the screenshot.</em></p>
|
||
<p>If you want to see the code so far in its entirety, <a href="https://github.com/TStand90/tcod_tutorial_v2/tree/2020/part-3">click
|
||
here</a>.</p>
|
||
<p><a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-4">Click here to move on to the next part of this
|
||
tutorial.</a></p>
|
||
|
||
</article>
|
||
</section>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
<footer class="footer">
|
||
<section class="container">
|
||
©
|
||
|
||
2023
|
||
|
||
·
|
||
|
||
Powered by <a href="https://gohugo.io/">Hugo</a> & <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
|
||
|
||
</section>
|
||
</footer>
|
||
|
||
</main>
|
||
|
||
|
||
|
||
|
||
|
||
<script src="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||
|
||
|
||
|
||
|
||
|
||
<script src="Part%203%20-%20Generating%20a%20dungeon%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</body></html> |