McRogueFace/roguelike_tutorial/rogueliketutorials.com/Part 11 - Delving into the ...

1810 lines
107 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 11 - Delving into the Dungeon · Roguelike Tutorials
</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<meta name="description" content="Our game isnt much of a “dungeon crawler” if theres only one floor to our dungeon. In this chapter, well allow the player to go down a level, and well put a very basic leveling up system in place, to make the dive all the more rewarding.
Before diving into the code for this section, lets add the color well need this chapter, for when the player descends down a level in the dungeon.">
<meta name="keywords" content="">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Part 11 - Delving into the Dungeon">
<meta name="twitter:description" content="Our game isnt much of a “dungeon crawler” if theres only one floor to our dungeon. In this chapter, well allow the player to go down a level, and well put a very basic leveling up system in place, to make the dive all the more rewarding.
Before diving into the code for this section, lets add the color well need this chapter, for when the player descends down a level in the dungeon.">
<meta property="og:title" content="Part 11 - Delving into the Dungeon">
<meta property="og:description" content="Our game isnt much of a “dungeon crawler” if theres only one floor to our dungeon. In this chapter, well allow the player to go down a level, and well put a very basic leveling up system in place, to make the dive all the more rewarding.
Before diving into the code for this section, lets add the color well need this chapter, for when the player descends down a level in the dungeon.">
<meta property="og:type" content="article">
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-11/"><meta property="article:section" content="tutorials">
<meta property="article:published_time" content="2020-07-21T00:00:00+00:00">
<meta property="article:modified_time" content="2020-07-21T00:00:00+00:00">
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-11/">
<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%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
<link rel="stylesheet" href="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
<link rel="stylesheet" href="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
<meta name="generator" content="Hugo 0.110.0">
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
<body class="colorscheme-dark vsc-initialized">
<div class="float-container">
<a id="dark-mode-toggle" class="colorscheme-toggle">
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
</a>
</div>
<main class="wrapper">
<nav class="navigation">
<section class="container">
<a class="navigation-title" href="https://rogueliketutorials.com/">
Roguelike Tutorials
</a>
<input type="checkbox" id="menu-toggle">
<label class="menu-button float-right" for="menu-toggle">
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
</label>
<ul class="navigation-list">
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
</li>
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
</li>
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
</li>
<li class="navigation-item">
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
</li>
</ul>
</section>
</nav>
<div class="content">
<section class="container page">
<article>
<header>
<h1 class="title">
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-11/">
Part 11 - Delving into the Dungeon
</a>
</h1>
</header>
<p>Our game isnt much of a “dungeon crawler” if theres only one
floor to our dungeon. In this chapter, well allow the player to go down
a level, and well put a very basic leveling up system in place, to
make the dive all the more rewarding.</p>
<p>Before diving into the code for this section, lets add the color
well need this chapter, for when the player descends down a level in
the dungeon. Open up <code>color.py</code> and add this line:</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>enemy_atk = (0xFF, 0xC0, 0xC0)
</span></span><span style="display:flex;"><span>needs_target = (0x3F, 0xFF, 0xFF)
</span></span><span style="display:flex;"><span>status_effect_applied = (0x3F, 0xFF, 0x3F)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+descend = (0x9F, 0x3F, 0xFF)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span>player_die = (0xFF, 0x30, 0x30)
</span></span><span style="display:flex;"><span>enemy_die = (0xFF, 0xA0, 0x30)
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>...
enemy_atk = (0xFF, 0xC0, 0xC0)
needs_target = (0x3F, 0xFF, 0xFF)
status_effect_applied = (0x3F, 0xFF, 0x3F)
<span class="new-text">descend = (0x9F, 0x3F, 0xFF)</span>
player_die = (0xFF, 0x30, 0x30)
enemy_die = (0xFF, 0xA0, 0x30)
...</pre>
</div>
</div>
<p>We will use this color later on, when adding a message to the message log that the player went down one floor.</p>
<p>Well also need a new tile type to represent the downward stairs in the dungeon. Typically, roguelikes represent this with the <code>&gt;</code> character, and well do the same. Add the following to <code>tile_types.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>wall = new_tile(
</span></span><span style="display:flex;"><span> walkable=False,
</span></span><span style="display:flex;"><span> transparent=False,
</span></span><span style="display:flex;"><span> dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
</span></span><span style="display:flex;"><span> light=(ord(" "), (255, 255, 255), (130, 110, 50)),
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+down_stairs = new_tile(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ walkable=True,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ transparent=True,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dark=(ord("&gt;"), (0, 0, 100), (50, 50, 150)),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord("&gt;"), (255, 255, 255), (200, 180, 50)),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+)
</span></span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>...
wall = new_tile(
walkable=False,
transparent=False,
dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
light=(ord(" "), (255, 255, 255), (130, 110, 50)),
)
<span class="new-text">down_stairs = new_tile(
walkable=True,
transparent=True,
dark=(ord("&gt;"), (0, 0, 100), (50, 50, 150)),
light=(ord("&gt;"), (255, 255, 255), (200, 180, 50)),
)</span></pre>
</div>
</div>
<p>To keep track of where our downwards stairs are located on the map, we can add a new variable in out <code>__init__</code> function in the <code>GameMap</code> class. The variable needs some sort of default, so to start, we can set that up to be <code>(0, 0)</code> by default. Add the following line to <code>game_map.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class GameMap:
</span></span><span style="display:flex;"><span> def __init__(
</span></span><span style="display:flex;"><span> self, engine: Engine, width: int, height: int, entities: Iterable[Entity] = ()
</span></span><span style="display:flex;"><span> ):
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span> self.explored = np.full(
</span></span><span style="display:flex;"><span> (width, height), fill_value=False, order="F"
</span></span><span style="display:flex;"><span> ) # Tiles the player has seen before
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.downstairs_location = (0, 0)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> @property
</span></span><span style="display:flex;"><span> def gamemap(self) -&gt; GameMap:
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class GameMap:
def __init__(
self, engine: Engine, width: int, height: int, entities: Iterable[Entity] = ()
):
...
self.explored = np.full(
(width, height), fill_value=False, order="F"
) # Tiles the player has seen before
<span class="new-text">self.downstairs_location = (0, 0)</span>
@property
def gamemap(self) -&gt; GameMap:
...</pre>
</div>
</div>
<p>Of course, <code>(0, 0)</code> wont be the actual location of the
stairs. In order to actually place the downwards stairs, well need to
edit our procedural dungeon generator to place the stairs at the proper
place. Well keep things simple and just place the stairs in the last
room that our algorithm generates, by keeping track of the center
coordinates of the last room we created. Modify <code>generate_dungeon</code> function in <code>procgen.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span> rooms: List[RectangularRoom] = []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ center_of_last_room = (0, 0)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> for r in range(max_rooms):
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span> for x, y in tunnel_between(rooms[-1].center, new_room.center):
</span></span><span style="display:flex;"><span> dungeon.tiles[x, y] = tile_types.floor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ center_of_last_room = new_room.center
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> place_entities(new_room, dungeon, max_monsters_per_room, max_items_per_room)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon.tiles[center_of_last_room] = tile_types.down_stairs
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon.downstairs_location = center_of_last_room
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> # Finally, append the new room to the list.
</span></span><span style="display:flex;"><span> rooms.append(new_room)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> return dungeon
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre> ...
rooms: List[RectangularRoom] = []
<span class="new-text">center_of_last_room = (0, 0)</span>
for r in range(max_rooms):
...
...
for x, y in tunnel_between(rooms[-1].center, new_room.center):
dungeon.tiles[x, y] = tile_types.floor
<span class="new-text">center_of_last_room = new_room.center</span>
place_entities(new_room, dungeon, max_monsters_per_room, max_items_per_room)
<span class="new-text">dungeon.tiles[center_of_last_room] = tile_types.down_stairs
dungeon.downstairs_location = center_of_last_room</span>
# Finally, append the new room to the list.
rooms.append(new_room)
return dungeon</pre>
</div>
</div>
<p>Whichever room is generated last, we take its center and set the <code>downstairs_location</code> equal to those coordinates. We also replace whatever tile type with the <code>down_stairs</code>, so the player can clearly see the location.</p>
<p>To hold the information about the maps, including the size, the room
variables (size and maximum number), along with the floor that the
player is currently on, we can add a class to hold these variables, as
well as generate new maps when the time comes. Open up <code>game_map.py</code> and add the following class:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class GameMap:
</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:#a6e22e">+class GameWorld:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Holds the settings for the GameMap, and generates new maps when moving down the stairs.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def __init__(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self,
</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: Engine,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_width: int,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_height: int,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_rooms: int,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_min_size: int,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_max_size: int,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_monsters_per_room: int,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_items_per_room: int,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ current_floor: int = 0
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ ):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine = engine
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.map_width = map_width
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.map_height = map_height
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.max_rooms = max_rooms
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.room_min_size = room_min_size
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.room_max_size = room_max_size
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.max_monsters_per_room = max_monsters_per_room
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.max_items_per_room = max_items_per_room
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.current_floor = current_floor
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def generate_floor(self) -&gt; None:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from procgen import generate_dungeon
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.current_floor += 1
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.game_map = generate_dungeon(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_rooms=self.max_rooms,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_min_size=self.room_min_size,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ room_max_size=self.room_max_size,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_width=self.map_width,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ map_height=self.map_height,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_monsters_per_room=self.max_monsters_per_room,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ max_items_per_room=self.max_items_per_room,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ engine=self.engine,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class GameMap:
...
<span class="new-text">class GameWorld:
"""
Holds the settings for the GameMap, and generates new maps when moving down the stairs.
"""
def __init__(
self,
*,
engine: Engine,
map_width: int,
map_height: int,
max_rooms: int,
room_min_size: int,
room_max_size: int,
max_monsters_per_room: int,
max_items_per_room: int,
current_floor: int = 0
):
self.engine = engine
self.map_width = map_width
self.map_height = map_height
self.max_rooms = max_rooms
self.room_min_size = room_min_size
self.room_max_size = room_max_size
self.max_monsters_per_room = max_monsters_per_room
self.max_items_per_room = max_items_per_room
self.current_floor = current_floor
def generate_floor(self) -&gt; None:
from procgen import generate_dungeon
self.current_floor += 1
self.engine.game_map = generate_dungeon(
max_rooms=self.max_rooms,
room_min_size=self.room_min_size,
room_max_size=self.room_max_size,
map_width=self.map_width,
map_height=self.map_height,
max_monsters_per_room=self.max_monsters_per_room,
max_items_per_room=self.max_items_per_room,
engine=self.engine,
)</span></pre>
</div>
</div>
<p>The <code>generate_floor</code> method will create the new maps each time we go down a floor, using the variables that <code>GameWorld</code>
stores. In this tutorial, we wont program in the ability to go back up
a floor after going down one, but you could perhaps modify <code>GameWorld</code> to hold the previous maps.</p>
<p>In order to utilize the new <code>GameWorld</code> class, well need to add it to the <code>Engine</code>, 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>if TYPE_CHECKING:
</span></span><span style="display:flex;"><span> from entity import Actor
</span></span><span style="display:flex;"><span><span style="color:#f92672">- from game_map import GameMap
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ from game_map import GameMap, GameWorld
</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 Engine:
</span></span><span style="display:flex;"><span> game_map: GameMap
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ game_world: GameWorld
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> def __init__(self, player: Actor):
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>...
if TYPE_CHECKING:
from entity import Actor
<span class="crossed-out-text">from game_map import GameMap</span>
<span class="new-text">from game_map import GameMap, GameWorld</span>
class Engine:
game_map: GameMap
<span class="new-text">game_world: GameWorld</span>
def __init__(self, player: Actor):
...</pre>
</div>
</div>
<p>Pretty simple. To utilize the new <code>game_world</code> class attribute, edit <code>setup_game.py</code> 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>import tcod
</span></span><span style="display:flex;"><span>import color
</span></span><span style="display:flex;"><span>from engine import Engine
</span></span><span style="display:flex;"><span>import entity_factories
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from game_map import GameWorld
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>import input_handlers
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from procgen import generate_dungeon
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span> engine = Engine(player=player)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">- engine.game_map = generate_dungeon(
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ engine.game_world = GameWorld(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ engine=engine,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> max_rooms=max_rooms,
</span></span><span style="display:flex;"><span> room_min_size=room_min_size,
</span></span><span style="display:flex;"><span> room_max_size=room_max_size,
</span></span><span style="display:flex;"><span> map_width=map_width,
</span></span><span style="display:flex;"><span> map_height=map_height,
</span></span><span style="display:flex;"><span> max_monsters_per_room=max_monsters_per_room,
</span></span><span style="display:flex;"><span> max_items_per_room=max_items_per_room,
</span></span><span style="display:flex;"><span><span style="color:#f92672">- engine=engine,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ engine.game_world.generate_floor()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> engine.update_fov()
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>import tcod
import color
from engine import Engine
import entity_factories
<span class="new-text">from game_map import GameWorld</span>
import input_handlers
<span class="crossed-out-text">from procgen import generate_dungeon</span>
...
...
engine = Engine(player=player)
<span class="crossed-out-text">engine.game_map = generate_dungeon(</span>
<span class="new-text">engine.game_world = GameWorld(
engine=engine,</span>
max_rooms=max_rooms,
room_min_size=room_min_size,
room_max_size=room_max_size,
map_width=map_width,
map_height=map_height,
max_monsters_per_room=max_monsters_per_room,
max_items_per_room=max_items_per_room,
<span class="crossed-out-text">engine=engine,</span>
)
<span class="new-text">engine.game_world.generate_floor()</span>
engine.update_fov()
...</pre>
</div>
</div>
<p>Now, instead of calling <code>generate_dungeon</code> directly, we create a new <code>GameWorld</code> and allow it to call its <code>generate_floor</code>
method. While this doesnt change anything for the first floor thats
created, it will allow us to more easily create new floors on the fly.</p>
<p>In order to actually take the stairs, well need to add an action and
a way for the player to trigger it. Adding the action is pretty simple.
Add the following to <code>actions.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class WaitAction(Action):
</span></span><span style="display:flex;"><span> pass
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+class TakeStairsAction(Action):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def perform(self) -&gt; None:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Take the stairs, if any exist at the entity's location.
</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 (self.entity.x, self.entity.y) == self.engine.game_map.downstairs_location:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.game_world.generate_floor()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.message_log.add_message(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ "You descend the staircase.", color.descend
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ raise exceptions.Impossible("There are no stairs here.")
</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 ActionWithDirection(Action):
</span></span><span style="display:flex;"><span> def __init__(self, entity: Actor, dx: int, dy: int):
</span></span><span style="display:flex;"><span> super().__init__(entity)
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class WaitAction(Action):
pass
<span class="new-text">class TakeStairsAction(Action):
def perform(self) -&gt; None:
"""
Take the stairs, if any exist at the entity's location.
"""
if (self.entity.x, self.entity.y) == self.engine.game_map.downstairs_location:
self.engine.game_world.generate_floor()
self.engine.message_log.add_message(
"You descend the staircase.", color.descend
)
else:
raise exceptions.Impossible("There are no stairs here.")</span>
class ActionWithDirection(Action):
def __init__(self, entity: Actor, dx: int, dy: int):
super().__init__(entity)</pre>
</div>
</div>
<p>To call this action, the player should be able to press the <code>&gt;</code> key. This can be accomplished by adding this to <code>input_handlers.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class MainGameEventHandler(EventHandler):
</span></span><span style="display:flex;"><span> def ev_keydown(self, event: tcod.event.KeyDown) -&gt; Optional[ActionOrHandler]:
</span></span><span style="display:flex;"><span> action: Optional[Action] = None
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> key = event.sym
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ modifier = event.mod
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> player = self.engine.player
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if key == tcod.event.K_PERIOD and modifier &amp; (
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ tcod.event.KMOD_LSHIFT | tcod.event.KMOD_RSHIFT
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ ):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return actions.TakeStairsAction(player)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> if key in MOVE_KEYS:
</span></span><span style="display:flex;"><span> dx, dy = MOVE_KEYS[key]
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class MainGameEventHandler(EventHandler):
def ev_keydown(self, event: tcod.event.KeyDown) -&gt; Optional[ActionOrHandler]:
action: Optional[Action] = None
key = event.sym
<span class="new-text">modifier = event.mod</span>
player = self.engine.player
<span class="new-text">if key == tcod.event.K_PERIOD and modifier &amp; (
tcod.event.KMOD_LSHIFT | tcod.event.KMOD_RSHIFT
):
return actions.TakeStairsAction(player)</span>
if key in MOVE_KEYS:
dx, dy = MOVE_KEYS[key]</pre>
</div>
</div>
<p><code>modifier</code> tells us if the player is holding a key like
control, alt, or shift. In this case, were checking if the user is
holding shift while pressing the period key, which gives us the “&gt;
key.</p>
<p>With that, the player can now descend the staircase to the next floor of the dungeon!</p>
<p><img src="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-11-stairs.png" alt="Part 11 - Stairs">
<img src="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-11-stairs-taken.png" alt="Part 11 - Stairs Taken"></p>
<p>One little touch we can add before moving on to the next section is
adding a way to see which floor the player is on. Its simple enough:
Well use the <code>current_floor</code> in <code>GameWorld</code> to know which floor were on, and well modify our <code>render_functions.py</code> file to add a method to print this information out to the UI.</p>
<p>Add this function to <code>render_functions.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>from __future__ import annotations
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from typing import TYPE_CHECKING
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+from typing import Tuple, TYPE_CHECKING
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span>import color
</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>def render_bar(
</span></span><span style="display:flex;"><span> console: Console, current_value: int, maximum_value: int, total_width: int
</span></span><span style="display:flex;"><span>) -&gt; None:
</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:#a6e22e">+def render_dungeon_level(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console: Console, dungeon_level: int, location: Tuple[int, int]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+) -&gt; None:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Render the level the player is currently on, at the given location.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x, y = location
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(x=x, y=y, string=f"Dungeon level: {dungeon_level}")
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>def render_names_at_mouse_location(
</span></span><span style="display:flex;"><span> console: Console, x: int, y: int, engine: Engine
</span></span><span style="display:flex;"><span>) -&gt; None:
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>from __future__ import annotations
<span class="crossed-out-text">from typing import TYPE_CHECKING</span>
<span class="new-text">from typing import Tuple, TYPE_CHECKING</span>
import color
...
...
def render_bar(
console: Console, current_value: int, maximum_value: int, total_width: int
) -&gt; None:
...
<span class="new-text">def render_dungeon_level(
console: Console, dungeon_level: int, location: Tuple[int, int]
) -&gt; None:
"""
Render the level the player is currently on, at the given location.
"""
x, y = location
console.print(x=x, y=y, string=f"Dungeon level: {dungeon_level}")</span>
def render_names_at_mouse_location(
console: Console, x: int, y: int, engine: Engine
) -&gt; None:
...</pre>
</div>
</div>
<p>The <code>render_dungeon_level</code> function is fairly straightforward: Given a set of <code>(x, y)</code> coordinates as a Tuple, it prints to the console which dungeon level was passed to the function.</p>
<p>To call this function, we can edit the <code>Engine</code>s <code>render</code> function, like so:</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>import exceptions
</span></span><span style="display:flex;"><span>from message_log import MessageLog
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from render_functions import (
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- render_bar,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- render_names_at_mouse_location,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-)
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+import render_functions
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span>if TYPE_CHECKING:
</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>class Engine:
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> def render(self, console: Console) -&gt; None:
</span></span><span style="display:flex;"><span> self.game_map.render(console)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.message_log.render(console=console, x=21, y=45, width=40, height=5)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">- render_bar(
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ render_functions.render_bar(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> console=console,
</span></span><span style="display:flex;"><span> current_value=self.player.fighter.hp,
</span></span><span style="display:flex;"><span> maximum_value=self.player.fighter.max_hp,
</span></span><span style="display:flex;"><span> total_width=20,
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">- render_names_at_mouse_location(console=console, x=21, y=44, engine=self)
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ render_functions.render_dungeon_level(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console=console,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dungeon_level=self.game_world.current_floor,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ location=(0, 47),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ render_functions.render_names_at_mouse_location(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console=console, x=21, y=44, engine=self
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> def save_as(self, filename: str) -&gt; None:
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>...
import exceptions
from message_log import MessageLog
<span class="crossed-out-text">from render_functions import (</span>
<span class="crossed-out-text">render_bar,</span>
<span class="crossed-out-text">render_names_at_mouse_location,</span>
<span class="crossed-out-text">)</span>
<span class="new-text">import render_functions</span>
if TYPE_CHECKING:
...
class Engine:
...
def render(self, console: Console) -&gt; None:
self.game_map.render(console)
self.message_log.render(console=console, x=21, y=45, width=40, height=5)
<span class="crossed-out-text">render_bar(</span>
<span class="new-text">render_functions.render_bar(</span>
console=console,
current_value=self.player.fighter.hp,
maximum_value=self.player.fighter.max_hp,
total_width=20,
)
<span class="crossed-out-text">render_names_at_mouse_location(console=console, x=21, y=44, engine=self)</span>
<span class="new-text">render_functions.render_dungeon_level(
console=console,
dungeon_level=self.game_world.current_floor,
location=(0, 47),
)
render_functions.render_names_at_mouse_location(
console=console, x=21, y=44, engine=self
)</span>
def save_as(self, filename: str) -&gt; None:
...</pre>
</div>
</div>
<p>Note that were now importing <code>render_functions</code> instead
of importing the functions it contains. After awhile, it makes sense to
just import the entire module rather than a few functions here and
there. Otherwise, the file can get a bit difficult to read.</p>
<p>The call to <code>render_dungeon_level</code> shouldnt be anything too surprising. We use <code>self.game_world.current_floor</code> as our <code>dungeon_level</code>, and the location of the printed string is below the health bar (feel free to move this somewhere else, if you like).</p>
<p>Try going down a few levels and make sure everything works as
expected. If so, congratulations! Your dungeon now has multiple levels!</p>
<p><img src="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-11-dungeon-level.png" alt="Part 11 - Dungeon Level"></p>
<p>Speaking of “levels”, many roguelikes (not all!) feature some sort of
level-up system, where your character gains experience and gets
stronger by fighting monsters. The rest of this chapter will be spent
implementing one such system.</p>
<p>In order to allow the rogue to level up, we need to modify the actors in two ways:</p>
<ol>
<li>The player needs to gain experience points, keeping track of the XP gained thus far, and know when its time to level up.</li>
<li>The enemies need to give experience points when they are defeated.</li>
</ol>
<p>There are several calculations we could use to compute how much XP a
player needs to level up (or, theoretically, you could just hard code
the values). Ours will be fairly simple: Well start with a base number,
and add the product of our players current level and some other
number, which will make it so each level up requires more XP than the
last. For this tutorial, the “base” will be 200, and the “factor” will
be 150 (so going to level 2 will take 350 XP, level 3 will take 500, and
so on).</p>
<p>We can accomplish both of these goals by adding one component: <code>Level</code>. The <code>Level</code> component will hold all of the information that we need to accomplish these goals. Create a file called <code>level.py</code> in the <code>components</code> directory, and put the following contents in 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:#f92672">from</span> __future__ <span style="color:#f92672">import</span> annotations
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> TYPE_CHECKING
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> components.base_component <span style="color:#f92672">import</span> BaseComponent
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> TYPE_CHECKING:
</span></span><span style="display:flex;"><span> <span style="color:#f92672">from</span> entity <span style="color:#f92672">import</span> Actor
</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">Level</span>(BaseComponent):
</span></span><span style="display:flex;"><span> parent: Actor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(
</span></span><span style="display:flex;"><span> self,
</span></span><span style="display:flex;"><span> current_level: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span> current_xp: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> level_up_base: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> level_up_factor: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">150</span>,
</span></span><span style="display:flex;"><span> xp_given: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> ):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">=</span> current_level
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">=</span> current_xp
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>level_up_base <span style="color:#f92672">=</span> level_up_base
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>level_up_factor <span style="color:#f92672">=</span> level_up_factor
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>xp_given <span style="color:#f92672">=</span> xp_given
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">experience_to_next_level</span>(self) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>level_up_base <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">*</span> self<span style="color:#f92672">.</span>level_up_factor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">requires_level_up</span>(self) <span style="color:#f92672">-&gt;</span> bool:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">&gt;</span> self<span style="color:#f92672">.</span>experience_to_next_level
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">add_xp</span>(self, xp: int) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> xp <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">or</span> self<span style="color:#f92672">.</span>level_up_base <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">+=</span> xp
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(<span style="color:#e6db74">f</span><span style="color:#e6db74">"You gain </span><span style="color:#e6db74">{</span>xp<span style="color:#e6db74">}</span><span style="color:#e6db74"> experience points."</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>requires_level_up:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"You advance to level </span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span><span style="color:#e6db74">}</span><span style="color:#e6db74">!"</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">increase_level</span>(self) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">-=</span> self<span style="color:#f92672">.</span>experience_to_next_level
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">increase_max_hp</span>(self, amount: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">20</span>) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>fighter<span style="color:#f92672">.</span>max_hp <span style="color:#f92672">+=</span> amount
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>fighter<span style="color:#f92672">.</span>hp <span style="color:#f92672">+=</span> amount
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(<span style="color:#e6db74">"Your health improves!"</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>increase_level()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">increase_power</span>(self, amount: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>fighter<span style="color:#f92672">.</span>power <span style="color:#f92672">+=</span> amount
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(<span style="color:#e6db74">"You feel stronger!"</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>increase_level()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">increase_defense</span>(self, amount: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>parent<span style="color:#f92672">.</span>fighter<span style="color:#f92672">.</span>defense <span style="color:#f92672">+=</span> amount
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(<span style="color:#e6db74">"Your movements are getting swifter!"</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>increase_level()
</span></span></code></pre></div><p>Lets go over what was just added.</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">Level</span>(BaseComponent):
</span></span><span style="display:flex;"><span> parent: Actor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(
</span></span><span style="display:flex;"><span> self,
</span></span><span style="display:flex;"><span> current_level: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span> current_xp: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> level_up_base: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> level_up_factor: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">150</span>,
</span></span><span style="display:flex;"><span> xp_given: int <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span> ):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">=</span> current_level
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">=</span> current_xp
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>level_up_base <span style="color:#f92672">=</span> level_up_base
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>level_up_factor <span style="color:#f92672">=</span> level_up_factor
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>xp_given <span style="color:#f92672">=</span> xp_given
</span></span></code></pre></div><p>The values in our <code>__init__</code> function break down like this:</p>
<ul>
<li>current_level: The current level of the Entity, defaults to 1.</li>
<li>current_xp: The Entitys current experience points.</li>
<li>level_up_base: The base number we decide for leveling up. Well set this to 200 when creating the Player.</li>
<li>level_up_factor: The number to multiply against the Entitys current level.</li>
<li>xp_given: When the Entity dies, this is how much XP the Player will gain.</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:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">experience_to_next_level</span>(self) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>level_up_base <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">*</span> self<span style="color:#f92672">.</span>level_up_factor
</span></span></code></pre></div><p>This represents how much experience
the player needs until hitting the next level. The formula is explained
above. Again, feel free to tweak this formula in any way you see fit.</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:#a6e22e">@property</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">requires_level_up</span>(self) <span style="color:#f92672">-&gt;</span> bool:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">&gt;</span> self<span style="color:#f92672">.</span>experience_to_next_level
</span></span></code></pre></div><p>Well use this property to determine if the player needs to level up or not. If the <code>current_xp</code> is higher than the <code>experience_to_next_level</code> property, then the player levels up. If not, nothing happens.</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">add_xp</span>(self, xp: int) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> xp <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">or</span> self<span style="color:#f92672">.</span>level_up_base <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">+=</span> xp
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(<span style="color:#e6db74">f</span><span style="color:#e6db74">"You gain </span><span style="color:#e6db74">{</span>xp<span style="color:#e6db74">}</span><span style="color:#e6db74"> experience points."</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>requires_level_up:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>engine<span style="color:#f92672">.</span>message_log<span style="color:#f92672">.</span>add_message(
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"You advance to level </span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span><span style="color:#e6db74">}</span><span style="color:#e6db74">!"</span>
</span></span><span style="display:flex;"><span> )
</span></span></code></pre></div><p>This method adds experience points
to the Entitys XP pool, as the name implies. If the value is 0, we just
return, as theres nothing to do. Notice that we also return if the <code>level_up_base</code> is set to 0. Why? In this tutorial, the enemies dont gain XP, so well set their <code>level_up_base</code> to 0 so that theres no way they could ever gain experience. Perhaps in your game, monsters <em>will</em> gain XP, and youll want to adjust this, but thats left up to you.</p>
<p>The rest of the method adds the xp, adds a message to the message log, and, if the Entity levels up, posts another message.</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">increase_level</span>(self) <span style="color:#f92672">-&gt;</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_xp <span style="color:#f92672">-=</span> self<span style="color:#f92672">.</span>experience_to_next_level
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>current_level <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
</span></span></code></pre></div><p>This method adds +1 to the <code>current_level</code>, while decreasing the <code>current_xp</code> by the <code>experience_to_next_level</code>. We do this because if we didnt it would always just take the <code>level_up_factor</code> amount to level up, which isnt what we want. If you wanted to keep track of the players <em>cumulative</em> XP throughout the playthrough, you could skip decrementing the <code>current_xp</code> and instead adjust the <code>experience_to_next_level</code> formula accordingly.</p>
<p>Lastly, the functions <code>increase_max_hp</code>, <code>increase_power</code>, and <code>increase_defense</code> all do basically the same thing: they raise one of the Entitys attributes, add a message to the message log, then call <code>increase_level</code>.</p>
<p>To use this component, we need to add it to our <code>Actor</code> class. Make the following changes to the file <code>entity.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>if TYPE_CHECKING:
</span></span><span style="display:flex;"><span> from components.ai import BaseAI
</span></span><span style="display:flex;"><span> from components.consumable import Consumable
</span></span><span style="display:flex;"><span> from components.fighter import Fighter
</span></span><span style="display:flex;"><span> from components.inventory import Inventory
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from components.level import Level
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> from game_map import GameMap
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>T = TypeVar("T", bound="Entity")
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>class Actor(Entity):
</span></span><span style="display:flex;"><span> def __init__(
</span></span><span style="display:flex;"><span> self,
</span></span><span style="display:flex;"><span> *,
</span></span><span style="display:flex;"><span> x: int = 0,
</span></span><span style="display:flex;"><span> y: int = 0,
</span></span><span style="display:flex;"><span> char: str = "?",
</span></span><span style="display:flex;"><span> color: Tuple[int, int, int] = (255, 255, 255),
</span></span><span style="display:flex;"><span> name: str = "&lt;Unnamed&gt;",
</span></span><span style="display:flex;"><span> ai_cls: Type[BaseAI],
</span></span><span style="display:flex;"><span> fighter: Fighter,
</span></span><span style="display:flex;"><span> inventory: Inventory,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ level: Level,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> ):
</span></span><span style="display:flex;"><span> super().__init__(
</span></span><span style="display:flex;"><span> x=x,
</span></span><span style="display:flex;"><span> y=y,
</span></span><span style="display:flex;"><span> char=char,
</span></span><span style="display:flex;"><span> color=color,
</span></span><span style="display:flex;"><span> name=name,
</span></span><span style="display:flex;"><span> blocks_movement=True,
</span></span><span style="display:flex;"><span> render_order=RenderOrder.ACTOR,
</span></span><span style="display:flex;"><span> )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.ai: Optional[BaseAI] = ai_cls(self)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.fighter = fighter
</span></span><span style="display:flex;"><span> self.fighter.parent = self
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.inventory = inventory
</span></span><span style="display:flex;"><span> self.inventory.parent = self
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.level = level
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.level.parent = self
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> @property
</span></span><span style="display:flex;"><span> def is_alive(self) -&gt; bool:
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>if TYPE_CHECKING:
from components.ai import BaseAI
from components.consumable import Consumable
from components.fighter import Fighter
from components.inventory import Inventory
<span class="new-text">from components.level import Level</span>
from game_map import GameMap
T = TypeVar("T", bound="Entity")
...
class Actor(Entity):
def __init__(
self,
*,
x: int = 0,
y: int = 0,
char: str = "?",
color: Tuple[int, int, int] = (255, 255, 255),
name: str = "&lt;Unnamed&gt;",
ai_cls: Type[BaseAI],
fighter: Fighter,
inventory: Inventory,
<span class="new-text">level: Level,</span>
):
super().__init__(
x=x,
y=y,
char=char,
color=color,
name=name,
blocks_movement=True,
render_order=RenderOrder.ACTOR,
)
self.ai: Optional[BaseAI] = ai_cls(self)
self.fighter = fighter
self.fighter.parent = self
self.inventory = inventory
self.inventory.parent = self
<span class="new-text">self.level = level
self.level.parent = self</span>
@property
def is_alive(self) -&gt; bool:
...</pre>
</div>
</div>
<p>Lets also modify our entities in <code>entity_factories.py</code> 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 components.ai import HostileEnemy
</span></span><span style="display:flex;"><span>from components import consumable
</span></span><span style="display:flex;"><span>from components.fighter import Fighter
</span></span><span style="display:flex;"><span>from components.inventory import Inventory
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from components.level import Level
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from entity import Actor, Item
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>player = Actor(
</span></span><span style="display:flex;"><span> char="@",
</span></span><span style="display:flex;"><span> color=(255, 255, 255),
</span></span><span style="display:flex;"><span> name="Player",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span> fighter=Fighter(hp=30, defense=2, power=5),
</span></span><span style="display:flex;"><span> inventory=Inventory(capacity=26),
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ level=Level(level_up_base=200),
</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>orc = Actor(
</span></span><span style="display:flex;"><span> char="o",
</span></span><span style="display:flex;"><span> color=(63, 127, 63),
</span></span><span style="display:flex;"><span> name="Orc",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span> fighter=Fighter(hp=10, defense=0, power=3),
</span></span><span style="display:flex;"><span> inventory=Inventory(capacity=0),
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ level=Level(xp_given=35),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
</span></span><span style="display:flex;"><span>troll = Actor(
</span></span><span style="display:flex;"><span> char="T",
</span></span><span style="display:flex;"><span> color=(0, 127, 0),
</span></span><span style="display:flex;"><span> name="Troll",
</span></span><span style="display:flex;"><span> ai_cls=HostileEnemy,
</span></span><span style="display:flex;"><span> fighter=Fighter(hp=16, defense=1, power=4),
</span></span><span style="display:flex;"><span> inventory=Inventory(capacity=0),
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ level=Level(xp_given=100),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>from components.ai import HostileEnemy
from components import consumable
from components.fighter import Fighter
from components.inventory import Inventory
<span class="new-text">from components.level import Level</span>
from entity import Actor, Item
player = Actor(
char="@",
color=(255, 255, 255),
name="Player",
ai_cls=HostileEnemy,
fighter=Fighter(hp=30, defense=2, power=5),
inventory=Inventory(capacity=26),
<span class="new-text">level=Level(level_up_base=200),</span>
)
orc = Actor(
char="o",
color=(63, 127, 63),
name="Orc",
ai_cls=HostileEnemy,
fighter=Fighter(hp=10, defense=0, power=3),
inventory=Inventory(capacity=0),
<span class="new-text">level=Level(xp_given=35),</span>
)
troll = Actor(
char="T",
color=(0, 127, 0),
name="Troll",
ai_cls=HostileEnemy,
fighter=Fighter(hp=16, defense=1, power=4),
inventory=Inventory(capacity=0),
<span class="new-text">level=Level(xp_given=100),</span>
)
...</pre>
</div>
</div>
<p>As mentioned, the <code>level_up_base</code> for the player is set to
200. Orcs give 35 XP, and Trolls give 100, since theyre stronger.
These values are completely arbitrary, so feel free to adjust them in
any way you see fit.</p>
<p>When an enemy dies, we need to give the player XP. This is as simple as adding one line to the <code>Fighter</code> component, so open up <code>fighter.py</code> and add 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>class Fighter(BaseComponent):
</span></span><span style="display:flex;"><span> def die(self) -&gt; None:
</span></span><span style="display:flex;"><span> ...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> self.engine.message_log.add_message(death_message, death_message_color)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.player.level.add_xp(self.parent.level.xp_given)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> def heal(self, amount: int) -&gt; int:
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class Fighter(BaseComponent):
def die(self) -&gt; None:
...
self.engine.message_log.add_message(death_message, death_message_color)
<span class="new-text">self.engine.player.level.add_xp(self.parent.level.xp_given)</span>
def heal(self, amount: int) -&gt; int:
...</pre>
</div>
</div>
<p>Now the player will gain XP for defeating enemies!</p>
<p>While the player does gain XP now, notice that we havent actually <em>called</em>
the functions that increase the players stats and levels the player
up. Well need a new interface to do this. The way it will work is that
as soon as the player gets enough experience to level up, well display a
message to the player, giving the player three choices on what stat to
increase. When chosen, the appropriate function will be called, and the
message will close.</p>
<p>Lets create a new event handler, called <code>LevelUpEventHandler</code>, that will do just that. Create the following class in <code>input_handlers.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class AskUserEventHandler(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:#a6e22e">+class LevelUpEventHandler(AskUserEventHandler):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ TITLE = "Level Up"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def on_render(self, console: tcod.Console) -&gt; None:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ super().on_render(console)
</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 self.engine.player.x &lt;= 30:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x = 40
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x = 0
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.draw_frame(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y=0,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ width=35,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ height=8,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ title=self.TITLE,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ clear=True,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ fg=(255, 255, 255),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ bg=(0, 0, 0),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(x=x + 1, y=1, string="Congratulations! You level up!")
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(x=x + 1, y=2, string="Select an attribute to increase.")
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y=4,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ string=f"a) Constitution (+20 HP, from {self.engine.player.fighter.max_hp})",
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y=5,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ string=f"b) Strength (+1 attack, from {self.engine.player.fighter.power})",
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y=6,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ string=f"c) Agility (+1 defense, from {self.engine.player.fighter.defense})",
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def ev_keydown(self, event: tcod.event.KeyDown) -&gt; Optional[ActionOrHandler]:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player = self.engine.player
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ key = event.sym
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ index = key - tcod.event.K_a
</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 0 &lt;= index &lt;= 2:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if index == 0:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.level.increase_max_hp()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif index == 1:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.level.increase_power()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player.level.increase_defense()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.message_log.add_message("Invalid entry.", color.invalid)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return None
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return super().ev_keydown(event)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def ev_mousebuttondown(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self, event: tcod.event.MouseButtonDown
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ ) -&gt; Optional[ActionOrHandler]:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Don't allow the player to click to exit the menu, like normal.
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return None
</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 InventoryEventHandler(AskUserEventHandler):
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class AskUserEventHandler(EventHandler):
...
<span class="new-text">class LevelUpEventHandler(AskUserEventHandler):
TITLE = "Level Up"
def on_render(self, console: tcod.Console) -&gt; None:
super().on_render(console)
if self.engine.player.x &lt;= 30:
x = 40
else:
x = 0
console.draw_frame(
x=x,
y=0,
width=35,
height=8,
title=self.TITLE,
clear=True,
fg=(255, 255, 255),
bg=(0, 0, 0),
)
console.print(x=x + 1, y=1, string="Congratulations! You level up!")
console.print(x=x + 1, y=2, string="Select an attribute to increase.")
console.print(
x=x + 1,
y=4,
string=f"a) Constitution (+20 HP, from {self.engine.player.fighter.max_hp})",
)
console.print(
x=x + 1,
y=5,
string=f"b) Strength (+1 attack, from {self.engine.player.fighter.power})",
)
console.print(
x=x + 1,
y=6,
string=f"c) Agility (+1 defense, from {self.engine.player.fighter.defense})",
)
def ev_keydown(self, event: tcod.event.KeyDown) -&gt; Optional[ActionOrHandler]:
player = self.engine.player
key = event.sym
index = key - tcod.event.K_a
if 0 &lt;= index &lt;= 2:
if index == 0:
player.level.increase_max_hp()
elif index == 1:
player.level.increase_power()
else:
player.level.increase_defense()
else:
self.engine.message_log.add_message("Invalid entry.", color.invalid)
return None
return super().ev_keydown(event)
def ev_mousebuttondown(
self, event: tcod.event.MouseButtonDown
) -&gt; Optional[ActionOrHandler]:
"""
Don't allow the player to click to exit the menu, like normal.
"""
return None</span>
class InventoryEventHandler(AskUserEventHandler):
...</pre>
</div>
</div>
<p>The idea here is very similar to <code>InventoryEventHandler</code> (it inherits from the same <code>AskUserEventHandler</code>
class), but instead of having a variable number of options, its set to
three, one for each of the primary attributes. Furthermore, theres no
way to exit this menu without selecting something. The user <strong>must</strong> level up before continuing. (Notice, we had to override <code>ev_mousebutton</code> to prevent clicks from closing the menu.)</p>
<p>Using <code>LevelUpEventHandler</code> is actually quite simple: We
can check when the player requires a level up at the same time when we
check if the player is still alive. Edit the <code>handle_events</code> method of <code>EventHandler</code> 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> if not self.engine.player.is_alive:
</span></span><span style="display:flex;"><span> # The player was killed sometime during or after the action.
</span></span><span style="display:flex;"><span> return GameOverEventHandler(self.engine)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif self.engine.player.level.requires_level_up:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return LevelUpEventHandler(self.engine)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> return MainGameEventHandler(self.engine) # Return to the main handler.
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre> if not self.engine.player.is_alive:
# The player was killed sometime during or after the action.
return GameOverEventHandler(self.engine)
<span class="new-text">elif self.engine.player.level.requires_level_up:
return LevelUpEventHandler(self.engine)</span>
return MainGameEventHandler(self.engine) # Return to the main handler.</pre>
</div>
</div>
<p>Now, when the player gains the necessary number of experience points, the player will have the chance to level up!</p>
<p><img src="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-11-level-up.png" alt="Part 11 - Level Up"></p>
<p>Before finishing this chapter, theres one last quick thing we can do
to improve the user experience: Add a “character information” screen,
which displays the players stats and current experience. Its actually
quite simple. Add the following class to <code>input_handlers.py</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class AskUserEventHandler(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:#a6e22e">+class CharacterScreenEventHandler(AskUserEventHandler):
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ TITLE = "Character Information"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def on_render(self, console: tcod.Console) -&gt; None:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ super().on_render(console)
</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 self.engine.player.x &lt;= 30:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x = 40
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x = 0
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y = 0
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ width = len(self.TITLE) + 4
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.draw_frame(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y=y,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ width=width,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ height=7,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ title=self.TITLE,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ clear=True,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ fg=(255, 255, 255),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ bg=(0, 0, 0),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1, y=y + 1, string=f"Level: {self.engine.player.level.current_level}"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1, y=y + 2, string=f"XP: {self.engine.player.level.current_xp}"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ y=y + 3,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ string=f"XP for next Level: {self.engine.player.level.experience_to_next_level}",
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1, y=y + 4, string=f"Attack: {self.engine.player.fighter.power}"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=x + 1, y=y + 5, string=f"Defense: {self.engine.player.fighter.defense}"
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span>class LevelUpEventHandler(AskUserEventHandler):
</span></span><span style="display:flex;"><span> ...
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre>class AskUserEventHandler(EventHandler):
...
<span class="new-text">class CharacterScreenEventHandler(AskUserEventHandler):
TITLE = "Character Information"
def on_render(self, console: tcod.Console) -&gt; None:
super().on_render(console)
if self.engine.player.x &lt;= 30:
x = 40
else:
x = 0
y = 0
width = len(self.TITLE) + 4
console.draw_frame(
x=x,
y=y,
width=width,
height=7,
title=self.TITLE,
clear=True,
fg=(255, 255, 255),
bg=(0, 0, 0),
)
console.print(
x=x + 1, y=y + 1, string=f"Level: {self.engine.player.level.current_level}"
)
console.print(
x=x + 1, y=y + 2, string=f"XP: {self.engine.player.level.current_xp}"
)
console.print(
x=x + 1,
y=y + 3,
string=f"XP for next Level: {self.engine.player.level.experience_to_next_level}",
)
console.print(
x=x + 1, y=y + 4, string=f"Attack: {self.engine.player.fighter.power}"
)
console.print(
x=x + 1, y=y + 5, string=f"Defense: {self.engine.player.fighter.defense}"
)</span>
class LevelUpEventHandler(AskUserEventHandler):
...</pre>
</div>
</div>
<p>Similar to <code>LevelUpEventHandler</code>, <code>CharacterScreenEventHandler</code> shows information in a window, but theres no real “choices” to be made here. Any input will simply close the screen.</p>
<p>To open the screen, well have the player press the <code>c</code> key. Add the following to <code>MainGameEventHandler</code>:</p>
<div>
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
Diff
</button>
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
Original
</button>
<div class="data-pane active" data-pane="diff">
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> elif key == tcod.event.K_d:
</span></span><span style="display:flex;"><span> return InventoryDropHandler(self.engine)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif key == tcod.event.K_c:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return CharacterScreenEventHandler(self.engine)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> elif key == tcod.event.K_SLASH:
</span></span><span style="display:flex;"><span> return LookHandler(self.engine)
</span></span></code></pre></div>
</div>
<div class="data-pane" data-pane="original">
<pre> elif key == tcod.event.K_d:
return InventoryDropHandler(self.engine)
<span class="new-text">elif key == tcod.event.K_c:
return CharacterScreenEventHandler(self.engine)</span>
elif key == tcod.event.K_SLASH:
return LookHandler(self.engine)</pre>
</div>
</div>
<p><img src="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/part-11-character-screen.png" alt="Part 11 - Character Screen"></p>
<p>Thats it for this chapter. Weve added the ability to go down
floors, and to level up. While the player can now “progress”, the
environment itself doesnt. The items that spawn on each floor are
always the same, and the enemies dont get tougher as we go down floors.
The next part will address that.</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-11">click here</a>.</p>
<p><a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-12">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%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
<script src="Part%2011%20-%20Delving%20into%20the%20Dungeon%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
</body></html>