content/blog/2012/07/caves-of-clojure-03-3.html @ 764059869338
...
| author | Steve Losh <steve@stevelosh.com> |
|---|---|
| date | Sun, 08 Jul 2012 22:48:44 -0400 |
| parents | (none) |
| children | 488cc81bd71c |
{% 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 -------  {% endblock article %}