I'm coding a videogame in Java using the JMonkeyEngine; it's a clone of Might & Magic: Clash of Heroes' combat system. Before starting the coding phase I tried to organize my thougts and finally decided to put everything into a post. This is what I ended up with.
What I really like of Might & Magic: Clash of Heroes (MM:CH from now on) is that its combat system encourages you to think twice before moving a pawn on the grid. From this point of view one can say it shows resemblances with a more complicated cousin of his, the game of chess. Seriously. Sometimes you are just stuck watching the screen while your brain struggles to compute the next move, comparing the benefits you could gain by moving certain tiles rather than others. And once you get a good grasp of what the whole thing is about, you'll always find yourself trying to land a combined hit (like creating a defensive wall and an attack formation all at once), mainly because this unlocks additional moves in your turn, and not-so-secondarily because it makes you feel pretty damn smart. I dare say that this is arguably one of the most addictive feature of the game: you place the right piece in the right cell and assist to a beautiful chain reaction which more often than not grants you the lead of the battle, at least until your opponent gets a chance to do the same.
With this thoughts in my mind I started designing my clone of MM:CH's combat system. I read and hacked the code of quite a few open source videogames before, but my only experience of designing one from the very beginning dates back to my first year at university, when I challenged myself coding Pong and Snake (which are maybe the unavoidable baptism of fire for every noob programmer, along with TicTacToe and Tetris). Let's talk about Snake briefly and we'll soon get to the point of this post.
Things were pretty simple in terms of logic and graphic. What I came up with was a two-dimensional array representing the world where every cell could be either an empty piece, a target or a slice of the snake. At every logic update a simple function checked whether everything was still ok, spotted out collisions and instantiated a new target somewhere on the matrix if necessary. And it was it, let alone score calculation and other few trivial tasks.
If we summarize those logic updates with a FSM and also include in the diagram the graphic chunk of the game's architecture, we get something like in the picture below.
It's clear that a biunivocal relation holds between the set of graphical states and that of logical states. Basically this means that for every change in the logic we have at least and no more than one graphic render. Also, it's just as much clear that if we think of the game's world as a data structure shared between different entities of our game, the logic acts as writer while the graphic as reader. Pretty neat. Now we're ready to get back to MM:CH, where things are a bit more complicated.
I want the chain reactions I was talking about above to be rendered, since this is probably the main reason I chose to clone this game. In Snake the logic takes only one update cycle to carry out all its tasks, then it's graphic's turn, and then logic's again restarting the loop. Unfortunately, dealing with chain reactions means that the logic is likely to do more than one write on the world's data structure before advancing to its next state and passing control to the graphic.
Now our function is no longer biunivocal. We lose track of those intermediate logical states (1.1, 1.2... etc.) which represent the logical bulk of the chain reactions we want to render so badly.
It's Model, View, Controller, right? Let's add a well-defined controller and see what we can do with it. Suppose the logic has a function that returns all the consequences of a given move in the shape of an ordered collection, abstracting the very concept of consequence with a class; the collection is then passed to the graphic and rendered on screen. This approach shows the evident advantage of decoupling logic and graphic by building a bridge between them, but its drawback is that it forces the graphic to wait for the logic to be done with all its computing before having something to render. A good CPU could downgrade this to a minor issue, but what if we want to play our game on a lower end machine?
Nah, no good. We can do better than this.
In #2 the logic elaborates an input and notifies the graphic at every change of state, even if intermediate. There's no information loss and the graphic gains the possibility to make what's on screen always perfectly consistent with what is stored within logic's data structures. Besides, what I don't like is that the logic needs to store a reference to a graphic class/interface to push its messages.
Approach #3 solves the problem. We're now in a producer (logic) / consumer (graphic) scenario, with a log recorder acting as a concurrently shared data buffer. Every change of state is logged (put into the registry) and retrieved by the renderer; logic and graphic aren't aware of each other and their interaction is only based on the assumption they both adhere to the same contract (one knows how to create logs, the other how to render them). And it's also easy to expand this solution to a multi-threaded one with one producer and many consumers that render what they can concurrently. I'll go for it.
Keeping logic and graphic separate might perhaps look like an elaborate exercise of style at a first glance, but really pays back during the development stages, because of its modularity and flexibility.