emacs.d/clones/lisp/stevelosh.com/blog/2012/07/caves-of-clojure-05/index.html

437 lines
37 KiB
HTML
Raw Normal View History

2022-10-07 15:47:14 +02:00
<!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 5 / 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 5</a></h1><p class='date'>Posted on July 13th, 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/09/roguelike-tutorial-05-stationary.html">post five 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-05</code> tag to see the code as it stands
after this post.</p>
<p>Also, I live streamed myself writing the code that this entry is based on. You
can view the recordings <a href="http://www.twitch.tv/stevelosh/">on twitch.tv</a>, though
as I write this the video links are stuck in an infinite HTTP redirect loop.
Perhaps they will be fixed eventually.</p>
<p>Finally, I've started hanging out in <code>##cavesofclojure</code> on Freenode if you have
questions. I may or may not be around at any given point.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-summary">Summary</a></li><li><a href="index.html#s2-multiple-entities">Multiple Entities</a></li><li><a href="index.html#s3-lichens">Lichens</a></li><li><a href="index.html#s4-populating-the-world">Populating the World</a></li><li><a href="index.html#s5-drawing-the-entities">Drawing the Entities</a></li><li><a href="index.html#s6-movement">Movement</a></li><li><a href="index.html#s7-killing">Killing</a></li><li><a href="index.html#s8-growing-lichens">Growing Lichens</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 fifth post he adds three things:</p>
<ul>
<li>A stationary monster</li>
<li>Attacking</li>
<li>A growing mechanic for the monster</li>
</ul>
<p>I'm going to add all three of those too, though I'll be doing it in the
entities/aspects fashion of the previous post.</p>
<h2 id="s2-multiple-entities"><a href="index.html#s2-multiple-entities">Multiple Entities</a></h2>
<p>First thing's first: I need to change a bit of code around to account for having
multiple entities instead of just a single player.</p>
<p>At the end of the previous post the player was stored in the world directly,
meaning the <code>game</code> object looked like this:</p>
<pre><code><span class="code">{:uis <span class="paren1">[<span class="code">...</span>]</span>
:world <span class="paren1">{<span class="code"><span class="keyword">:player</span> <span class="paren2">{<span class="code">...}
<span class="keyword">:tiles</span> <span class="paren3">[<span class="code">...</span>]</span></span>}</span></span>}</span></span></code></pre>
<p>I decided to remove some (but not all) of the special casing for the player and
make them an entity like any other. The <code>game</code> object is now structured like
this:</p>
<pre><code><span class="code">{:uis <span class="paren1">[<span class="code">...</span>]</span>
:world <span class="paren1">{<span class="code"><span class="keyword">:entities</span> <span class="paren2">{<span class="code"><span class="keyword">:player</span> <span class="paren3">{<span class="code">...}}
<span class="keyword">:tiles</span> <span class="paren4">[<span class="code">...</span>]</span></span>}</span></span>}</span></span></span></span></code></pre>
<p>Notice that the player has been moved into an <code>:entities</code> map, keyed by its id
(which is still the special-cased <code>:player</code>). I updated anywhere that needed to
change to account for this, and made sure it still worked. This was pretty easy
to do by just searching for <code>:player</code>, as the codebase is still small.</p>
<h2 id="s3-lichens"><a href="index.html#s3-lichens">Lichens</a></h2>
<p>Now it's time to actually add another type of entity. I play a lot of Nethack,
so naturally I decided to make it a simple lichen. I added
<code>entities/lichen.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.lichen
<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 get-id</span>]</span></span>]</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defrecord</span></i> Lichen <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> make-lichen <span class="paren2">[<span class="code">location</span>]</span>
<span class="paren2">(<span class="code">-&gt;Lichen <span class="paren3">(<span class="code">get-id</span>)</span> <span class="string">&quot;F&quot;</span> location</span>)</span></span>)</span>
<span class="paren1">(<span class="code">extend-type Lichen Entity
<span class="paren2">(<span class="code">tick <span class="paren3">[<span class="code">this world</span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">should-grow</span>)</span>
world</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Like the <code>Player</code>, <code>Lichen</code>s implement the <code>Entity</code> protocol. For now they
don't do anything special during a tick.</p>
<p>You may have noticed the new <code>get-id</code> function. Entities must have IDs so I can
get them in and out of the entity map. The player has a special ID of
<code>:player</code>, but I needed a way to get a unique ID for other entities.</p>
<p>The simplest way I could think of was to use a simple counter over in
<code>entities/core.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.core</span>)</span>
<span class="paren1">(<span class="code">def ids <span class="paren2">(<span class="code">ref 0</span>)</span></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 class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-id <span class="paren2">[<span class="code"></span>]</span>
<span class="paren2">(<span class="code">dosync
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">id @ids</span>]</span>
<span class="paren4">(<span class="code">alter ids inc</span>)</span>
id</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Not the prettiest solution, but it works. I might switch to a UUID library or
something in the future, but this'll do for now.</p>
<h2 id="s4-populating-the-world"><a href="index.html#s4-populating-the-world">Populating the World</a></h2>
<p>Unlike the <code>make-player</code> function, <code>make-lichen</code> takes a location directly
instead of trying to find an empty space for itself in the world. I figured it
was better to not have entities deciding where they emerge in the world all the
time! I went ahead and refactored <code>make-player</code> to act like this as well.</p>
<p>During this coding session I wasn't actually running and playing the full game
through as much as I should have been. I think as I get more and more of the
basic structure of the game in place I'll be able to do this more. Up to now
I've been doing large, sweeping refactorings that touch many different pieces of
code and break everything until they're finished.</p>
<p>Anyway, back to the world. I need a way to spawn some lichens in the world, so
I edited the <code>reset-game</code> function in <code>input.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> add-lichen <span class="paren2">[<span class="code">world</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"><span class="keyword">:as</span> lichen <span class="keyword">:keys</span> <span class="paren5">[<span class="code">id</span>]</span></span>}</span> <span class="paren4">(<span class="code">make-lichen <span class="paren5">(<span class="code">find-empty-tile world</span>)</span></span>)</span></span>]</span>
<span class="paren3">(<span class="code">assoc-in world <span class="paren4">[<span class="code"><span class="keyword">:entities</span> id</span>]</span> lichen</span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> populate-world <span class="paren2">[<span class="code">world</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">assoc-in world <span class="paren5">[<span class="code"><span class="keyword">:entities</span> <span class="keyword">:player</span></span>]</span>
<span class="paren5">(<span class="code">make-player <span class="paren6">(<span class="code">find-empty-tile world</span>)</span></span>)</span></span>)</span>
world <span class="paren4">(<span class="code">nth <span class="paren5">(<span class="code">iterate add-lichen world</span>)</span> 30</span>)</span></span>]</span>
world</span>)</span></span>)</span>
<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">update-in <span class="paren5">[<span class="code"><span class="keyword">:world</span></span>]</span> populate-world</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></code></pre>
<p>It should be pretty easy to read. <code>add-lichen</code> adds a new lichen to an empty
tile. <code>populate-world</code> takes a world and adds a player, then 30 lichens.</p>
<p>This is getting to be a bit much to keep in <code>input.clj</code>. I'll probably pull
this out into a separate file soon.</p>
<h2 id="s5-drawing-the-entities"><a href="index.html#s5-drawing-the-entities">Drawing the Entities</a></h2>
<p>So now the lichens are part of the world, but I still need to draw them on the
screen. I split the <code>draw-player</code> function in <code>drawing.clj</code> into two separate
functions:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> draw-entity <span class="paren2">[<span class="code">screen start-x start-y <span class="paren3">{<span class="code"><span class="keyword">:keys</span> <span class="paren4">[<span class="code">location glyph color</span>]</span></span>}</span></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">entity-x entity-y</span>]</span> location
x <span class="paren4">(<span class="code">- entity-x start-x</span>)</span>
y <span class="paren4">(<span class="code">- entity-y start-y</span>)</span></span>]</span>
<span class="paren3">(<span class="code">s/put-string screen x y glyph <span class="paren4">{<span class="code"><span class="keyword">:fg</span> color}</span>)</span></span>)</span></span>)</span>
<span class="paren2">(<span class="code"><i><span class="symbol">defn</span></i> highlight-player <span class="paren3">[<span class="code">screen start-x start-y player</span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code"><span class="paren5">[<span class="code">player-x player-y</span>]</span> <span class="paren5">(<span class="code"><span class="keyword">:location</span> player</span>)</span>
x <span class="paren5">(<span class="code">- player-x start-x</span>)</span>
y <span class="paren5">(<span class="code">- player-y start-y</span>)</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>And then I use those in the main <code>draw-ui</code> function for the <code>:play</code> UI:</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 entities</span>]</span></span>}</span> world
player <span class="paren4">(<span class="code"><span class="keyword">:player</span> entities</span>)</span>
<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">doseq <span class="paren4">[<span class="code">entity <span class="paren5">(<span class="code">vals entities</span>)</span></span>]</span>
<span class="paren4">(<span class="code">draw-entity screen start-x start-y entity</span>)</span></span>)</span>
<span class="paren3">(<span class="code">draw-hud screen game start-x start-y</span>)</span>
<span class="paren3">(<span class="code">highlight-player screen start-x start-y player</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Long but straightforward. I'm going to be cleaning this part of the code up
very soon, as I've just added a bunch of really useful stuff to the
<a href="https://sjl.bitbucket.io/clojure-lanterna/">clojure-lanterna</a> library that will let me delete a bunch of fiddly code
here.</p>
<p>If you're particularly eagle-eyed you might have noticed this new <code>color</code>
attribute that seems to be a part of entities. I didn't realize I had forgotten
to specify colors until I actually wrote this bit of code. Once I did I went
back and added the field to the <code>Player</code> and <code>Lichen</code> records, as well as
<code>make-player</code> and <code>make-lichen</code>.</p>
<p>Now the lichens appear on the screen!</p>
<p><img src="../../../../static/images/blog/2012/07/caves-05-01.png" alt="Screenshot"></p>
<h2 id="s6-movement"><a href="index.html#s6-movement">Movement</a></h2>
<p>At this point the lichens are on the screen but the player can walk straight
through them. I took care of that by first creating a few helper functions in
<code>world.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-entity-at <span class="paren2">[<span class="code">world coord</span>]</span>
<span class="paren2">(<span class="code">first <span class="paren3">(<span class="code">filter #<span class="paren4">(<span class="code">= coord <span class="paren5">(<span class="code"><span class="keyword">:location</span> %</span>)</span></span>)</span>
<span class="paren4">(<span class="code">vals <span class="paren5">(<span class="code"><span class="keyword">:entities</span> world</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> is-empty? <span class="paren2">[<span class="code">world coord</span>]</span>
<span class="paren2">(<span class="code">and <span class="paren3">(<span class="code">#<span class="paren4">{<span class="code"><span class="keyword">:floor}</span> <span class="paren5">(<span class="code">get-tile-kind world coord</span>)</span></span>)</span>
<span class="paren4">(<span class="code">not <span class="paren5">(<span class="code">get-entity-at world coord</span>)</span></span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
<p>They'll handle the grunt world of traversing the <code>world</code> data structure. Then
I updated the player's <code>can-move?</code> function:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">extend-type Player Mobile
<span class="paren2">(<span class="code">move ...</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">is-empty? world dest</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Previously <code>can-move</code> checked the world's tile itself — now it delegates to
a basic helper function instead. I have a feeling a lot of things are going to
need to use the idea of &quot;empty tiles&quot; so this function will probably get a lot
of mileage.</p>
<p>Now the player can't walk through fungus. Great.</p>
<h2 id="s7-killing"><a href="index.html#s7-killing">Killing</a></h2>
<p>It's time to give the player a way to cut the lichens into little licheny bits.
I implemented this with another pair of aspects: <code>Attacker</code> and <code>Destructible</code>.</p>
<p><code>Attacker</code> should be implemented by anything that can attack other things:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.aspects.attacker</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Attacker
<span class="paren2">(<span class="code">attack <span class="paren3">[<span class="code">this world target</span>]</span>
<span class="string">&quot;Attack the target.&quot;</span></span>)</span></span>)</span></span></code></pre>
<p><code>Destructible</code> should be implemented by anything that can &quot;take damage and go
away once it takes enough&quot;:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.entities.aspects.destructible</span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defprotocol</span></i> Destructible
<span class="paren2">(<span class="code">take-damage <span class="paren3">[<span class="code">this world damage</span>]</span>
<span class="string">&quot;Take the given amount of damage and update the world appropriately.&quot;</span></span>)</span></span>)</span></span></code></pre>
<p>Lichens will be <code>Destructible</code> (for now the player will remain invincible):</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">extend-type Lichen Destructible
<span class="paren2">(<span class="code">take-damage <span class="paren3">[<span class="code"><span class="paren4">{<span class="code"><span class="keyword">:keys</span> <span class="paren5">[<span class="code">id</span>]</span> <span class="keyword">:as</span> this} world damage</span>]</span>
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">[<span class="code">damaged-this <span class="paren6">(<span class="code">update-in this <span class="paren1">[<span class="code"><span class="keyword">:hp</span></span>]</span> - damage</span>)</span></span>]</span>
<span class="paren5">(<span class="code">if-not <span class="paren6">(<span class="code">pos? <span class="paren1">(<span class="code"><span class="keyword">:hp</span> damaged-this</span>)</span></span>)</span>
<span class="paren6">(<span class="code">update-in world <span class="paren1">[<span class="code"><span class="keyword">:entities</span></span>]</span> dissoc id</span>)</span>
<span class="paren6">(<span class="code">update-in world <span class="paren1">[<span class="code"><span class="keyword">:entities</span> id</span>]</span> assoc damaged-this</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
<p>The logic here is pretty basic. When a <code>Destructible</code> entity takes some damage,
first its hit points are updated. If they wind up to be zero or fewer, the
entity gracefully removes itself from the world.</p>
<p>I have a feeling there's a more elegant way to write the updatey bits of that
function. If you've got suggestions please let me know.</p>
<p>If I'm going to be basing damage on the entity's <code>:hp</code> attribute they'd better
have one! I added a simple <code>:hp</code> of <code>1</code> to lichens:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defrecord</span></i> Lichen <span class="paren2">[<span class="code">id glyph color location hp</span>]</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> make-lichen <span class="paren2">[<span class="code">location</span>]</span>
<span class="paren2">(<span class="code">-&gt;Lichen <span class="paren3">(<span class="code">get-id</span>)</span> <span class="string">&quot;F&quot;</span> <span class="keyword">:green</span> location 1</span>)</span></span>)</span></span></code></pre>
<p>Next I added the corresponding implementation of <code>Attacker</code> to the <code>Player</code> (for
now lichens can't strike back):</p>
<pre><code><span class="code"><span class="paren1">(<span class="code">extend-type Player Attacker
<span class="paren2">(<span class="code">attack <span class="paren3">[<span class="code">this world target</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">satisfies? Destructible target</span>)</span></span>]</span></span>}</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">damage 1</span>]</span>
<span class="paren4">(<span class="code">take-damage target world damage</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Again, a very basic system for the moment: all attacks do one damage. Lichens
only have one hit point, so this will kill them instantly.</p>
<p>Notice the precondition here: an attacker can attack something if and only if
it's something that satisfies the <code>Destructible</code> protocol.</p>
<p>Instead of doing something like checking if the target has <code>:hp</code> I simply check
if it's <code>Destructible</code>. This opens the door for things that don't necessarily
use hit points, like a monster whose mana and hit points are a single number.</p>
<p>Finally, I need to hook up the attacking functionality in the <code>move-player</code>
helper function:</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">get-in world <span class="paren5">[<span class="code"><span class="keyword">:entities</span> <span class="keyword">:player</span></span>]</span></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>
entity-at-target <span class="paren4">(<span class="code">get-entity-at world target</span>)</span></span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">cond</span></i>
entity-at-target <span class="paren4">(<span class="code">attack player world entity-at-target</span>)</span>
<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>This once again overloads the <code>hjkl</code> keys, so now the player will attack
a monster when they try to move into it. Otherwise the player will move or dig
as before.</p>
<h2 id="s8-growing-lichens"><a href="index.html#s8-growing-lichens">Growing Lichens</a></h2>
<p>Now for the last part of Trystan's post. Lichens should have a chance of
spreading slowly every turn. Unlike Trystan, I'm not going to limit the number
of times the lichen can spread, so the player will need to use their newfound
attacking ability if they want to stem the tide of invading fungus!</p>
<p>This turned out to be surprisingly painless:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> should-grow <span class="paren2">[<span class="code"></span>]</span>
<span class="paren2">(<span class="code">&lt; <span class="paren3">(<span class="code">rand</span>)</span> 0.01</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> grow <span class="paren2">[<span class="code">lichen world</span>]</span>
<span class="paren2">(<span class="code">if-let <span class="paren3">[<span class="code">target <span class="paren4">(<span class="code">find-empty-neighbor world <span class="paren5">(<span class="code"><span class="keyword">:location</span> lichen</span>)</span></span>)</span></span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">new-lichen <span class="paren5">(<span class="code">make-lichen target</span>)</span></span>]</span>
<span class="paren4">(<span class="code">assoc-in world <span class="paren5">[<span class="code"><span class="keyword">:entities</span> <span class="paren6">(<span class="code"><span class="keyword">:id</span> new-lichen</span>)</span></span>]</span> new-lichen</span>)</span></span>)</span>
world</span>)</span></span>)</span>
<span class="paren1">(<span class="code">extend-type Lichen Entity
<span class="paren2">(<span class="code">tick <span class="paren3">[<span class="code">this world</span>]</span>
<span class="paren3">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren4">(<span class="code">should-grow</span>)</span>
<span class="paren4">(<span class="code">grow this world</span>)</span>
world</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Every tick, the lichen has a one percent chance to spread to an empty
neighboring tile. If there are no empty neighboring tiles, it can't spread.</p>
<p>The <code>find-empty-neighbor</code> function is new, and located 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-neighbor <span class="paren2">[<span class="code">world coord</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">candidates <span class="paren4">(<span class="code">filter #<span class="paren5">(<span class="code">is-empty? world %</span>)</span> <span class="paren5">(<span class="code">neighbors coord</span>)</span></span>)</span></span>]</span>
<span class="paren3">(<span class="code">when <span class="paren4">(<span class="code">seq candidates</span>)</span>
<span class="paren4">(<span class="code">rand-nth candidates</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>It uses <code>neighbors</code>, which is another function I created after a quick refactor
of <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">def directions
<span class="paren2">{<span class="code"><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> 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">directions dir</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 class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> neighbors
<span class="string">&quot;Return the coordinates of all neighboring squares of the given coord.&quot;</span>
<span class="paren2">[<span class="code">origin</span>]</span>
<span class="paren2">(<span class="code">map offset-coords <span class="paren3">(<span class="code">vals directions</span>)</span> <span class="paren3">(<span class="code">repeat origin</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>Nothing too crazy here. The small, composable functions build on top of each
other to create more interesting ones.</p>
<p>But there's one thing left to do, which is actually <code>tick</code> entities in the main
game loop in <code>core.clj</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> tick-entity <span class="paren2">[<span class="code">world entity</span>]</span>
<span class="paren2">(<span class="code">tick entity world</span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> tick-all <span class="paren2">[<span class="code">world</span>]</span>
<span class="paren2">(<span class="code">reduce tick-entity world <span class="paren3">(<span class="code">vals <span class="paren4">(<span class="code"><span class="keyword">:entities</span> world</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> run-game <span class="paren2">[<span class="code">game screen</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">loop</span></i> <span class="paren3">[<span class="code"><span class="paren4">{<span class="code"><span class="keyword">:keys</span> <span class="paren5">[<span class="code">input uis</span>]</span> <span class="keyword">:as</span> game} game</span>]</span>
<span class="paren4">(<span class="code">when-not <span class="paren5">(<span class="code">empty? uis</span>)</span>
<span class="paren5">(<span class="code">draw-game game screen</span>)</span>
<span class="paren5">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren6">(<span class="code">nil? input</span>)</span>
<span class="paren6">(<span class="code">recur <span class="paren1">(<span class="code">get-input <span class="paren2">(<span class="code">update-in game <span class="paren3">[<span class="code"><span class="keyword">:world</span></span>]</span> tick-all</span>)</span> screen</span>)</span></span>)</span>
<span class="paren6">(<span class="code">recur <span class="paren1">(<span class="code">process-input <span class="paren2">(<span class="code">dissoc game <span class="keyword">:input</span></span>)</span> input</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
<p>Notice how the <code>tick-all</code> function reduces over the values in the entities map.
Maps aren't deterministically ordered (or at least they're not <em>guaranteed</em> to
be), so this means that our entities may process their ticks in a different
order each turn.</p>
<p>I think I'm okay with that. Yes, it means that ticking the world isn't going to
be a pure function, but it won't be pure no matter what since we're going to
have random numbers involved in attacking and damage soon enough.</p>
<h2 id="s9-results"><a href="index.html#s9-results">Results</a></h2>
<p>All-in-all it took roughly an hour and a half to code the stuff in this entry.
This might sound like a lot, but remember what was added:</p>
<ul>
<li>The entire concept of &quot;multiple entities in a map&quot;.</li>
<li>Support for drawing arbitrary entities on the map.</li>
<li>A new creature.</li>
<li>Entities blocking movement of others.</li>
<li>A rudimentary attacking gameplay mechanic.</li>
<li>A rudimentary killing mechanic.</li>
<li>Ticking entities.</li>
<li>Growing/spreading of creatures.</li>
<li>Lots of refactoring and helper functions.</li>
</ul>
<p>Not too bad!</p>
<p>You can view the code <a href="https://github.com/sjl/caves/tree/entry-05/src/caves">on GitHub</a> if you want to see the end
result.</p>
<p>And now some screenshots of our hero cutting a swath through some fungus!</p>
<p><img src="../../../../static/images/blog/2012/07/caves-05-02.png" alt="Screenshot"></p>
<p><img src="../../../../static/images/blog/2012/07/caves-05-03.png" alt="Screenshot"></p>
<p>I'll be moving on to Trystan's sixth post soon, but before that I'm going to
have another interlude where I explain some quick refactoring and then work
a bit of the blackest magic in Clojure: a non-trivial macro.</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>