1521 lines
98 KiB
HTML
1521 lines
98 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" style="color-scheme: dark;"><head>
|
||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||
<title>
|
||
Part 7 - Creating the Interface · 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 is looking more and more playable by the chapter, but before we move forward with the gameplay, we ought to take a moment to focus on how the project looks. Despite what some roguelike traditionalists may tell you, a good UI goes a long way.
|
||
One of the first things we can do is define a file that will hold our RGB colors. We’ve just been hard-coding them up until now, but it would be nice if they were all in one place and then imported when needed, so that we could easily update them if need be.">
|
||
<meta name="keywords" content="">
|
||
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:title" content="Part 7 - Creating the Interface">
|
||
<meta name="twitter:description" content="Our game is looking more and more playable by the chapter, but before we move forward with the gameplay, we ought to take a moment to focus on how the project looks. Despite what some roguelike traditionalists may tell you, a good UI goes a long way.
|
||
One of the first things we can do is define a file that will hold our RGB colors. We’ve just been hard-coding them up until now, but it would be nice if they were all in one place and then imported when needed, so that we could easily update them if need be.">
|
||
|
||
<meta property="og:title" content="Part 7 - Creating the Interface">
|
||
<meta property="og:description" content="Our game is looking more and more playable by the chapter, but before we move forward with the gameplay, we ought to take a moment to focus on how the project looks. Despite what some roguelike traditionalists may tell you, a good UI goes a long way.
|
||
One of the first things we can do is define a file that will hold our RGB colors. We’ve just been hard-coding them up until now, but it would be nice if they were all in one place and then imported when needed, so that we could easily update them if need be.">
|
||
<meta property="og:type" content="article">
|
||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-7/"><meta property="article:section" content="tutorials">
|
||
<meta property="article:published_time" content="2020-07-07T00:00:00+00:00">
|
||
<meta property="article:modified_time" content="2020-07-07T00:00:00+00:00">
|
||
|
||
|
||
|
||
|
||
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-7/">
|
||
|
||
|
||
<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%207%20-%20Creating%20the%20Interface%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="Part%207%20-%20Creating%20the%20Interface%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%207%20-%20Creating%20the%20Interface%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-7/">
|
||
Part 7 - Creating the Interface
|
||
</a>
|
||
</h1>
|
||
</header>
|
||
|
||
<p>Our game is looking more and more playable by the chapter, but
|
||
before we move forward with the gameplay, we ought to take a moment to
|
||
focus on how the project <em>looks</em>. Despite what some roguelike traditionalists may tell you, a good UI goes a long way.</p>
|
||
<p>One of the first things we can do is define a file that will hold our
|
||
RGB colors. We’ve just been hard-coding them up until now, but it would
|
||
be nice if they were all in one place and then imported when needed, so
|
||
that we could easily update them if need be.</p>
|
||
<p>Create a new file, called <code>color.py</code>, and fill it with the following:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span>white <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0xFF</span>)
|
||
</span></span><span style="display:flex;"><span>black <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0x0</span>, <span style="color:#ae81ff">0x0</span>, <span style="color:#ae81ff">0x0</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>player_atk <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0xE0</span>, <span style="color:#ae81ff">0xE0</span>, <span style="color:#ae81ff">0xE0</span>)
|
||
</span></span><span style="display:flex;"><span>enemy_atk <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0xC0</span>, <span style="color:#ae81ff">0xC0</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>player_die <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0x30</span>, <span style="color:#ae81ff">0x30</span>)
|
||
</span></span><span style="display:flex;"><span>enemy_die <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0xFF</span>, <span style="color:#ae81ff">0xA0</span>, <span style="color:#ae81ff">0x30</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>welcome_text <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0x20</span>, <span style="color:#ae81ff">0xA0</span>, <span style="color:#ae81ff">0xFF</span>)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>bar_text <span style="color:#f92672">=</span> white
|
||
</span></span><span style="display:flex;"><span>bar_filled <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0x0</span>, <span style="color:#ae81ff">0x60</span>, <span style="color:#ae81ff">0x0</span>)
|
||
</span></span><span style="display:flex;"><span>bar_empty <span style="color:#f92672">=</span> (<span style="color:#ae81ff">0x40</span>, <span style="color:#ae81ff">0x10</span>, <span style="color:#ae81ff">0x10</span>)
|
||
</span></span></code></pre></div><p>Some of these colors, like <code>welcome_text</code> and <code>bar_filled</code> are things we haven’t added yet, but don’t worry, we’ll utilize them by the end of the chapter.</p>
|
||
<p>Last chapter, we implemented a basic HP tracker for the player, with
|
||
the promise that we’d revisit in this chapter to make it look better.
|
||
And now, the time has come!</p>
|
||
<p>We’ll create a bar that will gradually decrease as the player loses
|
||
HP. This will help the player visualize how much HP is remaining. To do
|
||
this, we’ll create a generic <code>render_bar</code> function, which can accept different values and change the bar’s length based on the <code>current_value</code> and <code>maximum_value</code> we give to it.</p>
|
||
<p>To house this new function (as well as some other functions that are coming soon), let’s create a new file, called <code>render_functions.py</code>. Put the following into 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">import</span> color
|
||
</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> tcod <span style="color:#f92672">import</span> Console
|
||
</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">render_bar</span>(
|
||
</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>) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> bar_width <span style="color:#f92672">=</span> int(float(current_value) <span style="color:#f92672">/</span> maximum_value <span style="color:#f92672">*</span> total_width)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>draw_rect(x<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, y<span style="color:#f92672">=</span><span style="color:#ae81ff">45</span>, width<span style="color:#f92672">=</span>total_width, height<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, ch<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, bg<span style="color:#f92672">=</span>color<span style="color:#f92672">.</span>bar_empty)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> bar_width <span style="color:#f92672">></span> <span style="color:#ae81ff">0</span>:
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>draw_rect(
|
||
</span></span><span style="display:flex;"><span> x<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, y<span style="color:#f92672">=</span><span style="color:#ae81ff">45</span>, width<span style="color:#f92672">=</span>bar_width, height<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, ch<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, bg<span style="color:#f92672">=</span>color<span style="color:#f92672">.</span>bar_filled
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>print(
|
||
</span></span><span style="display:flex;"><span> x<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, y<span style="color:#f92672">=</span><span style="color:#ae81ff">45</span>, string<span style="color:#f92672">=</span><span style="color:#e6db74">f</span><span style="color:#e6db74">"HP: </span><span style="color:#e6db74">{</span>current_value<span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">{</span>maximum_value<span style="color:#e6db74">}</span><span style="color:#e6db74">"</span>, fg<span style="color:#f92672">=</span>color<span style="color:#f92672">.</span>bar_text
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span></code></pre></div><p>We’re utilizing the <code>draw_rect</code>
|
||
functions provided by TCOD to draw rectangular bars. We’re actually
|
||
drawing two bars, one on top of the other. The first one will be the
|
||
background color, which in the case of our health bar, will be a red
|
||
color. The second goes on top, and is green. The one on top will
|
||
gradually decrease as the player drops hit points, as its width is
|
||
determined by the <code>bar_width</code> variable, which is itself determined by the <code>current_value</code> over the <code>maximum_value</code>.</p>
|
||
<p>We also print the “HP” value over the bar, so the player knows the exact number.</p>
|
||
<p>In order to utilize this new function, make the following changes to <code>engine.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>from input_handlers import MainGameEventHandler
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from render_functions import render_bar
|
||
</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> def render(self, console: Console, context: Context) -> 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><span style="color:#a6e22e">+ render_bar(
|
||
</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">+ current_value=self.player.fighter.hp,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ maximum_value=self.player.fighter.max_hp,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ total_width=20,
|
||
</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 style="color:#f92672">- console.print(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- x=1,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- y=47,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- string=f"HP: {self.player.fighter.hp}/{self.player.fighter.max_hp}",
|
||
</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></span><span style="display:flex;"><span> context.present(console)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console.clear()
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>...
|
||
from input_handlers import MainGameEventHandler
|
||
<span class="new-text">from render_functions import render_bar</span>
|
||
|
||
if TYPE_CHECKING:
|
||
...
|
||
|
||
...
|
||
def render(self, console: Console, context: Context) -> None:
|
||
self.game_map.render(console)
|
||
|
||
<span class="new-text">render_bar(
|
||
console=console,
|
||
current_value=self.player.fighter.hp,
|
||
maximum_value=self.player.fighter.max_hp,
|
||
total_width=20,
|
||
)</span>
|
||
<span class="crossed-out-text">console.print(</span>
|
||
<span class="crossed-out-text">x=1,</span>
|
||
<span class="crossed-out-text">y=47,</span>
|
||
<span class="crossed-out-text">string=f"HP: {self.player.fighter.hp}/{self.player.fighter.max_hp}",</span>
|
||
<span class="crossed-out-text">)</span>
|
||
|
||
context.present(console)
|
||
|
||
console.clear()</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Run the project now, and you should have a functioning health bar!</p>
|
||
<p><img src="Part%207%20-%20Creating%20the%20Interface%20%C2%B7%20Roguelike%20Tutorials_files/part-7-health-bar.png" alt="Part 7 - Health bar"></p>
|
||
<p>What next? One obvious problem with our project at the moment is that
|
||
the messages get printed to the terminal rather than showing up in the
|
||
actual game. We can fix that by adding a message log, which can display
|
||
messages along with different colors for a bit of flash.</p>
|
||
<p>Create a new file, called <code>message_log.py</code>, and put the following contents inside:</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> List, Reversible, Tuple
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> textwrap
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> tcod
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> color
|
||
</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">Message</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, text: str, fg: Tuple[int, int, int]):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>plain_text <span style="color:#f92672">=</span> text
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>fg <span style="color:#f92672">=</span> fg
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>count <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:#a6e22e">@property</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">full_text</span>(self) <span style="color:#f92672">-></span> str:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""The full text of this message, including the count if necessary."""</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>count <span style="color:#f92672">></span> <span style="color:#ae81ff">1</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>plain_text<span style="color:#e6db74">}</span><span style="color:#e6db74"> (x</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>count<span style="color:#e6db74">}</span><span style="color:#e6db74">)"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>plain_text
|
||
</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">MessageLog</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>messages: List[Message] <span style="color:#f92672">=</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">add_message</span>(
|
||
</span></span><span style="display:flex;"><span> self, text: str, fg: Tuple[int, int, int] <span style="color:#f92672">=</span> color<span style="color:#f92672">.</span>white, <span style="color:#f92672">*</span>, stack: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>,
|
||
</span></span><span style="display:flex;"><span> ) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Add a message to this log.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> `text` is the message text, `fg` is the text color.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> If `stack` is True then the message can stack with a previous message
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> of the same text.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> """</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> stack <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>messages <span style="color:#f92672">and</span> text <span style="color:#f92672">==</span> self<span style="color:#f92672">.</span>messages[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>plain_text:
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>messages[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>count <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>messages<span style="color:#f92672">.</span>append(Message(text, fg))
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">render</span>(
|
||
</span></span><span style="display:flex;"><span> self, console: tcod<span style="color:#f92672">.</span>Console, x: int, y: int, width: int, height: int,
|
||
</span></span><span style="display:flex;"><span> ) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Render this log over the given area.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> `x`, `y`, `width`, `height` is the rectangular region to render onto
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> the `console`.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> """</span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>render_messages(console, x, y, width, height, self<span style="color:#f92672">.</span>messages)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@staticmethod</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">render_messages</span>(
|
||
</span></span><span style="display:flex;"><span> console: tcod<span style="color:#f92672">.</span>Console,
|
||
</span></span><span style="display:flex;"><span> x: int,
|
||
</span></span><span style="display:flex;"><span> y: int,
|
||
</span></span><span style="display:flex;"><span> width: int,
|
||
</span></span><span style="display:flex;"><span> height: int,
|
||
</span></span><span style="display:flex;"><span> messages: Reversible[Message],
|
||
</span></span><span style="display:flex;"><span> ) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Render the messages provided.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> The `messages` are rendered starting at the last message and working
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> backwards.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> """</span>
|
||
</span></span><span style="display:flex;"><span> y_offset <span style="color:#f92672">=</span> height <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> message <span style="color:#f92672">in</span> reversed(messages):
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> reversed(textwrap<span style="color:#f92672">.</span>wrap(message<span style="color:#f92672">.</span>full_text, width)):
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>print(x<span style="color:#f92672">=</span>x, y<span style="color:#f92672">=</span>y <span style="color:#f92672">+</span> y_offset, string<span style="color:#f92672">=</span>line, fg<span style="color:#f92672">=</span>message<span style="color:#f92672">.</span>fg)
|
||
</span></span><span style="display:flex;"><span> y_offset <span style="color:#f92672">-=</span> <span style="color:#ae81ff">1</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> y_offset <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 style="color:#75715e"># No more space to print messages.</span>
|
||
</span></span></code></pre></div><p>Let’s go through the additions piece by piece.</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">Message</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, text: str, fg: Tuple[int, int, int]):
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>plain_text <span style="color:#f92672">=</span> text
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>fg <span style="color:#f92672">=</span> fg
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>count <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:#a6e22e">@property</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">full_text</span>(self) <span style="color:#f92672">-></span> str:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""The full text of this message, including the count if necessary."""</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> self<span style="color:#f92672">.</span>count <span style="color:#f92672">></span> <span style="color:#ae81ff">1</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">"</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>plain_text<span style="color:#e6db74">}</span><span style="color:#e6db74"> (x</span><span style="color:#e6db74">{</span>self<span style="color:#f92672">.</span>count<span style="color:#e6db74">}</span><span style="color:#e6db74">)"</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>plain_text
|
||
</span></span></code></pre></div><p>The <code>Message</code> will be used to save and display messages in our log. It includes three pieces of information:</p>
|
||
<ul>
|
||
<li><code>plain_text</code>: The actual message text.</li>
|
||
<li><code>fg</code>: The “foreground” color of the message.</li>
|
||
<li><code>count</code>: This is used to display something like “The Orc
|
||
attacks (x3).” Rather than crowding our message log with the same
|
||
message over and over, we can “stack” the messages by increasing a
|
||
message’s count. This only happens when the same message appears several
|
||
times in a row.</li>
|
||
</ul>
|
||
<p>The <code>full_text</code> property returns the text with its count, if the count is greater than 1. Otherwise, it just returns the message as-is.</p>
|
||
<p>Now, the actual message log:</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">MessageLog</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>messages: List[Message] <span style="color:#f92672">=</span> []
|
||
</span></span></code></pre></div><p>It keeps a list of the <code>Message</code>s received. Nothing too complex here.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">add_message</span>(
|
||
</span></span><span style="display:flex;"><span> self, text: str, fg: Tuple[int, int, int] <span style="color:#f92672">=</span> color<span style="color:#f92672">.</span>white, <span style="color:#f92672">*</span>, stack: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>,
|
||
</span></span><span style="display:flex;"><span> ) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Add a message to this log.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> `text` is the message text, `fg` is the text color.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> If `stack` is True then the message can stack with a previous message
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> of the same text.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> """</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> stack <span style="color:#f92672">and</span> self<span style="color:#f92672">.</span>messages <span style="color:#f92672">and</span> text <span style="color:#f92672">==</span> self<span style="color:#f92672">.</span>messages[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>plain_text:
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>messages[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]<span style="color:#f92672">.</span>count <span style="color:#f92672">+=</span> <span style="color:#ae81ff">1</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>messages<span style="color:#f92672">.</span>append(Message(text, fg))
|
||
</span></span></code></pre></div><p><code>add_message</code> is what adds the message to the log. <code>text</code> is required, but <code>fg</code> will just default to white if nothing is given. <code>stack</code> tells us whether to stack messages or not (which allows us to disable this behavior, if desired).</p>
|
||
<p>If we are allowing stacking, and the added message matches the
|
||
previous message, we just increment the previous message’s count by 1.
|
||
If it’s not a match, we add it to the list.</p>
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">render</span>(
|
||
</span></span><span style="display:flex;"><span> self, console: tcod<span style="color:#f92672">.</span>Console, x: int, y: int, width: int, height: int,
|
||
</span></span><span style="display:flex;"><span> ) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Render this log over the given area.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> `x`, `y`, `width`, `height` is the rectangular region to render onto
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> the `console`.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> """</span>
|
||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>render_messages(console, x, y, width, height, self<span style="color:#f92672">.</span>messages)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">@staticmethod</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">render_messages</span>(
|
||
</span></span><span style="display:flex;"><span> console: tcod<span style="color:#f92672">.</span>Console,
|
||
</span></span><span style="display:flex;"><span> x: int,
|
||
</span></span><span style="display:flex;"><span> y: int,
|
||
</span></span><span style="display:flex;"><span> width: int,
|
||
</span></span><span style="display:flex;"><span> height: int,
|
||
</span></span><span style="display:flex;"><span> messages: Reversible[Message],
|
||
</span></span><span style="display:flex;"><span> ) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Render the messages provided.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> The `messages` are rendered starting at the last message and working
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> backwards.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"> """</span>
|
||
</span></span><span style="display:flex;"><span> y_offset <span style="color:#f92672">=</span> height <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> message <span style="color:#f92672">in</span> reversed(messages):
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> reversed(textwrap<span style="color:#f92672">.</span>wrap(message<span style="color:#f92672">.</span>full_text, width)):
|
||
</span></span><span style="display:flex;"><span> console<span style="color:#f92672">.</span>print(x<span style="color:#f92672">=</span>x, y<span style="color:#f92672">=</span>y <span style="color:#f92672">+</span> y_offset, string<span style="color:#f92672">=</span>line, fg<span style="color:#f92672">=</span>message<span style="color:#f92672">.</span>fg)
|
||
</span></span><span style="display:flex;"><span> y_offset <span style="color:#f92672">-=</span> <span style="color:#ae81ff">1</span>
|
||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> y_offset <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 style="color:#75715e"># No more space to print messages.</span>
|
||
</span></span></code></pre></div><p>This <code>render</code> calls <code>render_messages</code>,
|
||
which is a static method that actually renders the messages to the
|
||
screen. It renders them in reverse order, to make it appear that the
|
||
messages are scrolling in an upwards direction. We use the <code>textwrap.wrap</code>
|
||
function to wrap the text to fit within the given area, and then print
|
||
each line to the console. We can only print so many messages to the
|
||
console, however, so if <code>y_offset</code> reaches -1, we stop.</p>
|
||
<p>To utilize the message log, we’ll first need to add it to the <code>Engine</code> class. Modify <code>engine.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>...
|
||
</span></span><span style="display:flex;"><span>from input_handlers import MainGameEventHandler
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from message_log import MessageLog
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from render_functions import render_bar
|
||
</span></span><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> from game_map import GameMap
|
||
</span></span><span style="display:flex;"><span> from input_handlers import EventHandler
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>class Engine:
|
||
</span></span><span style="display:flex;"><span> game_map: GameMap
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def __init__(self, player: Actor):
|
||
</span></span><span style="display:flex;"><span> self.event_handler: EventHandler = MainGameEventHandler(self)
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.message_log = MessageLog()
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> self.player = player
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def render(self, console: Console, context: Context) -> 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><span style="color:#a6e22e">+ self.message_log.render(console=console, x=21, y=45, width=40, height=5)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> render_bar(
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>...
|
||
from input_handlers import MainGameEventHandler
|
||
<span class="new-text">from message_log import MessageLog</span>
|
||
from render_functions import render_bar
|
||
|
||
if TYPE_CHECKING:
|
||
from entity import Actor
|
||
from game_map import GameMap
|
||
from input_handlers import EventHandler
|
||
|
||
class Engine:
|
||
game_map: GameMap
|
||
|
||
def __init__(self, player: Actor):
|
||
self.event_handler: EventHandler = MainGameEventHandler(self)
|
||
<span class="new-text">self.message_log = MessageLog()</span>
|
||
self.player = player
|
||
...
|
||
|
||
def render(self, console: Console, context: Context) -> None:
|
||
self.game_map.render(console)
|
||
|
||
<span class="new-text">self.message_log.render(console=console, x=21, y=45, width=40, height=5)</span>
|
||
|
||
render_bar(
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’re adding an instance of <code>MessageLog</code> in the initializer, and rendering the log in the Engine’s <code>render</code> method. Nothing too complicated here.</p>
|
||
<p>We need to make a small change to <code>main.py</code> in order to actually make room for our message log. We can also add a friendly welcome message here.</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>#!/usr/bin/env python3
|
||
</span></span><span style="display:flex;"><span>import copy
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>import tcod
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import color
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from engine import Engine
|
||
</span></span><span style="display:flex;"><span>import entity_factories
|
||
</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> map_width = 80
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- map_height = 45
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ map_height = 43
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> room_max_size = 10
|
||
</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> engine.update_fov()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ engine.message_log.add_message(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ "Hello and welcome, adventurer, to yet another dungeon!", color.welcome_text
|
||
</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> with tcod.context.new_terminal(
|
||
</span></span><span style="display:flex;"><span> ...
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>#!/usr/bin/env python3
|
||
import copy
|
||
|
||
import tcod
|
||
|
||
<span class="new-text">import color</span>
|
||
from engine import Engine
|
||
import entity_factories
|
||
...
|
||
|
||
...
|
||
map_width = 80
|
||
<span class="crossed-out-text">map_height = 45</span>
|
||
<span class="new-text">map_height = 43</span>
|
||
|
||
room_max_size = 10
|
||
...
|
||
|
||
...
|
||
engine.update_fov()
|
||
|
||
<span class="new-text">engine.message_log.add_message(
|
||
"Hello and welcome, adventurer, to yet another dungeon!", color.welcome_text
|
||
)</span>
|
||
|
||
with tcod.context.new_terminal(
|
||
...</pre>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Feel free to experiment with different window and map sizes, if you like.</p>
|
||
<p>Run the project, and you should see the welcome message.</p>
|
||
<p><img src="Part%207%20-%20Creating%20the%20Interface%20%C2%B7%20Roguelike%20Tutorials_files/part-7-welcome-message.png" alt="Part 7 - Welcome Message"></p>
|
||
<p>Now that we’ve confirmed our message log accepts and displays messages, we’ll need to replace all of our previous <code>print</code> statements to push messages to the log instead.</p>
|
||
<p>Let’s start with our attack action, in <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>...
|
||
</span></span><span style="display:flex;"><span>from typing import Optional, Tuple, TYPE_CHECKING
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import color
|
||
</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> damage = self.entity.fighter.power - target.fighter.defense
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> attack_desc = f"{self.entity.name.capitalize()} attacks {target.name}"
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.entity is self.engine.player:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ attack_color = color.player_atk
|
||
</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">+ attack_color = color.enemy_atk
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> if damage > 0:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- print(f"{attack_desc} for {damage} hit points.")
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ self.engine.message_log.add_message(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ f"{attack_desc} for {damage} hit points.", attack_color
|
||
</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> target.fighter.hp -= damage
|
||
</span></span><span style="display:flex;"><span> else:
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- print(f"{attack_desc} but does no damage.")
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ self.engine.message_log.add_message(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ f"{attack_desc} but does no damage.", attack_color
|
||
</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>...
|
||
from typing import Optional, Tuple, TYPE_CHECKING
|
||
|
||
<span class="new-text">import color</span>
|
||
|
||
if TYPE_CHECKING:
|
||
...
|
||
|
||
...
|
||
damage = self.entity.fighter.power - target.fighter.defense
|
||
|
||
attack_desc = f"{self.entity.name.capitalize()} attacks {target.name}"
|
||
<span class="new-text">if self.entity is self.engine.player:
|
||
attack_color = color.player_atk
|
||
else:
|
||
attack_color = color.enemy_atk</span>
|
||
|
||
if damage > 0:
|
||
<span class="crossed-out-text">print(f"{attack_desc} for {damage} hit points.")</span>
|
||
<span class="new-text">self.engine.message_log.add_message(
|
||
f"{attack_desc} for {damage} hit points.", attack_color
|
||
)</span>
|
||
target.fighter.hp -= damage
|
||
else:
|
||
<span class="crossed-out-text">print(f"{attack_desc} but does no damage.")</span>
|
||
<span class="new-text">self.engine.message_log.add_message(
|
||
f"{attack_desc} but does no damage.", attack_color
|
||
)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We determine the color based on who is doing the attacking. Other
|
||
than that, there’s really nothing new here, we’re just pushing those
|
||
messages to the log rather than printing them.</p>
|
||
<p>Now we just need to update our death messages. Open up <code>fighter.py</code> and modify it 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>from __future__ import annotations
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>from typing import TYPE_CHECKING
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import color
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>from components.base_component import BaseComponent
|
||
</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 die(self) -> None:
|
||
</span></span><span style="display:flex;"><span> if self.engine.player is self.entity:
|
||
</span></span><span style="display:flex;"><span> death_message = "You died!"
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ death_message_color = color.player_die
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> self.engine.event_handler = GameOverEventHandler(self.engine)
|
||
</span></span><span style="display:flex;"><span> else:
|
||
</span></span><span style="display:flex;"><span> death_message = f"{self.entity.name} is dead!"
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ death_message_color = color.enemy_die
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> self.entity.char = "%"
|
||
</span></span><span style="display:flex;"><span> self.entity.color = (191, 0, 0)
|
||
</span></span><span style="display:flex;"><span> self.entity.blocks_movement = False
|
||
</span></span><span style="display:flex;"><span> self.entity.ai = None
|
||
</span></span><span style="display:flex;"><span> self.entity.name = f"remains of {self.entity.name}"
|
||
</span></span><span style="display:flex;"><span> self.entity.render_order = RenderOrder.CORPSE
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- print(death_message)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ self.engine.message_log.add_message(death_message, death_message_color)
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>from __future__ import annotations
|
||
|
||
from typing import TYPE_CHECKING
|
||
|
||
<span class="new-text">import color</span>
|
||
from components.base_component import BaseComponent
|
||
...
|
||
|
||
...
|
||
def die(self) -> None:
|
||
if self.engine.player is self.entity:
|
||
death_message = "You died!"
|
||
<span class="new-text">death_message_color = color.player_die</span>
|
||
self.engine.event_handler = GameOverEventHandler(self.engine)
|
||
else:
|
||
death_message = f"{self.entity.name} is dead!"
|
||
<span class="new-text">death_message_color = color.enemy_die</span>
|
||
|
||
self.entity.char = "%"
|
||
self.entity.color = (191, 0, 0)
|
||
self.entity.blocks_movement = False
|
||
self.entity.ai = None
|
||
self.entity.name = f"remains of {self.entity.name}"
|
||
self.entity.render_order = RenderOrder.CORPSE
|
||
|
||
<span class="crossed-out-text">print(death_message)</span>
|
||
<span class="new-text">self.engine.message_log.add_message(death_message, death_message_color)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Run the project now. You should see messages for both attacks and deaths!</p>
|
||
<p><img src="Part%207%20-%20Creating%20the%20Interface%20%C2%B7%20Roguelike%20Tutorials_files/part-7-death-messages.png" alt="Part 7 - Death Messages"></p>
|
||
<p>What next? One thing that would be nice is to see the names of the
|
||
different entities. This will become useful later on if you decide to
|
||
add more enemy types. It’s easy enough to remember “Orc” and “Troll”,
|
||
but most roguelikes have a wide variety of enemies, so it’s helpful to
|
||
know what each letter on the screen means.</p>
|
||
<p>We can accomplish this by displaying the names of the entities that
|
||
are currently under the player’s mouse. We’ll need to make a few changes
|
||
to our project to capture the mouse’s current position, however.</p>
|
||
<p>Edit <code>main.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> root_console = tcod.Console(screen_width, screen_height, order="F")
|
||
</span></span><span style="display:flex;"><span> while True:
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ root_console.clear()
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ engine.event_handler.on_render(console=root_console)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ context.present(root_console)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span><span style="color:#f92672">- engine.render(console=root_console, context=context)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ engine.event_handler.handle_events(context)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span><span style="color:#f92672">- engine.event_handler.handle_events()
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre> root_console = tcod.Console(screen_width, screen_height, order="F")
|
||
while True:
|
||
<span class="new-text">root_console.clear()
|
||
engine.event_handler.on_render(console=root_console)
|
||
context.present(root_console)</span>
|
||
<span class="crossed-out-text">engine.render(console=root_console, context=context)</span>
|
||
|
||
<span class="new-text">engine.event_handler.handle_events(context)</span>
|
||
<span class="crossed-out-text">engine.event_handler.handle_events()</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’re adding the console’s <code>clear</code> back to main, as well as the context’s <code>present</code>. Also, we’re calling a method that we haven’t defined yet: <code>on_render</code>, but don’t worry, we’ll define it in a moment. Basically, this method tells the engine to render.</p>
|
||
<p>We’re also passing the <code>context</code> to <code>handle_events</code> now, because we need to call an extra method on it to capture the mouse input.</p>
|
||
<p>Now let’s modify <code>input_handlers.py</code> to contain the methods we’re calling in <code>main.py</code>:</p>
|
||
<div>
|
||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||
Diff
|
||
</button>
|
||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||
Original
|
||
</button>
|
||
|
||
|
||
<div class="data-pane active" data-pane="diff">
|
||
|
||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class EventHandler(tcod.event.EventDispatch[Action]):
|
||
</span></span><span style="display:flex;"><span> def __init__(self, engine: Engine):
|
||
</span></span><span style="display:flex;"><span> self.engine = engine
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- def handle_events(self) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- raise NotImplementedError()
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def handle_events(self, context: tcod.context.Context) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ for event in tcod.event.wait():
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ context.convert_event(event)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.dispatch(event)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]:
|
||
</span></span><span style="display:flex;"><span> raise SystemExit()
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def on_render(self, console: tcod.Console) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.render(console)
|
||
</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 MainGameEventHandler(EventHandler):
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- def handle_events(self) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def handle_events(self, context: tcod.context.Context) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> for event in tcod.event.wait():
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ context.convert_event(event)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> action = self.dispatch(event)
|
||
</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 GameOverEventHandler(EventHandler):
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- def handle_events(self) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def handle_events(self, context: tcod.context.Context) -> None:
|
||
</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 EventHandler(tcod.event.EventDispatch[Action]):
|
||
def __init__(self, engine: Engine):
|
||
self.engine = engine
|
||
|
||
<span class="crossed-out-text">def handle_events(self) -> None:</span>
|
||
<span class="crossed-out-text">raise NotImplementedError()</span>
|
||
|
||
<span class="new-text">def handle_events(self, context: tcod.context.Context) -> None:
|
||
for event in tcod.event.wait():
|
||
context.convert_event(event)
|
||
self.dispatch(event)</span>
|
||
|
||
def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]:
|
||
raise SystemExit()
|
||
|
||
<span class="new-text">def on_render(self, console: tcod.Console) -> None:
|
||
self.engine.render(console)</span>
|
||
|
||
|
||
class MainGameEventHandler(EventHandler):
|
||
<span class="crossed-out-text">def handle_events(self) -> None:</span>
|
||
<span class="new-text">def handle_events(self, context: tcod.context.Context) -> None:</span>
|
||
for event in tcod.event.wait():
|
||
<span class="new-text">context.convert_event(event)</span>
|
||
|
||
action = self.dispatch(event)
|
||
...
|
||
|
||
|
||
class GameOverEventHandler(EventHandler):
|
||
<span class="crossed-out-text">def handle_events(self) -> None:</span>
|
||
<span class="new-text">def handle_events(self, context: tcod.context.Context) -> None:</span>
|
||
...</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’re modifying the <code>handle_events</code> method in <code>EventHandler</code> to actually have an implementation. It iterates through the events, and uses <code>context.convert_event</code> to give the event knowledge on the mouse position. It then dispatches that event, to be handled like normal.</p>
|
||
<p><code>on_render</code> just tells the <code>Engine</code> class to call its render method, using the given console.</p>
|
||
<p><code>MainGameEventHandler</code> and <code>GameOverEventHandler</code> have small changes to their <code>handle_events</code> methods to match the signature of <code>EventHandler</code>, and <code>MainGameEventHandler</code> also uses <code>context.convert_event</code>.</p>
|
||
<p>We’re no longer passing the <code>context</code> to the <code>Engine</code> class’s <code>render</code> method, so let’s change the method 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>...
|
||
</span></span><span style="display:flex;"><span>from typing import TYPE_CHECKING
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from tcod.context import Context
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>from tcod.console import Console
|
||
</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><span style="color:#f92672">- def render(self, console: Console, context: Context) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def render(self, console: Console) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></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> render_bar(
|
||
</span></span><span style="display:flex;"><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">- context.present(console)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- console.clear()
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>...
|
||
from typing import TYPE_CHECKING
|
||
|
||
<span class="crossed-out-text">from tcod.context import Context</span>
|
||
from tcod.console import Console
|
||
...
|
||
|
||
class Engine:
|
||
...
|
||
|
||
<span class="crossed-out-text">def render(self, console: Console, context: Context) -> None:</span>
|
||
<span class="new-text">def render(self, console: Console) -> None:</span>
|
||
self.game_map.render(console)
|
||
|
||
self.message_log.render(console=console, x=21, y=45, width=40, height=5)
|
||
|
||
render_bar(
|
||
console=console,
|
||
current_value=self.player.fighter.hp,
|
||
maximum_value=self.player.fighter.max_hp,
|
||
total_width=20,
|
||
)
|
||
|
||
<span class="crossed-out-text">context.present(console)</span>
|
||
|
||
<span class="crossed-out-text">console.clear()</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’ve also removed the <code>console.clear</code> call, as that’s being handled by <code>main.py</code>.</p>
|
||
<p>So we’re passing the context around to different classes and
|
||
converting the events to capture the mouse location. But where does that
|
||
information actually get stored? Let’s add a data point on to the <code>Engine</code> class to hold that information. Add the following to <code>engine.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 Engine:
|
||
</span></span><span style="display:flex;"><span> game_map: GameMap
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def __init__(self, player: Actor):
|
||
</span></span><span style="display:flex;"><span> self.event_handler: EventHandler = MainGameEventHandler(self)
|
||
</span></span><span style="display:flex;"><span> self.message_log = MessageLog()
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.mouse_location = (0, 0)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> self.player = player
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>class Engine:
|
||
game_map: GameMap
|
||
|
||
def __init__(self, player: Actor):
|
||
self.event_handler: EventHandler = MainGameEventHandler(self)
|
||
self.message_log = MessageLog()
|
||
<span class="new-text">self.mouse_location = (0, 0)</span>
|
||
self.player = player</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Okay, so we’ve got a place to store the mouse location, but where do we actually get that information?</p>
|
||
<p>There’s an easy way: by overriding a method in <code>EventHandler</code>, which is called <code>ev_mousemotion</code>. By doing that, we can write the mouse location to the engine for access later. Here’s how that looks:</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 EventHandler(tcod.event.EventDispatch[Action]):
|
||
</span></span><span style="display:flex;"><span> def __init__(self, engine: Engine):
|
||
</span></span><span style="display:flex;"><span> self.engine = engine
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> def handle_events(self, context: tcod.context.Context) -> None:
|
||
</span></span><span style="display:flex;"><span> for event in tcod.event.wait():
|
||
</span></span><span style="display:flex;"><span> context.convert_event(event)
|
||
</span></span><span style="display:flex;"><span> self.dispatch(event)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.engine.game_map.in_bounds(event.tile.x, event.tile.y):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.mouse_location = event.tile.x, event.tile.y
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span> def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]:
|
||
</span></span><span style="display:flex;"><span> raise SystemExit()
|
||
</span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>class EventHandler(tcod.event.EventDispatch[Action]):
|
||
def __init__(self, engine: Engine):
|
||
self.engine = engine
|
||
|
||
def handle_events(self, context: tcod.context.Context) -> None:
|
||
for event in tcod.event.wait():
|
||
context.convert_event(event)
|
||
self.dispatch(event)
|
||
|
||
<span class="new-text">def ev_mousemotion(self, event: tcod.event.MouseMotion) -> None:
|
||
if self.engine.game_map.in_bounds(event.tile.x, event.tile.y):
|
||
self.engine.mouse_location = event.tile.x, event.tile.y</span>
|
||
|
||
def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]:
|
||
raise SystemExit()</pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Great! Now we’re saving the mouse’s location, so it’s time to
|
||
actually make use of it. Our original goal was to display the entity
|
||
names that are in the mouse’s current position. The hard part is already
|
||
done, now all we need to do is check which entities are in the given
|
||
location, get their names, and print them out to the screen.</p>
|
||
<p>Since this has to do with rendering, let’s put these new functions in <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>from typing import TYPE_CHECKING
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>import color
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span>if TYPE_CHECKING:
|
||
</span></span><span style="display:flex;"><span> from tcod import Console
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from engine import Engine
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ from game_map import GameMap
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+def get_names_at_location(x: int, y: int, game_map: GameMap) -> str:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if not game_map.in_bounds(x, y) or not game_map.visible[x, y]:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ return ""
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ names = ", ".join(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ entity.name for entity in game_map.entities if entity.x == x and entity.y == y
|
||
</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">+ return names.capitalize()
|
||
</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_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>) -> None:
|
||
</span></span><span style="display:flex;"><span> bar_width = int(float(current_value) / maximum_value * total_width)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console.draw_rect(x=0, y=45, width=20, height=1, ch=1, bg=color.bar_empty)
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> if bar_width > 0:
|
||
</span></span><span style="display:flex;"><span> console.draw_rect(
|
||
</span></span><span style="display:flex;"><span> x=0, y=45, width=bar_width, height=1, ch=1, bg=color.bar_filled
|
||
</span></span><span style="display:flex;"><span> )
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span> console.print(
|
||
</span></span><span style="display:flex;"><span> x=1, y=45, string=f"HP: {current_value}/{maximum_value}", fg=color.bar_text
|
||
</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_names_at_mouse_location(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console: Console, x: int, y: int, engine: Engine
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ mouse_x, mouse_y = engine.mouse_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">+ names_at_mouse_location = get_names_at_location(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ x=mouse_x, y=mouse_y, game_map=engine.game_map
|
||
</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, y=y, string=names_at_mouse_location)
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>from __future__ import annotations
|
||
|
||
from typing import TYPE_CHECKING
|
||
|
||
import color
|
||
|
||
if TYPE_CHECKING:
|
||
from tcod import Console
|
||
<span class="new-text">from engine import Engine
|
||
from game_map import GameMap
|
||
|
||
|
||
def get_names_at_location(x: int, y: int, game_map: GameMap) -> str:
|
||
if not game_map.in_bounds(x, y) or not game_map.visible[x, y]:
|
||
return ""
|
||
|
||
names = ", ".join(
|
||
entity.name for entity in game_map.entities if entity.x == x and entity.y == y
|
||
)
|
||
|
||
return names.capitalize()</span>
|
||
|
||
|
||
def render_bar(
|
||
console: Console, current_value: int, maximum_value: int, total_width: int
|
||
) -> None:
|
||
bar_width = int(float(current_value) / maximum_value * total_width)
|
||
|
||
console.draw_rect(x=0, y=45, width=20, height=1, ch=1, bg=color.bar_empty)
|
||
|
||
if bar_width > 0:
|
||
console.draw_rect(
|
||
x=0, y=45, width=bar_width, height=1, ch=1, bg=color.bar_filled
|
||
)
|
||
|
||
console.print(
|
||
x=1, y=45, string=f"HP: {current_value}/{maximum_value}", fg=color.bar_text
|
||
)
|
||
|
||
|
||
<span class="new-text">def render_names_at_mouse_location(
|
||
console: Console, x: int, y: int, engine: Engine
|
||
) -> None:
|
||
mouse_x, mouse_y = engine.mouse_location
|
||
|
||
names_at_mouse_location = get_names_at_location(
|
||
x=mouse_x, y=mouse_y, game_map=engine.game_map
|
||
)
|
||
|
||
console.print(x=x, y=y, string=names_at_mouse_location)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>We’ve added two new functions, <code>render_names_at_mouse_location</code> and <code>get_names_at_location</code>. Let’s discuss what each one does.</p>
|
||
<p><code>render_names_at_mouse_location</code> takes the console, x and y
|
||
coordinates (the location to draw the names), and the engine. From the
|
||
engine, it grabs the mouse’s current x and y positions, and passes them
|
||
to <code>get_names_at_location</code>, which we can assume for the
|
||
moment will return the list of entity names we want. Once we have these
|
||
entity names as a string, we can print that string to the given x and y
|
||
location on the screen, with <code>console.print</code>.</p>
|
||
<p><code>get_names_at_location</code> also takes “x” and “y” variables,
|
||
though these represent a spot on the map. We first check that the x and y
|
||
coordinates are within the map, and are currently visible to the
|
||
player. If they are, then we create a string of the entity names at that
|
||
spot, separated by a comma. We then return that string, adding <code>capitalize</code> to make sure the first letter in the string is capitalized.</p>
|
||
<p>Now all we need to do is modify <code>engine.py</code> to import these functions and utilize them in the <code>render</code> method. Make the following modifications:
|
||
</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>from message_log import MessageLog
|
||
</span></span><span style="display:flex;"><span><span style="color:#f92672">-from render_functions import render_bar
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+from render_functions import render_bar, render_names_at_mouse_location
|
||
</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> def render(self, console: Console) -> 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> render_bar(
|
||
</span></span><span style="display:flex;"><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:#a6e22e">+ render_names_at_mouse_location(console=console, x=21, y=44, engine=self)
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>...
|
||
from message_log import MessageLog
|
||
<span class="crossed-out-text">from render_functions import render_bar</span>
|
||
<span class="new-text">from render_functions import render_bar, render_names_at_mouse_location</span>
|
||
|
||
if TYPE_CHECKING:
|
||
...
|
||
|
||
...
|
||
def render(self, console: Console) -> None:
|
||
self.game_map.render(console)
|
||
|
||
self.message_log.render(console=console, x=21, y=45, width=40, height=5)
|
||
|
||
render_bar(
|
||
console=console,
|
||
current_value=self.player.fighter.hp,
|
||
maximum_value=self.player.fighter.max_hp,
|
||
total_width=20,
|
||
)
|
||
|
||
<span class="new-text">render_names_at_mouse_location(console=console, x=21, y=44, engine=self)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
<p></p>
|
||
<p>Now if you hover your mouse over an entity, you’ll see its name. If
|
||
you stack a few corpses up, you’ll notice that it prints a list of the
|
||
names.</p>
|
||
<p>We’re almost finished with this chapter. Before we wrap up, let’s
|
||
revisit our message log for a moment. One issue with it is that we can’t
|
||
see messages that are too far back. However, HexDecimal was kind enough
|
||
to provide a method for viewing the whole log, with the ability to
|
||
scroll.</p>
|
||
<p>Add the following 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 GameOverEventHandler(EventHandler):
|
||
</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">+CURSOR_Y_KEYS = {
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ tcod.event.K_UP: -1,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ tcod.event.K_DOWN: 1,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ tcod.event.K_PAGEUP: -10,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ tcod.event.K_PAGEDOWN: 10,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+}
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+class HistoryViewer(EventHandler):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """Print the history on a larger window which can be navigated."""
|
||
</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__(self, engine: Engine):
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ super().__init__(engine)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.log_length = len(engine.message_log.messages)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.cursor = self.log_length - 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">+ def on_render(self, console: tcod.Console) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ super().on_render(console) # Draw the main state as the background.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ log_console = tcod.Console(console.width - 6, console.height - 6)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Draw a frame with a custom banner title.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ log_console.draw_frame(0, 0, log_console.width, log_console.height)
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ log_console.print_box(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ 0, 0, log_console.width, 1, "┤Message history├", alignment=tcod.CENTER
|
||
</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 the message log using the cursor parameter.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.message_log.render_messages(
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ log_console,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ 1,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ 1,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ log_console.width - 2,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ log_console.height - 2,
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.message_log.messages[: self.cursor + 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">+ log_console.blit(console, 3, 3)
|
||
</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) -> None:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Fancy conditional movement to make it feel right.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if event.sym in CURSOR_Y_KEYS:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ adjust = CURSOR_Y_KEYS[event.sym]
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if adjust < 0 and self.cursor == 0:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Only move from the top to the bottom when you're on the edge.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.cursor = self.log_length - 1
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif adjust > 0 and self.cursor == self.log_length - 1:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # Same with bottom to top movement.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.cursor = 0
|
||
</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">+ # Otherwise move while staying clamped to the bounds of the history log.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.cursor = max(0, min(self.cursor + adjust, self.log_length - 1))
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif event.sym == tcod.event.K_HOME:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.cursor = 0 # Move directly to the top message.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif event.sym == tcod.event.K_END:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.cursor = self.log_length - 1 # Move directly to the last message.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ else: # Any other key moves back to the main game state.
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.event_handler = MainGameEventHandler(self.engine)
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre>class GameOverEventHandler(EventHandler):
|
||
...
|
||
|
||
|
||
<span class="new-text">CURSOR_Y_KEYS = {
|
||
tcod.event.K_UP: -1,
|
||
tcod.event.K_DOWN: 1,
|
||
tcod.event.K_PAGEUP: -10,
|
||
tcod.event.K_PAGEDOWN: 10,
|
||
}
|
||
|
||
|
||
class HistoryViewer(EventHandler):
|
||
"""Print the history on a larger window which can be navigated."""
|
||
|
||
def __init__(self, engine: Engine):
|
||
super().__init__(engine)
|
||
self.log_length = len(engine.message_log.messages)
|
||
self.cursor = self.log_length - 1
|
||
|
||
def on_render(self, console: tcod.Console) -> None:
|
||
super().on_render(console) # Draw the main state as the background.
|
||
|
||
log_console = tcod.Console(console.width - 6, console.height - 6)
|
||
|
||
# Draw a frame with a custom banner title.
|
||
log_console.draw_frame(0, 0, log_console.width, log_console.height)
|
||
log_console.print_box(
|
||
0, 0, log_console.width, 1, "┤Message history├", alignment=tcod.CENTER
|
||
)
|
||
|
||
# Render the message log using the cursor parameter.
|
||
self.engine.message_log.render_messages(
|
||
log_console,
|
||
1,
|
||
1,
|
||
log_console.width - 2,
|
||
log_console.height - 2,
|
||
self.engine.message_log.messages[: self.cursor + 1],
|
||
)
|
||
log_console.blit(console, 3, 3)
|
||
|
||
def ev_keydown(self, event: tcod.event.KeyDown) -> None:
|
||
# Fancy conditional movement to make it feel right.
|
||
if event.sym in CURSOR_Y_KEYS:
|
||
adjust = CURSOR_Y_KEYS[event.sym]
|
||
if adjust < 0 and self.cursor == 0:
|
||
# Only move from the top to the bottom when you're on the edge.
|
||
self.cursor = self.log_length - 1
|
||
elif adjust > 0 and self.cursor == self.log_length - 1:
|
||
# Same with bottom to top movement.
|
||
self.cursor = 0
|
||
else:
|
||
# Otherwise move while staying clamped to the bounds of the history log.
|
||
self.cursor = max(0, min(self.cursor + adjust, self.log_length - 1))
|
||
elif event.sym == tcod.event.K_HOME:
|
||
self.cursor = 0 # Move directly to the top message.
|
||
elif event.sym == tcod.event.K_END:
|
||
self.cursor = self.log_length - 1 # Move directly to the last message.
|
||
else: # Any other key moves back to the main game state.
|
||
self.engine.event_handler = MainGameEventHandler(self.engine)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>To show this new view, all we need to do is this, in <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> ...
|
||
</span></span><span style="display:flex;"><span> elif key == tcod.event.K_ESCAPE:
|
||
</span></span><span style="display:flex;"><span> action = EscapeAction(player)
|
||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif key == tcod.event.K_v:
|
||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.engine.event_handler = HistoryViewer(self.engine)
|
||
</span></span></span></code></pre></div>
|
||
|
||
</div>
|
||
<div class="data-pane" data-pane="original">
|
||
|
||
<pre> ...
|
||
elif key == tcod.event.K_ESCAPE:
|
||
action = EscapeAction(player)
|
||
<span class="new-text">elif key == tcod.event.K_v:
|
||
self.engine.event_handler = HistoryViewer(self.engine)</span></pre>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<p>Now all the player has to do is press the “v” key to see a log of all
|
||
past messages. By using the up and down keys, you can scroll through
|
||
the log.</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-7">click here</a>.</p>
|
||
<p><a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-8">Click here to move on to the next part of this tutorial.</a></p>
|
||
|
||
</article>
|
||
</section>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
<footer class="footer">
|
||
<section class="container">
|
||
©
|
||
|
||
2023
|
||
|
||
·
|
||
|
||
Powered by <a href="https://gohugo.io/">Hugo</a> & <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
|
||
|
||
</section>
|
||
</footer>
|
||
|
||
</main>
|
||
|
||
|
||
|
||
|
||
|
||
<script src="Part%207%20-%20Creating%20the%20Interface%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||
|
||
|
||
|
||
|
||
|
||
<script src="Part%207%20-%20Creating%20the%20Interface%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</body></html> |