Hi!

I have listened to Rich's presentation Simple Made Easy, and he has 
convinced me of the virtures of simplicity. However, I am not so good at 
designing simple stuff myself. Writing things down makes me think more 
clearly, and I was also hoping that you can help me by giving some input 
and critique. One of the tips he gave was just to think about "what, how, 
who, when, where and why", and try to break those aspects apart, so that's 
what I'm gonna try to do.  

Let's start with "what". I want to make a role-playing game. But this post 
is not just for the game I want to make; I want to get better at designing 
simple stuff in general. Throughout this thread, try to think of the game 
as just an example. I care more about the general ideas. Anyhow, let's 
quickly go over some requirements, so that we can come up with 
abstractions.  I want the game to be multiplayer, over a network. A player 
is a character that can fight monsters, pick up things, use things, gain 
experience, etc. Non-player character can be friendly or not, fight, talk, 
etc. There must obviously be a user inferface, with graphics, sound, and an 
input system.  There must be a way to save the players' progress. And, 
let's not forget, there is a world, an environment, in which the game takes 
place. 

Rich said that one should try to come up with abstractions.  "Form 
abstractions from related sets of functions." Ok, let's start with an easy 
one; there must be two functions (impure, but anyway) that can save and 
load a character from some storage on the server. So, we have a storage 
abstractions that can save and load stuff. 

I think that seems like a good abstraction. A storage is not only an 
abstractions, but kind of a major subsystem in the game. But let's take an 
example from Clojure; the seq abstraction. The seq abstraction is a bit 
different, because it is not a subsystem in the same way; seqs are used 
everywhere. So, in a similar vein, should there be abstractions for items, 
like "usable", "breakable", "pickupable"? It sounds nice, because there 
might be many concrete things that share these capabilities. But how would 
it work? It is players that pick things up, the items themselves do not do 
anything when they are being picked up. So maybe pickupable is a useless 
abstraction. Maybe there should be a container abstraction instead. What 
about usable? What happens when a player uses an item? All sorts of things 
could happen; to the player, to the item, or to anything else. The use 
function has to take the whole world as input. Should it be associated with 
the item? Maybe. What if the result of the use depends not only on the item 
but on the user? And maybe several different items should have the same 
effect/action.  Have I complected the action with the item by making the 
item implement usable, i.e. have its own use function? An alternative would 
be to have a table describing what happens when different items are used by 
different users. I guess the important question is: How do I tell if I 
should be introducing an abstraction like "usable"? By introducing an 
abstraction I guess I mean by defining a protocol. Also, instead of 
inventing a container abstraction, I could just put the items (values) in 
data structures. But those are collections---an abstraction that is 
essentially a container. Then I have regular values in regular collections, 
and I have not introduced any abstractions.  Maybe that's OK, but I have 
not really got used to this whole "program to abstractions" thing. 

Another problem is the user interface, i.e. the graphics and sound. The 
graphics subsystem should implement an abstraction, because if I wanted to 
change graphics library or whatever, the rest of the game code should not 
have to care. Do the grahpics and sound subsystems have something in 
common?  Should they share an abstraction? The difference between them is 
that everything needs to be drawn/shown all the time, while sounds are 
played in a more reactive manner, when certain things occur. Rich said that 
we should not complect "what" with "how". But when I start to think about 
an abstraction, I start to think about how these subsystems will work. I 
mean, the answer to "What (and not how) should a graphics subsystem do?" is 
"Show stuff." But how useful is that answer? There is, for example, a 
difference between just drawing static images that do not move, and drawing 
things that move around. Because you can't just draw stuff that move around 
once. Maybe I should think about the graphics that way, that everything is 
just drawn once. Then I could, every frame, just call draw and pretend that 
everything needs to be drawn every frame, instead of be moved in a scene 
graph, which is how 3d engines usually work. But, I still think about the 
"how" part. I'm thinking: "Well, if I get as input to the draw function 
something that is alreay in the scenec graph, then I will just update the 
position instead of adding the thing to the scene graph. But for that I 
need some kind of id, to know if the thing is in the scene graph already. 
There will be id's for all characters in the game, and I can use that." But 
then I have complected the graphics with the rest of the game. The graphics 
should ideally not have to care about the id's that the rest of the games 
uses, because the id does not really have anything to do with the thing's 
graphical representation. The "how" and the "what" is complected. And not 
only that, I must take the thing out of the scene graph if it disappears 
from the game. So, I pretend that I must call "draw" every frame, but still 
I must call "remove" if the thing disappears. 

By the way, I have quick question: If I define a protocol for the graphics 
system with, let's say, just a draw function. Then I must make some sort of 
type or record that implements the protocol right? I usually think about 
subsystems as namespaces. So, would I make like a graphics namespace that 
defines a type GraphicsSubsystem that implements the protocol and that uses 
the functions in the namespace? And the rest of the game calls functions on 
the GraphicsSubsystem type? It just feels a little wierd to have to define 
a type when I have a namespace, in a way.  Do you see what I mean? And 
another quick question: The draw function by itself is not a "set of 
related functions". Is it somehow wrong to form an abstraction out of just 
one function?

But I have recently been thinking about another design for the whole game 
that would solve, I hope, the above problems.  Every function that does 
something to the model/state of the game world does not update the world 
right away, but instead produce a "delta", i.e. a piece of data that 
describes the change that would have to be made to the current state in 
order to produce the new state. Now instead of calling draw for all the 
characters and stuff every frame, what if the graphics subsystem just 
subscribed to the deltas. (The deltas would of course have to be sent over 
the network first.) Then the rest of the game would not have to know 
anything about the graphics subsystem. One great thing about this is that 
the sound subsystem and networking subsystem could do exactly the same. But 
what if I decide to change the data model of the game, and therefore the 
deltas now look a bit different? That would affect the subscribers.  But, 
somehow, they must know about the format of deltas, or some derivatives. 
 The best thing I could think of is to introduce a layer of indirection, so 
that the new kind of deltas can be translated to the old kind, if possible. 
Then at least the graphics subsystem would not have to change. 

Another good thing with deltas is that many functions can become pure by 
returning deltas instead of modifying the state. They could of course 
return a new state instead of deltas in order to be pure, but that would 
not help the subscribers. And lots of different delta-producing functions 
could be run in parallel. But that could not happen if they returned a new 
state instead. 

When Rich talked about "where and when" he said that if subsystems call 
each other directly, they are complected because they must know where the 
other one is, and it also implies a temporal connection. He talked about 
using queues to solve this problem. I have never used queues in that way, 
but it seems like using deltas and a publish-and-subscribe design is kind 
of what he was talking about, or is it? 

Comments, tips and suggestions? I'm all ears.

-- 
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

Reply via email to