1177 lines
85 KiB
HTML
1177 lines
85 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 2 - The generic Entity, the render functions, and the map · 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="Now that we can move our little ‘@’ symbol around, we need to give it something to move around in. But before that, let’s stop for a moment and think about the player object itself.
|
||
Right now, we just represent the player with the ‘@’ symbol, and its x and y coordinates. Shouldn’t we tie those things together in an object, along with some other data and functions that pertain to it?">
|
||
<meta name="keywords" content="">
|
||
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:title" content="Part 2 - The generic Entity, the render functions, and the map">
|
||
<meta name="twitter:description" content="Now that we can move our little ‘@’ symbol around, we need to give it something to move around in. But before that, let’s stop for a moment and think about the player object itself.
|
||
Right now, we just represent the player with the ‘@’ symbol, and its x and y coordinates. Shouldn’t we tie those things together in an object, along with some other data and functions that pertain to it?">
|
||
|
||
<meta property="og:title" content="Part 2 - The generic Entity, the render functions, and the map">
|
||
<meta property="og:description" content="Now that we can move our little ‘@’ symbol around, we need to give it something to move around in. But before that, let’s stop for a moment and think about the player object itself.
|
||
Right now, we just represent the player with the ‘@’ symbol, and its x and y coordinates. Shouldn’t we tie those things together in an object, along with some other data and functions that pertain to it?">
|
||
<meta property="og:type" content="article">
|
||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-2/"><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-2/">
|
||
|
||
|
||
<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%202%20-%20The%20generic%20Entity,%20the%20render%20functions,%20and%20the%20map%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%202%20-%20The%20generic%20Entity,%20the%20render%20functions,%20and%20the%20map%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%202%20-%20The%20generic%20Entity,%20the%20render%20functions,%20and%20the%20map%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-2/">
|
||
Part 2 - The generic Entity, the render functions, and the map
|
||
</a>
|
||
</h1>
|
||
</header>
|
||
|
||
<p>Now that we can move our little ‘@’ symbol around, we need to give it
|
||
something to move around <em>in</em>. But before that, let’s stop for a moment
|
||
and think about the player object itself.</p>
|
||
<p>Right now, we just represent the player with the ‘@’ symbol, and its x
|
||
and y coordinates. Shouldn’t we tie those things together in an object,
|
||
along with some other data and functions that pertain to it?</p>
|
||
<p>Let’s create a generic class to represent not just the player, but just
|
||
about <em>everything</em> in our game world. Enemies, items, and whatever other
|
||
foreign entities we can dream of will be part of this class, which we’ll
|
||
call <code>Entity</code>.</p>
|
||
<p>Create a new file, and call it <code>entity.py</code>. In that file, put the
|
||
following class:</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">Entity</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> A generic object to represent players, enemies, items, etc.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> """</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, x: int, y: int, char: str, color: Tuple[int, int, int]):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>x <span style="color:#f92672">=</span> x
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>y <span style="color:#f92672">=</span> y
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>char <span style="color:#f92672">=</span> char
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>color <span style="color:#f92672">=</span> color
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">move</span>(self, dx: int, dy: int) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># Move the entity by a given amount</span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>x <span style="color:#f92672">+=</span> dx
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>y <span style="color:#f92672">+=</span> dy</span></span></code></pre></div>
|
||
<p>The initializer (<code>__init__</code>) takes four arguments: <code>x</code>, <code>y</code>, <code>char</code>, and <code>color</code>.</p>
|
||
<ul>
|
||
<li><code>x</code> and <code>y</code> are pretty self explanatory: They represent the Entity’s “x” and “y” coordinates on the map.</li>
|
||
<li><code>char</code> is the character we’ll use to represent the
|
||
entity. Our player will be an “@” symbol, whereas something like a Troll
|
||
(coming in a later chapter) can be the letter “T”.</li>
|
||
<li><code>color</code> is the color we’ll use when drawing the Entity. We define <code>color</code> as a Tuple of three integers, representing the entity’s RGB values.</li>
|
||
</ul>
|
||
<p>The other method is <code>move</code>, which takes <code>dx</code> and <code>dy</code> as arguments, and uses them to modify the Entity’s position. This should look familiar to what we did in the last chapter.</p>
|
||
<p>Let’s put our fancy new class into action! Modify the first part of
|
||
<code>main.py</code> to look like this:</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 actions import EscapeAction, MovementAction
|
||
</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>from input_handlers import EventHandler
|
||
</span></span><span style="display:flex;"><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> screen_width = 80
|
||
</span></span><span style="display:flex;"><span> screen_height = 50
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- player_x = int(screen_width / 2)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- player_y = int(screen_height / 2)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span> tileset = tcod.tileset.load_tilesheet(
|
||
</span></span><span style="display:flex;"><span> "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> event_handler = EventHandler()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player = Entity(int(screen_width / 2), int(screen_height / 2), "@", (255, 255, 255))
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), "@", (255, 255, 0))
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ entities = {npc, player}
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> with tcod.context.new_terminal(
|
||
</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 actions import EscapeAction, MovementAction
|
||
<span class="new-text">from entity import Entity</span>
|
||
from input_handlers import EventHandler
|
||
|
||
|
||
def main() -> None:
|
||
screen_width = 80
|
||
screen_height = 50
|
||
|
||
<span class="crossed-out-text">player_x = int(screen_width / 2)</span>
|
||
<span class="crossed-out-text">player_y = int(screen_height / 2)</span>
|
||
|
||
tileset = tcod.tileset.load_tilesheet(
|
||
"dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||
)
|
||
|
||
event_handler = EventHandler()
|
||
|
||
<span class="new-text">player = Entity(int(screen_width / 2), int(screen_height / 2), "@", (255, 255, 255))
|
||
npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), "@", (255, 255, 0))
|
||
entities = {npc, player}</span>
|
||
|
||
with tcod.context.new_terminal(
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’re importing the <code>Entity</code> class into <code>main.py</code>, and using it to
|
||
initialize the player and a new NPC. We store these two in a set, that
|
||
will eventually hold all our entities on the map.</p>
|
||
<p>Also modify the part where we handle movement so that the Entity class
|
||
handles the actual movement.</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> if isinstance(action, MovementAction):
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- player_x += action.dx
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- player_y += action.dy
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ player.move(dx=action.dx, dy=action.dy)
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre> if isinstance(action, MovementAction):
|
||
<span class="crossed-out-text">player_x += action.dx</span>
|
||
<span class="crossed-out-text">player_y += action.dy</span>
|
||
<span class="new-text">player.move(dx=action.dx, dy=action.dy)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Lastly, update the drawing functions to use the new player object:</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> while True:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- root_console.print(x=player_x, y=player_y, string="@")
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ root_console.print(x=player.x, y=player.y, string=player.char, fg=player.color)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> context.present(root_console)
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre> while True:
|
||
<span class="crossed-out-text">root_console.print(x=player_x, y=player_y, string="@")</span>
|
||
<span class="new-text">root_console.print(x=player.x, y=player.y, string=player.char, fg=player.color)</span>
|
||
|
||
context.present(root_console)</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>If you run the project now, only the player gets drawn. We’ll need to
|
||
modify things to draw both entities, and eventually, draw the map we’re
|
||
going to create as well.</p>
|
||
<p>Before doing that, it’s worth stopping and taking a moment to think about our overall design. Currently, our <code>main.py</code> file is responsible for:</p>
|
||
<ul>
|
||
<li>Setting up the initial variables, like screen size and the tileset.</li>
|
||
<li>Creating the entities</li>
|
||
<li>Drawing the screen and everything on it.</li>
|
||
<li>Reacting to the player’s input.</li>
|
||
</ul>
|
||
<p>Soon, we’re going to need to add a map as well. It’s starting to become a bit much.</p>
|
||
<p>One thing we can do is pass of some of these responsibilities to
|
||
another class, which will be responsible for “running” our game. The <code>main.py</code> file can still set things up and tell that new class what to do, but this design should help keep the <code>main.py</code> file from getting too large over time.</p>
|
||
<p>Let’s create an <code>Engine</code> class, which will take the
|
||
responsibilities of drawing the map and entities, as well as handling
|
||
the player’s input. Create a new file, and call it <code>engine.py</code>. In that file, put the following contents:</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> Set, Iterable, Any
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> tcod.context <span style="color:#f92672">import</span> Context
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> tcod.console <span style="color:#f92672">import</span> Console
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> actions <span style="color:#f92672">import</span> EscapeAction, MovementAction
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> entity <span style="color:#f92672">import</span> Entity
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> input_handlers <span style="color:#f92672">import</span> EventHandler
|
||
</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">Engine</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, entities: Set[Entity], event_handler: EventHandler, player: Entity):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>entities <span style="color:#f92672">=</span> entities
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>event_handler <span style="color:#f92672">=</span> event_handler
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>player <span style="color:#f92672">=</span> player
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">handle_events</span>(self, events: Iterable[Any]) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> event <span style="color:#f92672">in</span> events:
|
||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>event_handler<span style="color:#f92672">.</span>dispatch(event)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> action <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> isinstance(action, MovementAction):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>player<span style="color:#f92672">.</span>move(dx<span style="color:#f92672">=</span>action<span style="color:#f92672">.</span>dx, dy<span style="color:#f92672">=</span>action<span style="color:#f92672">.</span>dy)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> isinstance(action, EscapeAction):
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">SystemExit</span>()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">render</span>(self, console: Console, context: Context) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> entity <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>entities:
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>print(entity<span style="color:#f92672">.</span>x, entity<span style="color:#f92672">.</span>y, entity<span style="color:#f92672">.</span>char, fg<span style="color:#f92672">=</span>entity<span style="color:#f92672">.</span>color)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> context<span style="color:#f92672">.</span>present(console)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>clear()
|
||
</span></span></code></pre></div><p>Let’s walk through the class a bit, to understand what we’re trying to get at here.</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">class</span> <span style="color:#a6e22e">Engine</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, entities: Set[Entity], event_handler: EventHandler, player: Entity):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>entities <span style="color:#f92672">=</span> entities
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>event_handler <span style="color:#f92672">=</span> event_handler
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>player <span style="color:#f92672">=</span> player
|
||
</span></span></code></pre></div><p>The <code>__init__</code> function takes three arguments:</p>
|
||
<ul>
|
||
<li><code>entities</code> is a set (of entities), which behaves kind of
|
||
like a list that enforces uniqueness. That is, we can’t add an Entity to
|
||
the set twice, whereas a list would allow that. In our case, having an
|
||
entity in <code>entities</code> twice doesn’t make sense.</li>
|
||
<li><code>event_handler</code> is the same <code>event_handler</code> that we used in <code>main.py</code>. It will handle our events.</li>
|
||
<li><code>player</code> is the player Entity. We have a separate reference to it outside of <code>entities</code> for ease of access. We’ll need to access <code>player</code> a lot more than a random entity in <code>entities</code>.</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:#66d9ef">def</span> <span style="color:#a6e22e">handle_events</span>(self, events: Iterable[Any]) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> event <span style="color:#f92672">in</span> events:
|
||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>event_handler<span style="color:#f92672">.</span>dispatch(event)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> action <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> isinstance(action, MovementAction):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>player<span style="color:#f92672">.</span>move(dx<span style="color:#f92672">=</span>action<span style="color:#f92672">.</span>dx, dy<span style="color:#f92672">=</span>action<span style="color:#f92672">.</span>dy)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> isinstance(action, EscapeAction):
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">SystemExit</span>()
|
||
</span></span></code></pre></div><p>This should look familiar: It’s almost identical to our event processing in <code>main.py</code>. We pass the <code>events</code> to it so it can iterate through them, and it uses <code>self.event_handler</code> to handle the events.</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">render</span>(self, console: Console, context: Context) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> entity <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>entities:
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>print(entity<span style="color:#f92672">.</span>x, entity<span style="color:#f92672">.</span>y, entity<span style="color:#f92672">.</span>char, fg<span style="color:#f92672">=</span>entity<span style="color:#f92672">.</span>color)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> context<span style="color:#f92672">.</span>present(console)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>clear()
|
||
</span></span></code></pre></div><p>This handles drawing our screen. We iterate through the <code>self.entities</code> and print them to their proper locations, then present the context, and clear the console, like we did in <code>main.py</code>.</p>
|
||
<p>To make use of our new <code>Engine</code> class, we’ll need to modify <code>main.py</code> quite a bit.</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><span style="color:#f92672">-from actions import EscapeAction, MovementAction
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+from engine import Engine
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from entity import Entity
|
||
</span></span><span style="display:flex;"><span>from input_handlers import EventHandler
|
||
</span></span><span style="display:flex;"><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> screen_width = 80
|
||
</span></span><span style="display:flex;"><span> screen_height = 50
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> tileset = tcod.tileset.load_tilesheet(
|
||
</span></span><span style="display:flex;"><span> "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> event_handler = EventHandler()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> player = Entity(int(screen_width / 2), int(screen_height / 2), "@", (255, 255, 255))
|
||
</span></span><span style="display:flex;"><span> npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), "@", (255, 255, 0))
|
||
</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:#a6e22e">+ engine = Engine(entities=entities, event_handler=event_handler, player=player)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> with tcod.context.new_terminal(
|
||
</span></span><span style="display:flex;"><span> screen_width,
|
||
</span></span><span style="display:flex;"><span> screen_height,
|
||
</span></span><span style="display:flex;"><span> tileset=tileset,
|
||
</span></span><span style="display:flex;"><span> title="Yet Another Roguelike Tutorial",
|
||
</span></span><span style="display:flex;"><span> vsync=True,
|
||
</span></span><span style="display:flex;"><span> ) as context:
|
||
</span></span><span style="display:flex;"><span> root_console = tcod.Console(screen_width, screen_height, order="F")
|
||
</span></span><span style="display:flex;"><span> while True:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- root_console.print(x=player_x, y=player_y, string="@")
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ engine.render(console=root_console, context=context)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- context.present(root_console)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ events = tcod.event.wait()
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ engine.handle_events(events)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span><span style="color:#f92672">- root_console.clear()
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- for event in tcod.event.wait():
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- action = event_handler.dispatch(event)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- if action is None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- continue
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- if isinstance(action, MovementAction):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- player_x += action.dx
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- player_y += action.dy
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- elif isinstance(action, EscapeAction):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- raise SystemExit()
|
||
</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>if __name__ == "__main__":
|
||
</span></span><span style="display:flex;"><span> main()
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>#!/usr/bin/env python3
|
||
import tcod
|
||
|
||
<span class="crossed-out-text">from actions import EscapeAction, MovementAction</span>
|
||
<span class="new-text">from engine import Engine</span>
|
||
from entity import Entity
|
||
from input_handlers import EventHandler
|
||
|
||
|
||
def main() -> None:
|
||
screen_width = 80
|
||
screen_height = 50
|
||
|
||
tileset = tcod.tileset.load_tilesheet(
|
||
"dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||
)
|
||
|
||
event_handler = EventHandler()
|
||
|
||
player = Entity(int(screen_width / 2), int(screen_height / 2), "@", (255, 255, 255))
|
||
npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), "@", (255, 255, 0))
|
||
entities = {npc, player}
|
||
|
||
<span class="new-text">engine = Engine(entities=entities, event_handler=event_handler, player=player)</span>
|
||
|
||
with tcod.context.new_terminal(
|
||
screen_width,
|
||
screen_height,
|
||
tileset=tileset,
|
||
title="Yet Another Roguelike Tutorial",
|
||
vsync=True,
|
||
) as context:
|
||
root_console = tcod.Console(screen_width, screen_height, order="F")
|
||
while True:
|
||
<span class="crossed-out-text">root_console.print(x=player_x, y=player_y, string="@")</span>
|
||
<span class="new-text">engine.render(console=root_console, context=context)</span>
|
||
|
||
<span class="crossed-out-text">context.present(root_console)</span>
|
||
<span class="new-text">events = tcod.event.wait()</span>
|
||
|
||
<span class="new-text">engine.handle_events(events)</span>
|
||
<span class="crossed-out-text">root_console.clear()</span>
|
||
|
||
<span class="crossed-out-text">for event in tcod.event.wait():</span>
|
||
<span class="crossed-out-text">action = event_handler.dispatch(event)</span>
|
||
|
||
<span class="crossed-out-text">if action is None:</span>
|
||
<span class="crossed-out-text">continue</span>
|
||
|
||
<span class="crossed-out-text">if isinstance(action, MovementAction):</span>
|
||
<span class="crossed-out-text">player_x += action.dx</span>
|
||
<span class="crossed-out-text">player_y += action.dy</span>
|
||
|
||
<span class="crossed-out-text">elif isinstance(action, EscapeAction):</span>
|
||
<span class="crossed-out-text">raise SystemExit()</span>
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Because we’ve moved the rendering and event handling code to the <code>Engine</code> class, we no longer need it in <code>main.py</code>. All we need to do is create the <code>Engine</code> instance, pass the needed variables to it, and use the methods we wrote for it.</p>
|
||
<p>Run the project now, and your screen should look like this:</p>
|
||
<p><img src="Part%202%20-%20The%20generic%20Entity,%20the%20render%20functions,%20and%20the%20map%20%C2%B7%20Roguelike%20Tutorials_files/part-2-drawing-both-entities.png" alt="Part 2 - Both Entities"></p>
|
||
<p>Our <code>main.py</code> file is looking a lot smaller and simpler,
|
||
and we’ve rendered both the player and the NPC to the screen. With that,
|
||
we’ll want to move on to creating a map for our entity to move around
|
||
in. We won’t do the procedural dungeon generation in this chapter
|
||
(that’s next), but we’ll at least get our class that will hold that map
|
||
set up.</p>
|
||
<p>We can represent the map with a new class, called <code>GameMap</code>.
|
||
The map itself will be made up of tiles, which will contain certain
|
||
data about if the tile is “walkable” (True if it’s a floor, False if its
|
||
a wall), “transparency” (again, True for floors, False for walls), and
|
||
how to render the tile to the screen.</p>
|
||
<p>We’ll create the <code>tiles</code> first. Create a new file called <code>tile_types.py</code> and fill it with the following contents:</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 style="color:#f92672">import</span> numpy <span style="color:#66d9ef">as</span> np <span style="color:#75715e"># type: ignore</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Tile graphics structured type compatible with Console.tiles_rgb.</span>
|
||
</span></span><span style="display:flex;"><span>graphic_dt <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>dtype(
|
||
</span></span><span style="display:flex;"><span> [
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"ch"</span>, np<span style="color:#f92672">.</span>int32), <span style="color:#75715e"># Unicode codepoint.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"fg"</span>, <span style="color:#e6db74">"3B"</span>), <span style="color:#75715e"># 3 unsigned bytes, for RGB colors.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"bg"</span>, <span style="color:#e6db74">"3B"</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><span style="color:#75715e"># Tile struct used for statically defined tile data.</span>
|
||
</span></span><span style="display:flex;"><span>tile_dt <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>dtype(
|
||
</span></span><span style="display:flex;"><span> [
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"walkable"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile can be walked over.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"transparent"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile doesn't block FOV.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"dark"</span>, graphic_dt), <span style="color:#75715e"># Graphics for when this tile is not in FOV.</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>
|
||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">new_tile</span>(
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span>, <span style="color:#75715e"># Enforce the use of keywords, so that parameter order doesn't matter.</span>
|
||
</span></span><span style="display:flex;"><span> walkable: int,
|
||
</span></span><span style="display:flex;"><span> transparent: int,
|
||
</span></span><span style="display:flex;"><span> dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||
</span></span><span style="display:flex;"><span>) <span style="color:#f92672">-></span> np<span style="color:#f92672">.</span>ndarray:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Helper function for defining individual tile types """</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> np<span style="color:#f92672">.</span>array((walkable, transparent, dark), dtype<span style="color:#f92672">=</span>tile_dt)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>floor <span style="color:#f92672">=</span> new_tile(
|
||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">150</span>)),
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span><span style="display:flex;"><span>wall <span style="color:#f92672">=</span> new_tile(
|
||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>, transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>, dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">100</span>)),
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span></code></pre></div><p>That’s quite a lot to take in all at once. Let’s go through 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"># Tile graphics structured type compatible with Console.tiles_rgb.</span>
|
||
</span></span><span style="display:flex;"><span>graphic_dt <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>dtype(
|
||
</span></span><span style="display:flex;"><span> [
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"ch"</span>, np<span style="color:#f92672">.</span>int32), <span style="color:#75715e"># Unicode codepoint.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"fg"</span>, <span style="color:#e6db74">"3B"</span>), <span style="color:#75715e"># 3 unsigned bytes, for RGB colors.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"bg"</span>, <span style="color:#e6db74">"3B"</span>),
|
||
</span></span><span style="display:flex;"><span> ]
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span></code></pre></div><p><code>dtype</code> creates a data type which Numpy can use, which behaves similarly to a <code>struct</code> in a language like C. Our data type is made up of three parts:</p>
|
||
<ul>
|
||
<li><code>ch</code>: The character, represented in integer format. We’ll translate it from the integer into Unicode.</li>
|
||
<li><code>fg</code>: The foreground color. “3B” means 3 unsigned bytes, which can be used for RGB color codes.</li>
|
||
<li><code>bg</code>: The background color. Similar to <code>fg</code>.</li>
|
||
</ul>
|
||
<p>We take this new data type and use it in the next bit:</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"># Tile struct used for statically defined tile data.</span>
|
||
</span></span><span style="display:flex;"><span>tile_dt <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>dtype(
|
||
</span></span><span style="display:flex;"><span> [
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"walkable"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile can be walked over.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"transparent"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile doesn't block FOV.</span>
|
||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"dark"</span>, graphic_dt), <span style="color:#75715e"># Graphics for when this tile is not in FOV.</span>
|
||
</span></span><span style="display:flex;"><span> ]
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span></code></pre></div><p>This is yet another <code>dtype</code>, which we’ll use in the actual tile itself. It’s also made up of three parts:</p>
|
||
<ul>
|
||
<li><code>walkable</code>: A boolean that describes if the player can walk across this tile.</li>
|
||
<li><code>transparent</code>: A boolean that describes if this tile does
|
||
or does not block the field of view. Not used in this chapter, but will
|
||
be in chapter 4.</li>
|
||
<li><code>dark</code>: This uses our previously defined <code>dtype</code>, which holds the character to print, the foreground color, and the background color. Why is it called <code>dark</code>? Because later on, we’ll want to differentiate between tiles that are and aren’t in the field of view. <code>dark</code> will represent tiles that are not in the current field of view. Again, we’ll cover that in part 4.</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:#66d9ef">def</span> <span style="color:#a6e22e">new_tile</span>(
|
||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span>, <span style="color:#75715e"># Enforce the use of keywords, so that parameter order doesn't matter.</span>
|
||
</span></span><span style="display:flex;"><span> walkable: int,
|
||
</span></span><span style="display:flex;"><span> transparent: int,
|
||
</span></span><span style="display:flex;"><span> dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||
</span></span><span style="display:flex;"><span>) <span style="color:#f92672">-></span> np<span style="color:#f92672">.</span>ndarray:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Helper function for defining individual tile types """</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> np<span style="color:#f92672">.</span>array((walkable, transparent, dark), dtype<span style="color:#f92672">=</span>tile_dt)
|
||
</span></span></code></pre></div><p>This is a helper function, that we’ll use in the next section to define our tile types. It takes the parameters <code>walkable</code>, <code>transparent</code>, and <code>dark</code>, which should look familiar, since they’re the same data points we used in <code>tile_dt</code>. It creates a Numpy array of just the one <code>tile_dt</code> element, and returns 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>floor <span style="color:#f92672">=</span> new_tile(
|
||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">150</span>)),
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span><span style="display:flex;"><span>wall <span style="color:#f92672">=</span> new_tile(
|
||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>, transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>, dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">100</span>)),
|
||
</span></span><span style="display:flex;"><span>)
|
||
</span></span></code></pre></div><p>Finally, we arrive to our actual tile types. We’ve got two: <code>floor</code> and <code>wall</code>.</p>
|
||
<p><code>floor</code> is both <code>walkable</code> and <code>transparent</code>. Its <code>dark</code>
|
||
attribute consists of the space character (feel free to change this to
|
||
something else, a lot of roguelikes use “#”) and defines its foreground
|
||
color as white (won’t matter since it’s an empty space) and a background
|
||
color.</p>
|
||
<p><code>wall</code> is neither <code>walkable</code> nor <code>transparent</code>, and its <code>dark</code> attribute differs from <code>floor</code> slightly in its background color.</p>
|
||
<p>Now let’s use our newly created tiles by creating our map class. Create a file called <code>game_map.py</code> and fill it with the following:</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">import</span> numpy <span style="color:#66d9ef">as</span> np <span style="color:#75715e"># type: ignore</span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> tcod.console <span style="color:#f92672">import</span> Console
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> 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:#66d9ef">class</span> <span style="color:#a6e22e">GameMap</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, width: int, height: int):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>width, self<span style="color:#f92672">.</span>height <span style="color:#f92672">=</span> width, height
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>tiles <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>full((width, height), fill_value<span style="color:#f92672">=</span>tile_types<span style="color:#f92672">.</span>floor, order<span style="color:#f92672">=</span><span style="color:#e6db74">"F"</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>tiles[<span style="color:#ae81ff">30</span>:<span style="color:#ae81ff">33</span>, <span style="color:#ae81ff">22</span>] <span style="color:#f92672">=</span> tile_types<span style="color:#f92672">.</span>wall
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">in_bounds</span>(self, x: int, y: int) <span style="color:#f92672">-></span> bool:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Return True if x and y are inside of the bounds of this map."""</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672"><=</span> x <span style="color:#f92672"><</span> self<span style="color:#f92672">.</span>width <span style="color:#f92672">and</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672"><=</span> y <span style="color:#f92672"><</span> self<span style="color:#f92672">.</span>height
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">render</span>(self, console: Console) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>tiles_rgb[<span style="color:#ae81ff">0</span>:self<span style="color:#f92672">.</span>width, <span style="color:#ae81ff">0</span>:self<span style="color:#f92672">.</span>height] <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>tiles[<span style="color:#e6db74">"dark"</span>]
|
||
</span></span></code></pre></div><p>Let’s break down <code>GameMap</code> a bit:</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> __init__(self, width: int, height: int):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>width, self<span style="color:#f92672">.</span>height <span style="color:#f92672">=</span> width, height
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>tiles <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>full((width, height), fill_value<span style="color:#f92672">=</span>tile_types<span style="color:#f92672">.</span>floor, order<span style="color:#f92672">=</span><span style="color:#e6db74">"F"</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>tiles[<span style="color:#ae81ff">30</span>:<span style="color:#ae81ff">33</span>, <span style="color:#ae81ff">22</span>] <span style="color:#f92672">=</span> tile_types<span style="color:#f92672">.</span>wall
|
||
</span></span></code></pre></div><p>The initializer takes <code>width</code> and <code>height</code> integers and assigns them, in one line.</p>
|
||
<p>The <code>self.tiles</code> line might look a little strange if
|
||
you’re not used to Numpy. Basically, we create a 2D array, filled with
|
||
the same values, which in this case, is the <code>tile_types.floor</code> that we created earlier. This will fill <code>self.tiles</code> with floor tiles.</p>
|
||
<p><code>self.tiles[30:33, 22] = tile_types.wall</code> creates a small,
|
||
three tile wide wall at the specified location. We won’t normally
|
||
hard-code walls like this, the wall is just for demonstration purposes.
|
||
We’ll remove it in the next part.</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">in_bounds</span>(self, x: int, y: int) <span style="color:#f92672">-></span> bool:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Return True if x and y are inside of the bounds of this map."""</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672"><=</span> x <span style="color:#f92672"><</span> self<span style="color:#f92672">.</span>width <span style="color:#f92672">and</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672"><=</span> y <span style="color:#f92672"><</span> self<span style="color:#f92672">.</span>height
|
||
</span></span></code></pre></div><p>As the docstring alludes to, this method returns <code>True</code>
|
||
if the given x and y values are within the map’s boundaries. We can use
|
||
this to ensure the player doesn’t move beyond the map, into the void.</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">render</span>(self, console: Console) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>tiles_rgb[<span style="color:#ae81ff">0</span>:self<span style="color:#f92672">.</span>width, <span style="color:#ae81ff">0</span>:self<span style="color:#f92672">.</span>height] <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>tiles[<span style="color:#e6db74">"dark"</span>]
|
||
</span></span></code></pre></div><p>Using the <code>Console</code> class’s <code>tiles_rgb</code> method, we can quickly render the entire map. This method proves much faster than using the <code>console.print</code> method that we use for the individual entities.</p>
|
||
<p>With our <code>GameMap</code> class ready to go, let’s modify <code>main.py</code> to make use of it. We’ll also need to modify <code>Engine</code> to hold the map. Let’s start with <code>main.py</code> though:</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:#a6e22e">+from game_map import GameMap
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from input_handlers import EventHandler
|
||
</span></span><span style="display:flex;"><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> screen_width = 80
|
||
</span></span><span style="display:flex;"><span> screen_height = 50
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_width = 80
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_height = 45
|
||
</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> "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> event_handler = EventHandler()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> player = Entity(int(screen_width / 2), int(screen_height / 2), "@", (255, 255, 255))
|
||
</span></span><span style="display:flex;"><span> npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), "@", (255, 255, 0))
|
||
</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:#a6e22e">+ game_map = 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:#f92672">- engine = Engine(entities=entities, event_handler=event_handler, player=player)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ engine = Engine(entities=entities, event_handler=event_handler, game_map=game_map, player=player)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> with tcod.context.new_terminal(
|
||
</span></span><span style="display:flex;"><span> screen_width,
|
||
</span></span><span style="display:flex;"><span> screen_height,
|
||
</span></span><span style="display:flex;"><span> tileset=tileset,
|
||
</span></span><span style="display:flex;"><span> title="Yet Another Roguelike Tutorial",
|
||
</span></span><span style="display:flex;"><span> vsync=True,
|
||
</span></span><span style="display:flex;"><span> ) as context:
|
||
</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="new-text">from game_map import GameMap</span>
|
||
from input_handlers import EventHandler
|
||
|
||
|
||
def main() -> None:
|
||
screen_width = 80
|
||
screen_height = 50
|
||
|
||
<span class="new-text">map_width = 80
|
||
map_height = 45</span>
|
||
|
||
tileset = tcod.tileset.load_tilesheet(
|
||
"dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||
)
|
||
|
||
event_handler = EventHandler()
|
||
|
||
player = Entity(int(screen_width / 2), int(screen_height / 2), "@", (255, 255, 255))
|
||
npc = Entity(int(screen_width / 2 - 5), int(screen_height / 2), "@", (255, 255, 0))
|
||
entities = {npc, player}
|
||
|
||
<span class="new-text">game_map = GameMap(map_width, map_height)</span>
|
||
|
||
<span class="crossed-out-text">engine = Engine(entities=entities, event_handler=event_handler, player=player)</span>
|
||
<span class="new-text">engine = Engine(entities=entities, event_handler=event_handler, game_map=game_map, player=player)</span>
|
||
|
||
with tcod.context.new_terminal(
|
||
screen_width,
|
||
screen_height,
|
||
tileset=tileset,
|
||
title="Yet Another Roguelike Tutorial",
|
||
vsync=True,
|
||
) as context:</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’ve added <code>map_width</code> and <code>map_height</code>, two integers, which we use in the <code>GameMap</code> class to describe its width and height. The <code>game_map</code> variable holds our initialized <code>GameMap</code>, and we then pass it into <code>engine</code>. The <code>Engine</code> class doesn’t yet accept a <code>GameMap</code> in its <code>__init__</code> function, so let’s fix that now.</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 Set, Iterable, Any
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>from tcod.context import Context
|
||
</span></span><span style="display:flex;"><span>from tcod.console import Console
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>from actions import EscapeAction, MovementAction
|
||
</span></span><span style="display:flex;"><span>from entity import Entity
|
||
</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"></span>from input_handlers import EventHandler
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>class Engine:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- def __init__(self, entities: Set[Entity], event_handler: EventHandler, player: Entity):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> self.entities = entities
|
||
</span></span><span style="display:flex;"><span> self.event_handler = event_handler
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map = game_map
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> self.player = player
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def handle_events(self, events: Iterable[Any]) -> None:
|
||
</span></span><span style="display:flex;"><span> for event in events:
|
||
</span></span><span style="display:flex;"><span> action = self.event_handler.dispatch(event)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> if action is None:
|
||
</span></span><span style="display:flex;"><span> continue
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> if isinstance(action, MovementAction):
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- self.player.move(dx=action.dx, dy=action.dy)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ if self.game_map.tiles["walkable"][self.player.x + action.dx, self.player.y + action.dy]:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.player.move(dx=action.dx, dy=action.dy)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> elif isinstance(action, EscapeAction):
|
||
</span></span><span style="display:flex;"><span> raise SystemExit()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def render(self, console: Console, context: Context) -> None:
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map.render(console)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> for entity in self.entities:
|
||
</span></span><span style="display:flex;"><span> console.print(entity.x, entity.y, entity.char, fg=entity.color)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> context.present(console)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console.clear()
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>from typing import Set, Iterable, Any
|
||
|
||
from tcod.context import Context
|
||
from tcod.console import Console
|
||
|
||
from actions import EscapeAction, MovementAction
|
||
from entity import Entity
|
||
<span class="new-text">from game_map import GameMap</span>
|
||
from input_handlers import EventHandler
|
||
|
||
|
||
class Engine:
|
||
<span class="crossed-out-text">def __init__(self, entities: Set[Entity], event_handler: EventHandler, player: Entity):</span>
|
||
<span class="new-text">def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):</span>
|
||
self.entities = entities
|
||
self.event_handler = event_handler
|
||
<span class="new-text">self.game_map = game_map</span>
|
||
self.player = player
|
||
|
||
def handle_events(self, events: Iterable[Any]) -> None:
|
||
for event in events:
|
||
action = self.event_handler.dispatch(event)
|
||
|
||
if action is None:
|
||
continue
|
||
|
||
if isinstance(action, MovementAction):
|
||
<span class="crossed-out-text">self.player.move(dx=action.dx, dy=action.dy)</span>
|
||
<span class="new-text">if self.game_map.tiles["walkable"][self.player.x + action.dx, self.player.y + action.dy]:
|
||
self.player.move(dx=action.dx, dy=action.dy)</span>
|
||
|
||
elif isinstance(action, EscapeAction):
|
||
raise SystemExit()
|
||
|
||
def render(self, console: Console, context: Context) -> None:
|
||
<span class="new-text">self.game_map.render(console)</span>
|
||
|
||
for entity in self.entities:
|
||
console.print(entity.x, entity.y, entity.char, fg=entity.color)
|
||
|
||
context.present(console)
|
||
|
||
console.clear()</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’ve imported the <code>GameMap</code> class, and we’re now passing an instance of it in the <code>Engine</code> class’s initializer. From there, we utilize it in two ways:</p>
|
||
<ul>
|
||
<li>In <code>handle_events</code>, we use it to check if the tile is “walkable”, and only then do we move the player.</li>
|
||
<li>In <code>render</code>, we call the <code>GameMap</code>’s <code>render</code> method to draw it to the screen.</li>
|
||
</ul>
|
||
<p>If you run the project now, it should look like this:</p>
|
||
<p><img src="Part%202%20-%20The%20generic%20Entity,%20the%20render%20functions,%20and%20the%20map%20%C2%B7%20Roguelike%20Tutorials_files/part-2-entities-and-map.png" alt="Part 2 - Both Entities and Map"></p>
|
||
<p>The darker squares represent the wall, which, if you try to move your character through, should prove to be impenetrable.</p>
|
||
<p>Before we finish this up, there’s one last improvement we can make, thanks to our new <code>Engine</code> class: We can expand our <code>Action</code> classes to do a bit more of the heavy lifting, rather than leaving it to the <code>Engine</code>. This is because we can pass the <code>Engine</code> to the <code>Action</code>, providing it with the context it needs to do what we want.</p>
|
||
<p>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><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><span style="color:#a6e22e">+from typing import TYPE_CHECKING
|
||
</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 TYPE_CHECKING:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from engine import Engine
|
||
</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>class Action:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- pass
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def perform(self, engine: Engine, entity: Entity) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """Perform this action with the objects needed to determine its scope.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ `engine` is the scope this action is being performed in.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ `entity` is the object performing the action.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ This method must be overridden by Action subclasses.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ raise NotImplementedError()
|
||
</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 EscapeAction(Action):
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- pass
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def perform(self, engine: Engine, entity: Entity) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ raise SystemExit()
|
||
</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 MovementAction(Action):
|
||
</span></span><span style="display:flex;"><span> def __init__(self, dx: int, dy: int):
|
||
</span></span><span style="display:flex;"><span> super().__init__()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> self.dx = dx
|
||
</span></span><span style="display:flex;"><span> self.dy = dy
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def perform(self, engine: Engine, entity: Entity) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dest_x = entity.x + self.dx
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dest_y = entity.y + self.dy
|
||
</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 not engine.game_map.in_bounds(dest_x, dest_y):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return # Destination is out of bounds.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if not engine.game_map.tiles["walkable"][dest_x, dest_y]:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return # Destination is blocked by a tile.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ entity.move(self.dx, self.dy)
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre><span class="new-text">from __future__ import annotations
|
||
|
||
from typing import TYPE_CHECKING
|
||
|
||
if TYPE_CHECKING:
|
||
from engine import Engine
|
||
from entity import Entity</span>
|
||
|
||
|
||
class Action:
|
||
<span class="crossed-out-text">pass</span>
|
||
<span class="new-text">def perform(self, engine: Engine, entity: Entity) -> None:
|
||
"""Perform this action with the objects needed to determine its scope.
|
||
|
||
`engine` is the scope this action is being performed in.
|
||
|
||
`entity` is the object performing the action.
|
||
|
||
This method must be overridden by Action subclasses.
|
||
"""
|
||
raise NotImplementedError()</span>
|
||
|
||
|
||
class EscapeAction(Action):
|
||
<span class="crossed-out-text">pass</span>
|
||
<span class="new-text">def perform(self, engine: Engine, entity: Entity) -> None:
|
||
raise SystemExit()</span>
|
||
|
||
|
||
class MovementAction(Action):
|
||
def __init__(self, dx: int, dy: int):
|
||
super().__init__()
|
||
|
||
self.dx = dx
|
||
self.dy = dy
|
||
|
||
<span class="new-text">def perform(self, engine: Engine, entity: Entity) -> None:
|
||
dest_x = entity.x + self.dx
|
||
dest_y = entity.y + self.dy
|
||
|
||
if not engine.game_map.in_bounds(dest_x, dest_y):
|
||
return # Destination is out of bounds.
|
||
if not engine.game_map.tiles["walkable"][dest_x, dest_y]:
|
||
return # Destination is blocked by a tile.
|
||
|
||
entity.move(self.dx, self.dy)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Now we’re passing in the <code>Engine</code> and the <code>Entity</code> performing the action to each <code>Action</code> subclass. Each subclass needs to implement its own version of the <code>perform</code> method. In the case of <code>EscapeAction</code>, we’re just raising <code>SystemExit</code>. In the case of <code>MovementAction</code>,
|
||
we double check that the move is “in bounds” and on a “walkable” tile,
|
||
and if either is true, we return without doing anything. If neither of
|
||
those cases prove true, then we move the entity, as before.</p>
|
||
<p>So what does this new technique do for us? As it turns out, we can simplify the <code>Engine.handle_events</code> method like this:</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><span style="color:#f92672">-from actions import EscapeAction, MovementAction
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>from entity import Entity
|
||
</span></span><span style="display:flex;"><span>from game_map import GameMap
|
||
</span></span><span style="display:flex;"><span>from input_handlers import EventHandler
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>class Engine:
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def handle_events(self, events: Iterable[Any]) -> None:
|
||
</span></span><span style="display:flex;"><span> for event in events:
|
||
</span></span><span style="display:flex;"><span> action = self.event_handler.dispatch(event)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> if action is None:
|
||
</span></span><span style="display:flex;"><span> continue
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ action.perform(self, self.player)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span><span style="color:#f92672">- if isinstance(action, MovementAction):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- if self.game_map.tiles["walkable"][self.player.x + action.dx, self.player.y + action.dy]:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- self.player.move(dx=action.dx, dy=action.dy)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- elif isinstance(action, EscapeAction):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- raise SystemExit()
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>...
|
||
<span class="crossed-out-text">from actions import EscapeAction, MovementAction</span>
|
||
from entity import Entity
|
||
from game_map import GameMap
|
||
from input_handlers import EventHandler
|
||
|
||
|
||
class Engine:
|
||
...
|
||
|
||
def handle_events(self, events: Iterable[Any]) -> None:
|
||
for event in events:
|
||
action = self.event_handler.dispatch(event)
|
||
|
||
if action is None:
|
||
continue
|
||
|
||
<span class="new-text">action.perform(self, self.player)</span>
|
||
<span class="crossed-out-text">if isinstance(action, MovementAction):</span>
|
||
<span class="crossed-out-text">if self.game_map.tiles["walkable"][self.player.x + action.dx, self.player.y + action.dy]:</span>
|
||
<span class="crossed-out-text">self.player.move(dx=action.dx, dy=action.dy)</span>
|
||
|
||
<span class="crossed-out-text">elif isinstance(action, EscapeAction):</span>
|
||
<span class="crossed-out-text">raise SystemExit()</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Much simpler! Run the project again, and it should function the same as before.</p>
|
||
<p>With that, Part 2 is now complete! We’ve managed to lay the
|
||
groundwork for generating dungeons and moving through them, which, as it
|
||
happens, is what the next part is all about.</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-2">click
|
||
here</a>.</p>
|
||
<p><a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-3">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%202%20-%20The%20generic%20Entity,%20the%20render%20functions,%20and%20the%20map%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||
|
||
|
||
|
||
|
||
|
||
<script src="Part%202%20-%20The%20generic%20Entity,%20the%20render%20functions,%20and%20the%20map%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</body></html> |