Alchemy Day 7: Completion!

Posted: March 18, 2013 in Uncategorized

Final day of coding on Alchemy!

It was a bit of a scramble to bring everything together and tune the gameplay, but it all somehow worked out as a finished game in the end.

2013-03-17 Final 7DRL screenshot

You can download the final .jar file here:

And then run it in the usual way (either double click it, or do “java -jar alchemy.jar”). You need Java 1.6 or higher.

Overall I’m reasonably happy with how it ended up, but it’s not quite perfect:

  • The game is feature complete, in the sense that it is playable, there is a clear objective and you can win. 
  • You are likely to die. A lot. Alchemy is still probably too hard for casual players. Experienced roguelike players should be able to beat it once they figure out the tricks. I might create a more friendly version after the challenge – getting game balance right is really hard if you only have a few hours!
  • The game is reasonably fun to play, with a feel quite similar to the original “Rogue”, which is roughly what I was aiming for.

Below are some of the more interesting features I did manage to get implemented:

Better map discovery

I spent some time trying to make the map display work better for visible / invisible areas. Rules are roughly:

  • LOS covers an area that fans out from the player providing nothing has the “:is-view-blocking” attribute (like walls). This area is considered visible.
  • Squares adjacent to the LOS area are also considered visible.
  • Squares that have been visible at least once in the past are considered as “discovered”. I keep a map of the last visible scenery in each square, which persists so that you can remember what the map looked like when it is out of your LOS.
  • Memory of discovered squares does not include movable things (creatures, items) but does include immobile scenery (alchemy apparatus, staircases etc). This is important so you can remember where to find that alchemy lab when you need it!
  • Undiscovered areas are shown “grayed out”. I think this visual effect works quite well, gives a good sense of uncovering the unknown, while still keeping an dark and dungeon-like feel overall.

In terms of implementation, this system works with a few different immutable grids stored in the top level  “Game” object. The Game is a Clojure record that includes the following visibility related structures:

   {:world            ;; grid of the the "actual" tiles in each square
    :things           ;; grid of vectors of things in each square
    :discovered-world ;; grid of discovered scenery
    :visibility       ;; grid indicating which squares are now visible
   }

All the grids are my own custom 3D persistent data structures (written in Java), so they play nicely with the immutable game state design that I’m using in Alchemy. I also wrote a couple of helper classes in Java that walk these data structures in a relatively efficient manner to handle updates and querying.

Together, this is enough to implement the visibility rules mentioned above. It strikes me that I had to be a bit more careful in choosing the right data structures because I was using Clojure. Some reasons for this:

  • Performance: In the absence of mutability, I don’t think I would have been able to get the necessary performance if I wasn’t using persistent data structures with structural sharing. alchemy isn’t a particularly big game (about 40,000 tiles) but it’s big enough that a copy-on-write approach wouldn’t be practical.
  • Data structure dependencies: Clojure encourages a style where you expresses data as pure data, and separately define functions that work on this data structure. This is a good design in general, but doesn’t lend itself well to refactoring since a lot of the functions end up depending on the exact structure of the data (e.g. which keys are present in each nested layer of hashmaps). In some circumstances I’d rather have slightly better enforced encapsulation so that I could modify a data structure design internally without the risk of breaking users of the data structure. If you make a mistake here then the compiler won’t tell you…. if you are lucky you unit tests will catch it, but quite often you only find out when a nasty error occurs at runtime.

Anyway, despite the complexity this ended up working well in the end

Wandering Monsters

Originally, all the monsters were generated at the start of the game. I wanted to have more monsters appear as the game progressed: this means you have to stay constantly on your toes and gives you a chance to farm wandering monsters for items if you need to (this might even be necessary in some cases if you can’t find the right potions).

The code was relatively short and sweet:

(def WANDERING_MONSTER_RATE 5000) ;; number of ticks per monster add

(defn maybe-add-wandering-monsters
  ([game aps-debt]
    (let [num (Rand/po aps-debt WANDERING_MONSTER_RATE)
          lmin (:min (:volume game))
          lmax (:max (:volume game))]
      (reduce
        (fn [game _]
          (if-let [l (mm/find-loc lmin lmax
                               #(and
                                  (not (get-blocking game %))
                                  (not (engine/is-square-visible? game %))))]
            (let [m (lib/create game "[:is-creature]" (:max-level game))]
                (dungeon/maybe-place-thing game l l m))
            game))
        game
        (range num)))))

The “Rand/po” function samples from a poisson distribution to determine the number of monsters to add. This is theoretically the right distribution to use if you want events to occur randomly at a fixed rate over time, but need to handle time periods of potentially different durations (which will happen if the player is hasted or slowed for example). Though in this case it is probably irrelevant: given the WANDERING_MONSTER_RATE defined above it is very unlikely (about 1 in 2500) that two or more monsters will get added in the same game step.

I made the level of wandering monster generation depend on the max level reached by the player. This seemed sensible in terms of game balance and makes for a more interesting tactical challenge once you reach level 10: your way back to the surface can be hindered by some very dangerous lv.10 monsters.

Gameplay tweaks

Probably the biggest use of time on my last dat was actually playing the game, and tweaking the gameplay based on my experiences. The kinds of things that happened:

  • Getting killed by a powerful orc champion on level 4. Whoops! Better change the creature definition to put that guy at lvl 7 or above.
  • Not being able to find certain important potions. Make them more frequent and/or appear at an earlier  level.
  • Getting bored of the same old room layouts. Add some room decoartions for interest / tactical variety.
  • Finding it a bit tedious to navigate the twisty map map – so added more connecting corridors and staircases to make it a bit easier to travel around.
  • Adding messages to notify the player of certain events.

Failed to make the cut

Sadly there were a few things I would have really liked to do that didn’t quite make it in due to time constraints (I’d basically given up hope of being able to finish them somewhere in the middle of day 6…). Some examples of these:

  • Multiple item stacking would have been very nice, but I just didn’t have time to complete it in the final day. Probably my biggest regret from a usability perspective, as the inventory lists can get quite long with all those ingredients…..
  • 3D dungeon generation was something I planned originally, and in fact all the data structures are there to support it (the game world is already a single 3D grid for example). I wanted open pits, balconies, walkways over gaping chasms etc. Would have needed quite a bit more work on the dungeon generator though, and extra features to make the gameplay around this feature (3D LOS ray casting,  looking down pits, monsters with ranged combat etc.)
  • Some more item types would add a bit more variety – another day or two of coding new content  would have been ideal here. I really wanted some magic rings, weapons and armour to give a more classic roguelike feel. But the Alchemy had to come first.
  • Summoners – always a fun feature to have summoning monsters, and scenery that generates new monsters (like a crypt). I actually wrote the summoning code and this probably needed only 30 mins to get working with some new content, but would have needed a lot more playtesting and I was running out of time so I decided to skip it….
  • Time travel – I had the concept of allowing players to escape tricky situations by jumping backwards in time a few turns via a “potion of recall”. This was a bit of a “because we can” feature: since the entire game state is an immutable persistent data structure, it is trivial to store the history of the last 30-40 turns by keep references to the old game state. And structural sharing means that the overhead of doing this would be small: most of the data would be fully shared with the current game state. The same trick would also potentially allow game replays.

Conclusion and future plans

Overall, despite the challenges I’m pretty happy with how Alchemy turned out. It is a complete game, pretty much in the spirit that I intended.

Alchemy also a great example of how Clojure can be used to create a game in a functional programming style, without sacrificing performance or gameplay flexibility. I hope it proves useful to others as an example of game coding in Clojure, or as a fun code base to hack on.

In particular, the game engine is IMHO really good: the 3D grid system, the “effects” system, the hierarchical inventory system and the prototyped-based object library all worked exceptionally well, and on top of that they are all purely functional with immutable data structures! To my knowledge this implementation is pretty unique and was a lot of fun to write (even if it did absorb at least 4 out of my 7 days….)

Some initial thoughts on the future:

  • I enjoyed the 7DRL challenge sufficiently that I think it would fun to complete some more of the extra ideas I had. I will maybe create an Alchemy 2.0 with the missing features (when I get the time!)
  • I plan to write a post-mortem blog post here that focuses more on the Clojure-related aspects of the challenge. Language features I liked, disliked, tips and tricks etc.
  • I’ll refactor some of the generally useful code out into the open source orculje library – which is basically a bare-bones toolkit for writing roguelike games in Clojure. Then it will be there if I ever want to do some more roguelike hacking in the future (7DRL 2014 perhaps?!?) , or if other Clojure developers feel inspired to build on the work.
Comments
  1. cmdrdats0 says:

    Congratulations on finishing this up! Looks awesome :)

    Are you at clojure/west by any chance? Would love to chat.

    I’m writing a multiplayer, pluggable variant of dwarf fortress and I’d be keen to see the source lf Alchemy, if possible, to draw inspiration esp. for the entity system, data structures and map generation

    • mikera7 says:

      Yeah the alchemy code base should be great for that – I had a similar idea myself :-) It does proper 3D maps, effect system, containment hierarchies etc… pretty much everything you need.

      All open source (AGPL) here:
      https://github.com/mikera/alchemy

    • mikera7 says:

      Note going to be at Clojure/west unfortunately (a bit far for me to come) but happy to chat roguelikes any time…and I’m a big dwarf fortress fan…. skype maybe? I’m mike_r_anderson

  2. lenand says:

    Congratulations. It work on an Apple run by an amateur.

  3. georgek says:

    NIce work! I’ve enjoyed following your progress over the last week.

  4. esden says:

    Just played it for an hour or so on my Mac. Works smoothly, and it is a lot of fun! Great job!

Leave a reply to esden Cancel reply