This document explains how the HvZ game works, at a high level, and what everything is for.
At 10,000 feet up, things look like this:
Client <---------------> Server
^ ^ ^
| | |
| | |
Client <-----------------+ | |
| |
| |
| |
Client <--------------+ |
|
|
|
|
Client <---------+
The "client" is what's running on the user's machine. Clients can start games or join games; to do either, they must be connected to a server. A connection persists throughout the session. However, games are run on the server, and each client runs in lockstep with the server. Clients generate commands like Move, Throw, Turn, etc etc (via AIs or some other method -- it doesn't really matter how it's done) and send those commands to the server; the server executes the commands internally, and if they're valid commands, it reflects them to all the clients. Important: the clients only ever execute commands that they receive from the server. They never execute commands by themselves. In fact, they don't even execute commands immediately as they receive them from the server (more on this later). Even commands that they generate are sent to the server, then reflected back to them. This keeps everyone synched.
When a client receives a command, it creates a function which, when executed, will do something (e.g., move a player forward). It doesn't execute this function yet. Subsequent commands may overwrite this function, which means that later commands supersede earlier commands.
Every so often (say, every 100ms) the client gets given the command from the server to Move. When it receives this, it executes a game-turn. Among other things, a game-turn:
- Decreases everyone's lifespan
- Executes commands (this is somewhat simplified...)
- Checks if anyone is dead
Every game-turn, the client gets asked to decide about what it wants to do (see IHumanAI
and IZombieAI
below). Each client has received the same command-set and is at the same place by the time that Move is issued (thanks, TCP!). Surviving players can continue to issue commands.
There are a bunch of interfaces. Here's what the higher-level ones are for:
IIdentified
is for things (e.g. Humans, Zombies, ResupplyPoints) which must be identified by a unique ID.ITakeSpace
is for things that take up space on a map. This means that they have a place (x,y) and a size (radius). Everything's a circle; there ain't no squares here, cat.IWalker
inherits fromITakeSpace
, and is for things which both take up space and also move about. The most important thing about it is theHeading
member, which says which direction it's going in. At present, it's a bit bloated with .Name, .Lifespan, etc. Perhaps those will be shifted somewhere later on, or perhaps not, if they're not causing trouble where they are.IHumanPlayer
andIZombiePlayer
inherit fromIWalker
. They also include methods for doing either human or zombie things, depending, and they provide properties for accessing current information about the player (e.g. current heading, map width, maximum lifespan, etc).IHumanAI
andIZombieAI
really only differ in what they take as their first parameter: either anIHumanPlayer
or anIZombiePlayer
. The methods on these send commands to the server, which are then reflected back and executed on the next game-turn, and so on.
At the lower level, we talk pure TCP with a F# backend and a bunch of helpers to hide as much of the lower-levels as we can. Stuff like HvZConnection
is low-level, HvZClient
is relatively low-level (but should be somewhat understandable, given the above explanations), and things like the example RandomWalker
AI and the Map
class could be considered to be high-level.