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.

Reply via email to