For the coursework forming 100% of my programming module grade, we were asked to create a graphical game for a banking client. The game should use real physics and be appropriate for a waiting room in a bank.
I know, I thought it was odd to have to make this game for a bank but programming for banking clients is a lucrative industry, so I believe they were preparing us to intern for Goldman-Sachs or similar.
Here’s the 10-page report which accompanied the program and the code can be found on Github here!
This report describes the working of a computer program, written to satisfy the needs of a banking client, as well as justifications for methods used and an evaluation of the finished program. The finished program is a video game with mechanics related to finances, designed to entertain clients who are waiting for an appointment whilst being suited to the bank environment.
2- Problem Analysis
The brief given was to create a playable graphics game in which the user launches a projectile which follows real physics at a target. The game should have a scoring system and at least one extended feature. Also, the banking client must find the game suited to entertaining and perhaps informing customers waiting to see a personal bankers. The main aspects of the problem are described below.
A graphical interface which will, as an absolute minimum, display a launcher avatar, plot the trajectory of a projectile fired from the launcher, display an obstacle and display a target. The launcher avatar chosen is a stick-man holding a bow, with the projectile firing from the point at which the bow meets the hand, much like in real life. The trajectory of the arrow fired will be progressively drawn across the screen in a red arc, until the projectile hits a boundary or an object. The first of two targets is a large traditional target of concentric circles, drawn horizontally on the floor, with each coloured ring being assigned a different score value based on its distance from the center. A second target is a small red box, meant to represent an apple, which sits atop the chosen obstacle which is a stick-man dubbed ‘the banker’.
This is similar to the William Tell story, the idea being that the player will want to hit the small target to get a high score though this risks hitting ‘the banker’ who would, be quite upset, if he were to survive the encounter, resulting in the player losing a large amount of points.
The scoring system for the game is simple, the player has a points score which is displayed to them after each attempt. They can raise this score by hitting a target but if they miss all targets or hit the banker they will lose a small or large amount of points, respectively. To provide some relevance to the banking world, points earned or lost correlate to stock values. The player will seek to raise their score to the highest value as their current score and scoring history are displayed on a graph much like they will have seen before. Much like usual, their final score will be the sum of all scores achieved across their attempts, with this directly corresponding to the height of the graph at its leftmost point, but the visualisation of their playing on the graph, coupled with the potential for a negative final score, gives a resemblance to the visualisation of stock market trends.
As an extended feature, the highest player scores will be saved to and read from a text file. This will allow the highest scores achieved across all games played, even upon closing the game and shutting down the host machine, to be displayed at the end of each game and amended if the current player places higher than anyone on the current list. To resemble classic arcade games, a three-character initial is also saved alongside the score to identify the high-scoring player.
In order to give the game realistic physics, equations of motion with uniform acceleration were used to model real physics, often referred to as the “SUVAT” equations. The acceleration forces considered were wind, acting on the x axis only, as is common in video games, and gravity, which is approximated to be -9.81ms-2 and is applied to the y axis.
The graphics were drawn using the York Graphics library exclusively.
3 – Specification
Two new types of structure are defined in the program; the first is used to hold coordinates. The structure ‘Coordinates_t’ contains two integers, used to hold x and y coordinates. This means less individual variables need to be passed into and out of functions which require coordinates, diminishing user error and allowing for both coordinates to be passed out of a function at once. The second structure, ‘PlayerData_t’ is used to hold the player’s 3 character initial and their score, as a 4 character array and an integer respectively.
Defines and Constants
There are many defines used as global constants in the program. These determine the size of the graphics window, the size of the target, the number of attempts the user is allowed and more. Many of these were originally variables but it was found that their value was never changed during the running of the program. Paired with the fact that many of these are used extensively in different functions and many of these were having a large amount of variables passed into them, it was decided that many of these should be changed to defines.
Actual constants are used in several places where a would-be variable is found to be completely static but is only used in the scope of one function and, therefore, does not need to be defined globally.
The main program is also divided into several subdivisions, denoted by comment headings like shown below. Sections like this allow for a clear workflow and greater legibility.
At the start of the main program, as denoted by the comment shown above, a graphics window is opened, an event cue is started and all necessary initialisation subroutines are called. These are all functions from the York Graphics library which houses a multitude of functions for drawing graphics and taking user input without the use of the console. Functions from the library can be spotted by the prefix ‘GFX_’.
After this, a function is called which draws a background for the game with some instructions as to what the game is. The function ‘WaitForClickBox’ is then called, a function which draws a small box with ‘NEXT’ written inside and uses the graphics function ‘GFX_WaitForEvent’ inside a do-while loop to pause the program until a specific event occurs. The original function will continue after any event, including mouse movement and keypresses, occurs. The new function uses if statements to filter these events such that only a mouse click inside the ‘NEXT’ box will break the loop and continue the program.
A do-while loop, dubbed ‘the menu loop’ is then used which will break once the user has chosen to fire a projectile the allowed amount of times, as determined by comparing the variable ‘tries’, which is incremented when a projectile is fired, against a define at the beginning of the program named ‘SHOTS’, A do-while loop is used, because the program will always need to run at least once, meaning it makes more sense to have the check come after the contents of the loop, much like in the ‘WaitForClickBox’ function.
Inside this loop, the wind acceleration is initialised, in order to change the wind after each shot. Functions are then called to draw the background for the game, the target and the user’s avatar, before a second do-while loop is used to allow the user to move their avatar infinitely, with the loop only breaking when the projectile is fired, determined with an integer variable named ‘boolean’ which is given values of 0 and 1 to denote true and false.
The latter of the do-while loops begins by waiting for an event. There are then two nested if statements to determine whether or not the event is one of three events which triggers a change in the program, a left-mouse click, pressing the ‘a’ key or pressing the ‘d’ key. If the event was a key down, the key pressed is obtained and tested to see if it was an ‘a’ or ‘d’ and also if the current x coordinate of the avatar is within allowed bounds. These bounds are set to keep the avatar on screen and also to make sure the user can’t move the avatar right above or next to the target.
If the keypress is determined to be an ‘a’ or ‘d’ the x coordinate variable will be decremented or incremented by 10 respectively. Regardless of which key was pressed, the background, target and avatar will be redrawn when a key is pressed. This will reduce duplicated code but could cause flickering if many keys are pressed in rapid succession.
If the event is a left mouse click, the projectile will be fired and the following events will be triggered.
The function ‘DrawProjectilePath’ is called, a function which takes the coordinates structure, the x coordinate of the center of the target and the wind acceleration. The coordinate structure, at this point in the program, will hold the position of the avatar’s left foot. The function first gets the coordinates of the mouse and then uses them to calculate the magnitude and angle before using these in equations of physical motion to determine the movements of a projectile fired from the bow of the avatar.
The coordinates of the avatar’s left foot are converted to the coordinates from which the projectile is fired and the cursor is moved there. The target position and radius are also used to calculate the position of the apple atop the banker’s head, which is saved as the variable ‘xAppleHitbox’.
The current mouse coordinates are read in and a small circle is drawn there as an indicator to the user. The mouse coordinates and position of the stickman’s hand are then used to create the hypotenuse of a right-angled triangle. This means that trigonometry can be used to turn these two coordinates into the magnitude and angles used to obtain the individual velocities of the projectile in the x and y axis. The magnitude, however, is scaled by dividing it by a constant, in order to allow more variation in mouse coordinates with a diminished effect on the projectile’s path.
A do-while loop is then used to calculate the projectile’s movement and check whether the projectile has hit anything yet. Since there are multiple scenarios which will cause the projectile to stop moving, the integer variable ‘done’ is used as a boolean to decide whether the loop should continue; it is initially set to ‘0’ but is changed to ‘1’ if any of several scenarios are found to be true.
Since the projectile moves in two axis, both axis must be considered, and a link between them must be established. In this function, the velocity in the x axis, calculated through the magnitude and angle, the acceleration due to wind and the current x coordinate are used to find the initial velocity in this axis. From this, the projectile’s position along the x axis is incremented in each iteration and this is used to get the time from launch it would take for the projectile to reach that point. Finally, the time is used to find where on the y axis the projectile would be at the same moment.
Four checks are performed after this, to determine whether or not the projectile should stop moving. The projectile’s current coordinates are checked to see if it has hit the floor, the rightmost boundary of the graphics window, the banker or the apple. If any of these are true, the boolean ‘done’ is set to ‘1’ and the loop is broken. In either case, the graphics window is updated to progressively draw the projectile’s path as it is calculated.
Outside of the loop, once the projectile’s final location is determined, the final distance from the target center is calculated, the absolute distance is calculated and written onscreen. The non-absolute distance is returned from the function to avoid the same distance being returned from two locations; if the banker is hit when distance equals +100 and the projectile lands at a distance of -100, the absolute value could incorrectly indicate that the banker has been hit.
After the distance is saved to an array, this is converted to a score and also saved to a separate array. This is done by the function ‘GetScore’ which initially sets the score to -20 and then use a series of if statements to determine whether something of interest is hit. This way, if a blank section of floor or the screen boundary is hit, the score is set to the default -20 whereas if a target, the banker or the apple is hit then the score is set differently, depending on which is hit. Which of these is hit is determined by the distance returned. For example, the apple will always be hit when the distance equals the target radius plus 40 pixels. If neither the apple or banker is hit, the target is tested.
The variable ‘xtarget’ holds the x coordinate of the center of the target, meaning the rings extend either side of this value and the absolute distance should be tested to avoid unnecessary code to test either side of the center. Earlier, it was stated that this could cause confusion by also duplicating a value which would imply that the banker or apple were hit. However, since the distance is only made absolute in the ‘else’ code after the banker and apple are tested, this will not cause a problem.
To allow the user to view the projectile path before moving on, a ‘next’ continue box is used before the score graph is displayed by calling the function ‘DrawScoreGraph’. In the function, the window is first cleared, the cursor set to white and two axis are drawn. A variable is used to define a margin of ten pixels to better position the graph in the window and the offset, which is the y coordinate of the x axis, is calculated to be half the window height, to sit the axis centrally.
The cursor is next set to red and the cursor is moved to the origin of the axis. A for loop is then used to read in values from the score array and plot them on the graph. The loop uses an integer, ‘i’, which begins at ‘0’ and is incremented in each iteration, to read as many values from the score array as there have been attempts. This is to avoid the program plotting uninitialised points.
The x value at which to plot each point is calculated by dividing the window size minus the margin on either side by the maximum number of shots allowed. This allows the maximum number of shots to be changed without this code having to be altered.
The y value at which the score is plotted is simply the current coordinate held by ‘coords’, which at that point would be the origin, and taking the score value of the current loop iteration multiplied by the ‘graphscale’ constant which is used to ensure that the maximum or minimum possible scores will fit on the graph. This is calculated by taking the size of one half of the graph and dividing it by the maximum score for one shot, multiplied by the number of shots.
After the coordinate of the next score to be plotted on the graph is determined, a line is drawn to it and a small circle is drawn at the point. The cursor will always be at either the origin or, if a point is already plotted in this calling of the function, the previous point drawn. This means that the line plotted from point to point on the graph will be continuous.
The y coordinate of the final point of the graph is then divided by the graph scale to return it to the player’s total score which it will accurately represent, at this point, before the total and change in score are then drawn on screen, the screen is updated and, once the function is left, the function ‘WaitForClickBox’ is called.
Closing the loops
After the projectile path is drawn and the score has been shown to the player, the game background, target and avatar are all redrawn in that order. The boolean variable which shows whether or not the projectile has been fired is set to ‘1’, indicating that it has. After the screen is updated, this will break the second do-while loop in the main program.
Outside this loop, ‘tries’ is incremented to indicate that a projectile is fired and keep track of how many times this has happened. Then, the first do-while loop will test to see if the allowed amount of shots have been taken and, if this is true, the loop will be broken. Statistics
A function is then called to calculate a few statistics about the player’s abilities, draw these onscreen and return the player’s total score. A for loop is used to loop through each value in the scores array and add them while doing the same for the distances array. As well as this, each distance is compared to the current maximum and minimum distances. For this reason, the variable holding the current maximum is initialised as zero and the minimum is initialised as ‘XWINDOW’, the define used to set the graphics window’s size in the x axis. This ensures that the minimum value will change as it’s impossible for the user to take a shot which lands further from the target than the width of the entire window.
Outside this for loop, the mean distance is calculated from the total distance, a rectangle is drawn to cover everything on screen above the floor and all calculated statistics are drawn on screen over this rectangle. The function then returns to the main function.
Extended Feature – Saving
The extended feature present in this program is the ability to save the high scores from players into a file for displaying later. Several functions are used to read and write files, the first of which (in the order in which they’re called in the program) is ‘CheckHiScore’.
This function opens the file for appending. Since this is the first function called which involves files, this is the first point of failure if for some reason the program can’t find the file. If this happens and the program is attempting to open the file in appending mode, the program will create a new file to use. This guarantees that, even if previous high scores are lost, the program can still save the current player’s score to a file, whether or not it was just created.
The file is then closed and reopened for reading. A for loop is used to read each line of the file until the counter exceeds the define ‘HISCORES’ which determines how many high scores are saved and displayed. The top score and initial associated with each is saved into an array of ‘PlayerData_t’ structures. The current player’s score is then compared to all scores in the file and the integer ‘boolean’ is set to ‘1’ if the current player’s score exceeds any of the saved scores. The file is then closed and the function returns the value of ‘boolean’.
If the function returns a ‘0’, then text to the effect of ‘sorry, better luck next time’ is displayed. If the function returns a ‘1’, functions are called to get the user’s initial and save the current player’s score to the file. The first of these functions is ‘GetUserName’.
This function begins by drawing text stating that the user has achieved a new high score and instructs them to type their name. A for loop is then used to ensure that the program only allows for the reading in of three characters. Much like far earlier in the program, a do-while loop is then used and begins by calling the function ‘GFX_WaitForEvent’. An if statement then checks if the latest event was a keypress and, if it was, checks which key was pressed, passing a value to the variable ‘key’ by pointer.
Another if statement then filters by which iteration of the loop the program is currently in. This is done such that each iteration of the loop saves the last keypress to a new character variable. Bar the variable names, the code under each if statement is the same.
In each case, the character variable is assigned the value of the variable ‘key’ plus 96, turning the value returned by the function ‘GFX_GetKeyPress’ into the ASCII value of the pressed key. This character is then placed into a string and drawn onscreen. A variable named ‘charOffset’ which is initialised with the value 0, is incremented with each iteration of the loop and added to the x value controlling where each character is drawn in order to replicate the spacing of a vintage arcade game.
After the Function returns, ‘SaveUserScore’ is called, the function used to take the player’s initial and score and saves it to a file. This is done by first opening the file ‘hiscores.txt’ in read mode. A for loop is then used to read all but the lowest of the high scores into an array of PlayerData structures. The lowest score is not read since getting to this point means that the score has been beaten and will be bumped from the list by the new score to be added. The file is then closed.
The current player’s score will be added to the empty PlayerData structure and the entire array will then be sorted using a bubble-sort algorithm. The algorithm was chosen for its simplicity. Since a very small number of scores will be sorted, speed is not an issue. After this, the file is opened in write mode and the entire sorted array of PlayerData is written to the file in order, the file is closed and the function returns.
The program ends by waiting for the user to click a ‘next’ box before reading in and displaying the high scores, with the function ‘DrawHiScores’. Another ‘next’ box is then displayed and, once it is clicked, the graphics window is closed and the program is terminated.
Testing the Code
To ensure that the program runs on multiple machines, the program will be copied across multiple systems, in the same way in which the final program is to be delivered, and built on these other machines. The program should compile and run on all machines in the same way for this to be deemed a success. User feedback will be obtained by allowing peers to play the game without instruction. This will test the program’s ease of use and the effectiveness of the instructions on screen which is important for deciding how the program should be changed to make it easy to deploy to bank waiting areas with little intervention, as is the intention.
A success will be determined by the user’s understanding of mechanics in the game. For example, users being able to reach the end screen without having to ask what to do next and correctly answering questions after their first play about how the game works will be considered a success. Obtaining a high score is preferable but users’ aptitude for video games is not the focus of the test.
4 – Evaluation
The criteria established in the program specification was met, as determined by two main factors. The first is straightforward; the program was compiled on several machines and all ran the program in much the same way as one another. The user feedback portion of the evaluation was deemed successful when user’s playing the game began to finish with fewer and fewer questions about the mechanics. Initially, user’s struggled to play the game without intervention, leading to simplification of the mechanics (such as removing the requirement to click twice to initiate the firing of a projectile) and the addition of further instructions, answering common questions before new users needed to ask them.
The program is structured to be readable by using proper indentation and white space to effectively group similar and complimentary code, such as the three functions used to draw the game background, target and player avatar.
Comments are used to explain and justify code throughout the program. The program features comments above most lines, especially those whose purpose is not entirely clear from a layman perspective. For example, there are no comments in the below code, since the name of the function and the contents of the strings effectively show the purpose of the code.
Variables and functions are also named to demonstrate their use within the code. Variables and constants beginning with ‘x’ or ‘y’, for example, are used to hold x and y coordinates and functions beginning with ‘Draw’ are named as such as they all are used to draw graphics or text on screen or otherwise affect what is displayed. As well as this, all functions are defined in alphabetical order for ease of finding a specific function.
Bugs and limitations
Only one bug has been found in the program which hasn’t been fixed. The bug no longer affects the program but the fix employed merely moves over the issue. The wind acceleration is defined by a random number between 0 and 6 inclusive. Originally, the number was to be between -6 and 6 but it was found that when a negative acceleration caused the projectile to move left from its previous position, the program would react unexpectedly. This is believed to be due to the equations of motion used, since, when debugging the program line by line, it was found that the coordinates of the projectile were assigned unexpected values at this time.