Alchemy Day 1: A room with a view

Posted: March 12, 2013 in Uncategorized

I’m entering this year’s 7 day Roguelike challenge using Clojure. It’s a fun challenge to write a complete game in just 7 days and hopefully I will learn some new Clojure tricks doing so.

My game is “Alchemy”, where you play as a brave young Alchemist who must descend into a dangerous dungeon to obtain the Philosopher’s Stone. Roguelikes, as you can see, are characterised (among other things) by the level of originality in their story lines.

Here’s my progress report from day 1

Alchemy first map

Project setup

First job was to set up the project. If you want to be productive, it’s critical to have your environment and tools set up well.

  • Repository on Github: Alchemy
  • Set up Maven to allow building a “jar-with-dependencies” so I can distribute Alchemy as a single bundle. Good tactic to set that up and test it now, so I don’t waste time fiddling with it at the end.
  • Pulling in all the dependencies I think I’m likely to need
  • Setting up the project structure and test suite
  • Creating a main class in Java . I’m using my handy clojure-utils library here to provide effective launching of Clojure code.

With these formalities out of the way, on to the actual development!

Data structures

My first focus was to get the main game data structure in place. This had a few requirements:

  •  Immutability: I’m writing the game in pure functional style, so the game state needs to be fully immutable.
  • 3D grid: The game world requires a grid of (x,y,z) cubes to store both the levels of the dungeon and objects in it. Luckily I have a ready-made persistent 3D grid data structure, which probably saves me a day’s work in itself….
  • Indexing: games often need indexing in their data structures to ensure reasonable performance, so the game data structure needs to be able to accomodate these (and update them as needed)
  • Flexibility for storing extra data. In a 7 day exercise you don’t want to be spending too much time on refactoring. Fortunately having the game data structure defined using defrecord helps enormously here, as it enables you to attach arbitrary extra data with map-like semantics to the game state.

Fortunately I had some library code that was pretty close to what I wanted (orculje) so it was mostly a matter of hacking this into something usable.

Here’s the defrecord for the main game data structure

(defrecord Game [^PersistentTreeGrid world  ;; grid of tiles
                 ^PersistentTreeGrid things ;; grid of things
                  thing-map                 ;; map of id -> thing
 ])

Pretty straightforward: we have the tiles that make up the world (a 3D grid) the things in the world (a 3D grid of vectors where the vector contains the things in each grid position) and a map of {thing ID -> thing}

“Things” themselves will start out as general purpose records. These will hold a map of properties that will define the behaviour of each thing in the world:

(defrecord Thing [])

Previous game development experience has taught me that it’s good to write your data structures a abstractly as possible, hence the use of “Thing” in the abstract to refer to anything that may exist on a map. We’ll define the specific types of thing later.

I also defined a “Location” type. This is going to be very useful throughout the game logic. It needs to be fast and lightweight, so I defined it using deftype with primitive int field values. deftype also allows me to make Location implement a number of useful protocols / interfaces (ILookup, Indexed, Object etc.)

(deftype Location [^int x ^int y ^int z]
   ;; lots of interface method implementations follow....
   )

Alongside this I created a few helpful utility functions and tests (add-thing, move-thing, remove-thing etc.).

Library generation

I decided to adopt an approach I used in Tyrant and create a “Library” of game objects. The library defines “prototypes” for every kind of game object, so that it is easy to generate new instances of any type with the correct properties.

I used a bit of macro magic to create a nice syntax for building the library:

    (proclaim "base wall" "base tile"
              {:is-blocking true
               :colour-fg (colour 0x808080)
               :colour-bg (colour 0x808000)
               :char (char 0x2591)
               :z-order 50})

The proclaim macro above defines a “base wall” thing to inherit all the properties of a “base tile” and adds the additional properties listed.

This creates an pseudo-inheritance hierarchy, but one that is constructed at runtime and is prototype-based rather than fixed at compile time and class-based. This gives us a set of big advantages:

  • It’s all pure data. You can write functions to process / extend the library as a data structure
  • You can have features like multiple inheritance and mixins easily, just by merging in the right set of properties
  • It’s really easy to add / edit new object types. It is literally as simple as adding a proclaim statement in the right place. This is going to help us under the time limit!

Map display

Penultimate task for day one was to actually get something visible. My rationale for doing this as early as possible is because having visual feedback is absolutely essential for rapid iteration of game features. At the same time, it’s valuable to write the data structures first so that you know exactly what you are displaying.

I used my swing-console library to provide the main display component which (in typical roguelike style) is a grid of ASCII characters.

Here’s the main display code as it currently stands:

(defn displayable-thing
  "Gets the thing that should be displayed in a given square
   (i.e. highest z-order of all visible things)"
  [game ^long x ^long y ^long z]
  (let [t (or (get-tile game x y z) world/BLANK_TILE)]
    (loop [z-order (long -100)
           ct t
           ts (seq (get-things game x y z))]
      (if ts
        (let [nt (first ts)
              nz (long (:z-order nt 0))]
          (if (and (> nz z-order) (:is-visible nt))
            (recur nz nt (next ts))
            (recur z-order ct (next ts))))
        ct))))

(defn redraw-world
  "Redraws the game world playing area"
  ([state]
	  (let [^JConsole jc (:console state)
	        game @(:game state)
                hero (world/hero game)
                ^mikera.orculje.engine.Location hloc (:location hero)
	        w (.getColumns jc)
	        h (.getRows jc)
	        gw (- w 20)
	        gh (- h MESSAGE_WINDOW_HEIGHT)
         ox (long (- (.x hloc) (quot gw 2)))
         oy (long (- (.y hloc) (quot gh 2)))
         oz (long (.z hloc))]
	    (dotimes [y gh]
	      (dotimes [x gw]
	        (let [t (displayable-thing game (+ ox x) (+ oy y) 0)]
	          (.setForeground jc ^Color (:colour-fg t))
	          (.setBackground jc ^Color (:colour-bg t))
	          (gui/draw jc x y (char (:char t))))))
	    (.repaint jc))))

A couple of things of interest to note here:

  • The display code itself is very concise! Ignoring the parameter setup, it’s just a (x,y) loop that draws a character with the correct forground and background colour
  • The magic is in the displayable-thing function that queries a grid square to determine what Thing should be displayed. I defined a :z-order property so that it is easy to control what Things appear on top of others.

Creating the map

Full dungeon generation will come later, but I wanted to create a basic room so that I could see what is happening with the map and debug my display. Here is the first map gen function:

(defn generate
  "Main dungeon generation algorithm"
  [game]
  (as-> game game
    (mm/fill-block game (loc -4 -4 0) (loc 4 4 0) (lib/create game "wall"))
    (mm/fill-block game (loc -3 -3 0) (loc 3 3 0) (lib/create game "floor"))))

This is just a square room with a solid wall around it. Note that I’m using the “Location” objects I defined earlier as parameters.

I also got to use Clojure 1.5’s new as-> threading macro. I think I like it – it has the effect of rebinding game to the updated game value after each step, which is very useful when you want to perform a sequence of operations that update an immutable data structure each time.

Note that the generate function takes a Game as input and returns an updated Game with the dungeon generated inside it. Yes, this is a purely functional dungeon generator.

Walking around the map

Finally, I implemented keypress handling and the ability to walk a hero character “@” around the map. Nothing particularly fancy here – just a bit of fiddly code linking the Swing key binding functionality to call the right event / action handlers in my code. I’m a big believer in separating the GUI (View) from the game world (Model) so glue code is unavoidable here.

The good news is that I can now walk a little @ around the map.

I feel like that is pretty good progress for day one: not much game content developed, but I’ve invested the time in the game foundations which paves the way to building all the fun stuff over the next few days.

Advertisements
Comments
  1. logaan says:

    I’ve recently started implementing a roguelike in clojure as well (It’s also at the stage of an @ walking around a room). I’ve been using it as a pet project to test out some functional reactive programming ideas, though I’ve found simple function composition is suiting my needs so far. The code’s up at https://github.com/logaan/event-stream/tree/master/src/event_stack if you’re interested.

  2. Martin says:

    Thank you for putting together this series of blog posts. Is there a particular reason that you are using Maven and going through the hoops of adding a Java main, instead of using Leiningen and uberjar?

    • mikera7 says:

      Mostly just because it fits better with my toolchain preferences: I’m a big Eclipse user (since I also write a lot of Java), and I prefer the automatic IDE integration with Maven and Git rather than using various command line tools. The Counterclockwise plugin is excellent but the tool integration isn’t quite there yet, so I just use CCW for the REPL and editor features.

      I’d like to be able to auto-generate a project.clj when building as a convenience for lein users but haven’t found a good tool to do that yet…..

  3. Web Ranking says:

    Hello! I simply would like to give a huge thumbs up for the nice information you’ve got here on this post.
    I can be coming again to your weblog for more soon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s