Fly Catch
The fly catch game is going to really benefit from structs to help model the entities in the game. When we think about the game, the entities are really clear. We know that the game is made up of a spider and a fly. We can now model these as structs in our code.
Spider Struct
The player in the game controls a spider. Reviewing the existing code, we can see that the spider needs to store its location on the screen through a combination of spider_x
and spider_y
variables.
To create our spider, we can create a struct with the following details:
Fly Struct
The fly is a bit more interesting. It has a location on the screen, knows if it has appeared, and keeps track of the time to appear and time to escape. These are all individual variables that can now be coded into a fly data struct.
Game Data
Looking at the code, there are hints at another type. We have a procedure called draw_game
that draws everything to the screen. While we could pass this the spider and fly, it would be more convenient to pass it a single value representing the game. This will also mean that we can expand this type as the game grows, adding things like scores and other data.
Implementing the code
Usually you would start with the structs, and then build the functions and procedures up to work with these. As we already have code, we are going to need to adjust this to work with the new data types.
Start by creating the types at the top of the cpp file. Remember that the types need to exist before you can use them. In our code we will need to code the spider_data
and fly_data
structs before the game_data
.
Once you have all three structs, jump down to main, and you can replace the variable declarations with a single game_data game;
declaration. This will contain all the data for the spider and the fly.
The old variable declarations also initialised the variables. We can create some functions to help us with this.
New Spider
To initialise the spider data, I thought to create a new_spider
function. This can set up all the fields of the spider, and then return this data to the caller. The pseudocode would look like this:
In main, you can now call new_spider
and assign the result to the game’s spider:
New Fly
As with the spider, we need to initialise the fly’s data. We can use the same pattern to solve this, and create a function to perform this initialisation. This could be a new_fly
function, using the pseudocode below.
In main, you can now call new_fly
and assign the result to the game’s fly:
Handling Input
The code for handing input is the next part of main that needs to be updated. In this you will need to adjust the old spider_x
and spider_y
to access the spider in the game. To do this, spider_x
will become game.spider.x
and spider_y
will become game.spider.y
.
Update Game
In update game we need can switch the fly details to read from game.fly
. The first place we encounter this is in the if statement to check if it is time for the fly to appear. The old code for this is:
Rather than just coding this as is, we can use this as an opportunity to make the code easier to follow. Here the test is checking if it is time for the fly to appear, so we could create a time_to_appear
function. We be pass the fly and the current time, and return a boolean to say if it is time or not.
In main, we can now call the new function using:
Notice how this helps explain what program is doing at this point. While there is only one line of code in the function, it makes this part of the program easier to understand.
We can do the same thing for time_to_escape
. This would look like this in our code:
Similarly, we can re-code the circle intersection test by creating another function spider_caught_fly
. This can be passed the spider and the fly, and can return true if the spider has caught the fly, once again making the code easier to follow.
We can then call this in main using:
Draw Game
Lastly, we have draw_game
. This already exists as a procedure, but now we can change this to accept just a single game_data
parameter.
Becomes:
The logic for draw game can now also be split out. Passing around spider and fly data is now easy, making it less of a challenge to create functions and procedures that work on this information. When we re-code draw game, we can create draw_spider
and draw_fly
procedures. These can be passed spider_data
, or fly_data
, and use that to draw the spider or fly to the screen. The logic in draw_game
is now much more expressive, showing the digital reality we are creating.
The logic for draw_spider
is very simple at the moment. This will just draw a circle to the screen. The code for this is shown below:
Draw fly will include the check to see if the fly should be drawn. We can code this up as a guard. This is an if statement at the start of the code that returns if the required conditions are not met.
You should now have reimplemented the fly catch game with the new structs. This has helped to make it easier to read and understand the code. We can now pass around spiders, flies, and the whole game data to our functions and procedures.
Going forward we can also make changes to these structs and to the functions and procedures that work on them. As we add new features, think first about the extra data we need and add this to the structs. You can then adjust the functionality in the functions and procedures, which will have access to any new data you add to these structs.
Notice, also, how the new code is starting to incorporate more of the details from the domain itself. We now have functions to test if the spider caught the fly, and things to draw spiders, flies, and the game. The more we can bring the game to life in the code, the easier it will be to work with.