An Experiment in Real-Time Networking.



by

Stuart Cheshire

Sidney Sussex College


Previous | Table of Contents | Next

Chapter 3 -- The Core

Discussion In the Foreground...
Building Control
Pillbox Aiming
Forest Growing
Automatic Scrolling
At 50Hz...
Timers
Keyboard
Tank Movement
Man Movement
Shell Movement
At Last, a Real Flood-Fill!

Discussion

The central body of code is in two distinct sections. Part of it is executed as a normal foreground process, and deals with growing new trees around the periphery of forests, aiming the pillboxes, and updating the screen. All of these things should ideally be attended to as frequently as possible, but the regularity of execution is not important. For example, if, for a moment, when the screen is very crowded (and the program slows down because of all of the extra drawing which must be done), a pillbox takes 1/4 of a second to aim at your tank instead of 1/50, it will probably not be noticed.

The other part of the program is called every 1/50 of a second from the 50Hz video interrupt, and deals with all the processes which must happen at a constant speed, such as the movement of flying shells and driving of the tank. It would not be acceptable if you were being chased along the road by an enemy when your tank suddenly slowed down because your screen was a little more crowded.

In the prototype program the tank movement was a foreground process, and the constant speed was achieved by measuring the how long it was since the last time the tank was moved, and multiplying this value by the velocity to find out how large to make the movement step.

Now that the tank is moved at regular intervals instead, the code is much simpler, and the way it reacts when colliding with buildings and other tanks is much more acceptable.

In the Foreground...

Building Control

Building operations in the game are performed by a man who normally rides in the tank. When any building is done, he leaves the tank, runs the specified location, builds what is required, and then returns to the tank.

The keyboard is scanned for the keys which select the building mode. The available options are: Building, road/bridge, lay mine, place pillbox, and farm forest. When such a key is pressed, various things are checked, such as whether the tank is carrying the required resources, and whether the man is currently inside the tank and available to do building work. If all is well, then the man is given the required resources from the tank's inventory. If for any reason the man is unable to achieve the objective, for example he is unable to find a way past an obstacle in his way, then the resources will be replaced in the tank's inventory when (and if) he returns.

Pillbox Aiming

The pillboxes do not work in the immediately obvious way.

The obvious way implement them is for each machine to fire shells from its player's pillboxes towards enemy tanks, in the same way that it handles shells fired by its player's tank towards enemy tanks.

The problem with this is that a large amount of workspace must be reserved for holding many moving shells, to cope with the common situation where several players team up to attack one who is winning. If six players attack another player's fortress which is fortified by pillboxes, then that player's machine will have to handle perhaps 24 shells simultaneously, plus any the player fires manually from the tank. The problem is not so much the extra memory required, but the extra work which will be imposed upon the machine, making other processes run slower. It would be unfortunate if a player was defeated in a battle such as this because he was doing too well and had captured too many pillboxes and his machine couldn't handle the number of shells that they were trying to fire.

There is another problem. When the game starts, all pillboxes are allied to no one and are equally hostile to all players. Who has the responsibility of firing them? Is the work somehow shared equally between all the machines? If a machine is disconnected, who takes over its work?

The logical jump which had to be made to solve the problem was to move the responsibility for handling shells from the player who is firing them to the player who is being fired at. This means that if a machine can handle eight such shells, then the inability to handle more is likely to be of little significance when eight shells from different pillboxes are simultaneously converging upon a tank. It also means that, in the example above, of six players attacking one, that player's pillboxes could be firing up to 48 shells on his behalf without the undue extra load that that would otherwise impose upon his machine.

The algorithm works as follows: For each pillbox:

Forest Growing

A map square is picked at random. A `forest growing score' is calculated for it, based on the type of terrain already there and the density of trees surrounding it. A single square of grass surrounded by forest has an extremely good score, and a piece of road in the middle of a built-up area has an extremely bad score. The score is compared with the one which was remembered the previous time the routine was called, and if it is better, the new square replaces the old one as the one being remembered, otherwise the old one remains. When the same square has been remembered for a full 10 seconds without a better one being found, it is decided that it is a suitable location to grow a new tree. The next time the Token arrives, the appropriate instruction to change the map is despatched, unless some other change to that area of the map has occurred which makes the growing of a tree inappropriate, in which case the new tree is abandoned.

Automatic Scrolling

Various objects, such as hostile tanks and pillboxes, are checked to see if any are close enough to the player's tank to pose a potential threat. If any are, and it is possible to do so, then the screen is scrolled to bring them onto the screen. If there is a conflict, then the following order of priority is used:

If none of these apply, then the routine checks to see if the tank is travelling at a reasonable speed in a straight line. If it is, then the routine attempts to scroll the screen to show the area in front of the tank, in the direction it is travelling, in preference to the area it has just come from.

There are four keys which can be used to move the screen manually, overriding these automatic rules. This enables the player to look at areas of the map other than that which the program assumes he will be interested in.

All of these movements are subject to the restriction that the player's tank can never be moved off the screen. It is not possible to move the screen to look at any part of the map the player wishes, he can only see the region in the immediate vicinity of his tank.

At 50Hz...

This suite of routines was originally called directly from the 50Hz interrupt, but now it is called indirectly via the scheduler. This has two advantages:

Timers

The game uses several software timers which decrement to zero and then wait. These include:

Keyboard

Much of the keyboard reading is done here. This ensures that the rate at which the tank rotates, and accelerates and decelerates, and the rate at which the building cursor and the target cross-hairs move, are all constant, regardless of the speed of execution of the foreground routines.

Tank Movement

If the tank intersects with any other player's tank, then it is pushed away from it by a small amount. This allows players to `shove' each others tanks along by pushing against them with their tanks.

The tank is then moved forwards a distance according to its current speed and direction.

It is then checked to see if it intersects with any buildings. If so then it is pushed away until it no longer intersects with any buildings. By careful tuning of the algorithm, the behaviour of the tank when it hits a solid object is made to appear natural. The calculation of the correct behaviour of colliding objects is often a problem in games. Often it is not solved at all well.

A similar procedure is used when the tank is on a boat, to make the river banks appear semi-solid. This means that if the boat makes a glancing collision with the bank of a river, it will be gently pushed back into the river, but if it hits a river bank hard then the tank leaves the boat and moves onto the land.

Man Movement

The man runs straight towards either the target location (when on his way out to perform some building) or the tank (when returning afterwards).

If he runs into a solid obstacle (or a river) he will run along the edge looking for a gap (or a bridge). If none can be found then the man gives up and returns to the tank. No attempt is made to plan the best route beforehand. Such planning algorithms are in the scope of A.I. research, although if time had permitted it would have been interesting to try to find some kind of simple solution. This will be easier to experiment with in the C implementation than in 6502 assembler.

When he reaches the target location, he stops moving and waits.

When the Token arrives, the man generates the instruction to perform the required building operation, unless something else has happened to that region of the map which makes the building operation impossible. The flag is then set which causes the man to head back towards the tank.

Shell Movement

Moving shells are moved in fixed steps of about 1/2 a map square. This step size is chosen to be the maximum possible (in order to minimize the amount of work done by the processor) without risking the possibility of a shell completely jumping over a building or tank in its path.

If the shell hits a solid obstacle, then a flag is set which makes it stop moving, and wait. When the Token arrives, an instruction is generated to damage the tank or building which was hit, unless someone else's shell has already destroyed the object while this shell was waiting.

The x and y components of the shell's movement are calculated using a sine wave table to quickly find sin(theta) and cos(theta), where the angle theta is the direction in which the shell is travelling.

At Last, a Real Flood-Fill!

This is more usually thought of as a graphics algorithm, although here it is actually being used more in the context from which it derives its name than the context in which computer scientists generally regard it.

It is not a scan-line algorithm; it is the simplest, naïve, ink-blot type flood-fill algorithm which, when it fills a square, simply checks the four adjacent squares to see if they, too, are eligible for filling.

However, in this case, it is not a recursive routine using a stack; it uses a queue -- and when it inserts coordinates into the queue a time delay of about 2/5 of a second is also attached. This means that the routine may often examine the queue and find it not empty, but nevertheless have nothing to do because the objects there are not yet ready to be removed. Only when a timer has decremented to zero can the flooding operation proceed further. This causes the flooding to progress at a leisurely rate of about 21/2 squares per second.

As you will realize, this routine is not used for flooding pixels with colour, it is used for flooding craters with water. Whenever a crater is created in the map (by a mine or tank exploding, or any other cause) the four adjacent squares are checked to see if they contain water. If they do, then the M-coordinates of the newly formed crater are inserted into the flooding queue with a small time delay attached. The crater then fills up with water a small time later, and if any of its neighbours are also craters, then their M-coordinates are similarly inserted into the flooding queue with a small time delay, so that large areas can be gradually flooded with water.

This routine performs the propagation of chain-reactions in exploding minefields too. A bit-flag in each queue item identifies whether it is a pending flood operation or a pending mine explosion.

The time delay is to produce a pleasing looking flood, which proceeds along a trench of craters at a sensible rate. Any tank which is in the water also gets pushed along in the direction of the `flooding'.

If the queue is full when the routine tries to insert flooding co-ordinates then they are lost, but the flood dæmon is woken up. This then slowly crawls across the map as a foreground process, looking for craters adjacent to water which have obviously been lost by the normal flooding routine. This means that it is possible to get an open trench next to the sea which is not flooded, but eventually it will be discovered and the appropriate changes will be made to the map.

If the queue is full when the routine tries to insert explosion co-ordinates then they are lost and cannot be recovered. This is not a major problem for three reasons:


Previous | Table of Contents | Next

[ Intro | News | Links | Archive | Guides | Gallery | People | Misc ]
The Bolo Home Page is copyrighted by
Joseph Lo & Chris Hwang