Gary, Thanks for the detailed answer.
What you're describing is pretty much exactly what I'm trying to do, including the game loop you mention. The basic implementation is pretty simple: a player who can move from room to room, picking up objects, putting them down, and so forth, and the world-state var you describe captures it just fine. As I noted in a response to someone else, I've been writing adventure games as an exercise in learning languages since the mid-80's. Because I've done it so many times, I'm looking not just for how to do the basic, but how to do the complicated stuff: places that are also objects that can move (e.g., vehicles), places that change, rooms and items that have complicated logic associated with them. You suggest: When you want to save the game, just write out the world-state to an EDN file. By reading it back in again later, you can restore the game to exactly the same state it was in before. And for this case, this is probably just fine. Putting my software engineer hat on, I'd prefer to have the data that can change segregated from the data that doesn't, so as to save the minimal amount of data...and so that the same save file can work with successive versions of the code. If I fix a typo in a room description, I don't want the old typo to still be present when I load a saved game! But this is an exercise, and I probably don't need to worry about that. You suggest: ```clojure (defn do-important-plot-changing-action [world-state] (-> world-state (update :flags conj :major-plot-point-reached) (assoc-in [:rooms :fancy-room :description] "Gosh! This room isn't nearly as fancy anymore!"))) ``` I take your point here, about how to update the world-state when something changes, and I thank you for the example. In terms of the game logic, though, this is precisely how I *don't* want to solve the problem of a room with complex description logic. In fact, I'd regard it as an anti-pattern. There are a couple of reasons: 1. Just as an aesthetic thing, I want to keep the details about a given room all in one place, with the room definition. If I've got a bug related to a particular room, I want to know where to go looking, and not have to look all over the code for places where I mention :fancy-room. But the do-important-plot-changing-action function might be called in some other context altogether. It's precisely my goal to use the :major-plot-point-reached flag as a way to decouple the :fancy-room logic from the logic that determines that the plot point has been reached. 2. Suppose the description of the room might be affected by half-a-dozen different flags. That would be an exceptional case; but adventure games are all about exceptional cases. Using your function up above, it couldn't simply say, "Oh, the :fancy-room description is this now." It would need to query the current state of the :fancy-room and decide what makes sense now. What we have here is an object whose state depends on the state of the exterior world. In my experience (in other problem domains) it's much easier to simply recompute the object's state as a function of the state of the world, rather than to compute the change to the object as a function of the change to the state of the world. Your do-important-plot-changing-action function does the latter. But in general, yeah, this approach makes sense. We've agreed on the menu, now we're arguing over the recipes. :-) Will On Friday, March 30, 2018 at 8:01:59 AM UTC-7, Gary Johnson wrote: > > Hi Will, > > Welcome to the wide world of functional programming, where data flows, and > functions transmute without destroying their inputs. > > As some others in this thread have already suggested, a general approach > to viewing any problem space from a functional perspective is to imagine > your problem as a pipeline of data transformations from start to finish. To > the greatest extent possible, try to represent your data transformations as > pure functions over immutable arguments rather than holding any global > state in a var and mutating it at each time step. > > In the context of a game program, consider depicting all of your game > state as a single hierarchical map. Then thread this map through each > function and return a new derived map as each output result. > > ```clojure > (def world-state {:flags #{} > :location :fancy-room > :inventory #{:sledgehammer :car-keys :lamp} > :rooms {:creepy-corridor {:name "Creepy Corridor" > :description "Whoa! Spooky..." :items #{:halloween-mask :monkeys-paw > :troll-doll}} > :fancy-room {:name "Fancy Room" > :description "The fanciest!" :items #{:chandelier :toy-poodle > :looking-glass}} > ...}}) > > ``` > Let's imagine this is the initial world state. When your application > starts up, it enters into the game loop (which would be a nice, friendly > recursion, of course). In each round of the game loop, you call a sequence > of functions to get the description of the current room, print it out, ask > the user for the next command, process that command, and then recurse back > to the top of the next iteration of the game loop. Although printing to > stdout and reading from stdin are obviously side effecting functions, you > should be able to keep your functions for retrieving the room description > and processing the user's command as pure functions of the current > world-state. Just make sure that the functions that process a user's > command always return a new copy of the world-state value. When you want to > save the game, just write out the world-state to an EDN file. By reading it > back in again later, you can restore the game to exactly the same state it > was in before. > > In response to your specific question about having a different room > description after some event has happened, consider this approach: > > ```clojure > (defn do-important-plot-changing-action [world-state] > (-> world-state > (update :flags conj :major-plot-point-reached) > (assoc-in [:rooms :fancy-room :description] "Gosh! This room > isn't nearly as fancy anymore!"))) > ``` > > That is, the function just uses assoc, assoc-in, update, and update-in to > modify the values in the new map and return it for use in future iterations > of the game loop. And that's pretty much all there is to it. You just pass > the changing world state down through the stack rather than mutating it in > place on the heap. (Ultimately, the world-state data structure is, of > course, actually stored on the heap, and you are just passing references to > it through the stack, but I hope you get my meaning here.) > > Good luck and happy hacking! > > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.