324 lines
24 KiB
HTML
324 lines
24 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 3.1 / 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 3.1</a></h1><p class='date'>Posted on July 9th, 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-03-scrolling-through.html">post three 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-03-1</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-organization">Organization</a></li><li><a href="index.html#s3-creating-a-random-world">Creating a Random World</a></li><li><a href="index.html#s4-displaying">Displaying</a></li><li><a href="index.html#s5-results">Results</a></li></ol>
|
||
|
|
||
|
<h2 id="s1-summary"><a href="index.html#s1-summary">Summary</a></h2>
|
||
|
|
||
|
<p>In Trystan's third post he introduces three new things:</p>
|
||
|
|
||
|
<ul>
|
||
|
<li>Random world generation.</li>
|
||
|
<li>Smoothing the world to look nicer.</li>
|
||
|
<li>The "play screen" and scrolling.</li>
|
||
|
</ul>
|
||
|
|
||
|
<p>I'm going to split this up into three separate short posts, so you can see the
|
||
|
process I actually went through as I built it. This post will deal with the
|
||
|
world generation and basic displaying. The next one will be about smoothing,
|
||
|
and the one after that about scrolling around.</p>
|
||
|
|
||
|
<h2 id="s2-organization"><a href="index.html#s2-organization">Organization</a></h2>
|
||
|
|
||
|
<p>First of all, my single <code>core.clj</code> is going to get a bit crowded by the time
|
||
|
we're done with this, so it's time to start splitting it apart. I made
|
||
|
a <code>world.clj</code> file alongside <code>core.clj</code>. That'll do for now.</p>
|
||
|
|
||
|
<h2 id="s3-creating-a-random-world"><a href="index.html#s3-creating-a-random-world">Creating a Random World</a></h2>
|
||
|
|
||
|
<p>First I set up the namespace:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.world</span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Next I defined a constant for the size of the world. I chose 160 by 50 tiles
|
||
|
arbitrarily:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">def world-size <span class="paren2">[<span class="code">160 50</span>]</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>I moved the <code>World</code> record declaration out of <code>core.clj</code> and into this file:</p>
|
||
|
|
||
|
<pre><code><span class="code"><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></code></pre>
|
||
|
|
||
|
<p>I also added the <code>tiles</code> attribute to it. For now I'm going to store the tiles
|
||
|
as a vector of rows, where each row is a vector of tiles.</p>
|
||
|
|
||
|
<p>What is a tile? For now it's going to be a simple record:</p>
|
||
|
|
||
|
<pre><code><span class="code"><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></code></pre>
|
||
|
|
||
|
<p>Tiles are immutable, so let's make a map of some of the ones we'll be needing:</p>
|
||
|
|
||
|
<pre><code><span class="code"><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">new 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">new 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">new Tile <span class="keyword">:bound</span> <span class="string">"X"</span> <span class="keyword">:black</span></span>)</span></span>}</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>The <code>:bound</code> Tile represents "out of bounds". It will be returned if you try to
|
||
|
get a tile outside the bounds of the map. There are other ways to handle that,
|
||
|
but this is the one Trystan used so I'm going to use it too.</p>
|
||
|
|
||
|
<p>Next I created a little helper function for retrieving a specific tile:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-tile <span class="paren2">[<span class="code">tiles x y</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></code></pre>
|
||
|
|
||
|
<p>Remember that we're storing the tiles as a vector of rows, so when we index into
|
||
|
it we need to get the y coordinate (i.e.: the row) first, then index in for the
|
||
|
column with the x coordinate.</p>
|
||
|
|
||
|
<p>This is a bit ugly, but storing the screen as rows is going to help us later as
|
||
|
we draw the screen, and it gives us the opportunity to do bounds checking as
|
||
|
well.</p>
|
||
|
|
||
|
<p>The <code>get-in</code> call is a really handy way to check bounds. No worrying about
|
||
|
comparing indexes to dimensions — just try to get it and if you fail it must be
|
||
|
out of bounds.</p>
|
||
|
|
||
|
<p>That's about it for the world structure. Time to move to the generation:</p>
|
||
|
|
||
|
<pre><code><span class="code"><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> random-world <span class="paren2">[<span class="code"></span>]</span>
|
||
|
<span class="paren2">(<span class="code">new World <span class="paren3">(<span class="code">random-tiles</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Nothing too fancy happening here. In <code>random-tiles</code> I built up a series of
|
||
|
helper functions to make it easier to read. You could save some LOC by just
|
||
|
using anonymous functions instead, but to me this way is easier to read.
|
||
|
Personal preference, I guess.</p>
|
||
|
|
||
|
<p>For now we're just going to generate a world where every tile has an equal
|
||
|
chance of being a wall or a floor. I might revisit this later if I want to make
|
||
|
the caves sparser or denser.</p>
|
||
|
|
||
|
<p>That's it for the world generation. Next we'll move on to actually displaying
|
||
|
the new random worlds.</p>
|
||
|
|
||
|
<h2 id="s4-displaying"><a href="index.html#s4-displaying">Displaying</a></h2>
|
||
|
|
||
|
<p>Let's switch back to <code>core.clj</code>. First I updated the namespace to pull in the
|
||
|
<code>random-world</code> function:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">ns caves.core
|
||
|
<span class="paren2">(<span class="code"><span class="keyword">:use</span> <span class="paren3">[<span class="code">caves.world <span class="keyword">:only</span> <span class="paren4">[<span class="code">random-world</span>]</span></span>]</span></span>)</span>
|
||
|
<span class="paren2">(<span class="code"><span class="keyword">:require</span> <span class="paren3">[<span class="code">lanterna.screen <span class="keyword">:as</span> s</span>]</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Before going further I decided to do a bit of cleanup. Instead of hardcoding
|
||
|
the 80 by 24 terminal size, I pulled it out into a constant:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">def screen-size <span class="paren2">[<span class="code">80 24</span>]</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>I updated <code>clear-screen</code> to use that:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> clear-screen <span class="paren2">[<span class="code">screen</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> screen-size
|
||
|
blank <span class="paren4">(<span class="code">apply str <span class="paren5">(<span class="code">repeat cols \space</span>)</span></span>)</span></span>]</span>
|
||
|
<span class="paren3">(<span class="code">doseq <span class="paren4">[<span class="code">row <span class="paren5">(<span class="code">range rows</span>)</span></span>]</span>
|
||
|
<span class="paren4">(<span class="code">s/put-string screen 0 row blank</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>It's still not perfect (what if the user's terminal isn't 80 by 24?) but it's
|
||
|
not something I care enough to fix right now. I'll get to it later. At least
|
||
|
now the hardcoded numbers are in one spot.</p>
|
||
|
|
||
|
<p>There's a few things I need to do to get the world on the screen. First
|
||
|
I created a <code>:play</code> UI similar to Trystan's. I'm not a big fan of the
|
||
|
generic-sounding name, but I couldn't come up with anything better in a few
|
||
|
minutes of thinking.</p>
|
||
|
|
||
|
<p>Creating a UI requires implementing the <code>draw-ui</code> and <code>process-input</code>
|
||
|
multimethods from the previous post. I'll start with the easy one: input
|
||
|
processing.</p>
|
||
|
|
||
|
<p>For now the flow of the game will go like this:</p>
|
||
|
|
||
|
<ol>
|
||
|
<li>The player is shown an introduction screen with some instructions.</li>
|
||
|
<li>They press a key and see the world.</li>
|
||
|
<li>Pressing enter wins the game. Backspace loses the game. Any other key does
|
||
|
nothing.</li>
|
||
|
<li>Once they win or lose, they see a text blurb and can press escape to quit, or
|
||
|
any other key to GOTO 1.</li>
|
||
|
</ol>
|
||
|
|
||
|
<p>With that in mind, I wrote the <code>process-input</code> implementation for <code>:play</code> UIs:</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">new 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">new UI <span class="keyword">:lose</span></span>)</span></span>]</span></span>)</span>
|
||
|
game</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>I'm still replacing the entire UI stack at once. I'm going to be throwing that
|
||
|
code away later anyway so it's not a big deal.</p>
|
||
|
|
||
|
<p>Now I need to update the <code>:start</code> UI to send the user to the <code>:play</code> UI instead
|
||
|
of directly to the <code>:win</code> or <code>:lose</code> UIs.</p>
|
||
|
|
||
|
<pre><code><span class="code"><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">-> game
|
||
|
<span class="paren3">(<span class="code">assoc <span class="keyword">:world</span> <span class="paren4">(<span class="code">random-world</span>)</span></span>)</span>
|
||
|
<span class="paren3">(<span class="code">assoc <span class="keyword">:uis</span> <span class="paren4">[<span class="code"><span class="paren5">(<span class="code">new UI <span class="keyword">:play</span></span>)</span></span>]</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>I also decided that this is where I'd generate the new random world. It makes
|
||
|
sense to put that here, because every time you restart the game you should get
|
||
|
a different world.</p>
|
||
|
|
||
|
<p>On the other hand, this means that the <code>process-input</code> function is no longer
|
||
|
pure for <code>:start</code> UIs (all the other input processing functions are still pure).
|
||
|
I'm not sure how I feel about that. For now I'm going to accept it, but I may
|
||
|
rethink it in the future.</p>
|
||
|
|
||
|
<p>Now that I have the world generation in there, I can remove the <code>(new World)</code>
|
||
|
call from the <code>new-game</code> helper function:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> new-game <span class="paren2">[<span class="code"></span>]</span>
|
||
|
<span class="paren2">(<span class="code">new Game nil <span class="paren3">[<span class="code"><span class="paren4">(<span class="code">new UI <span class="keyword">:start</span></span>)</span></span>]</span> nil</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>Now <code>new-game</code> does even less than before. It just sets up a game object with
|
||
|
the initial UI and <code>nil</code> world/input.</p>
|
||
|
|
||
|
<p>Okay, on to the last piece: drawing the world. This is some pretty dense code,
|
||
|
but don't be scared — I'll guide you through it and we'll make it together:</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 <span class="paren3">{<span class="code"><span class="paren4">{<span class="code"><span class="keyword">:keys</span> <span class="paren5">[<span class="code">tiles</span>]</span></span>}</span> <span class="keyword">:world</span> <span class="keyword">:as</span> game} screen</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">cols rows</span>]</span> screen-size
|
||
|
vcols cols
|
||
|
vrows <span class="paren5">(<span class="code">dec rows</span>)</span>
|
||
|
start-x 0
|
||
|
start-y 0
|
||
|
end-x <span class="paren5">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-y <span class="paren5">(<span class="code">+ start-y vrows</span>)</span></span>]</span>
|
||
|
<span class="paren4">(<span class="code">doseq <span class="paren5">[<span class="code"><span class="paren6">[<span class="code">vrow-idx mrow-idx</span>]</span> <span class="paren6">(<span class="code">map vector
|
||
|
<span class="paren1">(<span class="code">range 0 vrows</span>)</span>
|
||
|
<span class="paren1">(<span class="code">range start-y end-y</span>)</span></span>)</span>
|
||
|
<span class="keyword">:let</span> <span class="paren6">[<span class="code">row-tiles <span class="paren1">(<span class="code">subvec <span class="paren2">(<span class="code">tiles mrow-idx</span>)</span> start-x end-x</span>)</span></span>]</span></span>]</span>
|
||
|
<span class="paren5">(<span class="code">doseq <span class="paren6">[<span class="code">vcol-idx <span class="paren1">(<span class="code">range vcols</span>)</span>
|
||
|
<span class="keyword">:let</span> <span class="paren1">[<span class="code"><span class="paren2">{<span class="code"><span class="keyword">:keys</span> <span class="paren3">[<span class="code">glyph color</span>]</span></span>}</span> <span class="paren2">(<span class="code">row-tiles vcol-idx</span>)</span></span>]</span></span>]</span>
|
||
|
<span class="paren6">(<span class="code">s/put-string screen vcol-idx vrow-idx glyph <span class="paren1">{<span class="code"><span class="keyword">:fg</span> color}</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></span></span></span></span></code></pre>
|
||
|
|
||
|
<p>Let's look at this beast line-by-line. First we pull the tile vector out of the
|
||
|
game object with Clojure's destructuring:</p>
|
||
|
|
||
|
<pre><code><span class="code">{<span class="paren1">{<span class="code"><span class="keyword">:keys</span> <span class="paren2">[<span class="code">tiles</span>]</span></span>}</span> :world :as game}</span></code></pre>
|
||
|
|
||
|
<p>This seems a bit hard to read to me, so I might move it into the <code>let</code> statement
|
||
|
with a <code>get-in</code> call later. Maybe.</p>
|
||
|
|
||
|
<p>Next I bind a bunch of symbols I'll be using:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren2">[<span class="code"><span class="paren3">[<span class="code">cols rows</span>]</span> screen-size
|
||
|
vcols cols
|
||
|
vrows <span class="paren3">(<span class="code">dec rows</span>)</span>
|
||
|
start-x 0
|
||
|
start-y 0
|
||
|
end-x <span class="paren3">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-y <span class="paren3">(<span class="code">+ start-y vrows</span>)</span></span>]</span>
|
||
|
,,,</span>)</span></span></code></pre>
|
||
|
|
||
|
<p><code>cols</code> and <code>rows</code> are the dimensions of the screen (hardcoded at 80 by 24 for
|
||
|
the moment).</p>
|
||
|
|
||
|
<p><code>vcols</code> and <code>vrows</code> are the "viewport columns" and "viewport rows". The
|
||
|
"viewport" is what I'm calling the part of the screen that's actually showing
|
||
|
the world. I'm reserving one row at the bottom of the screen to use for
|
||
|
displaying the player's hit points, score, and so on. It would be trivial to
|
||
|
increase that to two rows if I need more later.</p>
|
||
|
|
||
|
<p><code>start-x</code> and <code>start-y</code> are the coordinates of the upper-left corner of the
|
||
|
viewport in the full map, and <code>end-x</code> and <code>end-y</code> are the coordinates of the
|
||
|
lower-right corner. For now I'm just displaying the upper-left section of the
|
||
|
map. In the entry after the next one I'll add the ability to scroll around.</p>
|
||
|
|
||
|
<p>It's easier to explain with a diagram. Imagine I reduced the size of the world
|
||
|
to 10 by 10 and the terminal to 5 by 3, and the user was standing near the
|
||
|
middle of the map:</p>
|
||
|
|
||
|
<pre><code> columns (x)
|
||
|
0123456789
|
||
|
rows 0..........
|
||
|
(y) 1..........
|
||
|
2..........
|
||
|
3..VVVVV...
|
||
|
4..VVVVV...
|
||
|
5..VVVVV...
|
||
|
6..........
|
||
|
7..........
|
||
|
8..........
|
||
|
9..........</code></pre>
|
||
|
|
||
|
<p>Here <code>V</code> represents the portion of the map the viewport can show (which is what
|
||
|
we'll be drawing to the user's terminal). In this example <code>start-x</code> would
|
||
|
be <code>2</code>, <code>start-y</code> would be <code>3</code>, <code>end-x</code> would be <code>6</code>, and <code>end-y</code> would be <code>5</code>.</p>
|
||
|
|
||
|
<p>Okay, so I've calculated the part of the map that needs to be drawn. Now
|
||
|
I loop through the rows:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">doseq <span class="paren2">[<span class="code"><span class="paren3">[<span class="code">vrow-idx mrow-idx</span>]</span> <span class="paren3">(<span class="code">map vector
|
||
|
<span class="paren4">(<span class="code">range 0 vrows</span>)</span>
|
||
|
<span class="paren4">(<span class="code">range start-y end-y</span>)</span></span>)</span>
|
||
|
<span class="keyword">:let</span> <span class="paren3">[<span class="code">row-tiles <span class="paren4">(<span class="code">subvec <span class="paren5">(<span class="code">tiles mrow-idx</span>)</span> start-x end-x</span>)</span></span>]</span></span>]</span>
|
||
|
,,,</span>)</span></span></code></pre>
|
||
|
|
||
|
<p>This is a bit obtuse, but basically the <code>map</code> call pairs up the viewport row and
|
||
|
map row indices. In our example it would result in this:</p>
|
||
|
|
||
|
<pre><code><span class="code">[<span class="paren1">[<span class="code">0 3</span>]</span>
|
||
|
<span class="paren1">[<span class="code">1 4</span>]</span>
|
||
|
<span class="paren1">[<span class="code">2 5</span>]</span>]</span></code></pre>
|
||
|
|
||
|
<p>So viewport row <code>1</code> corresponds to map row <code>4</code>. There's probably a less
|
||
|
"clever" way to do this that I should use instead.</p>
|
||
|
|
||
|
<p>For each row, I grab the tiles we're going to draw and store them in a vector
|
||
|
called <code>row-tiles</code> by grabbing the row of tiles from the world and taking
|
||
|
a slice of it.</p>
|
||
|
|
||
|
<p>Almost there! Next I loop through each column in the row:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">doseq <span class="paren2">[<span class="code">vcol-idx <span class="paren3">(<span class="code">range vcols</span>)</span>
|
||
|
<span class="keyword">:let</span> <span class="paren3">[<span class="code"><span class="paren4">{<span class="code"><span class="keyword">:keys</span> <span class="paren5">[<span class="code">glyph color</span>]</span></span>}</span> <span class="paren4">(<span class="code">row-tiles vcol-idx</span>)</span></span>]</span></span>]</span>
|
||
|
,,,</span>)</span></span></code></pre>
|
||
|
|
||
|
<p>For each column I grab the appropriate tile and figure out what glyph and color
|
||
|
to draw (remember the definition of <code>Tile</code>: <code>(defrecord Tile [kind glyph
|
||
|
color])</code>).</p>
|
||
|
|
||
|
<p>Finally I can actually draw the tile to the screen at the appropriate place:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code">s/put-string screen vcol-idx vrow-idx glyph <span class="paren2">{<span class="code"><span class="keyword">:fg</span> color}</span>)</span></span></span></span></code></pre>
|
||
|
|
||
|
<p>Whew! If that seemed painful and fiddly to you, trust me, I agree. I'm open to
|
||
|
suggestions on making it easier to read.</p>
|
||
|
|
||
|
<h2 id="s5-results"><a href="index.html#s5-results">Results</a></h2>
|
||
|
|
||
|
<p>Now that the <code>:play</code> UI knows how to draw itself and process its input, and is
|
||
|
properly hooked up by the <code>:start</code> UI, it's time to give it a shot!</p>
|
||
|
|
||
|
<p><img src="../../../../static/images/blog/2012/07/caves-03-1-01.png" alt="Screenshot"></p>
|
||
|
|
||
|
<p><img src="../../../../static/images/blog/2012/07/caves-03-1-02.png" alt="Screenshot"></p>
|
||
|
|
||
|
<p><img src="../../../../static/images/blog/2012/07/caves-03-1-03.png" alt="Screenshot"></p>
|
||
|
|
||
|
<p>Each time we start we get a different random world. Great!</p>
|
||
|
|
||
|
<p>The code is getting a bit big to include in its entirety, but you can view it
|
||
|
<a href="https://github.com/sjl/caves/tree/entry-03-1/src/caves">on GitHub</a>.</p>
|
||
|
|
||
|
<p>That covers the first part of Trystan's third post. Aside from the painful
|
||
|
<code>draw-ui</code> function it was pretty easy to add. In the next post I'll add the
|
||
|
smoothing code to make the caves look a bit nicer.</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>
|