Chapter 7: Pong - Single Player Mode

MPG LEVEL 2
Chapter 7: Pong - Single Player Mode
With the initialization and first state transition complete, the focus is shifted to the game play itself on
the local device. By the end of this chapter, you will be able to play Pong on your device, albeit only
with yourself as there will not be any communication to an external device yet.
The tasks to take care of are:
1.
2.
3.
4.
5.
6.
7.
8.
Understanding and setting up Pong game data
Updating the position of the local paddle based on the trackball input
Understanding and processing Pong game messages
Activating the system timing
Drawing the paddles and ball on the LCD during game play
Defining and maintaining the ball physics (i.e. how and where does the ball move)
Updating the position of the ball based on the game physics
Determining interactions of the ball with objects on the screen (walls and paddles) and deciding
when the ball is out of play.
To assist in these tasks, some code will be added to existing functions and a few new functions will be
written to take care of other functionality. The starting and ending code is again available on the
website, and areas in pong.c where code is added or updated can be found by searching MPGL2. You
will also note the addition of some preprocessor definitions that will change the way the code is built
depending on how they are defined.
#define SINGLE_PLAYER_MODE
#define NO_PLAYER_MODE
#define BALL_STOPPED
1
1
1
“NO_PLAYER_MODE” which sets up walls on both sides of the game screen so that the Pong game play
will continue forever since no one can score. The purpose is to evaluate the game play to make sure the
ball and paddle movements are working correctly without having to worry about points being scored
that stop the game play. “SINGLE_PLAYER_MODE” is also added to provide optional game play.
These switches can be commented out or uncommented as required and will enable the special modes
that are being programmed to be turned on and off as needed. This is a common tactic used in
programs so that a single file can be maintained while still offering several different operating modes. It
saves maintaining separate copies of the code that do slightly different things for testing or other
required operations, though at a cost of making some code look a bit more complicated as various #ifdef
/ #ifndef, #else and #endif statements are populated around your files.
notes_mpgl2_chapter7.docx
Release 1.0
Page 1 of 21
MPG LEVEL 2
When switches like this are made available, sometimes not more than one should be uncommented for
a build. In this case, it is permissible to uncomment both. In fact, SINGLE_PLAYER_MODE has to be
uncommented as the code for normal game play is not yet written. If your design includes configuration
like this, be sure to clearly identify how the switches are to be used with comments.
7.1 PongGameData
The first thing we will do is set up the core data structures that must be in place to store all the game
information that the system will use. When the Pong application was first being designed, the majority
of the parameters needed were anticipated by visualizing what will happen during game play. So think
about the specific events that happen in a round of Pong. As you imagine the game play, start breaking
everything down into small bits and pieces that can be managed by different functions in the code.
For example, whenever a paddle moves location, the pixels that are lit in the current location must be
cleared, and the pixels in the new location must be loaded with the paddle bit map. To “know” where
the paddle was and where the paddle is, both the old and the new locations will have to be saved in
memory. Since paddles can only move up and down, only the row position data needs to be saved. So
we will need to track that information:
u8
u8
u8
u8
u8Paddle0LocationLast;
u8Paddle0LocationCurrent;
u8Paddle1LocationLast;
u8Paddle1LocationCurrent;
/*
/*
/*
/*
Previous row location of Paddle 0 to erase */
Current row location of Paddle 0 to write */
Previous row location of Paddle 1 to erase */
Current row location of Paddle 1 to write */
The action of the ball is very similar, except both the column and row positions need to be known. The
last and current position of the ball must also be known so it can be erased and redrawn correctly.
u8
u8
u8
u8
u8BallLocationRowLast;
u8BallLocationColumnLast;
u8BallLocationRowCurrent;
u8BallLocationColumnCurrent;
/*
/*
/*
/*
Previous row location of ball to erase */
Previous column location of ball to erase */
Current row location of ball to write */
Current column location of ball to write */
How does the ball move? In this particularly simple implementation of Pong, the ball is only allowed to
move at 45 degree angles. To move the ball one position in any given direction involves changing the
ball location by one row and one column. However, if the ball moves only this amount 10 times per
second (which is the defined update rate of the game), then it will take almost 16 seconds for the ball to
move all the way across the screen since the screen is 160 pixels wide. It needs to move more quickly!
A bit of testing shows that 3 pixels per update looks pretty good. Since this value will clearly impact the
game play, it is a very good idea to write the code in a way that can be easily changed. Regardless of the
values chosen, the selected numbers must be stored as part of the Pong game data.
notes_mpgl2_chapter7.docx
Release 1.0
Page 2 of 21
MPG LEVEL 2
u8 u8BallMotionRow;
u8 u8BallMotionColumn;
/* Number of vertical pixels to move with each update */
/* Number of horizontal pixels to move with each update */
What is missing from the ball movement information? Direction! The ball can move four different
ways, either increasing or decreasing the row and column value. We can therefore define the ball
vector with a value of + or – 1 to indicate the current row and column direction. To make the value
intuitive, a signed number is used.
s8 s8BallVectorRow;
/* -1 or +1 to indicate direction ball is moving (row) */
s8 s8BallVectorColumn; /* -1 or +1 to indicate direction ball is moving
(column) */
The last bit of information that can be included in the Pong data structure is the current game scores
(and at this point we can go back to the PongUpdateScoresLCD() function that was added in Chapter 6
and properly reference LGstPongGameData to get the current score before writing it to the screen).
u8 u8Player0Score;
u8 u8Player1Score;
/* Current Player 0 score */
/* Current Player 1 score */
The complete struct is already defined as PongGameData Type and added to Pong.c . The Local Global
LGstPongGameData is defined as part of the Typedef. LGstPongGameData will be continuously
referenced throughout the game.
7.1.1 Initializing Pong Game Data
As with any variable, LGstPongGameData must be initialized. To be consistent with the way that all
applications have been developed for this system, this initialization shall occur inside PongInitialize().
Find the code “MPGL2” label inside PongInitialize() and add the missing code to complete the
initialization. Notice that the function PongResetGameObjects() makes up part of the initialization.
Take a quick look at that function to see what it does. The paddle and ball information needs to be reset
more often, to the part is given a function to carry out that task. You will have to fill in the missing parts
inside that function, too.
7.2 ANT Initialization
The last part of PongInitialize that needs to be completed is setting up the public ANT radio parameters.
This can be done now since the system knows almost all the setup parameters required for ANT. The
ANT radio needs to be setup because it will have to be activated as soon as the user selects Master or
Slave from the startup screen. Note that even in single player mode the ANT channel needs to be open
so that timing messages are generated and the system will update. Since no communication is expected
in Single Player mode that we are working in for this chapter, we will force the system to be in Master
mode or else we will not get the ANT timing messages that will be used to clock the Pong updates.
notes_mpgl2_chapter7.docx
Release 1.0
Page 3 of 21
MPG LEVEL 2
From the ANT API we see that the Global Global variable GGstAntSetupData needs to be configured with
the Pong application channel parameters. Most of the values were arbitrarily selected for the Pong
application including the main Channel ID parameters for serial number, transmission type and device
type. The network must be 0 since Pong uses the public ANT network. The frequency and transmit
power levels are selected for optimum performance (mid-band and full power). The only Pong-specific
value is the channel period which is set for 0x0ccd = 3277. This value comes about because the
maximum ANT period is 1 second which corresponds to period of 32768 and 3277/32768 = 0.100 which
is the 100ms channel period we want.
All of these definitions are in pong.h. Any changes – like to the device serial number when it comes time
to play Pong with a bunch of other people – should be made only in the .h file. The code in
PongInitialize() is simply assigning all the setup values to the elements of the struct:
/* Configure the ANT radio */
GGstAntSetupData.AntChannel
GGstAntSetupData.AntChannelType
GGstAntSetupData.AntNetwork
GGstAntSetupData.AntSerialLo
GGstAntSetupData.AntSerialHi
GGstAntSetupData.AntDeviceType
GGstAntSetupData.AntTransmissionType
GGstAntSetupData.AntChannelPeriodLo
GGstAntSetupData.AntChannelPeriodHi
GGstAntSetupData.AntFrequency
GGstAntSetupData.AntTxPower
=
=
=
=
=
=
=
=
=
=
=
ANT_CHANNEL_PONG;
ANT_CHANNEL_TYPE_PONG;
ANT_NETWORK_PONG;
ANT_SERIAL_LO_PONG;
ANT_SERIAL_HI_PONG;
ANT_DEVICE_TYPE_PONG;
ANT_TRANSMISSION_TYPE_PONG;
ANT_CHANNEL_PERIOD_LO_PONG;
ANT_CHANNEL_PERIOD_HI_PONG;
ANT_FREQUENCY_PONG;
ANT_TX_POWER_PONG;
Since you are working with a variable declared in another source file, it must be declared as external in
Pong.c. This should be done in the Global Global variable section under the “Existing variables”. While
you are up there writing that code, we might as well add the extern declarations for the ANT flag
register and the incoming and outgoing message lists that are needed.
extern
extern
extern
extern
u32 GGu32AntFlags;
AntSetupDataType GGstAntSetupData;
AntDataMessageStructType *GGsAntDataIncomingMsgList;
AntDataMessageStructType *GGsAntDataOutgoingMsgList;
/*
/*
/*
/*
From
From
From
From
ant.c
ant.c
ant.c
ant.c
*/
*/
*/
*/
7.3 Initializing Trackball Data
You may have noticed the code in PongResetGameObjects that sets up a value for the trackball data. To
find out what the GGstButtonTrackballPosition object is, look at the button API. The counters in the
struct will change based on input from the trackball that is handled by the button application. Pong will
use this Global Global struct to determine paddle location. The button API provides the functionality to
control how the counters that increment and decrement from the trackball are moved. Maximum,
minimum and starting values are all defined. The starting value is reset often, so it is set inside
notes_mpgl2_chapter7.docx
Release 1.0
Page 4 of 21
MPG LEVEL 2
PongResetGameObjects(). However, the maximum and minimum values are always fixed in the Pong
app, so they can be set once inside PongInitialize.
One other reset function is added to the code, PongResetGameFlags(), but it does not do much yet.
Right now it clears a single flag but this will grow to include more flag clears as the application itself
grows.
The chapter 7 start code has been modified to leave you with some code to add to get the trackball data
properly configured. To allow access to the variable needed, it must be brought in to the Pong.c file
using an extern Global Global definition. Then add initializations for the maximum and minimum
counter values. You can find these value definitions in the pong.h header file. Like all the constant
definitions, their names will tell you what their purpose is, so it should be easy to find them.
7.4 Pong Messaging
We have to start tying in to the ANT application and using some of the information from Chapter 5. The
Pong Message protocol has to be introduced at this point because the AntTick() message discussed in
the ANT chapter is the essential driver of parts of the Pong application. Remember that ANT
initialization is already complete using the Global Global ANT setup data type. From the API we have the
public functions AntOpenChannel and AntCloseChannel. We also have the incoming and outgoing
message lists that applications can use to access messages received from the ANT radio, or provide
messages to be sent to the radio. All of that "just has to be used" because by this point it should be
understood (if you do not understand it yet, you should go back to chapter 5 or re-read the API overview
in chapter 6).
What has not been discussed and is not defined in the API is the format of the data that is contained in
the messages. This is where some more design decisions have to be made. Whenever two systems
trade information, there must be a protocol in place to define the data that is passed back and forth –
just like the entire ANT protocol messaging structure. For most of the discussion about Pong Messaging,
we should be talking about two Pong devices exchanging data over the air. Those messages will be sent
and received through the Message Lists that the ANT application manages. While ANT does not care
about the content of the data messages sent between two Pong players, that content must be defined
and setup so that each side of the communication knows what to do with it when it arrives.
This is important now because the Pong protocol format will also be used by the ANT application for the
AntTick() message. Even though we are only running Pong in Single Player Mode in this chapter, the
Pong application must know how to read Pong Protocol messages, parse the data, and react accordingly
because AntTick() will be populating messages critical to the system operation.
notes_mpgl2_chapter7.docx
Release 1.0
Page 5 of 21
MPG LEVEL 2
The message protocol designed for the system should be constructed to provide the messaging needed
for Pong, but also be expandable within the Pong application and further to other applications that may
be created in the future. Once again there is a choice to be made about the level of complexity to be
designed into the system. The basic fact is that with every ANT data message, there are 8 bytes of data
available to assign meaning to when they come over the air (meaning that is interpreted only by an
application that reads the message). In many packet-based communication systems – even in ANT itself
– messages will contain information like message number, message length, checksums and the data
payload. Since the larger ANT message frame takes care of checksums and the message integrity, that
level of detail can be eliminated from the system protocol (the assumption is made that ANT will
correctly pass a verified message to the message buffer). The data size is fixed at 8 bytes, so there is no
need for a length byte.
The system does need to have various messages, so having a message number makes sense. Is one byte
enough? That provides 256 different messages that the system could process while still leaving seven
data bytes for the message content. If a two-byte message number was used, there would be many
more messages available for system growth, but one less byte of information could be communicated all
the time. Given the relatively simple complexity of the Pong application and even considering the future
needs of the system for many apps to come, it is decided that a single message number will be used. If
the system grew beyond expectations, it would be very easy to assign the last available message as a
special case where additional bytes of the message could make up an addition part of the message
number.
For now, the message numbers will be broken down as follows:
1.
2.
3.
4.
Message number 0xF0 – 0xFF: Reserved for ANT system information messages
0x01 – 0x20: Pong application messages
0x20 – 0xEF: Unassigned
0x00 – undefined
The document MessageProtocol.pdf shows the complete message protocol required for Pong even
though we have not yet reviewed what is needed with all the messages. For now, take a look at the
ANT_TICK message to see how the data fields are assigned (Figure 7.4.1).
notes_mpgl2_chapter7.docx
Release 1.0
Page 6 of 21
MPG LEVEL 2
Pong ANT Message Protocol
Message Name
IDLE
Purpose
Establish connection
MSG ID
0x01
D0
0xAA
D1
0x55
D2
0x00
D3
0x00
D4
Master
Status
D5
MSG #
HIGH
D6
MSG #
LOW
DATA
Exchange game data
0x02
Column
motion
Master
Status
MSG #
HIGH
MSG #
LOW
Update score and report
user readiness to begin
0x03
Starting
Column
Vector
(signed)
Player 1
Score
Row
motion
READY
Starting
Row
Vector
(signed)
Player 0
Score
Player 0
Status
Player 1
Status
0x00
MSG #
HIGH
MSG #
LOW
GAME
Game data
0x04
Current
Row
Vector
(signed)
Current
Column
Vector
(signed)
Paddle 0
Current
Row
Paddle 1
Current
Row
Ball
Current
Row
ANT_TICK
ANT_TICK
0xFF
0x00
0x00
0x00
0x00
MSG #
HIGH
Ball
Game
Current Status
Column
MSG #
MID
Notes
D0 and D1 are sentinel values that
could be used to further verify the
authenticity and integrity of this
message.
Signed values are -1, 1 or 2. When
received by ANT, this will appear as
0xff, 0x01 or 0x2.
Game status bits:
Bit 0: Set if game in progress
Bit 1: Set if player 0 has scored
Bit 2: Set if player 1 has scored
Bit 3: Set when Player 0 has won
Bit 4: Set when Player 1 has won
Bit 5: Game play is paused
Bit 6: Player 0 has quit
Bit 7: Player 1 has quit
MSG #
LOW
Figure 7.4.1: ANT message protocol definition
Since there is no control over what application reads what message, the applications themselves must
be responsible for following the system rules and not destroy messages that do not belong to them. The
only thing controlling that right now is the simple statement above that assigns certain message
numbers to certain applications. A more public system could better protect messages if it were
necessary, but that level of complexity is not needed here.
7.5 Processing Pong Game Messages
By the time the Pong application is complete, all of the messages shown in the protocol will be
implemented and doing useful things in the code. The Pong application will be updated to handle all of
the different message types in Chapter 8. Regardless of how many messages there are for the Pong
application, the process for reading and parsing a message is the same:
1. Determine if there are any messages in the queue
2. Look at the message number of each message:
a. If the message is for the application, read the message and then delete it from the
queue.
b. If the message is not for the application, do not read the message and do not delete it
from the queue.
This functionality will be provided by PongProcessGameMessages(). For now, the message handler
written for Pong will only handle the ANT_TICK message. This is a system-level message designed to
provide an indication that an ANT message period has elapsed. The use of this message is described
further in the next section.
notes_mpgl2_chapter7.docx
Release 1.0
Page 7 of 21
MPG LEVEL 2
PongProcessGameMessages() follows a very similar structure as AntProcessMessage() from Chapter 5.
However, the message is not a local array in the function, but contained at the end of the Global Global
GGsAntDataIncomingMsgList . You will need your pointer and linked list hat on to figure out how the
messages are processed. Remember that this entire function does not care that it is running on an
embedded system: it is simply C programming. If this were a lab assignment, you would be given the
format of the linked list that holds the message structure and the task would be to write code to parse
the list based on the first byte inside the message data array.
As part of this chapter’s exercises, there are four places in PongProcessGameMessages() where code
needs to be added to make the function work properly. A very good question to ask yourself is if you
know how to setup the debugger to allow you to see the pointers and data structures you are working
with and make sure that your pointers are properly being managed? Expertly using the development
tools is essential when it comes to debugging tasks that are increasingly complicated. Work through the
code and add the missing lines where prompted. Build the code and run it to make sure what you
intend to be happening is actually happening.
The only message that PongProcessGameMessages() currently handles is MESSAGE_ANT_TICK and all it
does is set _PONG_UPDATE_TIME which is a critical part of the application code later on. The function
also dequeues the message that was just read per the rules of the system. The switch statement will
grow in Chapter 8 to include handlers for the four Pong messages that you see in the protocol.
7.6 System Timing
As previously discussed, the LCD and game play updates will be designed to occur at 100ms intervals.
Since data is transferred between devices with an ANT message period of this interval and because
updates occur using the data transferred, it makes sense to use ANT as the time signal to trigger each
update. The big danger here is if ANT stops sending messages. The Master device will never stop
sending messages as long as the channel is open, so AntTick() messages will always occur and
subsequently the _PONG_UPDATE_TIME flag can always be relied upon for timing. The only caveat is
that the application must be sure to clear the flag every time it sees that it is set.
Similarly, the Slave will always send a message to the Host (not necessarily a radio message unless one
has been queued up) at the message period as long as its channel is open and it has paired with a
Master. Even if the Slave missed receiving the last message it was expecting from the Master, it will
send a message to the host to tell the system that it did not hear any data from a Master on a particular
period. The Slave device will eventually stop sending messages if enough consecutive messages are
missed from the Master, as that causes it to timeout and close its channel so it is no longer paired. This
happens to be a limitation / known bug of the firmware at this point in the design, though it is easily
fixed by detecting the dropped channel. This is left as a self-directed exercise if you would like.
notes_mpgl2_chapter7.docx
Release 1.0
Page 8 of 21
MPG LEVEL 2
For testing in SINGLE_PLAYER_MODE the system will always be the Master device so timing should be
100% reliable. Since the message processing function is already written to read ANT_TICK messages,
really all that is left is to make a call to open the ANT channel which will immediately start sending
broadcast messages even if no data is supplied – this is the essence of the ANT radio system.
The channel does not need to be opened until the game play start sequence begins (i.e. after the Idle
state). Once the channel is open, then the Pong application must make frequent calls to
PongProcessGameMessages() to ensure that the ANT message buffer / mailbox does not fill up (though
in this case, if ANT_TICK messages overflowed each other it would not matter. The system still runs with
a presumed 1ms interval between state machine services, so system updates should have no problem
running correctly and updating in a manner that appears flawless to the user.
7.7 Physics
Perhaps the most interesting part about the Pong application is the movement on screen and the
interaction between objects that takes place – at least from the user’s perspective. While programming
the movement is straight forward, the object interaction is a bit tricky. That being said, careful thought
is needed for all of these tasks to ensure the movement looks good.
7.7.1 Paddle Movement
Moving the paddles ends up being very simple and it is a good place to start this discussion because the
hardest part about moving a paddle is directly transferable to moving the ball. The majority of the work
was done a long time ago when the trackball limits were defined based on the size of the paddle. All of
the data needed is in the GGstButtonTrackballPosition struct. The code is nearly identical for Paddle0
and Paddle1, although it is different enough that there was no advantage in creating a PongMovePaddle
function that could be made generic enough to pass a paddle number in. So you will find code
separately in for both paddles within the PongGamePlay() function. Since the code for the two paddles
is so close to being the same, all of the Paddle0 code has been left for you to author as a chapter
exercise. Since we are in Single Player mode, your system will not have any paddle movement until you
put the firmware in.
The essence of any object movement in this entire system is summarized as follows:
1. You must know the size of the object
2. You must know where the object currently is so you can erase it
3. You must know where the object is supposed to go so you can redraw it there.
So let us look at the code segments that implement Paddle 1’s movement:
notes_mpgl2_chapter7.docx
Release 1.0
Page 9 of 21
MPG LEVEL 2
/* Clear the paddle bitmap where it was before */
LCDClearRAMArea(LGstPongGameData.u8Paddle1LocationLast,
LCD_PONG_PADDLE1_COLUMN,
LCD_PONG_PADDLE_YSIZE, LCD_PONG_PADDLE_XSIZE);
The key parameter for clearing the current paddle location is the “LocationLast” field inside
LGstPongGameData. The rest of the function call arguments are just constants from pong.h that will
never change.
/* Draw the paddle in the new spot */
LCDLoadGrayscaleBitmap(LGstPongGameData.u8Paddle1LocationCurrent,
LCD_PONG_PADDLE1_COLUMN,
LCD_PONG_PADDLE_YSIZE, LCD_PONG_PADDLE_XSIZE,
&aau8SymPaddlePlayer1[0][0]);
“LocationCurrent” is updated automatically by the system outside of the PongGamePlay() function. For
a Master device, Paddle0 is updated by movement of the trackball. Paddle1 is updated by messages
that come in over the air from the other device who reports its paddle position. This is why Paddle1
does not move in Single Player mode, because there is no device messaging occurring. For a slave
device, the exact opposite is true. Remember that the last argument in the function call is the address
of the first element of the paddle bitmap. There is a separate bitmap for Paddle0 and Paddle1 since
they are shaded differently.
The next part of the function takes care of redrawing a paddle. Any time an object is erased and
redrawn, we want to update the minimum number of LCD pixels. That minimum area will be defined by
a rectangular area of pixels starting with the pixel of either the old image or the new image that is
closest to pixel 0,0 and stretching to the pixel of the either the old image or the new image that is
closest to 159, 160.
Figure 7.7.1.1 illustrates the concept considering a rectangular object currently at position 8,3 (the top
right pixel in the bitmap) that needs to move to the new position at 14,12. The code must carefully
compute the area and consider which direction the object is actually moving to determine what pixel it
needs to reference to define the whole update area. The bitmap position is always referenced to the
pixel in the top right corner, but since the object is moving down, the lowest pixel in the update area is
found by adding the new bitmap position (top right pixel) plus the vertical and horizontal size of the
object. The total area to update in the example moving the 5 x 5 pixel object is defined by the rectangle
starting at (8,3) and ending at ( (14 + 5 - 1), (12 + 5 – 1 ) ) = (18, 16). The ‘-1’ term comes in there
because of the way the pixels are referenced.
notes_mpgl2_chapter7.docx
Release 1.0
Page 10 of 21
MPG LEVEL 2
Figure 7.7.1.1: Determining LCD area to update during object movement
So the code must set the four values in GGstLCDUpdateDataparams u16RowStart, u16RowEnd, u16
ColumnStart and u16ColumnEnd. For the paddles, the column values never change since paddles can
only move up and down (unless you change the game to let them move left and right, too!).
/* Set the parameters to update paddle 1 on the LCD screen*/
if(LGstPongGameData.u8Paddle1LocationLast < LGstPongGameData.u8Paddle1LocationCurrent)
{
GGstLCDUpdateDataParams.u16RowStart = LGstPongGameData.u8Paddle1LocationLast;
GGstLCDUpdateDataParams.u16RowEnd
= LGstPongGameData.u8Paddle1LocationCurrent +
LCD_PONG_PADDLE_YSIZE - 1;
}
else
{
GGstLCDUpdateDataParams.u16RowStart = LGstPongGameData.u8Paddle1LocationCurrent;
GGstLCDUpdateDataParams.u16RowEnd
= LGstPongGameData.u8Paddle1LocationLast +
LCD_PONG_PADDLE_YSIZE - 1;
}
GGstLCDUpdateDataParams.u16ColumnStart = LCD_PADDLE1_COLUMN;
GGstLCDUpdateDataParams.u16ColumnEnd = LCD_PADDLE1_COLUMN + LCD_PONG_PADDLE_XSIZE - 1;
GGu32LCDFlags |= _LCD_UPDATE_SCREEN;
notes_mpgl2_chapter7.docx
Release 1.0
Page 11 of 21
MPG LEVEL 2
Ultimately, GGstLCDUpdateDataParams get set to the correct values for the LCD_RAM update. The flag
to queue an LCD update is set and that completes the paddle update.
7.7.2 Ball Movement
Updating the ball is the most complicated piece of code in this section. The new ball position is
calculated inside PongGamePlay () after both paddles have been updated. This order is important
because the paddles should be in their new position before the ball moves to give the players the
benefit of the doubt on last-minute reactions. The code to redraw the ball is nearly identical to the
Paddle movement except both row and columns need to be updated. The code that actually
recalculates the ball position is PongUpdateBallPosition() which is the function we need to look at.
The task that PongUpdateBallPosition accomplishes is not that difficult, but the implementation is a bit
challenging. Instead of the new position being provided by human input through a trackball, the new
location is provided by the PongGameData ball movement and ball vector values in conjunction with the
ANT timing that is driving the system.
The code has been designed for expandability so that each ball update (which will be occurring every
100ms) can move the ball in any way, not just at 45 degree angles, though for now we are limiting the
movement to 45°. Other obstacles could be added into the game arena to spice things up a bit on
different levels of challenge and would be automatically accommodated. It also supports changing the
number of players from 0 to how ever many can fit on the screen (four-player Pong would be fun). The
only thing that it does not support immediately is multi-ball, though that could be added with just a bit
more work.
The premise of ball movement is single-pixel, single-direction updates until all of the pixels to move have
been moved. As long as the ball does not hit anything, then ball movement becomes straight forward.
This is accomplished by simply looping through code that looks at the ball movement values and ball
vector values and moves the ball one pixel at a time while calculating if it “hits” anything. The ball
cannot simply be moved to the new location calculated from the ball movement values all at once – it is
very important to move one pixel at a time, and for every column pixel moved, a row pixel should be
moved as long as there are remaining column and row pixels left to move. With 45° motion this will
always be true, but later the ball movement values may hold different row and column values.
To figure out if the ball has hit something, the “proposed” location of the ball is checked to see if there
are any lit pixels in locations where the ball’s pixels would also occupy if it moved there. This is pretty
easy as long as you are careful to check ever pixel location along the leading column edge pixel during
row movement, and every leading row edge pixel during column movement. The “leading edge” is the
pixels that would hit an obstacle first as a result of the movement. The assumption that the current
algorithm makes is that the object being moved is rectangular (the stationary objects do not have to be
rectangular). Row and column collisions work in the same way.
notes_mpgl2_chapter7.docx
Release 1.0
Page 12 of 21
MPG LEVEL 2
If an interaction does occur, then the direction of the ball must be changed and the current position
recalculated taking the “bounce” into consideration. This has to be done carefully to ensure that a ball
bounces correctly in all scenarios, like if it ends up exactly in a corner where the wall and a paddle come
together. We also must make sure that any bounces that occur do not change the “speed” of the ball by
missing a pixel movement or miscalculating where the ball should be after it bounces.
The implementation of PongUpdateBallPosition() is described here briefly. Details on looking up pixel
values are further described in Chapter 4 during discussions of the LCD RAM organization. The entire
movement code is contained in a while loop that runs until no more pixels are left to move, where the
total pixels to move are the sum of the columns and rows to move that initially come from
LGstPongGameData u8BallMotionRow and u8BallMotionColumn:
while( (u8RowsToMove + u8ColumnsToMove) != 0)
{
}
The ball row is moved first followed by the column movement. During each pixel movement, a collision
has to be checked.
/* The new ball row is the current location plus the vector */
u8BallLocationRowNew = LGstPongGameData.u8BallLocationRowCurrent +
(u8)LGstPongGameData.s8BallVectorRow;
/* The row to check depends on which way the ball is currently moving. If the ball is
moving up on the screen, then check collisions with the top row of the ball graphic.
If the ball is moving down on the screen, then check collisions with the bottom row of
the ball graphic. */
u8RowToCheck = u8BallLocationRowNew;
if(LGstPongGameData.s8BallVectorRow > 0)
{
u8RowToCheck = u8BallLocationRowNew + LCD_PONG_BALL_YSIZE - 1;
}
/* Check each of the columns that the current row is occupying.
For even column, check the low byte, otherwise check the high byte */
u8ColumnToCheck = LGstPongGameData.u8BallLocationColumnCurrent;
u8NibbleMask = 0x0f;
if(u8ColumnToCheck & 0x01)
{
u8NibbleMask = 0xf0;
}
bCollision = FALSE;
/* Check for any lit pixels in the LCD RAM where the ball will occupy */
for(u8 i = 0; i < LCD_PONG_BALL_XSIZE; i++)
notes_mpgl2_chapter7.docx
Release 1.0
Page 13 of 21
MPG LEVEL 2
{
/* Column index is divided by two (fractions are dropped) */
if(GGaau8LCDRAMImage[u8RowToCheck][(u8ColumnToCheck + i) / 2] & u8NibbleMask)
{
bCollision = TRUE;
}
/* Toggle the nibble mask to the opposite nibble */
u8NibbleMask ^= 0xff;
}
The last part of the function loop handles a collision. In the event of a collision, the proposed new ball
location is not valid because the ball should bounce off the pixel that is in the way. When the ball
bounces, the current ball vector must be flipped since the ball is now bouncing the other way. This is
where the code would have to change to allow different bounce angles. For now, it is very easy to make
the switch by simply negating the correct ball vector value depending on if the bounce was due to
horizontal or vertical movement.
/* If there is a collision, bounce the ball: assign new position and vector */
if(bCollision)
{
LGstPongGameData.s8BallVectorRow = -LGstPongGameData.s8BallVectorRow;
u8BallLocationRowNew = LGstPongGameData.u8BallLocationRowCurrent +
(u8)LGstPongGameData.s8BallVectorRow;
}
LGstPongGameData.u8BallLocationRowCurrent = u8BallLocationRowNew;
u8RowsToMove--;
} /* end of row collision check */
The code is repeated for the column movement, except the end check also has to determine if the ball is
out of bounds in which case the update could be stopped and the function could exit. This is the
essence of scoring a point and a flag is set accordingly.
7.7.3 Scoring
Determining if a point has been scored also happens inside the ball movement function as they are
directly related. The hardest part of wireless Pong is determining if a point has actually been scored.
While it is easy to do on the local device because the local paddle and ball position are always up to
date, making the calculation using the current position data of the remote paddle poses a problem.
Even if the game could be designed to rely on information being received on time, there is always a
possibility of missing a message or two at exactly the right (or rather, wrong) moment such that the
game play would make decisions on stale information and incorrectly end or continue a game.
notes_mpgl2_chapter7.docx
Release 1.0
Page 14 of 21
MPG LEVEL 2
A good way to get around this (at least until a better way is figured out), is to allow only the local device
to determine if the remote device scores a point. That means that when the ball is on the remote
device's side, the local device should assume the remote player has moved their paddle into position so
the ball bounces. Then the local device waits for the next message to arrive which will contain a flag bit
to indicate if the assumption was correct or not. In the worst case of missed messages, the ball will
appear to bounce off nothing, or it will appear to hit the paddle but a moment later "Point Score" will be
announced, until the next message is properly received with the “scored” bit set at which time the local
device would redraw the ball back in the scored position and indicate a point was scored. During
testing, this ended up not looking too bad, though it is certainly noticeable at times and if this were a
real consumer product you would definitely get some complaints. Room for improvement, for sure!
7.8 State Machine Updates
All the mechanics are in place now so we can start looking at additions to the state machine to make use
of the new functions and get the game engine running. Code was written in Chapter 6 to start up Pong
and advance from the Idle state when a button was pressed. That functionality is the starting point for
providing the application progression to complete Chapter 7. Figure 7.8.1 shows an updated – but still
fairly basic and incomplete – state diagram for what the state machine will become by the end of this
chapter.
7.8.1 PongIdle()
The updated Idle state will advance on either button press but always selects Master mode for ANT
when SINGLE_PLAYER_MODE is active. When a button is pressed and the state beings to exit,
AntOpenChannel() is used to tell the ANT application to turn on the radio. Even though we do not
intend to communicate to a remote device in single player mode, the radio must be operating to
provide AntTick() messages to the Pong application.
Note that as the state transitions, all the code to initialize the next state is executed during the exiting
code of the previous state (LED updates, LCD updates if necessary, flag setting, etc.). This is really the
only place that code that should only execute once can be put inside a state machine since all the other
code in the state will run continuously while the state is held. You could use some flag bits to force
certain code to run only once, but taking care of initializing the next state in the previous state really
makes the most sense and saves extra code and RAM for flags.
That being said, there are other flags necessary to help manage the system.
_PONG_STATUS_ADVANCE_DATA is a flag used to queue the next state to advance. This is not
something that you would think to design in at this point, but rather a product of the future when two
devices are communicating and additional state control is required. It is added at this time to help the
code here in Chapter 7 more closely match the final code that you will see in Chapter 8 at which time
the purpose of the flag and others like it will be discussed.
notes_mpgl2_chapter7.docx
Release 1.0
Page 15 of 21
MPG LEVEL 2
Figure 7.8.1: Updated state diagram
notes_mpgl2_chapter7.docx
Release 1.0
Page 16 of 21
MPG LEVEL 2
7.8.2 PongDataExchange
This is somewhat of a dummy state for now and will only run once since the flag
_PONG_STATUS_ADVANCE_DATA is already set. A single device does not need to exchange data with
itself, but this is the point in the dual device mode that the two systems would exchange any game
information that both devices would need to synchronize. The framework is put in place now for single
player mode to allow easier integration of the final code for two player mode.
The state does give us a chance to process any game messages that would have been queued from the
ANT application. You will notice from this point on that every state will start with a call to
PongProcessGameMessages() because the ANT radio is on and will be adding messages continually to
the incoming buffer at a rate of 10 per second. If any state fails to include a call to process the Pong
messages, then the buffer will quickly overflow. The buffer is designed to handle this properly by
wrapping around and overwriting old messages, it would be poor design practice to allow this to happen
when the system should avoid it. PongProcessGameMessages() will do something only if there are
messages received, so most of the time the state will process very quickly and not bog down the overall
system.
In the final version of the code, DataExchange() exits when both devices have confirmed they have
traded the information they need. That implies that the system is just about ready to start playing Pong.
As the system advances to PongReady(), the LEDs and LCD are updated so the users know that
everything is good to go. The exit from DataExchange() writes a message prompt on the LCD to ask the
users to press Button1 to start the game.
7.8.3 PongReady()
PongReady() is a user-held state where the only way to advance is through user input. This ensures the
players have time to react to what is going on and ensure that they are prepared to start some serious
gaming. The green LED will be blinking to imply the system is happy though not totally happy as it is
trying to gain the attention of the user to start playing. The call to PongProcessGameMessages here is
very important since user input can be anywhere from slow to infinitely slow and a lot of messages
could accumulate if they were not being read and cleared off the queue.
In single player mode, all we need to look for is Button1 and the state can advance. There is no need to
worry about synchronizing any devices here, so the state simply updates the LCD and LEDs and
progresses to the next state upon button press. It also sets some critical flags needed for the main game
function to properly run.
notes_mpgl2_chapter7.docx
Release 1.0
Page 17 of 21
MPG LEVEL 2
7.8.4 PongStartDelay()
The user interface is again considered here. If you clicked the button to start a game and the game
immediately changed screens and started running, you might be lost for a second while your eyes found
your paddle, saw where the ball was starting and took in any other visual information that was available.
When the user presses the button because they are ready in the previous state, the screen is redrawn to
remove the message and draw the entire game screen for the users to observe. PongStartDelay()
ensures that the players have a few seconds to look at everything and be ready to start. This will also be
an important part of device synchronization in two player mode. A nice feature would be to display a
countdown in the messaging area at the bottom of the screen to really give the users warning about
when the ball will start moving.
7.8.5 PongGamePlay()
PongGamePlay() is just about 200 lines long making it one of the longest functions / states in the entire
firmware package for this course. However, about half of that code is practically repeated as there is
very similar functionality involved in moving Paddle0, moving Paddle1 and moving the ball as we have
already discussed. There are two key concepts to understand about the code while this state is running:
1. Only when an ANT_TICK message is read in PongProcessGameMessages() will the state start
running the code that updates the game objects. The game status also must be in the right state
for the updates to occur.
2. When an object update is started, PongGamePlay() must run 3 times to completely update the
two paddles and the ball. This is because separate LCD updates are carried out for each object
on the screen to minimize the amount of data that must be sent to the LCD with each screen
refresh. During each update, the LCD state machine will run to refresh the screen element.
Once that is complete, PongGamePlay() runs the next set of the three object updates on the
next ANT_TICK.
Controlling all of those conditions is done with a few flags in LGu32PongFlags along with some other
status bits. During SINGLE_PLAYER_MODE, some of these flags have been permanently set to force the
game play to work properly in this mode. The code shown here is ultimately what runs to allow the LCD
objects to update correctly.
/* Process the game loop only if the game is on or a final update is required */
if( ( (LGu32PongFlags & _PONG_STATUS_REMOTE_GAMEON) && (LGu8PongGameStatus &
_PONG_MSG_STATUS_IN_PROGRESS) ) ||
(LGu32PongFlags & _PONG_LCD_LAST_UPDATE) )
{
/* Refresh the LCD every time the _PONG_UPDATE_TIME bit is set as long as another
update isn't already in progress. This bit is set based on the ANT message rate. */
if(((LGu32PongFlags & _PONG_UPDATE_TIME)||(LGu32PongFlags & _PONG_LCD_UPDATING) ) &&
!(GGu32LCDFlags & _LCD_FLAGS_UPDATE_IN_PROGRESS) )
notes_mpgl2_chapter7.docx
Release 1.0
Page 18 of 21
MPG LEVEL 2
The rest of the code is just the paddle and ball updates. PongGamePlay() will run endlessly until
someone scores a point in the Pong game. If you are an extraordinarily skilled Pong player, or happen to
be in NO_PLAYER_MODE, the game will last forever and never stop. In SINGLE_PLAYER_MODE you can
kill yourself to quit (the game is set to go to 3 points). In NO_PLAYER_MODE, you will have to press the
reset button to terminate game play, or write some code to allow a button press of some sort to force
an exit.
7.8.6 PongPointScore()
Inside the ball movement function a flag is set if a point is scored. This queues a final execution of
PongGamePlay() to make sure the screen updates and shows the position of the ball that scored and
then the state machine advances. The ANT channel is still open at this time, so
PongProcessGameMessages() must still be called to keep the message buffer clean. The state waits for
the current LCD update to take place and then queues up data for a new LCD update that announces the
point that was scored. More flags are used to choose the correct message to display based on who
scored. The player scores are also updated.
If the total number of points to win a game have been reached by one of the players, than additional
updates are done to the LEDs and some other flags are set to steer the state machine execution toward
wrapping up the game and returning to the start screen.
7.8.7 PongPointWait()
The SM hands off to PongPointWait() to provide a delay while the scoring message is displayed to the
user. PongProcessGameMessages() is still executing continuously during this delay because the ANT
radio is still broadcasting. The flags set in the previous state are assessed to choose the next state that
executes once the delay is over. If more points are needed to finish the game, the application returns to
PongDataExchange(). If the game is over, the app heads to PongAntChannelClose() just as the state
diagram indicates.
Depending on which choice is made, some housekeeping has to be done. If the game is over, the
sequence to close the ANT channel is started with a call to AntCloseChannel(). From this point on, we no
longer need any ANT timing signals, so no worries about a lack of AntTick() messages. Note that
AntCloseChannel() does not immediately close the channel, so the system must keep processing Pong
messages from ANT for a little while longer.
In the case of continued game play, a bunch of game variables need to be reset or else the next round of
game play will not start correctly. The PongReset functions take care of all the re-initializing.
if( IsTimeUp(&GGu32SysTime1s, &LGu32Timeout, SCORE_DISPLAY_TIME) )
{
/* Choose next state based on WINS flags */
if(LGu8PongGameStatus & (_PONG_MSG_STATUS_P0_WINS | _PONG_MSG_STATUS_P1_WINS) )
notes_mpgl2_chapter7.docx
Release 1.0
Page 19 of 21
MPG LEVEL 2
{
/* Close the ANT channel and advance states to wait for channel to close */
AntCloseChannel();
GGPongStateMachine = PongAntChannelClose;
}
else
{
/* Make sure all game data is reset */
PongResetGameObjects();
PongResetGameFlags();
7.8.8 PongAntChannelClose()
Closing the ANT channel usually takes one ANT period at which time a message response to the Close
request will be provided to the host (this is used only by the ANT application). On the next cycle, the
channel will be closed unless something goes wrong, and a confirmation message will be sent to the
ANT application. The ANT system reads this message and clears _ANT_FLAGS_CHANNEL_OPEN which is
the queue to the Pong application that it can finally stop looking for incoming Pong messages and go
back to the start of the program. A timeout should be added in case ANT somehow gets stuck and never
clears the flag, but it is not currently present.
/* Wait until the channel has been closed */
if( !(GGu32AntFlags & _ANT_FLAGS_CHANNEL_OPEN) )
{
AntUnassignChannel();
/* #PCUR# */
/* Return to the opening screen (sets SM to Idle) */
PongInitialize();
}
Two functions are called to completely reset the system. The ANT channel must be unassigned because
the next game played could have the Master and Slave roles reversed on the devices. Changing
between Master and Slave is one of the functions that ANT will not allow on a channel that is already
assigned.
PongInitialize() is called to ensure EVERYTHING else is reset and the system is clean for the next run.
You might argue that calling PongInitialize breaks the rules of the system that state that application
initialization functions are never called in the main program loop. We carefully break this rule in the
name of code optimization! There is a flag inside the initialize function near the very beginning that is
allows us to get around the code that would be a problem if it was executed during the main loop.
Following the tradition of using “male” and “female” in the context of plugs and sockets, the name
“virgin” is used for a flag that indicates something has not been run before, so this flag is called
_PONG_GAME_VIRGIN.
notes_mpgl2_chapter7.docx
Release 1.0
Page 20 of 21
MPG LEVEL 2
The virgin flag is set when the Pong application variables are first initialized by the processor (the
assignment is made when the variable is declared). The code guarantees that the first time
PongInitialize() runs is during the startup code before the main loop. On a power cycle or processor
reset can cause this. _PONG_GAME_VIRGIN is cleared immediately inside the function so if it ever runs
again the code that would break the system is bypassed. There are more elegant ways to handle this,
but simplicity is sometimes the best route!
With all the code in place, you should have hours of entertainment with single player Pong. Make sure
you try running any code you have written to ensure the action on the game screen is correct. Be sure
to test “edge cases” where special conditions could occur (like the corner bounce of the ball). Also make
sure you test the code with NO_PLAYER_MODE commented in and out so you know everything is
working before proceeding to Chapter 8.
notes_mpgl2_chapter7.docx
Release 1.0
Page 21 of 21