# HG changeset patch # User Steve Losh # Date 1341802124 14400 # Node ID 7640598693385d5a031fee67700f3d5d1bee8517 # Parent 6c523d39515b491e1329680f5af61a13549f7dbd ... diff -r 6c523d39515b -r 764059869338 content/blog/2012/07/caves-of-clojure-03-2.html --- a/content/blog/2012/07/caves-of-clojure-03-2.html Sun Jul 08 21:32:01 2012 -0400 +++ b/content/blog/2012/07/caves-of-clojure-03-2.html Sun Jul 08 22:48:44 2012 -0400 @@ -34,7 +34,9 @@ a roguelike. This post is going to show how I added Trystan's world smoothing to make -nicer-looking caves. Read his post and the link to learn the ideas behind it. +nicer-looking caves. He uses a [cellular automata-based world-smoothing +algorithm][ca-wiki] that I think is really cool, so I'm going to do pretty much +the same thing. Debugging --------- @@ -241,8 +243,8 @@ game)) Yes, it only took one line to add that. I simply replace the world with the -smooth world and return the resulting game. I don't need to touch the UI stack -because I want to remain at the play UI for subsequent commands. +smooth(er) world and return the resulting game. I don't need to touch the UI +stack because I want to remain at the play UI for subsequent commands. I'm really liking this immutable game structure so far! diff -r 6c523d39515b -r 764059869338 content/blog/2012/07/caves-of-clojure-03-3.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/blog/2012/07/caves-of-clojure-03-3.html Sun Jul 08 22:48:44 2012 -0400 @@ -0,0 +1,157 @@ + {% extends "_post.html" %} + + {% hyde + title: "The Caves of Clojure: Part 3.3" + snip: "Scrolling." + created: 2012-07-11 9:45:00 + flattr: true + %} + +{% block article %} + +This post is part of an ongoing series. If you haven't already done so, you +should probably start at [the beginning][]. + +This entry corresponds to [post three in Trystan's tutorial][trystan-tut]. + +If you want to follow along, the code for the series is [on Bitbucket][bb] and +[on GitHub][gh]. Update to the `entry-03-3` tag to see the code as it stands +after this post. + +[the beginning]: /blog/2012/07/caves-of-clojure-01/ +[trystan-tut]: http://trystans.blogspot.com/2011/08/roguelike-tutorial-03-scrolling-through.html +[bb]: http://bitbucket.org/sjl/caves/ +[gh]: http://github.com/sjl/caves/ + +[TOC] + +Summary +------- + +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. + +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. + +Refactoring +----------- + +This is going to involve changing the worst function in the code so far +(`draw-ui` for `:player` UIs), so before I start hacking away I want to factor +out a bit of functionality so things are a bit cleaner. + +Right now that `draw-ui` function in `core.clj` looks like this: + + :::clojure + (defmethod draw-ui :play [ui {{:keys [tiles]} :world :as game} screen] + (let [[cols rows] screen-size + vcols cols + vrows (dec rows) + start-x 0 + start-y 0 + end-x (+ start-x vcols) + end-y (+ start-y vrows)] + (doseq [[vrow-idx mrow-idx] (map vector + (range 0 vrows) + (range start-y end-y)) + :let [row-tiles (subvec (tiles mrow-idx) start-x end-x)]] + (doseq [vcol-idx (range vcols) + :let [{:keys [glyph color]} (row-tiles vcol-idx)]] + (s/put-string screen vcol-idx vrow-idx glyph {:fg color}))))) + +I decided to pull out the guts of that function into a helper function: + + :::clojure + (defn draw-world [screen vrows vcols start-x start-y end-x end-y tiles] + (doseq [[vrow-idx mrow-idx] (map vector + (range 0 vrows) + (range start-y end-y)) + :let [row-tiles (subvec (tiles mrow-idx) start-x end-x)]] + (doseq [vcol-idx (range vcols) + :let [{:keys [glyph color]} (row-tiles vcol-idx)]] + (s/put-string screen vcol-idx vrow-idx glyph {:fg color})))) + + (defmethod draw-ui :play [ui {{:keys [tiles]} :world :as game} screen] + (let [[cols rows] screen-size + vcols cols + vrows (dec rows) + start-x 0 + start-y 0 + end-x (+ start-x vcols) + end-y (+ start-y vrows)] + (draw-world screen vrows vcols start-x start-y end-x end-y tiles))) + +No functionality has changed, I just pulled the body out into its own function. +This will make things cleaner as we add more functionality. + +I also don't like the distructuring in the argument list here. Let's remove +that: + + :::clojure + (defmethod draw-ui :play [ui game screen] + (let [world (:world game) + tiles (:tiles world) + [cols rows] screen-size + vcols cols + vrows (dec rows) + start-x 0 + start-y 0 + end-x (+ start-x vcols) + end-y (+ start-y vrows)] + (draw-world screen vrows vcols start-x start-y end-x end-y tiles))) + +It's a few more lines of code but I find it more readable. If you prefer the +more consice syntax feel free to use the destructuring -- it's not really that +important either way. + +Crosshairs +---------- + +Trystan draws an `X` as a kind of crosshair to take the place of the traditional +roguelike `@` (since there's no player yet), so let's do that. I made +a separate function to draw the crosshair as a red `X` in the center of the +screen: + + :::clojure + (defn draw-crosshairs [screen vcols vrows] + (let [crosshair-x (int (/ vcols 2)) + crosshair-y (int (/ vrows 2))] + (s/put-string screen crosshair-x crosshair-y "X" {:fg :red}) + (s/move-cursor screen crosshair-x crosshair-y))) + +And I need to call that in the `:play` UI: + + :::clojure + (defmethod draw-ui :play [ui game screen] + (let [world (:world game) + tiles (:tiles world) + [cols rows] screen-size + vcols cols + vrows (dec rows) + start-x 0 + start-y 0 + end-x (+ start-x vcols) + end-y (+ start-y vrows)] + (draw-world screen vrows vcols start-x start-y end-x end-y tiles) + (draw-crosshairs screen vcols vrows))) + +The only change here is the `(draw-crosshairs screen vcols vrows)` after I draw +the world. This draws the crosshair `X` 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 `X`. + +Now there's a red `X` in the center of the screen. Great, but we still need to +add the main point of this post: scrolling. + +Scrolling +--------- + +Results +------- + +![Screenshot](/media/images{{ parent_url }}/caves-03-2-01.png) + +{% endblock article %}