583 lines
48 KiB
HTML
583 lines
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">"Offset the starting coordinate by the given amount, returning the result coordinate."</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">"Convert a direction to the offset for moving 1 in that direction."</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">"Take an origin's coords and a direction and return the destination's coords."</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">->Tile <span class="keyword">:floor</span> <span class="string">"."</span> <span class="keyword">:white</span></span>)</span>
|
||
|
<span class="keyword">:wall</span> <span class="paren3">(<span class="code">->Tile <span class="keyword">:wall</span> <span class="string">"#"</span> <span class="keyword">:white</span></span>)</span>
|
||
|
<span class="keyword">:bound</span> <span class="paren3">(<span class="code">->Tile <span class="keyword">:bound</span> <span class="string">"X"</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">>= 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">->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 "things in the world"
|
||
|
it's not just creatures — the list includes both creatures and items. It uses
|
||
|
the word "entity" 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 "glyph" that I'll be using to
|
||
|
display them to the player. Both will have some kind of "id" 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 "pixie" creature that wanders around and does normal creaturey
|
||
|
things, but if you attack it while wielding a butterfly net you "catch" it and
|
||
|
it gets picked up and put in your inventory like an item.</p>
|
||
|
|
||
|
<p>Once you've got one you could "apply" it (maybe that means "setting it free") 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 "entity" system.
|
||
|
A bag that eat things, where you need to remember to "feed" 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 ("pixie", "player", "goblin", "steel helmet") 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 "tick" 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
|
||
|
"aspects".</p>
|
||
|
|
||
|
<p>An "aspect" 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">"Update the world to handle the passing of a tick for this entity."</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">->Player <span class="keyword">:player</span> <span class="string">"@"</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">-> 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">->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">"loc: ["</span> x <span class="string">"-"</span> y <span class="string">"]"</span></span>)</span>
|
||
|
info <span class="paren4">(<span class="code">str info <span class="string">" start: ["</span> start-x <span class="string">"-"</span> start-y <span class="string">"]"</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
|
||
|
"aspect".</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">"Move this entity to a new location."</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">"Return whether the entity can move to the new location."</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">"Check that the tile at the destination passes the given predicate."</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">->Player <span class="keyword">:player</span> <span class="string">"@"</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">->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">->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">"Dig a location."</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">"Return whether the entity can dig the new location."</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>
|