McRogueFace/roguelike_tutorial/rogueliketutorials.com/Part 2 - The generic Entity...

1177 lines
85 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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, lets 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. Shouldnt 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, lets 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. Shouldnt 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, lets 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. Shouldnt 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, lets 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. Shouldnt we tie those things together in an object,
along with some other data and functions that pertain to it?</p>
<p>Lets 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 well
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">-&gt;</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 Entitys “x” and “y” coordinates on the map.</li>
<li><code>char</code> is the character well 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 well use when drawing the Entity. We define <code>color</code> as a Tuple of three integers, representing the entitys 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 Entitys position. This should look familiar to what we did in the last chapter.</p>
<p>Lets 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() -&gt; 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() -&gt; 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>Were 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. Well need to
modify things to draw both entities, and eventually, draw the map were
going to create as well.</p>
<p>Before doing that, its 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 players input.</li>
</ul>
<p>Soon, were going to need to add a map as well. Its 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>Lets create an <code>Engine</code> class, which will take the
responsibilities of drawing the map and entities, as well as handling
the players 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">-&gt;</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">-&gt;</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>Lets walk through the class a bit, to understand what were 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 cant add an Entity to
the set twice, whereas a list would allow that. In our case, having an
entity in <code>entities</code> twice doesnt 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. Well 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">-&gt;</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: Its 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">-&gt;</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, well 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() -&gt; 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() -&gt; 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 weve 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 weve rendered both the player and the NPC to the screen. With that,
well want to move on to creating a map for our entity to move around
in. We wont do the procedural dungeon generation in this chapter
(thats next), but well 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 its 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>Well 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">-&gt;</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>Thats quite a lot to take in all at once. Lets 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. Well 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 well use in the actual tile itself. Its 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, well want to differentiate between tiles that are and arent in the field of view. <code>dark</code> will represent tiles that are not in the current field of view. Again, well 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">-&gt;</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 well 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 theyre 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. Weve 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 (wont matter since its 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 lets 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">-&gt;</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">&lt;=</span> x <span style="color:#f92672">&lt;</span> self<span style="color:#f92672">.</span>width <span style="color:#f92672">and</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&lt;=</span> y <span style="color:#f92672">&lt;</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">-&gt;</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>Lets 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
youre 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 wont normally
hard-code walls like this, the wall is just for demonstration purposes.
Well 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">-&gt;</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">&lt;=</span> x <span style="color:#f92672">&lt;</span> self<span style="color:#f92672">.</span>width <span style="color:#f92672">and</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&lt;=</span> y <span style="color:#f92672">&lt;</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 maps boundaries. We can use
this to ensure the player doesnt 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">-&gt;</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> classs <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, lets modify <code>main.py</code> to make use of it. Well also need to modify <code>Engine</code> to hold the map. Lets 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() -&gt; 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() -&gt; 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>Weve 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 doesnt yet accept a <code>GameMap</code> in its <code>__init__</code> function, so lets 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]) -&gt; 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) -&gt; 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]) -&gt; 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) -&gt; 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>Weve imported the <code>GameMap</code> class, and were now passing an instance of it in the <code>Engine</code> classs 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, theres 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>Heres 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) -&gt; 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) -&gt; 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) -&gt; 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) -&gt; 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) -&gt; 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) -&gt; 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 were 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>, were 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]) -&gt; 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]) -&gt; 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! Weve 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> &amp; <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>