I'm an experienced programmer, but a Clojure newbie; as a beginner project, I'm looking into how one would idiomatically write a text adventure of sorts in Clojure. I'm less interested in producing a playable game than I am in learning how to do such a thing in a proper functional style.
Suppose in this game I have a room whose description changes based on a global flag. For example, there's something in the Fancy Room that you won't notice until you've reached the major plot point.
The world map is (for the sake of argument) a hash-map whose keys are the room IDs and whose values are room records, where each record is a hash-map.
(def world {:fancy-room {:name "Fancy Room" :description "This is a fancy room." ...}})
I'm aware that I could use a (defstruct) or (defrecord); I'm keeping it simple for now. Then, the flags are saved in a ref; the intent is that mutable set is segregated, so that it can more easily be written to a save file.
;; Global set of flags
(def flags (ref #{})
(defn flag-set [flag]
(dosync (alter flags conj flag))
;; When the major plot point is reached
(flag-set :major-plot-point-reached)
Normally, to describe a room you just return its :description.
(defn describe [room] (:description (world get room)))
But for the :fancy-room, the returned description depends on the global flag, and it will be specific to :fancy-room. I could add this logic directly to the (describe) function's body, but that would be ugly. What I'd like to do is attach a lambda to the :fancy-room in some way. The (describe) function looks for a :describer, and if it's there it calls it; and if not it just returns the :description:
(defn describe [entity]
(if (:describer entity)
((:describer entity) entity)
(:description entity)))
Question 1: this works, but it looks ugly to me; I figure there's a better, more idiomatic way to do this kind of thing that's probably obvious to anyone with any real experience. Multimethods, maybe? Define a Room protocol, then let most rooms be NormalRoom records, but let :fancy-room be a FancyRoom record?
Question 2: Whatever code actually computes the description, it will need access to the :major-plot-point-reached flag. What's the cleanest way to give the description code access to the flags ref? It could simply access "@flags" directly:
(if (:major-plot-point-reached @flags)
"This is a fancy room. Hey, that light sconce looks movable!"
"This is a fancy room.")
But that doesn't seem properly functional. Would it be better to pass the game state into each method?
(defn describe [entity state]
(if (:describer entity)
((:describer entity) entity state)
(:description entity)))
Any ideas?