Bolo

From WinBolo

(Difference between revisions)
Jump to: navigation, search
m (Added LGM)
(Added starts information)
Line 238: Line 238:
Holds all the starts in the game. Used to get a random starting position.
Holds all the starts in the game. Used to get a random starting position.
 +
 +
The two functions used in game are:
 +
 +
<pre>
 +
void startsGetRandStart(starts *value, BYTE *x, BYTE *y, TURNTYPE *dir);
 +
</pre>
 +
 +
Returns a random starting point. This is used to decide where the LGM starts
 +
 +
<pre>
 +
void startsGetStart(starts *value, BYTE *x, BYTE *y, TURNTYPE *dir, BYTE playerNum);
 +
</pre>
 +
 +
Returns a starting point for a new tank. It currently returns a random starting points that is not directly occupied by another tank. This previously implemented the bolo start algorithm. From the bolo version history document:
 +
 +
''"Improved start position allocation for maps with very few start squares defined, to cope better with certain "creative" map designs. For example, if you have a map with only two start squares, and you have a game of two teams where each team has a pillbox close to one of the start squares, then each team will effectively have their own 'private' start square, because with only two start squares to choose from when bringing a new tank into the game, Bolo will choose the one near the friendly pillbox, not the one near the hostile pillbox."''
 +
 +
This was added in [[Version History#Version_1.09 | version 1.09]] and removed in a later version ([[Version History#1.15 | 1.15]]?) after much play testing. The results of the play testing showed that the start algorithm was unfairly disadvantaging the team that was losing. For example in a [[Game Types#Strict_Tournament | strict]] game when a player/team is down to one quad or corner of the map and they die, they will restart on the other side of the map and have to make their way back to refuel and continue. This leaves the other side more time to continue advancing. Conversely, the winning team will start in territory they control and be able to refuel straight away.
 +
 +
Another potential start algorithm could be to start closest to an captured base, giving preferences to them being out of pillbox range (and possibly stock levels?)
=== shells.c ===
=== shells.c ===

Revision as of 13:35, 20 December 2008

The bolo directory contains the backend code for simulating a game world.

screen.c is the main entry point for client applications to talk to the backend.

The backend is not threadsafe and access to any functions must be synchronized.

Contents

Design

Although WinBolo is not written in C++ the backend has object orientated principles for managing the game world. This is due to the use of abstract data types within each module. (Note not all modules) For instance here is a list of some of pillbox functions:

void pillsCreate(pillboxes *value);
void pillsDestroy(pillboxes *value);
void pillsSetNumPills(pillboxes *value, BYTE numPills);
BYTE pillsGetNumPills(pillboxes *value);
void pillsSetPill(pillboxes *value, pillbox *item, BYTE pillNum);
bool pillsExistPos(pillboxes *value, BYTE xValue, BYTE yValue);
void pillsUpdate(pillboxes *value, map *mp, bases *bs, tank *tnk, shells *shs);

The ADT concept as well as New() and Dispose() macro's for ADT memory management come from Geoff Whales book - Data Structures and Abstraction Using C.


Client vs Server

The backend has code that provides processing for a single player game and game server as well as a dumber client. This is handled by the function threadsGetContext(); which returns TRUE if it is a server performing the action or FALSE if it is a client. Note that a single player game will return FALSE. Single player games need to be able to use some of the server logic to work to work correctly. So the backend will typically contain.

if (threadsGetContext() == TRUE || netGetType() == netSingle) {

The reason these two tests can not be simplified into threadsGetContext() function is they are also used to determine which structures are passed to the modules. Single player games use the client structures memory addresses.


Modules/Files

backend.c

Backend.c primary purpose is to switch between server and client functions based upon the current threads game context. In doing so it implements some of the functions of backend.h. For example the backend function screenGetNumNeutralBases(); is implemented:

/*********************************************************
*NAME:          screenGetNumNeutralBases
*AUTHOR:        John Morrison
*CREATION DATE: 21/2/99
*LAST MODIFIED: 21/2/99
*PURPOSE:
* Returns the number of neutral bases
*
*ARGUMENTS:
*
*********************************************************/
BYTE screenGetNumNeutralBases(void) {
  if (threadsGetContext() == FALSE) {
    return clientGetNumNeutralBases();
  }
  return serverCoreGetNumNeutralBases();
}

The reason for this switching module is because of WinBolo's development history. The very first versions were built as a single player clone. The backend functions all started with screen*(); When the game became networkable (around WIP release 8) (todo full version history) the server functions were created using the prefix of serverCore*(); Any backend code that needed to access screen*(); functions could be added into the servercore file. The problem arose when trying to merge the server and client applications into the one binary.

The result was the API (function names) remained screen*(); but backend.c would switch between client*(); and serverCore*(); depending upon thread context.

This could be simplified by creating a game world structure that contains references to each of the ADT memory locations and having a single function to return the correct ADT rather then switching per ADT. The JBolo design uses this design.

screen.c

screen.c is the main entry point to the backend for clients. Overview of key functions:

/*********************************************************
*NAME:          screenLoadMap
*AUTHOR:        John Morrison
*CREATION DATE: 29/10/98
*LAST MODIFIED: 30/01/02
*PURPOSE:
*  Loads a map. Returns if it was sucessful reading the
*  map or not.
*
*ARGUMENTS:
* fileName - File name and path to map to open
*  game - The game type-Open/tournament/strict tournament
*  hiddenMines - Are hidden mines allowed
*  srtDelay    - Game start delay (50th second increments)
*  gmeLen      - Length of the game (in 50ths)
*                (-1 =unlimited)
*  playerName  - Name of the player
*  wantFree    - Should we free the backend after loading
*                Usually TRUE if you only want to check
*                if a map is valid
*********************************************************/
bool screenLoadMap(char *fileName, gameType game, bool hiddenMines, long srtDelay, 
                   long gmeLen, char *playerName, bool wantFree);

Sets up a single player game by loading a map.

void screenDestroy();

Called to shutdown the game simulation and free all memory.

/*********************************************************
*NAME:          screenGameTick
*AUTHOR:        John Morrison
*AUTHOR:        John Morrison
*CREATION DATE: 29/10/98
*LAST MODIFIED: 4/1/00
*PURPOSE:
*  This function is called whenever a game tick occurs.
*
*ARGUMENTS:
* tb        - The combinations of tank buttons being
*             pressed
* tankShoot - Is the fire key being pressed?
* isBrain   - TRUE if a brain is running. Other
*             parameters are ignored.
*********************************************************/
void screenGameTick(tankButton tb, bool tankShoot, bool isBrain);

The main simulation loop. Must be called every 20ms. Frontends must repeatedly call if it falls behind processing. It runs everything in the game world from moving the tank, refueling a tank calculating base refueling, growing trees, moving the LGM. This is done by calling the Update() function to each of the ADTs within the game world. Note: If the backend is a client in a multiplayer game some of the ADT's may do nothing.

/*********************************************************
*NAME:          screenKeysTick
*AUTHOR:        John Morrison
*CREATION DATE: 8/2/99
*LAST MODIFIED: 27/11/99
*PURPOSE:
* This function is called whenever a update keys tick
* happens (twice the game tick length)
*
*ARGUMENTS:
* tb      - The combinations of tank buttons being pressed
* isBrain - TRUE if brain is running. Other parameter is
*           ignored
*********************************************************/
void screenKeysTick(tankButton tb, bool isBrain);

This function was added in version 1.04 of WinBolo. It is called very other 10ms to allow for smoother aiming of the tank. Its a cut down version of screenGameTick(); that just updates the tanks direction.

/*********************************************************
*NAME:          screenUpdate
*AUTHOR:        John Morrison
*CREATION DATE: 28/10/98
*LAST MODIFIED: 21/01/01
*PURPOSE:
*  Updates the screen. Takes numerous directions
*
*ARGUMENTS:
*  value - Pointer to the starts structure
*********************************************************/
void screenUpdate(updateType value);

This function is called when the front end has moved the view up/down/left/right or wishes to refresh the main screen. The function calculates the tiles that are currently in view and creates ADTs of of screen items (tanks/shells/explosions/lgms) to be draw onto the main screen before calling the front end function frontEndDrawMainScreen(); and then finally cleans up the data. As this call needs to be synchronized performance could be increased by creating a screenPrepareDraw() function that would prepare all the data but allow the rendering to take place outside of the synchronized function.

In the future this function probably will not do the tile calculation as different front ends such as 3D will care what the real terrain is, not what the tile that should be shown is.


Screen.c also contains many functions for setting values from the front end or getting data back to display. E.g:

void screenShowMessages(BYTE msgType, bool isShown);
void screenSetAutoScroll(bool isAuto);
bool screenSetPlayerName(char *value);
void screenGetKillsDeaths(int *kills, int *deaths);
void screenSendMessageAllPlayers(char *messageStr);
bool screenSaveMap(char *fileName)
void screenGetMapName(char *value);
BYTE screenGetNumPlayers();
long screenGetGameTimeLeft();
...
etc.

network.c

See Networking.

udppackets.c

See Networking.

netplayers.c

See Networking.

netpnb.c and netmnt.c

See Networking.

bolo_map.c

  • Stores the map file
  • Provides functions to read and write them back
  • Center them (and bases/starts/pillboxes) in the game world
  • mapNet functions.

Map Net functions provide a form of client side prediction to occur which makes game play smoother. When in a network game a client is allowed to make any changes to the map they like (a building getting shot, tree getting farmed) the change is reflected instantly on their screen. It is then added to the mapNet list with a timeout. As map changes are sent from the server it checks against the list of changes. If the incoming change matches an item on the list it is removed. If a change comes through that doesn't match the list it is actioned straight away (and any items on the list are are removed) with an "at" network debug message displayed. If an item on the list reaches its MAX_SERVER_WAIT time and the server hasn't confirmed the change, the change is undone and a "pt" network debug message is displayed.

There is a design bug that exists as the map net code only allows one change to be made per terrain map square. If multiple changes occur the previous ones are removed. This can occur when a mine is trigger so the terrain turns to crater then the crater gets filled by water. The client is only aware of the last change crater->water. If a prediction timeout (pt) occurs the terrain will be reset to the wrong thing, the crater, not the original mined terrain. This design bug is possibly also related to map corruption where the DEEP_SEA terrain can appear on the map.

tank.c

The player's tank. Handles movement, collisions with things, refueling, getting hit by shells and mines. More details to go here. This is where you'd want to go to improve the driving physics. The collision code with other tanks was only meant to be temporary and was to be replaced with bit masking collision detection but this was never implemented.

lgm.c

All things LGM...

players.c

Holds information about each player in the game. In a client it also stores the location of each player so they can be shown on the screen as required. A server has a tank and LGM ADT per player and only uses the player ADT for the player name. The following fields are stored against each player.

/* The different types of games there are */
typedef struct {
  bool inUse;                       /* Is player slot in use? */
  char playerName[PLAYER_NAME_LEN]; /* Player name */
  char location[512];               /* Location field */
  allience allie;                   /* Alliences this player has */
  BYTE mapX;                        /* Map X and Y co-ordinates */
  BYTE mapY;
  BYTE pixelX;                      /* Pixel X and Y co-ordinates */
  BYTE pixelY;
  BYTE frame;                       /* Animation frame */
  bool onBoat;                      /* Is this player on a boat ? */
  /* LGM Stuff */
  BYTE lgmMapX;                     /* LGM Map X and Y positions */
  BYTE lgmMapY;
  BYTE lgmPixelX;                   /* LGM Pixel X and Y positions */
  BYTE lgmPixelY;
  BYTE lgmFrame;                    /* LGM Frame */
  /* Misc */
  bool isChecked;                   /* Is this item checked */
  bool needUpdate;
} player;

starts.c

Holds all the starts in the game. Used to get a random starting position.

The two functions used in game are:

void startsGetRandStart(starts *value, BYTE *x, BYTE *y, TURNTYPE *dir);

Returns a random starting point. This is used to decide where the LGM starts

void startsGetStart(starts *value, BYTE *x, BYTE *y, TURNTYPE *dir, BYTE playerNum);

Returns a starting point for a new tank. It currently returns a random starting points that is not directly occupied by another tank. This previously implemented the bolo start algorithm. From the bolo version history document:

"Improved start position allocation for maps with very few start squares defined, to cope better with certain "creative" map designs. For example, if you have a map with only two start squares, and you have a game of two teams where each team has a pillbox close to one of the start squares, then each team will effectively have their own 'private' start square, because with only two start squares to choose from when bringing a new tank into the game, Bolo will choose the one near the friendly pillbox, not the one near the hostile pillbox."

This was added in version 1.09 and removed in a later version ( 1.15?) after much play testing. The results of the play testing showed that the start algorithm was unfairly disadvantaging the team that was losing. For example in a strict game when a player/team is down to one quad or corner of the map and they die, they will restart on the other side of the map and have to make their way back to refuel and continue. This leaves the other side more time to continue advancing. Conversely, the winning team will start in territory they control and be able to refuel straight away.

Another potential start algorithm could be to start closest to an captured base, giving preferences to them being out of pillbox range (and possibly stock levels?)

shells.c

Shells is and ADT the controls all shells in the game. each shell starts with a WORLD coordinate and length the shell travels. From there the shellsUpdate(); function updates its location. The most important function in this ADT is shellsCalcCollision(); which tests to see if the shell hits anything. If the game is a client the shell can hit anything however only hitting your own tank causes any damage. Hitting any other item in the game the client will expire the shell and show the explosion animation but the server has to confirm the change. It the shell makes a change to the map (not a pillbox/base etc) the change occurs in the client immediately but the server has to come through and confirm it. If it does not the change is rolled back. See Networking.

bases.c

pillboxes.c

ADT of all the pillboxes. Probably the most interesting function is pillsTargetTankMove(); that calculates the angle to shoot to hit a moving tank. Note this is done client side currently so it is possible to cheat by modifying this to make the pills not shoot at you.

scroll.c

Module to that determines if the screen can be scrolled. e.g. if the tank is still on the screen and runs the autoscroll algorithm.

messages.c

Handles the scrolling message window via char arrays of MESSAGE_WIDTH size. Also responsible for whether or not a message is displayed and setting of the player name. Calls frontEndMessages(); when a message is to be updated.

mines.c

Simple ADT to remember if a player has seen a mine before or not.

allience.c

Simple ADT module that contains functions to allow to players to be allied. Uses a list structure. Yes I know I spelt it wrong 10 years ago! Each player is connected to an alliance. This could be replaced with something much simplier such as a bit field. However it would limit the number of players to the size of the bit field.

playersrejoin.c

Stores what a player owns when they quit the game. If they rejoin within 5 minutes and the bases/pillboxes are still NEUTRAL then the player is given them back. The player is considered the same if the player name is the same.

screencalc.c

Simple module that calculates the tile from the map square.

sounddist.c

Called by other modules in the backend to play sound. This calculates whether the sound should be loud of soft (or not played at all) based upon its distance to the player. Then calls the front end function frontEndPlaySound();.

treegrow.c

Grows the trees in the game world. Once every game tick treeGrowUpdate(); is called. The algorithm works as such:

  • Randomly choose a square on the map and calculates the tree grow score based upon the surrounding map squares (surrounded by more trees the higher the score)
  • Does this square have a higher score then the square selected last tick?
    • If it does, replace that square co-ordinates with this one and reset tree growing time.
    • If it does not decrement tree growing time.
      • If tree growing time equals zero grow a tree at the location.

building.c grass.c rubble.c swamp.c

Simple ADTs that all do a similar function. They remember how many times a square has been hit by a shell before it turns into

  • grass->swamp (only when on a boat)
  • swamp->water (only when on a boat)
  • building->halfbuilding->rubble

They could all be replaced with the one ADT that does the same job. JBolo does this.

screenbullet.c screentank.c screenlgm.c

ADT's that contain items to be drawn on the main screen.

screenbrainmap.c

Simple ADT that stores the map file that is passed to the brain. This is used rather then the original map structure as under certain brain modes they do not have access to the entire map.

explosions.c and tankexp.c

ADT's responsible for storing the animation frame and lifecycle of the explosion animation.

  • explosions.c is the simple explosion animation when a shell reaches its target.
  • tankexp.c also takes the direction and speed the animation is moving in. It achieves the animation effect by creating explosions. Does damage when the tank finally explodes.

floodfill.c and minesexp.c

Implements a simple flood fill algorithm to fill adjacent craters with water or explode surrounding mines. E.g. for water flood fill

  • Each tick decrement wait time on each item in the flood fill list.
  • If the wait time reaches 0
    • Fill the current square if it meets the conditions (e.g. current square is crater and one surround square is water)
    • Add each of the surround squares to the flood fill list
    • Remove this entry from the list.

log.c

Builds the log file as the game runs. When events occur in the game server they are added to the log file.

void logAddEvent(logitem itemNum, BYTE opt1, BYTE opt2, BYTE opt3, BYTE opt4, unsigned short short1, char *words);

Every tick

void logWriteTick();

is called to write out the location of all tanks, lgms, explosions and shells on the map. It reuses code from the screen redraw preparation function but rather then just getting these elements that are visible on the screen it retrieves them for the whole game world.

After some time you will notice that winbolonetAddEvent(); is called at the same time logAddEvent(); is call as netPNBAddEvent();/netMNTAddEvent(); this is because all 3 functions perform a similar job in collecting the event to either send it to WBN or the log file or to the game server/client. These functions were all written at different times. The code could be simplified by combining them in some form. Maybe not replacing the modules but simplify the complexity in the other modules. Replace the three calls with an eventAdd() which would then call the three different functions as required.

crc.c

Calculates 16 bit CRC's. Code based upon publicly available code.

util.c

Bunch of utility code.

Personal tools