emacs.d/clones/lisp/stevelosh.com/blog/2012/07/caves-of-clojure-04/index.html
2022-10-07 15:47:14 +02:00

583 lines
No EOL
48 KiB
HTML

<!DOCTYPE html>
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='../../../../static/css/style.css' rel='stylesheet' type='text/css' /><link href='../../../../static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>The Caves of Clojure: Part 4 / Steve Losh</title></head><body><header><a id='logo' href='https://stevelosh.com/'>Steve Losh</a><nav><a href='../../../index.html'>Blog</a> - <a href='https://stevelosh.com/projects/'>Projects</a> - <a href='https://stevelosh.com/photography/'>Photography</a> - <a href='https://stevelosh.com/links/'>Links</a> - <a href='https://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>The Caves of Clojure: Part 4</a></h1><p class='date'>Posted on July 12th, 2012.</p><p>This post is part of an ongoing series. If you haven't already done so, you
should probably start at <a href="../caves-of-clojure-01/index.html">the beginning</a>.</p>
<p>This entry corresponds to <a href="http://trystans.blogspot.com/2011/08/roguelike-tutorial-04-player.html">post four in Trystan's tutorial</a>.</p>
<p>If you want to follow along, the code for the series is <a href="http://bitbucket.org/sjl/caves/">on Bitbucket</a> and
<a href="http://github.com/sjl/caves/">on GitHub</a>. Update to the <code>entry-04</code> tag to see the code as it stands
after this post.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-summary">Summary</a></li><li><a href="index.html#s2-refactoring">Refactoring</a></li><li><a href="index.html#s3-entities">Entities</a></li><li><a href="index.html#s4-protocols">Protocols</a></li><li><a href="index.html#s5-the-player">The Player</a></li><li><a href="index.html#s6-displaying-the-player">Displaying the Player</a></li><li><a href="index.html#s7-movement">Movement</a></li><li><a href="index.html#s8-digging">Digging</a></li><li><a href="index.html#s9-results">Results</a></li></ol>
<h2 id="s1-summary"><a href="index.html#s1-summary">Summary</a></h2>
<p>In Trystan's fourth post he adds three main things:</p>
<ul>
<li>A player</li>
<li>Player movement</li>
<li>Digging</li>
</ul>
<p>I'm going to add all three of those, but I'm going to do things very differently
than he did.</p>
<p>My goal is to play around with some Clojurey concepts and see how far I can
stretch them. I have a feeling that it's going to let me do some very cool
things in the future.</p>
<h2 id="s2-refactoring"><a href="index.html#s2-refactoring">Refactoring</a></h2>
<p>Before I started I wanted to clean up the <code>world</code> namespace a bit. I'm not
going to go in depth — I'll just post the code and you can read over it or skip
it if you trust me.</p>
<p>First I created <code>coords.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.coords</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> offset-coords
<span class="string">&quot;Offset the starting coordinate by the given amount, returning the result coordinate.&quot;</span>
<span class="paren2">[<span class="code"><span class="paren3">[<span class="code">x y</span>]</span> <span class="paren3">[<span class="code">dx dy</span>]</span></span>]</span>
<span class="paren2">[<span class="code"><span class="paren3">(<span class="code">+ x dx</span>)</span> <span class="paren3">(<span class="code">+ y dy</span>)</span></span>]</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> dir-to-offset
<span class="string">&quot;Convert a direction to the offset for moving 1 in that direction.&quot;</span>
<span class="paren2">[<span class="code">dir</span>]</span>
<span class="paren2">(<span class="code">case dir
<span class="keyword">:w</span> <span class="paren3">[<span class="code">-1 0</span>]</span>
<span class="keyword">:e</span> <span class="paren3">[<span class="code">1 0</span>]</span>
<span class="keyword">:n</span> <span class="paren3">[<span class="code">0 -1</span>]</span>
<span class="keyword">:s</span> <span class="paren3">[<span class="code">0 1</span>]</span>
<span class="keyword">:nw</span> <span class="paren3">[<span class="code">-1 -1</span>]</span>
<span class="keyword">:ne</span> <span class="paren3">[<span class="code">1 -1</span>]</span>
<span class="keyword">:sw</span> <span class="paren3">[<span class="code">-1 1</span>]</span>
<span class="keyword">:se</span> <span class="paren3">[<span class="code">1 1</span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> destination-coords
<span class="string">&quot;Take an origin's coords and a direction and return the destination's coords.&quot;</span>
<span class="paren2">[<span class="code">origin dir</span>]</span>
<span class="paren2">(<span class="code">offset-coords origin <span class="paren3">(<span class="code">dir-to-offset dir</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Then I cleaned up <code>world.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.world</span>)</span>
<span class="comment">; Constants -------------------------------------------------------------------
</span><span class="paren1">(<span class="code">def world-size <span class="paren2">[<span class="code">160 50</span>]</span></span>)</span>
<span class="comment">; Data structures -------------------------------------------------------------
</span><span class="paren1">(<span class="code"><i><span class="symbol">defrecord</span></i> World <span class="paren2">[<span class="code">tiles</span>]</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defrecord</span></i> Tile <span class="paren2">[<span class="code">kind glyph color</span>]</span></span>)</span>
<span class="paren1">(<span class="code">def tiles
<span class="paren2">{<span class="code"><span class="keyword">:floor</span> <span class="paren3">(<span class="code">-&gt;Tile <span class="keyword">:floor</span> <span class="string">&quot;.&quot;</span> <span class="keyword">:white</span></span>)</span>
<span class="keyword">:wall</span> <span class="paren3">(<span class="code">-&gt;Tile <span class="keyword">:wall</span> <span class="string">&quot;#&quot;</span> <span class="keyword">:white</span></span>)</span>
<span class="keyword">:bound</span> <span class="paren3">(<span class="code">-&gt;Tile <span class="keyword">:bound</span> <span class="string">&quot;X&quot;</span> <span class="keyword">:black</span></span>)</span></span>}</span></span>)</span>
<span class="comment">; Convenience functions -------------------------------------------------------
</span><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-tile-from-tiles <span class="paren2">[<span class="code">tiles <span class="paren3">[<span class="code">x y</span>]</span></span>]</span>
<span class="paren2">(<span class="code">get-in tiles <span class="paren3">[<span class="code">y x</span>]</span> <span class="paren3">(<span class="code"><span class="keyword">:bound</span> tiles</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> random-coordinate <span class="paren2">[<span class="code"></span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code"><span class="paren4">[<span class="code">cols rows</span>]</span> world-size</span>]</span>
<span class="paren3">[<span class="code"><span class="paren4">(<span class="code">rand-int cols</span>)</span> <span class="paren4">(<span class="code">rand-int rows</span>)</span></span>]</span></span>)</span></span>)</span>
<span class="comment">; World generation ------------------------------------------------------------
</span><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> random-tiles <span class="paren2">[<span class="code"></span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code"><span class="paren4">[<span class="code">cols rows</span>]</span> world-size</span>]</span>
<span class="paren3">(<span class="code">letfn <span class="paren4">[<span class="code"><span class="paren5">(<span class="code">random-tile <span class="paren6">[<span class="code"></span>]</span>
<span class="paren6">(<span class="code">tiles <span class="paren1">(<span class="code">rand-nth <span class="paren2">[<span class="code"><span class="keyword">:floor</span> <span class="keyword">:wall</span></span>]</span></span>)</span></span>)</span></span>)</span>
<span class="paren5">(<span class="code">random-row <span class="paren6">[<span class="code"></span>]</span>
<span class="paren6">(<span class="code">vec <span class="paren1">(<span class="code">repeatedly cols random-tile</span>)</span></span>)</span></span>)</span></span>]</span>
<span class="paren4">(<span class="code">vec <span class="paren5">(<span class="code">repeatedly rows random-row</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-smoothed-tile <span class="paren2">[<span class="code"><i><span class="symbol">block</span></i></span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">tile-counts <span class="paren4">(<span class="code">frequencies <span class="paren5">(<span class="code">map <span class="keyword">:kind</span> <i><span class="symbol">block</span></i></span>)</span></span>)</span>
floor-threshold 5
floor-count <span class="paren4">(<span class="code">get tile-counts <span class="keyword">:floor</span> 0</span>)</span>
result <span class="paren4">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren5">(<span class="code">&gt;= floor-count floor-threshold</span>)</span>
<span class="keyword">:floor</span>
<span class="keyword">:wall</span></span>)</span></span>]</span>
<span class="paren3">(<span class="code">tiles result</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> block-coords <span class="paren2">[<span class="code">x y</span>]</span>
<span class="paren2">(<span class="code">for <span class="paren3">[<span class="code">dx <span class="paren4">[<span class="code">-1 0 1</span>]</span>
dy <span class="paren4">[<span class="code">-1 0 1</span>]</span></span>]</span>
<span class="paren3">[<span class="code"><span class="paren4">(<span class="code">+ x dx</span>)</span> <span class="paren4">(<span class="code">+ y dy</span>)</span></span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-block <span class="paren2">[<span class="code">tiles x y</span>]</span>
<span class="paren2">(<span class="code">map <span class="paren3">(<span class="code">partial get-tile-from-tiles tiles</span>)</span>
<span class="paren3">(<span class="code">block-coords x y</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-smoothed-row <span class="paren2">[<span class="code">tiles y</span>]</span>
<span class="paren2">(<span class="code">mapv <span class="paren3">(<span class="code">fn <span class="paren4">[<span class="code">x</span>]</span>
<span class="paren4">(<span class="code">get-smoothed-tile <span class="paren5">(<span class="code">get-block tiles x y</span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">range <span class="paren4">(<span class="code">count <span class="paren5">(<span class="code">first tiles</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-smoothed-tiles <span class="paren2">[<span class="code">tiles</span>]</span>
<span class="paren2">(<span class="code">mapv <span class="paren3">(<span class="code">fn <span class="paren4">[<span class="code">y</span>]</span>
<span class="paren4">(<span class="code">get-smoothed-row tiles y</span>)</span></span>)</span>
<span class="paren3">(<span class="code">range <span class="paren4">(<span class="code">count tiles</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> smooth-world <span class="paren2">[<span class="code"><span class="paren3">{<span class="code"><span class="keyword">:keys</span> <span class="paren4">[<span class="code">tiles</span>]</span> <span class="keyword">:as</span> world}</span>]</span>
<span class="paren3">(<span class="code">assoc world <span class="keyword">:tiles</span> <span class="paren4">(<span class="code">get-smoothed-tiles tiles</span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">defn</span></i> random-world <span class="paren3">[<span class="code"></span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">world <span class="paren5">(<span class="code">-&gt;World <span class="paren6">(<span class="code">random-tiles</span>)</span></span>)</span>
world <span class="paren5">(<span class="code">nth <span class="paren6">(<span class="code">iterate smooth-world world</span>)</span> 3</span>)</span></span>]</span>
world</span>)</span></span>)</span></span></span></span></code></pre>
<p>The changes were mostly centered around removing debugging functions and making
all the world functions take an <code>[x y]</code> coordinate vector in place of two
separate <code>x</code> and <code>y</code> arguments.</p>
<p>Read through the code if you're curious, it's only about 100 total lines.</p>
<h2 id="s3-entities"><a href="index.html#s3-entities">Entities</a></h2>
<p>Now it's time to add a player. Rather than take the approach Trystan used of
creating a <code>Creature</code> class and <code>Player</code> subclass, I went with something a bit
different.</p>
<p>In Minecraft's network protocol, when you get a list of &quot;things in the world&quot;
it's not just creatures — the list includes both creatures and items. It uses
the word &quot;entity&quot; to refer to them. I'm sure it's not the first game to do
that, but it's the first time I've run across it because I'm not hugely into
game programming.</p>
<p>That got me thinking: are items and creatures really so different that I need to
represent them as two completely separate ideas?</p>
<p>Both have a location in the world. Both have a &quot;glyph&quot; that I'll be using to
display them to the player. Both will have some kind of &quot;id&quot; so I can use them
in mappings efficiently.</p>
<p>On the other hand, there are definitely some differences. Creatures move
around, eat things, attack things, can be attacked (and killed), have an AI to
decide what to do, and so on.</p>
<p>Items can be picked up and dropped, can contain other items, can be eaten or
quaffed, can rot over time (e.g.: corpses), can be used as weapons or armor, et
cetera.</p>
<p>But wait a second: are things really so clear? The bag of tricks in Nethack can
attack the player. Cockatrice corpses can be wielded and used as
petrification-inducing clubs. In Dwarf Fortress discarded pieces from
slaughtered animals can come alive if a necromancer sieges your fortress.</p>
<p>I can think of a lot of cool things I could do when I eliminate the distinction
between items and creatures.</p>
<p>Maybe there's a &quot;pixie&quot; creature that wanders around and does normal creaturey
things, but if you attack it while wielding a butterfly net you &quot;catch&quot; it and
it gets picked up and put in your inventory like an item.</p>
<p>Once you've got one you could &quot;apply&quot; it (maybe that means &quot;setting it free&quot;) to
heal yourself, or quaff it to restore mana (mmm, delicious pixie blood).</p>
<p>Oh, and it still has an AI so maybe every 100 turns it has a chance of escaping
from your inventory. Unless you put it in a jar.</p>
<p>I can think of tons of interesting things to do with a unified &quot;entity&quot; system.
A bag that eat things, where you need to remember to &quot;feed&quot; it normal food or
it'll start digesting the other items! Giant venus fly traps that eat unwary
pixies! Potions that evaporate over time if you don't use them!</p>
<p>The possibilities are really exciting. But how can I actually <em>code</em> all this
crazy stuff without special-casing <em>everything</em>?</p>
<h2 id="s4-protocols"><a href="index.html#s4-protocols">Protocols</a></h2>
<p>After thinking about this problem for a while, I came up with a solution that
I think has some real promise.</p>
<p>Individual types of entity (&quot;pixie&quot;, &quot;player&quot;, &quot;goblin&quot;, &quot;steel helmet&quot;) will be
defined with simple <code>(defrecord)</code>s. Each should have an <code>:id</code>, <code>:glyph</code>, and
<code>:location</code>, but beyond that the rest of their state is flexible.</p>
<p>I'm going to create an <code>Entity</code> protocol that such records will implement. That
protocol will have a single <code>tick</code> function that they need to define. This will
be called once per game &quot;tick&quot; and will be how the various types of entity
decide what to do over time. They may define a <code>tick</code> that does nothing if they
don't change over time.</p>
<p>On its own an entity record can't do anything except exist, be displayed on
the map, and update itself every tick. To actually <em>do</em> something during
a tick (or have things done to them) they'll implement what I'm calling
&quot;aspects&quot;.</p>
<p>An &quot;aspect&quot; is a protocol that defines a group of related functions, probably
all having to do with a simple gameplay mechanic. Here are a few rough examples
from the top of my head:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Edible
<span class="paren2">(<span class="code">can-be-eaten? <span class="paren3">[<span class="code">this eater world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">nutrition-value <span class="paren3">[<span class="code">this world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">eat <span class="paren3">[<span class="code">this eater world</span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Eater
<span class="paren2">(<span class="code">can-eat? <span class="paren3">[<span class="code">this food world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">eat <span class="paren3">[<span class="code">this food world</span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Item
<span class="paren2">(<span class="code">can-be-contained-in? <span class="paren3">[<span class="code">this container world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">insert-into <span class="paren3">[<span class="code">this container world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">remove-from <span class="paren3">[<span class="code">this container world</span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Container
<span class="paren2">(<span class="code">get-contained <span class="paren3">[<span class="code">this world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">can-contain? <span class="paren3">[<span class="code">this item world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">insert <span class="paren3">[<span class="code">this item world</span>]</span></span>)</span>
<span class="paren2">(<span class="code">remove <span class="paren3">[<span class="code">this item world</span>]</span></span>)</span></span>)</span></span></code></pre>
<p>As you can see, many aspects will be paired up. Some entities can have things
done to them by other entities, which will actually do those things. Both will
have the opportunity to override the default method implementations to customize
the behavior.</p>
<p>Anyway, I think this way of adding in functionality (basically mixin-style, but
decoupled from the entity class declaration and without namespace clashes)
could be very cool. I'm going to give it a shot and see how it works.</p>
<h2 id="s5-the-player"><a href="index.html#s5-the-player">The Player</a></h2>
<p>Let's start with the first and most important entity: the player. This game
isn't going to be much fun without one of those.</p>
<p>First I added a new file: <code>entities/core.clj</code>. It'll contain the basic <code>Entity</code>
definition:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.core</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Entity
<span class="paren2">(<span class="code">tick <span class="paren3">[<span class="code">this world</span>]</span>
<span class="string">&quot;Update the world to handle the passing of a tick for this entity.&quot;</span></span>)</span></span>)</span></span></code></pre>
<p>Simple enough. <code>tick</code>ing an entity will return a new immutable world that
accounts for whatever the entity decides to do during that tick.</p>
<p>Now to add a player!</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.player
<span class="paren2">(<span class="code"><span class="keyword">:use</span> <span class="paren3">[<span class="code">caves.entities.core <span class="keyword">:only</span> <span class="paren4">[<span class="code">Entity</span>]</span></span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defrecord</span></i> Player <span class="paren2">[<span class="code">id glyph location</span>]</span></span>)</span>
<span class="paren1">(<span class="code">extend-type Player Entity
<span class="paren2">(<span class="code">tick <span class="paren3">[<span class="code">this world</span>]</span>
world</span>)</span></span>)</span></span></code></pre>
<p>Right now the player doesn't do anything during a tick — the world will remain
unchanged.</p>
<p>We'll need to actually place the player somewhere in the world to start, so I'll
make a helper function like Trystan's to find an empty spot for them in
<code>world.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> find-empty-tile <span class="paren2">[<span class="code">world</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">loop</span></i> <span class="paren3">[<span class="code">coord <span class="paren4">(<span class="code">random-coordinate</span>)</span></span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">#<span class="paren5">{<span class="code"><span class="keyword">:floor}</span> <span class="paren6">(<span class="code">get-tile-kind world coord</span>)</span></span>)</span>
coord
<span class="paren5">(<span class="code">recur <span class="paren6">(<span class="code">random-coordinate</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
<p>Basically I just try a bunch of random coordinates until I find one that's
a <code>:floor</code> tile. Maybe not the most efficient way to do things, but it's fine
for now.</p>
<p>Back in <code>player.clj</code> I'll need a way to make a new player when we start a new
game:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> make-player <span class="paren2">[<span class="code">world</span>]</span>
<span class="paren2">(<span class="code">-&gt;Player <span class="keyword">:player</span> <span class="string">&quot;@&quot;</span> <span class="paren3">(<span class="code">find-empty-tile world</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>For now I'll use the special ID <code>:player</code> for the entity ID. Since this is
going to be a single player game, with no chance of ever being multiplayer, it's
okay to special case things for the player a bit.</p>
<p>Now to actually add the new player into the main <code>game</code> object. Remember that
the <code>:start</code> screen is the one that makes fresh games, so I updated that:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> reset-game <span class="paren2">[<span class="code">game</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">fresh-world <span class="paren4">(<span class="code">random-world</span>)</span></span>]</span>
<span class="paren3">(<span class="code">-&gt; game
<span class="paren4">(<span class="code">assoc <span class="keyword">:world</span> fresh-world</span>)</span>
<span class="paren4">(<span class="code">assoc-in <span class="paren5">[<span class="code"><span class="keyword">:world</span> <span class="keyword">:player</span></span>]</span> <span class="paren5">(<span class="code">make-player fresh-world</span>)</span></span>)</span>
<span class="paren4">(<span class="code">assoc <span class="keyword">:uis</span> <span class="paren5">[<span class="code"><span class="paren6">(<span class="code">-&gt;UI <span class="keyword">:play</span></span>)</span></span>]</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> process-input <span class="keyword">:start</span> <span class="paren2">[<span class="code">game input</span>]</span>
<span class="paren2">(<span class="code">reset-game game</span>)</span></span>)</span></span></code></pre>
<p>I pulled out the guts of the <code>process-input</code> function into a helper, which:</p>
<ul>
<li>Creates a fresh, random world.</li>
<li>Replaces the game's world with the new one.</li>
<li>Creates a fresh player at some empty location in that world.</li>
<li>Attaches the player to the world.</li>
<li>Replaces the UI stack of the game with the main <code>:play</code> UI.</li>
</ul>
<p>I could have made a completely new <code>game</code> object instead of just overwriting
some fields here, but this way if I decide to store configuration options on the
<code>game</code> later they won't be lost when restarting.</p>
<h2 id="s6-displaying-the-player"><a href="index.html#s6-displaying-the-player">Displaying the Player</a></h2>
<p>Now that I've got a player it's time to display them on the map as the
traditional <code>@</code>. I opened up <code>input.clj</code> and replaced the crosshair-drawing
code from the last two posts with code to draw the player:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> draw-player <span class="paren2">[<span class="code">screen start-x start-y player</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code"><span class="paren4">[<span class="code">player-x player-y</span>]</span> <span class="paren4">(<span class="code"><span class="keyword">:location</span> player</span>)</span>
x <span class="paren4">(<span class="code">- player-x start-x</span>)</span>
y <span class="paren4">(<span class="code">- player-y start-y</span>)</span></span>]</span>
<span class="paren3">(<span class="code">s/put-string screen x y <span class="paren4">(<span class="code"><span class="keyword">:glyph</span> player</span>)</span> <span class="paren4">{<span class="code"><span class="keyword">:fg</span> <span class="keyword">:white}</span></span>)</span>
<span class="paren4">(<span class="code">s/move-cursor screen x y</span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
<p>If the screen's <code>start-x</code> (i.e.: its left edge) is at 10, and the player is at
24, then I need to draw the <code>@</code> at screen coordinate 14. Same goes for the
y coordinates.</p>
<p>Now to tweak the main <code>draw-ui</code> function to account for this change:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> draw-ui <span class="keyword">:play</span> <span class="paren2">[<span class="code">ui game screen</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">world <span class="paren4">(<span class="code"><span class="keyword">:world</span> game</span>)</span>
<span class="paren4">{<span class="code"><span class="keyword">:keys</span> <span class="paren5">[<span class="code">tiles player</span>]</span></span>}</span> world
<span class="paren4">[<span class="code">cols rows</span>]</span> screen-size
vcols cols
vrows <span class="paren4">(<span class="code">dec rows</span>)</span>
<span class="paren4">[<span class="code">start-x start-y end-x end-y</span>]</span> <span class="paren4">(<span class="code">get-viewport-coords game <span class="paren5">(<span class="code"><span class="keyword">:location</span> player</span>)</span> vcols vrows</span>)</span></span>]</span>
<span class="paren3">(<span class="code">draw-world screen vrows vcols start-x start-y end-x end-y tiles</span>)</span>
<span class="paren3">(<span class="code">draw-player screen start-x start-y player</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Instead of a <code>center-x</code> and <code>center-y</code> that are based on an arbitrary value
in the <code>game</code> object, I'm now basing things off of the player's coordinates.
Otherwise not much has changed here.</p>
<p>One more thing I decided to do is the start using that line at the bottom of the
screen that I reserved for stats. I added a simple function to draw it:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> draw-hud <span class="paren2">[<span class="code">screen game start-x start-y</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">hud-row <span class="paren4">(<span class="code">dec <span class="paren5">(<span class="code">second screen-size</span>)</span></span>)</span>
<span class="paren4">[<span class="code">x y</span>]</span> <span class="paren4">(<span class="code">get-in game <span class="paren5">[<span class="code"><span class="keyword">:world</span> <span class="keyword">:player</span> <span class="keyword">:location</span></span>]</span></span>)</span>
info <span class="paren4">(<span class="code">str <span class="string">&quot;loc: [&quot;</span> x <span class="string">&quot;-&quot;</span> y <span class="string">&quot;]&quot;</span></span>)</span>
info <span class="paren4">(<span class="code">str info <span class="string">&quot; start: [&quot;</span> start-x <span class="string">&quot;-&quot;</span> start-y <span class="string">&quot;]&quot;</span></span>)</span></span>]</span>
<span class="paren3">(<span class="code">s/put-string screen 0 hud-row info</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>And add a call for that to the <code>draw-ui</code> function. I'm sure you can figure that
out yourself.</p>
<p>Now the last line of the screen will look like:</p>
<pre><code>loc: [30-53] start: [10-34]</code></pre>
<p>I can now see the coordinates of the player and the top left corner of the
screen at all times. This was really handy when debugging display/movement
problems later.</p>
<h2 id="s7-movement"><a href="index.html#s7-movement">Movement</a></h2>
<p>Now that I'm basing the viewport on the player's location, I need a way for
players to move around. I could just tweak the current code, but lots of things
are going to need to move so this sounds like a great place for my first
&quot;aspect&quot;.</p>
<p>I created the <code>entities/aspects/mobile.clj</code> file and added the protocol
representing the aspect:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.aspects.mobile</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Mobile
<span class="paren2">(<span class="code">move <span class="paren3">[<span class="code">this world dest</span>]</span>
<span class="string">&quot;Move this entity to a new location.&quot;</span></span>)</span>
<span class="paren2">(<span class="code">can-move? <span class="paren3">[<span class="code">this world dest</span>]</span>
<span class="string">&quot;Return whether the entity can move to the new location.&quot;</span></span>)</span></span>)</span></span></code></pre>
<p>Right now I'm just defining some simple functions. Mobile entities must be able
to check if they can move into a coordinate, as well as actually move themselves
into it.</p>
<p>Why allow entities to check for movement and move themselves instead of having
a single movement handling chunk of code for all entities?</p>
<p>Well this means we can customize how movement works on a per-entity basis.
Maybe we'll have a minotaur that can move into other entities' spaces,
displacing them. Or a stone elemental that can walk through wall tiles.</p>
<p>Next I made the Player entity implement Mobile back in <code>entities/player.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.player
<span class="paren2">(<span class="code"><span class="keyword">:use</span> <span class="paren3">[<span class="code">caves.entities.core <span class="keyword">:only</span> <span class="paren4">[<span class="code">Entity</span>]</span></span>]</span>
<span class="paren3">[<span class="code">caves.entities.aspects.mobile <span class="keyword">:only</span> <span class="paren4">[<span class="code">Mobile move can-move?</span>]</span></span>]</span>
<span class="paren3">[<span class="code">caves.coords <span class="keyword">:only</span> <span class="paren4">[<span class="code">destination-coords</span>]</span></span>]</span>
<span class="paren3">[<span class="code">caves.world <span class="keyword">:only</span> <span class="paren4">[<span class="code">find-empty-tile get-tile-kind</span>]</span></span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defrecord</span></i> Player <span class="paren2">[<span class="code">id glyph location</span>]</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> check-tile
<span class="string">&quot;Check that the tile at the destination passes the given predicate.&quot;</span>
<span class="paren2">[<span class="code">world dest pred</span>]</span>
<span class="paren2">(<span class="code">pred <span class="paren3">(<span class="code">get-tile-kind world dest</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code">extend-type Player Entity
<span class="paren2">(<span class="code">tick <span class="paren3">[<span class="code">this world</span>]</span>
world</span>)</span></span>)</span>
<span class="paren1">(<span class="code">extend-type Player Mobile
<span class="paren2">(<span class="code">move <span class="paren3">[<span class="code">this world dest</span>]</span>
<span class="paren3">{<span class="code"><span class="keyword">:pre</span> <span class="paren4">[<span class="code"><span class="paren5">(<span class="code">can-move? this world dest</span>)</span></span>]</span></span>}</span>
<span class="paren3">(<span class="code">assoc-in world <span class="paren4">[<span class="code"><span class="keyword">:player</span> <span class="keyword">:location</span></span>]</span> dest</span>)</span></span>)</span>
<span class="paren2">(<span class="code">can-move? <span class="paren3">[<span class="code">this world dest</span>]</span>
<span class="paren3">(<span class="code">check-tile world dest #<span class="paren4">{<span class="code"><span class="keyword">:floor}</span></span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">defn</span></i> make-player <span class="paren3">[<span class="code">world</span>]</span>
<span class="paren3">(<span class="code">-&gt;Player <span class="keyword">:player</span> <span class="string">&quot;@&quot;</span> <span class="paren4">(<span class="code">find-empty-tile world</span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">defn</span></i> move-player <span class="paren3">[<span class="code">world dir</span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">player <span class="paren5">(<span class="code"><span class="keyword">:player</span> world</span>)</span>
target <span class="paren5">(<span class="code">destination-coords <span class="paren6">(<span class="code"><span class="keyword">:location</span> player</span>)</span> dir</span>)</span></span>]</span>
<span class="paren4">(<span class="code"><i><span class="symbol">cond</span></i>
<span class="paren5">(<span class="code">can-move? player world target</span>)</span> <span class="paren5">(<span class="code">move player world target</span>)</span>
<span class="keyword">:else</span> world</span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
<p>Notice how simple (and concise) this was to add. I defined <code>can-move?</code> to
simply make sure that the destination is a floor tile.</p>
<p><code>move</code> itself uses a Clojure function precondition to sanity-check that the
entity isn't trying to cheat and move somewhere illegal. If everything's okay,
I simply update the player's location.</p>
<p><code>move-player</code> is an ugly helper function that most entities won't need. Players
are special because we're going to want to make certain keystrokes do multiple
things, as we'll see shortly. For now don't worry too much about that one.</p>
<p>Before going on make sure you understand how movement is actually going to
happen, from the point where <code>(move-player game :s)</code> is called and down.</p>
<p>The last thing to do to actually make the player movable is handling the actual
keystrokes from the user, so I did that next over in <code>ui/input.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmethod</span></i> process-input <span class="keyword">:play</span> <span class="paren2">[<span class="code">game input</span>]</span>
<span class="paren2">(<span class="code">case input
<span class="keyword">:enter</span> <span class="paren3">(<span class="code">assoc game <span class="keyword">:uis</span> <span class="paren4">[<span class="code"><span class="paren5">(<span class="code">-&gt;UI <span class="keyword">:win</span></span>)</span></span>]</span></span>)</span>
<span class="keyword">:backspace</span> <span class="paren3">(<span class="code">assoc game <span class="keyword">:uis</span> <span class="paren4">[<span class="code"><span class="paren5">(<span class="code">-&gt;UI <span class="keyword">:lose</span></span>)</span></span>]</span></span>)</span>
\q <span class="paren3">(<span class="code">assoc game <span class="keyword">:uis</span> <span class="paren4">[<span class="code"></span>]</span></span>)</span>
\h <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:w</span></span>)</span>
\j <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:s</span></span>)</span>
\k <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:n</span></span>)</span>
\l <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:e</span></span>)</span>
\y <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:nw</span></span>)</span>
\u <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:ne</span></span>)</span>
\b <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:sw</span></span>)</span>
\n <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:world</span></span>]</span> move-player <span class="keyword">:se</span></span>)</span>
game</span>)</span></span>)</span></span></code></pre>
<p>Each of the traditional roguelike movement keys will now move the player around
the world. Because the screen drawing is already updated to be based on the
player's location, movement is pretty much complete!</p>
<p>I added the <code>yubn</code> diagonal movement keys because as I was trying out movement
myself it felt like I needed them.</p>
<p>This is something that I've noticed while watching Notch's Ludum Dare recordings
(Google for them if you want to see them). He plays the game he's making for
longer periods than you might think. He doesn't just make a feature and make
sure it works, he makes a feature and then plays the game normally for a few
minutes to make sure it fits into the game right (and is fun)!</p>
<p>Those <code>update-in</code> statements are a bit ugly, but not ugly enough for me to want
to do something clever to remove them. They can stay for now.</p>
<h2 id="s8-digging"><a href="index.html#s8-digging">Digging</a></h2>
<p>As Trystan mentioned in his post, we're not doing anything special to make sure
the caves we generate are connected. The player may very well start in a tiny
cave.</p>
<p>To make this less of a problem, he added the ability for the player to dig
through walls.</p>
<p>Digging sounds like a great candidate for another aspect, so I added
<code>entities/aspects/digger.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.aspects.digger</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Digger
<span class="paren2">(<span class="code">dig <span class="paren3">[<span class="code">this world target</span>]</span>
<span class="string">&quot;Dig a location.&quot;</span></span>)</span>
<span class="paren2">(<span class="code">can-dig? <span class="paren3">[<span class="code">this world target</span>]</span>
<span class="string">&quot;Return whether the entity can dig the new location.&quot;</span></span>)</span></span>)</span></span></code></pre>
<p>Nothing fancy here. Then I made the Player entity implement it:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">extend-type Player Digger
<span class="paren2">(<span class="code">dig <span class="paren3">[<span class="code">this world dest</span>]</span>
<span class="paren3">{<span class="code"><span class="keyword">:pre</span> <span class="paren4">[<span class="code"><span class="paren5">(<span class="code">can-dig? this world dest</span>)</span></span>]</span></span>}</span>
<span class="paren3">(<span class="code">set-tile-floor world dest</span>)</span></span>)</span>
<span class="paren2">(<span class="code">can-dig? <span class="paren3">[<span class="code">this world dest</span>]</span>
<span class="paren3">(<span class="code">check-tile world dest #<span class="paren4">{<span class="code"><span class="keyword">:wall}</span></span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
<p>This looks very similar to the Mobile implementation, except instead of changing
the player's location I change the map tile from a wall to a floor.</p>
<p>Finally, I update the <code>move-player</code> function (which is called when we receive
a keystroke):</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> move-player <span class="paren2">[<span class="code">world dir</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">player <span class="paren4">(<span class="code"><span class="keyword">:player</span> world</span>)</span>
target <span class="paren4">(<span class="code">destination-coords <span class="paren5">(<span class="code"><span class="keyword">:location</span> player</span>)</span> dir</span>)</span></span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">cond</span></i>
<span class="paren4">(<span class="code">can-move? player world target</span>)</span> <span class="paren4">(<span class="code">move player world target</span>)</span>
<span class="paren4">(<span class="code">can-dig? player world target</span>)</span> <span class="paren4">(<span class="code">dig player world target</span>)</span>
<span class="keyword">:else</span> world</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Now if the space the user is telling the player to enter is open, the player
will move there, otherwise if it's diggable the player will dig it, otherwise
nothing will happen.</p>
<p>This means that moving into a space that is currently a wall will take two
keypresses: the first digs out the wall, the second moves into the newly open
space.</p>
<p>I like how this feels. It takes longer to travel through rock, which makes
sense. If you prefer to dig and move all at once you could dig and move in the
same action. It's up to you.</p>
<h2 id="s9-results"><a href="index.html#s9-results">Results</a></h2>
<p>Finally, after seven entries I've got a hero in the game! It's taken a while,
but I've laid the groundwork for what I think is some really cool stuff down the
line.</p>
<p>You can view the code <a href="https://github.com/sjl/caves/tree/entry-04/src/caves">on GitHub</a> if you want to see the end
result. From now on I'm going to start moving a bit faster, not always showing
the namespace declarations and such. If you want the full code for each post
look at the GitHub repository.</p>
<p>And the obligatory screenshots of our intrepid hero:</p>
<p><img src="../../../../static/images/blog/2012/07/caves-04-01.png" alt="Screenshot"></p>
<p><img src="../../../../static/images/blog/2012/07/caves-04-02.png" alt="Screenshot"></p>
<p><img src="../../../../static/images/blog/2012/07/caves-04-03.png" alt="Screenshot"></p>
<p>Next time I'll be adding some monsters for the hero to slay.</p>
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a><a href='https://twitter.com/stevelosh/'>Twitter</a><a href='https://instagram.com/thirtytwobirds/'>Instagram</a><a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html>