364 lines
31 KiB
HTML
364 lines
31 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.3 / 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.3</a></h1><p class='date'>Posted on July 11th, 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-3</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-crosshairs">Crosshairs</a></li><li><a href="index.html#s4-scrolling">Scrolling</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>When the last post left off I had a random world generated and smoothed to
|
||
|
create some nice looking caves. The world was displayed on the screen, but it
|
||
|
would only display the upper left corner of the map.</p>
|
||
|
|
||
|
<p>This post is going to be about scrolling the viewport so we can view the entire
|
||
|
map. It's the last remaining piece of Trystan's third post that I still need to
|
||
|
implement.</p>
|
||
|
|
||
|
<h2 id="s2-refactoring"><a href="index.html#s2-refactoring">Refactoring</a></h2>
|
||
|
|
||
|
<p>This is going to involve changing the worst function in the code so far
|
||
|
(<code>draw-ui</code> for <code>:player</code> UIs), so before I start hacking away I want to factor
|
||
|
out a bit of functionality to clean things up.</p>
|
||
|
|
||
|
<p>Right now that <code>draw-ui</code> function in <code>core.clj</code> looks like this:</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>I pulled out the guts of that function into a helper function:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> draw-world <span class="paren2">[<span class="code">screen vrows vcols start-x start-y end-x end-y tiles</span>]</span>
|
||
|
<span class="paren2">(<span class="code">doseq <span class="paren3">[<span class="code"><span class="paren4">[<span class="code">vrow-idx mrow-idx</span>]</span> <span class="paren4">(<span class="code">map vector
|
||
|
<span class="paren5">(<span class="code">range 0 vrows</span>)</span>
|
||
|
<span class="paren5">(<span class="code">range start-y end-y</span>)</span></span>)</span>
|
||
|
<span class="keyword">:let</span> <span class="paren4">[<span class="code">row-tiles <span class="paren5">(<span class="code">subvec <span class="paren6">(<span class="code">tiles mrow-idx</span>)</span> start-x end-x</span>)</span></span>]</span></span>]</span>
|
||
|
<span class="paren3">(<span class="code">doseq <span class="paren4">[<span class="code">vcol-idx <span class="paren5">(<span class="code">range vcols</span>)</span>
|
||
|
<span class="keyword">:let</span> <span class="paren5">[<span class="code"><span class="paren6">{<span class="code"><span class="keyword">:keys</span> <span class="paren1">[<span class="code">glyph color</span>]</span></span>}</span> <span class="paren6">(<span class="code">row-tiles vcol-idx</span>)</span></span>]</span></span>]</span>
|
||
|
<span class="paren4">(<span class="code">s/put-string screen vcol-idx vrow-idx glyph <span class="paren5">{<span class="code"><span class="keyword">:fg</span> color}</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">defmethod</span></i> draw-ui <span class="keyword">:play</span> <span class="paren3">[<span class="code">ui <span class="paren4">{<span class="code"><span class="paren5">{<span class="code"><span class="keyword">:keys</span> <span class="paren6">[<span class="code">tiles</span>]</span></span>}</span> <span class="keyword">:world</span> <span class="keyword">:as</span> game} screen</span>]</span>
|
||
|
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">[<span class="code"><span class="paren6">[<span class="code">cols rows</span>]</span> screen-size
|
||
|
vcols cols
|
||
|
vrows <span class="paren6">(<span class="code">dec rows</span>)</span>
|
||
|
start-x 0
|
||
|
start-y 0
|
||
|
end-x <span class="paren6">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-y <span class="paren6">(<span class="code">+ start-y vrows</span>)</span></span>]</span>
|
||
|
<span class="paren5">(<span class="code">draw-world screen vrows vcols start-x start-y end-x end-y tiles</span>)</span></span>)</span></span>)</span></span></span></span></span></span></code></pre>
|
||
|
|
||
|
<p>No functionality has changed, I just pulled the body out into its own function.
|
||
|
This will make things cleaner as we add more functionality.</p>
|
||
|
|
||
|
<p>As I mentioned in the last post, I don't like the distructuring in the argument
|
||
|
list here. Let's remove that:</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>
|
||
|
tiles <span class="paren4">(<span class="code"><span class="keyword">:tiles</span> world</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>
|
||
|
start-x 0
|
||
|
start-y 0
|
||
|
end-x <span class="paren4">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-y <span class="paren4">(<span class="code">+ start-y 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>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>It's a few more lines of code but I find it more readable. If you prefer the
|
||
|
more concise syntax feel free to use the destructuring — it's not really that
|
||
|
important either way.</p>
|
||
|
|
||
|
<h2 id="s3-crosshairs"><a href="index.html#s3-crosshairs">Crosshairs</a></h2>
|
||
|
|
||
|
<p>Trystan draws an <code>X</code> as a kind of crosshair to take the place of the traditional
|
||
|
roguelike <code>@</code> (since there's no player yet), so let's do that. I made
|
||
|
a separate function to draw the crosshair as a red <code>X</code> in the center of the
|
||
|
screen:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> draw-crosshairs <span class="paren2">[<span class="code">screen vcols vrows</span>]</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">crosshair-x <span class="paren4">(<span class="code">int <span class="paren5">(<span class="code">/ vcols 2</span>)</span></span>)</span>
|
||
|
crosshair-y <span class="paren4">(<span class="code">int <span class="paren5">(<span class="code">/ vrows 2</span>)</span></span>)</span></span>]</span>
|
||
|
<span class="paren3">(<span class="code">s/put-string screen crosshair-x crosshair-y <span class="string">"X"</span> <span class="paren4">{<span class="code"><span class="keyword">:fg</span> <span class="keyword">:red}</span></span>)</span>
|
||
|
<span class="paren4">(<span class="code">s/move-cursor screen crosshair-x crosshair-y</span>)</span></span>)</span></span>)</span></span></span></span></code></pre>
|
||
|
|
||
|
<p>This function seems pretty straightforward. It finds the x and y coordinates of
|
||
|
the viewport where the <code>X</code> should go and puts it there. It also moves the
|
||
|
cursor on top of it because I like how that looks.</p>
|
||
|
|
||
|
<p>Yeah, it might not actually end up in the exact center of the screen because the
|
||
|
<code>int</code> will truncate if we've got an odd number of rows or columns. Honestly,
|
||
|
I'm going to be throwing away this crosshair once we've got a player on the
|
||
|
screen, so it's not worth fixing.</p>
|
||
|
|
||
|
<p>I need to call <code>draw-crosshairs</code> that in the <code>:play</code> UI-drawing function:</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>
|
||
|
tiles <span class="paren4">(<span class="code"><span class="keyword">:tiles</span> world</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>
|
||
|
start-x 0
|
||
|
start-y 0
|
||
|
end-x <span class="paren4">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-y <span class="paren4">(<span class="code">+ start-y 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-crosshairs screen vcols vrows</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>The only change here is the <code>(draw-crosshairs screen vcols vrows)</code> after I draw
|
||
|
the world. This draws the crosshair <code>X</code> on top of the world, which isn't an
|
||
|
issue because Lanterna's double buffering will ensure that the user never sees
|
||
|
an intermediate render that's missing the <code>X</code>.</p>
|
||
|
|
||
|
<p>Now there's a red <code>X</code> in the center of the screen. Great, but we still need to
|
||
|
add the main point of this post: scrolling.</p>
|
||
|
|
||
|
<h2 id="s4-scrolling"><a href="index.html#s4-scrolling">Scrolling</a></h2>
|
||
|
|
||
|
<p>Right now the <code>start-x</code> and <code>start-y</code> in the <code>draw-ui</code> function are hardcoded at
|
||
|
<code>0</code>. All I need to do is change those to modify which part of the map the
|
||
|
viewport draws, and I'll have scrolling!</p>
|
||
|
|
||
|
<p>First of all, I need a way to keep track of where the viewport should be
|
||
|
centered. This will get thrown away once we have a player (the player will be
|
||
|
the center of the viewport), so I'll just slap it right in the <code>game</code> object for
|
||
|
now:</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">assoc <span class="paren3">(<span class="code">new Game nil <span class="paren4">[<span class="code"><span class="paren5">(<span class="code">new UI <span class="keyword">:start</span></span>)</span></span>]</span> nil</span>)</span>
|
||
|
<span class="keyword">:location</span> <span class="paren3">[<span class="code">40 20</span>]</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>The <code>new-game</code> function now <code>assoc</code>s a <code>:location</code> into the <code>game</code> before
|
||
|
returning it.</p>
|
||
|
|
||
|
<p>I <em>could</em> have modified the <code>(defrecord Game [world uis input])</code> to add the
|
||
|
location as a proper field. But I know I'm going to be removing this soon
|
||
|
anyway, so I may as well take advantage of the fact that Clojure's record can
|
||
|
have extra fields <code>assoc</code>ed onto them on the fly.</p>
|
||
|
|
||
|
<p><code>[40 20]</code> is an arbitrary location. It's kind of in the middleish area of the
|
||
|
map. Good enough.</p>
|
||
|
|
||
|
<p>Okay, now I need to actually display the correct area of the map in the
|
||
|
viewport. I'm going to need to modify <code>draw-ui</code> again, which, just as
|
||
|
a reminder, looks like this:</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>
|
||
|
tiles <span class="paren4">(<span class="code"><span class="keyword">:tiles</span> world</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>
|
||
|
start-x 0
|
||
|
start-y 0
|
||
|
end-x <span class="paren4">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-y <span class="paren4">(<span class="code">+ start-y 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-crosshairs screen vcols vrows</span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>I had a feeling this is going to get a bit gross, so I pulled out the code for
|
||
|
getting the viewport coordinates into its own helper function:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-viewport-coords <span class="paren2">[<span class="code">game vcols vrows</span>]</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">start-x 0
|
||
|
start-y 0
|
||
|
end-x <span class="paren4">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-y <span class="paren4">(<span class="code">+ start-y vrows</span>)</span></span>]</span></span>]</span>
|
||
|
<span class="paren2">[<span class="code">start-x start-y end-x end-y</span>]</span></span>)</span>)
|
||
|
|
||
|
<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>
|
||
|
tiles <span class="paren4">(<span class="code"><span class="keyword">:tiles</span> world</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 vcols vrows</span>)</span></span>]</span></span>]</span>
|
||
|
<span class="paren2">(<span class="code">draw-world screen vrows vcols start-x start-y end-x end-y tiles</span>)</span>
|
||
|
<span class="paren2">(<span class="code">draw-crosshairs screen vcols vrows</span>)</span></span>)</span>)</span></code></pre>
|
||
|
|
||
|
<p>No functionality changed, I just shuffled a bit of code out of that ugly
|
||
|
<code>draw-ui</code> function. As a bonus, the <code>get-viewport-coords</code> function is now pure.
|
||
|
It'll be easy to add unit tests for it later if I want. Cool.</p>
|
||
|
|
||
|
<p>Now that the viewport coordinates are isolated, it's time to calculate them
|
||
|
correctly instead of hardcoding them at <code>0</code>:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> get-viewport-coords <span class="paren2">[<span class="code">game vcols vrows</span>]</span>
|
||
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">location <span class="paren4">(<span class="code"><span class="keyword">:location</span> game</span>)</span>
|
||
|
<span class="paren4">[<span class="code">center-x center-y</span>]</span> location
|
||
|
|
||
|
tiles <span class="paren4">(<span class="code"><span class="keyword">:tiles</span> <span class="paren5">(<span class="code"><span class="keyword">:world</span> game</span>)</span></span>)</span>
|
||
|
|
||
|
map-rows <span class="paren4">(<span class="code">count tiles</span>)</span>
|
||
|
map-cols <span class="paren4">(<span class="code">count <span class="paren5">(<span class="code">first tiles</span>)</span></span>)</span>
|
||
|
|
||
|
start-x <span class="paren4">(<span class="code">max 0 <span class="paren5">(<span class="code">- center-x <span class="paren6">(<span class="code">int <span class="paren1">(<span class="code">/ vcols 2</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
start-y <span class="paren4">(<span class="code">max 0 <span class="paren5">(<span class="code">- center-y <span class="paren6">(<span class="code">int <span class="paren1">(<span class="code">/ vrows 2</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
|
||
|
end-x <span class="paren4">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-x <span class="paren4">(<span class="code">min end-x map-cols</span>)</span>
|
||
|
|
||
|
end-y <span class="paren4">(<span class="code">+ start-y vrows</span>)</span>
|
||
|
end-y <span class="paren4">(<span class="code">min end-y map-rows</span>)</span>
|
||
|
|
||
|
start-x <span class="paren4">(<span class="code">- end-x vcols</span>)</span>
|
||
|
start-y <span class="paren4">(<span class="code">- end-y vrows</span>)</span></span>]</span>
|
||
|
<span class="paren3">[<span class="code">start-x start-y end-x end-y</span>]</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>This is long, but very straightforward. I use the fact that <code>let</code> doesn't care
|
||
|
if you rebind variables many times in the same binding vector to write
|
||
|
imperative code. There may be a more "clever" way to do this, but I like the
|
||
|
clarity.</p>
|
||
|
|
||
|
<p>First it finds the location of the crosshair (which will be <code>[40 20]</code> from
|
||
|
<code>new-game</code> at the moment). It calls that <code>center-x</code> and <code>center-y</code>.</p>
|
||
|
|
||
|
<p>It also pulls the tile vector out of the <code>game</code> object and uses it to
|
||
|
determine the full dimensions of the map. I'm thinking of having a <code>map-size</code>
|
||
|
constant somewhere instead of doing it this way. I may do that in a later post.</p>
|
||
|
|
||
|
<p>Next come these scary lines:</p>
|
||
|
|
||
|
<pre><code><span class="code">start-x <span class="paren1">(<span class="code">max 0 <span class="paren2">(<span class="code">- center-x <span class="paren3">(<span class="code">int <span class="paren4">(<span class="code">/ vcols 2</span>)</span></span>)</span></span>)</span></span>)</span>
|
||
|
start-y <span class="paren1">(<span class="code">max 0 <span class="paren2">(<span class="code">- center-y <span class="paren3">(<span class="code">int <span class="paren4">(<span class="code">/ vrows 2</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>They're not as scary as they look. Both are exactly the same except for which
|
||
|
dimension they're working on. First I subtract half the viewport size from the
|
||
|
center coordinate. This should give me either the topmost or leftmost
|
||
|
coordinate we're going to be drawing.</p>
|
||
|
|
||
|
<p>Then I use <code>max</code> to make sure that if the starting coordinate would be less than
|
||
|
zero (i.e.: off of the map) I just use 0 instead.</p>
|
||
|
|
||
|
<p>Okay, so now I've got the coordinates of the top left point I need to draw, and
|
||
|
I'm sure that it doesn't fall off the top or left edge of the map. Cool. Time
|
||
|
to get the bottom right coordinate.</p>
|
||
|
|
||
|
<pre><code><span class="code">end-x <span class="paren1">(<span class="code">+ start-x vcols</span>)</span>
|
||
|
end-x <span class="paren1">(<span class="code">min end-x map-cols</span>)</span>
|
||
|
|
||
|
end-y <span class="paren1">(<span class="code">+ start-y vrows</span>)</span>
|
||
|
end-y <span class="paren1">(<span class="code">min end-y map-rows</span>)</span></span></code></pre>
|
||
|
|
||
|
<p>This is similar to how we get the starting coordinates. We calculate a "naive
|
||
|
end x" by adding the viewport size to the start, and then make sure the end
|
||
|
doesn't fall off the map.</p>
|
||
|
|
||
|
<p>I did this all in one line for the start coordinates, but split it into two for
|
||
|
the end coordinates. I'm not sure why I did it like that — I just noticed it
|
||
|
now. I'm going to go ahead and change the start to be the expanded, two-line
|
||
|
form. I think it's clearer.</p>
|
||
|
|
||
|
<p>Okay, so now I've ensured that the end coordinate doesn't fall off the map. I'm
|
||
|
done, right?</p>
|
||
|
|
||
|
<p>Well, not quite. If I truncated the end coordinate here I'll have ended up with
|
||
|
a smaller-than-normal viewport. To fix that I'll reset the start coordinates
|
||
|
one more time:</p>
|
||
|
|
||
|
<pre><code><span class="code">start-x <span class="paren1">(<span class="code">- end-x vcols</span>)</span>
|
||
|
start-y <span class="paren1">(<span class="code">- end-y vrows</span>)</span></span></code></pre>
|
||
|
|
||
|
<p>This time I don't need to check any bounds. I know the end coordinate is good
|
||
|
because it was based on a known start coordinate (the top/left side is good) and
|
||
|
I corrected the bottom/right side. So I simply use this known-good end
|
||
|
coordinate to get a known-good start coordinate and I'm done.</p>
|
||
|
|
||
|
<p>If the map is smaller than the viewport size this is probably going to explode.
|
||
|
I'm going to ignore that for now. I may revisit it later, or I may just stick
|
||
|
with an 80 by 24 viewport for all time like Nethack.</p>
|
||
|
|
||
|
<p>That was a lot of work, but the only thing that's changed is I'm now displaying
|
||
|
a section of the map near the middle instead of at the upper left. The last
|
||
|
piece is to add the ability to adjust the <code>:location</code> in the <code>game</code> object on
|
||
|
the fly.</p>
|
||
|
|
||
|
<p>The player should be able to scroll around when they're at the <code>:play</code> UI, so
|
||
|
let's add the appropriate input handling:</p>
|
||
|
|
||
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> move <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">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>
|
||
|
\q <span class="paren3">(<span class="code">assoc game <span class="keyword">:uis</span> <span class="paren4">[<span class="code"></span>]</span></span>)</span>
|
||
|
|
||
|
\s <span class="paren3">(<span class="code">assoc game <span class="keyword">:world</span> <span class="paren4">(<span class="code">smooth-world <span class="paren5">(<span class="code"><span class="keyword">:world</span> game</span>)</span></span>)</span></span>)</span>
|
||
|
|
||
|
\h <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">-1 0</span>]</span></span>)</span>
|
||
|
\j <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">0 1</span>]</span></span>)</span>
|
||
|
\k <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">0 -1</span>]</span></span>)</span>
|
||
|
\l <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">1 0</span>]</span></span>)</span>
|
||
|
|
||
|
\H <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">-5 0</span>]</span></span>)</span>
|
||
|
\J <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">0 5</span>]</span></span>)</span>
|
||
|
\K <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">0 -5</span>]</span></span>)</span>
|
||
|
\L <span class="paren3">(<span class="code">update-in game <span class="paren4">[<span class="code"><span class="keyword">:location</span></span>]</span> move <span class="paren4">[<span class="code">5 0</span>]</span></span>)</span>
|
||
|
|
||
|
game</span>)</span></span>)</span></span></code></pre>
|
||
|
|
||
|
<p>I did a few things here. First I added the <code>q</code> key mapping to quit the game
|
||
|
without going through the win or lose screens, just to same myself some time.
|
||
|
Enter and backspace still win and lose the game respectively.</p>
|
||
|
|
||
|
<p><code>s</code> still smooths the world map for now. No reason to remove that yet.</p>
|
||
|
|
||
|
<p>To handle the movement inputs I first made a <code>move</code> helper function which takes
|
||
|
a coordinate and an amount to move by and returns the new coordinate.</p>
|
||
|
|
||
|
<p>The <code>process-input</code> function uses this to get the new coordiate when it gets an
|
||
|
<code>h</code>, <code>j</code>, <code>k</code>, or <code>l</code> keypress. I also added the shifted versions of the
|
||
|
letters as "fast movement" keys for convenience.</p>
|
||
|
|
||
|
<p>Right now there's no bounds checking here, so it's possible for your <code>:location</code>
|
||
|
to get scrolled off the edge of the map. This won't be a problem for the
|
||
|
display (it will just snap the viewport to the edge of the map), but will make
|
||
|
the input a bit weird.</p>
|
||
|
|
||
|
<p>For example, if you scroll to the right edge of the map and press right 10 more
|
||
|
times, you'll need to press left 10 times before it will actually start
|
||
|
scrolling left again.</p>
|
||
|
|
||
|
<p>This is a bug, but not one I care to fix right now. I'll be replacing this code
|
||
|
with player-based code soon enough, so it's just going to get thrown out anyway.</p>
|
||
|
|
||
|
<h2 id="s5-results"><a href="index.html#s5-results">Results</a></h2>
|
||
|
|
||
|
<p>That's it! Running the game, I can now scroll around the map and/or smooth it
|
||
|
whenever I like:</p>
|
||
|
|
||
|
<p><img src="../../../../static/images/blog/2012/07/caves-03-3-01.png" alt="Screenshot"></p>
|
||
|
|
||
|
<p><img src="../../../../static/images/blog/2012/07/caves-03-3-02.png" alt="Screenshot"></p>
|
||
|
|
||
|
<p>This doesn't look much different in pictures, but I can scroll through the world
|
||
|
with <code>hjkl</code>. Here's a screencast showing what that looks like:
|
||
|
<a href="http://www.screenr.com/T1k8">http://www.screenr.com/T1k8</a></p>
|
||
|
|
||
|
<p>As always, you can view the code <a href="https://github.com/sjl/caves/tree/entry-03-3/src/caves">on GitHub</a> if you want to see it
|
||
|
all at once.</p>
|
||
|
|
||
|
<p>That's it for Trystan's third post. Next time I'll tackle his fourth (adding
|
||
|
an actual player).</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>
|