Chapter 7: PunchDroid: An Android Punch Bug Game
In This Chapter
• Using the TinyWebDB component for multi-handset communication
• Using a timer to poll a datasource to keep apps up-to-date
• Employing a choose
block for variable situations
• Implementing a multiplayer game between handsets
• Using check boxes as radio buttons
Remember playing the Punch Bug game back in the day when Volkswagen Beetles were a rare sight? You know the game. Whenever someone sees the distinctive little car, she would punch the other player on the arm and yell “Punch Bug!” and get a point. Well, the game you played as a child is about to get an update. The PunchDroid project allows your user to play the same game regardless of the distance between the players. Whether you’re looking for VW Beetles or Android phones, PunchDroid is a fun little game that can be played between two phones.
This application introduces the TinyWebDB component. Previously you used the TinyDB component to store data between application settings. TinyDB stores its data on the local device as an .XML file in the Settings section of the Android file system. TinyWebDB, by contrast, uses either Wi-Fi or cell phone networks to communicate with a database running on a Web server. The TinyWebDB service runs on a Web server, accepts incoming data, and responds to requests for stored data from your application. TinyWebDB is an important part of your skill set for creating connected applications and devices.
The TinyWebDB component uses a service URL to connect to the TinyWebDB service running on a Web server. Google has provided a test TinyWebDB service that is used in this project. You share that TinyWebDB service with every other person testing the TinyWebDB service. If you want a TinyWebDB service to use for just yourself and your applications, set up your own TinyWebDB service using the instructions in Appendix B. If you do not set up your own TinyWebDB service, you can expect to have someone else using this project chapter to overwrite your data.
Creating the PunchDroid Application
The key concepts I introduce in this project include
• Using the TinyWebDB service
• Handling returns from the TinyWebDB service
• Creating test conditions and multiple test conditions for complex tests
• Creating check boxes that act as radio buttons to force a choice
• Keeping and storing data between multiple handsets
The TinyWebDB service as it is used in this project can be used for multiplayer games, reference applications, or data mining applications. The advanced use of the TinyWebDB service is integral to taking advantage of the networked nature of Android smartphones. Think of the TinyWebDB not just as a data storage component, but as a thread that can tie multiple devices together and as a gateway to other devices. Advanced hacks are available on the Google App Inventor forums that turn the TinyWebDB service into an even more powerful gateway to other data sources. Learning the basic fundamentals of how TinyWebDB works is the first step towards more advanced uses of the component.
Your design
Figure 7-1 shows the sketched user interface for the PunchDroid application. The application has two VirtualScreens: One for the main play interface and one for the Settings interface.
Figure 7-1: The PunchDroid design sketches
The PunchDroid application is a multiplayer game that can be played by two players across the Internet. The user inputs their name and player number to identify themselves uniquely. The user then has a button to tap whenever the user sees a VW Bug.
Your primitives
These are the core programming concepts for this app, broken down into simple statements to aid in programming the design goals:
• A button that increments the user’s score
• A method to distinguish between the local and the remote player
• A method to transmit data to the opposing player’s phone
• A method to store the user’s name and player number between sessions
• A method to start a new game
• A method to display the local and remote players’ scores
• A method to keep both players' scores up to date
Make sure you download the Chapter 7 project files from the companion Web site for this book and save them somewhere where you can find them easily during the project build. See this book's Introduction for more on downloading the files from the Web site.
Your progression
These are the basic steps you take in order to build the application:
1. Place the VirtualScreen1 user interface elements.
2. Place the VirtualScreen2 user interface elements.
3. Define the variables required for local information storage.
4. Build out the Screen1.Initialize
event.
5. Build blocks to handle the events on the Settings page.
6. Build blocks to handle the events on the main play page.
Getting Started on the PunchDroid Application
The PunchDroid application introduces you to a major component for interacting with data across the Internet: TinyWebDB. TinyWebDB is very useful for getting information to multiple handsets and allowing data to persist beyond the state of the local application. The PunchDroid application is a fun proof-of-concept application that can be expanded to fit any number of entertaining game ideas. The PunchDroid app only allows for two players, Player1 and Player2. The user determines when they start the application for the first time whether they are going to be Player1 or Player2.
1. Start a new project from the My Projects window. Name the project PunchDroid1_0
.
2. Select the Screen1 component in the Components column. Uncheck the Scrollable
property and change the Title
property to PunchDroid 1.0
.
Make sure the Display Invisible Components in Viewer check box is selected. That keeps even your invisible screen arrangements visible in the Design view.
3. Click on the Icon
property field to bring up the drop-down list. Click the Add button to bring up the Upload File pop-up. Click the Choose File button and navigate to your Chapter 7 project files. Double-click the punchdroid_ico.png file to select the icon file for upload. Click OK on the Upload File pop-up.
The PunchDroid application will have two screens. VirtualScreen1 is the main play screen, where the user can tap the I Got One! button to increment their score. VirtualScreen2 is the Settings screen, where the user can set whether they are Player1 or Player2 and enter their name.
4. From the Design view, drag and drop two VerticalArrangements onto the Viewer. Rename the first VerticalArrangement VirtualScreen1
. Rename the second VerticalArrangement VirtualScreen2
.
As you build the user interface, remember to refer to the design sketches or Figure 7-2 if you get confused.
5. Uncheck the Visible
property for both VirtualScreens. The Screen1.Intialize
event decides whether the user should set their settings first or proceed to the main play screen. Set the Width
and Height
property of both VirtualScreens to Fill Parent
.
6. Drag a HorizontalArrangement into the VirtualScreen1 component. This holds the two score boxes that display the score for the two players. Each score box is a VerticalArrangement that displays the player’s name label above their score label.
7. Set the Width
and Height
property of the HorizontalArrangement1 to Fill Parent
.
8. Drag and drop two VerticalArrangements into the HorizontalArrangement1. These are the boxes to hold the labels for score display.
9. Set the Width
and Height
property on the VerticalArrangements1 and 2 to Fill Parent
.
10. Drag and drop another HorizontalArrangement below the HorizontalArrangement1 that contains the score boxes. Set the Width
property to Fill Parent
. Don’t set the Height
property to Fill Parent
. You want the buttons pushed to the bottom of the screen. This arrangement holds the I Got One! button and the button used to access the settings page.
Now place all the Basic palette components:
1. Drag and drop a label into the VerticalArrangement1 that is the left box for your score display. (See Figure 7-2.) Rename the label lblThisPlayerName
. Change the default Text
property to Your Score:
. This label is changed programmatically when your user inputs their name, but having this default text to begin with helps you if you have to troubleshoot. It also helps you get a feel for the overall layout as you build the user interface.
Figure 7-2: The completed user interface for PunchDroid
2. Drag and drop a second label below the label you just named lblThisPlayerName
. Rename the new label lblThisPlayerScore
. Change the Alignment
property to Center
with the Property drop-down list. Check the FontBold
property check box. Set the FontSize
property to 75
and the FontTypeFace
to monospace
. Delete the default text in the Text
property field. This label displays the score of the local user.
Now set up the right score box in the same way:
1. Drag and drop a label into the VerticalArrangement2 that is the right score box in VirtualScreen1. Rename the label lblOtherPlayerName
. Change the default Text
property to Their Score:
. Again this is mostly for your benefit as the actual text changes to the name of the other player.
2. Drag and drop a label directly below the lblOtherPlayerName
label. Rename the new label lblOtherPlayerScore
. Change the Alignment
property to Center
with the Property drop-down list box. Check the FontBold
property check box. Set the FontSize
property to 75
and the FontTypeSpace
to monospace
. Delete the default text in the Text
property. This label displays the remote player’s score whether he is Player1 or Player2.
Now place the buttons for play and for the Settings screen:
1. Click on the HorizontalArrangement2 to highlight it in the Design view. Drag and drop a button into the Horizontal Arrangement2 component. Rename the Button btnGotOne
. Set the FontSize
property to 35
. Change the default Text property to I Got One!
.
2. Drag and drop another button to the right of the btnGotOne
button. Rename the button btnSettings
. Change the default Text
property to Settings
.
VirtualScreen1 is now completed and should look like VirtualScreen1 in Figure 7-2.
Follow the next steps to set up VirtualScreen2. VirtualScreen2 is the screen for your player settings. It contains check boxes to allow your user to specify whether they are Player1 or Player2. It also contains the Player Name setting and the Reset Game button:
1. Drag and drop a CheckBox component into the VirtualScreen2. Rename the CheckBox component chkPlayer1
. Change the default Text
property in the Properties column to Player1
.
2. Drag and drop a second CheckBox component into the VirtualScreen2 below the chkPlayer1 check box. Rename the CheckBox component chkPlayer2
. Change the default Text
property to Player2
. These two check boxes allow the user to select whether they are Player1 or Player2. Because they must be one or the other but cannot be both, you set up special logic that requires one to be checked but does not allow both to be checked.
3. Drag and drop a label below the chkPlayer2 check box. Rename the label lblName
. This label marks the following text box as the spot for your user to put their player name. Change the default Text
property to Name:
.
4. Drag and drop a TextBox component below the lblName label. Rename the TextBox txtPlayerName
. Set the Hint
property to Enter Player Name
. Change the default Hint
property to Enter Player Name
. This is the TextBox where the user can enter her name. That name is stored locally and uploaded to TinyWebDB.
5. Drag and drop a button below the txtPlayerName TextBox. Rename the button btnSaveSettings
. This button is a major event in your application. It stores all the settings and initializes the game. You need one more button to give the players the option of resetting the score and starting a new game. However, you don’t want it to be accidently hit, so you use a little vertical space to separate it from the other elements on the Settings screen.
6. Drag and drop a label below the btnSaveSettings button. Rename the label padLabel1
. This label acts as padding between the buttons. Remove the default text in the Text
property. Set the Height
property to Fill Parent
. This pushes the maximum vertical space between the two buttons on the Settings screen.
Now you need to place all of the application’s non-visible components. You need to add TinyWebDB as the data component for communication between the player’s phones. You also need to add a TinyDB component to store the local user’s name and player number locally. You must add a notifier to provide pop-up notifications for several different application events you will program later. Finally, you should add a Clock component for keeping both players’ games up-to-date on a reasonable schedule.
1. Drag and drop a TinyWebDB component from the Not Ready for Prime Time palette. The TinyWebDB component makes URL calls against a Web database application running on a Web server. The TinyWebDB component has one very important property: The ServiceURL
property tells the TinyWebDB component where the Web database and application are located.
The component has a default ServiceURL property value of appinvtinywebdb.appspot.com
. This URL points to a testing Web database that Google has set up on the Google AppSpot servers. The testing database is for testing and development, not for apps you actually want to use. It is subject to going down frequently. It is also used by anyone else testing a TinyWebDB component in an application. This makes it slow sometimes and means that your data can accidently be overwritten. Appendix B shows how to set up your own private WebDB to work in conjunction with TinyWebDB.
For the purposes of learning and creating the PunchDroid application, the testing database at http://appinvtinywebdb.appspot.com
is sufficient.
2. Drag and drop a TinyDB1 component from the Basic palette.
3. Drag and drop a Notifier component from the Other Stuff palette.
4. Drag and drop a Clock component from the Basic palette.
Your user interface should look like Figure 7-2. Make sure that the VirtualScreens do not have the Visible
property checked. Make sure the Display Invisible Components in Viewer check box is selected. Check that all arrangements have the Fill Parent
property set as the Width
and Height
properties.
Handling the Settings page events
Switch over to the Blocks Editor. Click the Open the Blocks Editor button if the Blocks Editor isn’t already open. The PunchDroid programming logic is almost entirely event-driven. The application does most of the work and then communicates the result to TinyWebDB for the PunchDroid application running on another device to download. You need to handle each of the button events on the user interface as well as one special event from TinyWebDB that is not user-generated.
You need to make provisions for storing several pieces of information locally on your application. For local storage, you use variables. When a variable value changes, you have to communicate it to TinyWebDB so that it can be accessed by the other player. You also need to make provisions for some information to be locally persistent. In other words, you use variables for storing immediate data locally, TinyDB for storing long-term user data, and TinyWebDB for storing persistent game information.
First you have to create all of the needed variables. You use typeblocking predominately in this project. I use the App Inventor syntax to represent the blocks. A quick review of App Inventor typeblock syntax: The set Label1.Text to
block is referred to in App Inventor typeblocking as Label1.Text [to]
.
Typeblock and create the following variables.
You create a new variable by typeblocking the keyword Variable and pressing Enter. You can then change the name to a unique name that is memorable to you. You need to plug a default value (usually a blank value) into the newly created variables.
• varPlayerName
: This stores the name of the player who is using the phone. Snap in a blank text
block.
• varPlayerName1
: This stores Player1’s name, whether he is on this phone or another. Snap in a blank text
block.
• varPlayerName2
: This stores Player2’s name, whether she is on this phone or another. Snap in a blank text
block.
• varPlayerNumber
: This stores the user’s player number (Player1 or Player2).
• VarPlayerScore1
: This stores Player1’s score.
• varPlayerScore2
: This stores Player2’s score.
Because a single game may well last across multiple instances of the application, you need to store the user's player number and name locally in TinyDB. Otherwise, the user would have to initialize those settings every time the application starts.
The Screen1.Initialize
event checks to see whether TinyDB has player number information stored. If it does, the user has set his settings previously. If the user has not set their settings, the settings page needs to be displayed. If the user has set their settings, all the variables need to be initialized and the main game screen displayed. Some of the variable information comes from TinyDB, such as PlayerName and PlayerNumber. The others are initialized with calls to TinyWebDB.
First build an IfElse
block to test if TinyDB has stored information. The IfElse
block handles two cases. The first directs the user to the Settings page; the second initializes the variables:
1. Typeblock the Screen1.Initialize
event handler block.
The IfElse
control block is your test decision-maker for the .Initilize
event. With the .Initialize
block selected, typeblock an IfElse
block and snap it into the .Intialize
event block.
2. Build the test for the IfElse
block to test whether any data is stored in TinyDB. You do this by posing the question, “Does the contents of a specific TinyDB tag equal nothing?”
Throughout this project, you use the variable names for all of the database tags minus the variable prefix. So, varPlayerNumber
stored in a database uses the tag playernumber
and varPlayerName
uses the tag playername
. TinyDB and TinyWebDB tags are not case-sensitive, but using all lowercase characters can help differentiate them from variable names in your head.
3. Select the IfElse
block and typeblock the equals comparison operator (=
) and snap it into the test
socket on the IfElse
block. Typeblock a TinyDB1.GetValue
block by typing TinyDB1.GetValue
. Make sure it snaps into the first socket on the comparison operator. Now it needs a tag to try to pull data with. If the user has entered any settings, the playernumber
tag contains data. Typeblock a text
block and replace the default text with playernumber
. Snap it into the .GetValue
block. Typeblock a text
block and remove the default text, leaving a blank text
block. Snap the blank text
block into the second socket on the comparison operator.
The then-do
first case of the IfElse
block is fairly straightforward to build. If the user needs to set their player information, you need to make the Settings screen visible and create a pop-up to inform the user what is expected of them.
4. Typeblock the VirtualScreen2.Visible [to]
block and snap it into the then-do
socket on the IfElse
block. Typeblock a true
block and snap it into the .Visible
block.
5. Typeblock the Notifier1.ShowMessageDialog
block and snap it into the then-do
socket on the IfElse
block. This block is set up to notify the user that they need to enter their player information. Typeblock a text
block and replace the default text with You
need to set your player information
. Snap the text
block into the message
socket on the .ShowMessageDialog
block. Typeblock another text
block and set its text to First run!
.
Snap that text
block into the title
socket on the .ShowMessageDialog
block. Typeblock a third text
block and set its text to OK
. Snap this text box into the buttontext
socket on the .ShowMessageDialog
box.
The Notifier component has a special event handler for whenever you use a notification that has a button to press. The Notifier
block you just placed has an OK button. Clicking the OK button signals the Settings screen to become visible. You build the instructions for the OK button press in the Notifier1.AfterChoosing
event handler.
6. Typeblock the Notifier1.AfterChoosing
event handler. Just to keep everything clean and symmetrical, you handle both VirtualScreens with the AfterChoosing
event handler.
The .AfterChoosing block is the event called when the OK button is clicked. It is also the event that is called if you use a Yes/No or other multi-button notification. The .AfterChoosing event has a parameter that contains the results or choice that your user selected. In this notification, the user has only one choice: OK. In multi-button notifications, the user’s choice is contained in the parameter value block named whatever is snapped into the choice socket.
App Inventor should automatically populate the choice socket on the .AfterChoosing event. However, sometimes the Blocks Editor glitches and that socket winds up empty. When that happens, you can populate the choice
socket with a name
block and change the name
block name to something memorable. The default name block is named choice.
7. Typeblock the VirtualScreen1.Visible [to]
block and snap it into the .AfterChoosing
event handler. Typeblock a false
block and snap it into the socket on the VirtualScreen1
block.
8. Typeblock the VirtualScreen2.Visible [to]
block and snap it in below the previous block. Typeblock a true
block and snap it into the VirtualScreen2
block.
9. Now whenever the OK button on the notification is tapped, the Settings screen becomes visible to enable the user to set the player settings.
That’s the complete first case of the IfElse
block that is executed on start-up. Flip ahead to check out Figure 7-3 if you have any issues.
Next, you need to build the second case else-do
socket on the IfElse
block. If the settings have been set and stored in TinyDB, that information as well as whatever information exists in the TinyWebDB needs to be used to initialize your variables. You set the main play screen to visible and then start initializing variables with database calls:
1. Typeblock the VirtualScreen1.Visible [to]
block and snap it into the else-do
socket on the IfElse
block in the Screen1.Initialize
block. Typeblock a true
block and snap it into the VirtualScreen1.Visible
block.
2. Now for your first variable initialization call: Typeblock the varPlayerNumber [to]
block and snap it under the VirtualScreen1
block.
3. To pull information out of TinyDB and place it into a variable or label, use the TinyDB1.GetValue
block socketed directly into where you want the data to go.
4. Typeblock the TinyDB1.GetValue
block and snap it into the varPlayerNumber [to]
block. Now you need to tell the .GetValue
block what tag to pull the data from. Typeblock a text
block and replace the default text with playernumber
. Snap the text
block into the .GetValue
tag
socket. You populate the tag playernumber
with the correct data when you handle the Save Settings button event on the VirtualScreen1.
5. When the user enters their player number and name, your Save Settings event stores all the information under the correct tags.
Now you will initialize the varPlayerName
variable. Typeblock the varPlayerName [to]
block and snap next in the else-do
socket. Typeblock a TinyDB1.GetValue
block and snap it into the varPlayerName [to]
socket. Typeblock a text
block and replace the default text with playername
.
Now you need to create a series of calls to the TinyWebDB for the other variable values. TinyWebDB works differently than TinyDB. TinyDB stores information on the local handset in the Settings location for applications on your phone. TinyWebDB, on the other hand, is a simple database that runs on a server located on the Internet. (See Appendix B for information on how to set up your own private Web database.) That means that when you submit a call for data to the TinyWebDB, the request is sent over the Internet to the URL placed in the ServiceURL
property. The response is then sent over the Internet back to the phone.
The upshot of this is that data calls to TinyWebDB are not instantaneous as they are with TinyDB. Whenever you use TinyWebDB, you make calls for data, but you can’t actually place that data or process that data until it actually comes back to the phone. You handle the responses to data request to the TinyDBWeb service using a special event provided by the TinyWebDB component. The TinyWebDB1.GotValue
event is used to process any incoming data requested from any other blocks in your application. You build that event later; for now, you are just going to tell the TinyWebDB component to get the data for the variables based on the appropriate tags. You actually place that data in the variables in the .GotValue
event handler.
You continue building the Screen1.Initialize
event in the following steps by placing all of the .GetValue
blocks in the IfElse
block:
1. Typeblock a TinyWebDB1.GetValue
block and snap it in the else-do
socket on the IfElse
block. Typeblock a text
block for the tag and replace the default text with playername1
. Snap the text
block into the tag
socket on the .GetValue
block. This block sends the request across the Internet for the data stored under the tag playername1
, which is the name of the player who declared himself as Player1.
2. Typeblock a TinyWebDB1.GetValue
block and snap it in. Typeblock a text
block for the tag and replace the default text with playername2
. Snap the text
block into the tag
socket on the .GetValue
block. This block sends the request for the data stored under the tag playername2
.
3. Typeblock a TinyWebDB1.GetValue
block and snap it in next. Typeblock a text
block for the tag and replace the default text with PlayerScore1
. Snap the text
block into the tag
socket on the .GetValue
block.
4. Typeblock a TinyWebDB1.GetValue
block and snap it in next. Typeblock a text
block for the tag and replace the default text with PlayerScore2
. Snap the text
block into the tag
socket on the .GetValue
block.
After you’ve built all the calls to the TinyWebDB for the Screen1.Initialize
block, you need to place the user’s player name in the Label in their score box.
Typeblock the lblThisPlayerName.Text [to]
block and snap it in as the last block in the Screen1.Initialize
event handler block. Typeblock the varPlayerName
global variable block and snap it into the text
block. This sets the score label in the score box on the right side to represent the local player’s name.
Your completed Screen1.Intialize
block should look like Figure 7-3.
Figure 7-3: The completed Screen1.Initialize event handler
Next, get ready to build the programming logic for the components and events on the Settings screen of your application. You have several components to handle and two buttons to handle events for on the Settings page. You need to handle the Save Settings button event, and you also need to set up logic that ensures that one check box is selected but not both. You also need to handle the event for the New Game button.
The logic for the check boxes seems complex, but the check boxes come with a very useful event handler, the CheckBox.Changed
event. This event is called whenever the value of the check box is changed. A check box is always either true
or false
. If that value changes, you can build logic to check on and or change the other check box. You need to build logic that says, “When the check box is changed, set the other check box to the opposite value.”
1. Typeblock the chkPlayer1.Changed
event handler. With the chkPlayer1.Changed
block selected, typeblock the chkPlayer2.Value [to]
block.
This is the .Value [to] block opposite of the event handler. Make sure the .Value [to] block snaps into the event handler. The ChkPlayer1.Changed has the chkPlayer1.Value block and vice versa.
2. Typeblock a not
block and snap it into the to
socket on the chkPlayer2.Value
block. Now typeblock the chkPlayer1.Value
reporting block and snap it into the not
block. The logic of these blocks now reads, Set the Value of chkPlayer2 to the opposite of chkPlayer1 whenever chkPlayer2 is changed
. (See Figure 7-4.) You set up the same thing for the chkPlayer2 check box next.
3. Typeblock the chkPlayer2.Changed
event handler. Typeblock the chkPlayer1.Value [to]
block and snap it into the event handler. Typeblock a not
block and snap it into the to
socket on the .Value
block. Typeblock the chkPlayer2.Value
reporting block and snap it into the not
block. Your Player1 and Player2 selection check box event handlers should now look like Figure 7-4.
Whenever one check box is selected, the other is automatically set to the opposite value, ensuring that one but not both are always selected.
Figure 7-4: The completed .Changed event handlers
You use a new block to create the logic in the event handler for the Save Settings button. The choose
block allows your blocks to make a choice about which value to use in a string or logic pattern. In this case, you use the choose
block to choose which number to store with the playernumber
tag in TinyDB. If the user has selected the Player1 check box, your blocks will store the value 1
in with the tag playernumber
. If the user has selected the Player2 check box, your blocks store the value 2
with the tag playernumber
. That way, when the Screen1.Initialize
event pulls the information from TinyDB, your application has the correct value in the varPlayerNumber
variable:
1. Start off by typeblocking the btnSaveSettings.Click
event handler. Typeblock a TinyDB1.StoreValue
block.
The TinyDB1.StoreValue
block allows you to save any data with a tag so that it can be retrieved later. You store the player number with the tag playernumber
.
2. With the TinyDB1.StoreValue
block selected, typeblock a text
block and replace the default text with playernumber
. Make sure the text
block is snapped into the tag
socket on the .StoreValue
block.
3. Typeblock a choose
block and snap it into the valueToStore
socket on the .StoreValue
block. The choose value
block chooses which number to return to the valueToStore
socket based on a test much like an IfElse
block.
Now build the test for the choose
block. The logic of your test goes like this: If the chkPlayer1 value is set to “true” then return the value in the first return socket; otherwise, return the value in the second return socket
.
1. Typeblock an equals comparison operator (=) and snap it into the test
socket on the choose
block. Typeblock the chkPlayer1.Value
reporting block and snap it into the first socket on the comparison operator. Typeblock a true
block and snap it into the second socket on the comparison operator.
Now you need to set the values that the test case will choose between. If the test evaluates true
, you want the value 1
to be stored because that is the number the player chose. If the test case evaluates to false
, you want to return the value 2
. You know that if chkPlayer1.Value
is false
, chkPlayer2.Value
must be set to true because one of the check boxes must be checked.
2. Typeblock a numeral 1
block and snap it into the then-return
socket on the choose
block.
3. Typeblock a numeral 2
block and snap it into the else-return
socket on the choose
block.
Now you use the value just stored in TinyDB to set the value of the variable varPlayerNumber
so the player can start playing.
1. Typeblock the varPlayerNumber [to]
block and snap it in under the TinyDB1.StoreValue
block. Typeblock a TinyDB1.GetValue
block and snap it into the varPlayerNumber
block. Typeblock a text
block and replace the text with the tag text playernumber
. Snap it into the tag
socket on the .GetValue
block. Because TinyDB instantly stores and returns data, we can populate the variable with TinyDB data immediately after storing it.
Next, store the text from the txtPlayerName
text box in TinyDB so your application can remember your player’s name.
2. Typeblock a TinyDB1.StoreValue
block and snap it in next in the btnSaveSettings.Click
event handler. Typeblock a text
block and replace the default text with playername
. Snap it into the tag
socket on the .StoreValue
block. Typeblock the txtPlayerName.Text
reporting block. Snap the text
block into the valueToStore
socket on the .StoreValue
block. The text from the txtPlayerName
text box is stored in TinyDB under the tag playername
.
Next, place the player name in the varPlayerName
variable and set the label on the main play screen to the player’s name.
3. Typeblock the varPlayerName [to]
block and snap it in next under the .StoreValue
block. Typeblock a TinyDB1.GetValue
block and snap it into the to
block on the varPlayerName
block. Typeblock a text
block and replace the text with playername
. Snap the text
block into the tag
socket on the .GetValue
block.
4. Now set the lblThisPlayerName label on the main play screen to represent the name just entered. Typeblock the lblThisPlayerName.Text [to]
block and snap it in under the previous block. Typeblock the varPlayerName
variable reporting block and snap it into the lblThisPlayerName.Text
block.
Next, you need to store the player’s name in TinyWebDB under the tag that represents the player’s number. In other words, if the player chose to be Player1, the player’s name should be stored under the tag playername1
and playername2
if the player chose to be Player2. To accomplish storing the local player’s name with their selected player number, use a text join block to join the text playername
with the value of the variable varPlayerNumber
and use the resulting text string as the tag to store the value of the varPlayerName
. If that is confusing, look at Figure 7-5. You can see that the player’s name is joined with the player’s selected number. That string is used to store the player’s name. For instance, if the player has entered his name as Joe
and selected the Player2 check box, the following would be stored:
playername2 = Joe
The application uses TinyWebDB to retrieve the tags playername1
and playername2
and place them in the correct variable. In the Screen1.Intialize
event, you created the TinyWebDB calls that retrieve those values. You handle those returns when you set up the .GotValue
event a little later:
1. Typeblock a TinyWebDB1.StoreValue
block and snap it into the btnSaveSettings
event handler. Typeblock a join
block and snap it into the tag
socket on the .StoreValue
block. Typeblock a text
block and replace the text with playername
. Snap the text
block into the first socket on the join
block. Next, typeblock the varPlayerName
block and snap it into the second socket on the join
block.
2. Typeblock the varPlayerName
global variable block and snap it into the valueToStore
socket on the .StoreValue
block.
Now the player’s name is stored with the tag playername#
with the number depending on what number is stored in the varPlayerNumber
.
When the user taps the Save Settings button, you want to also retrieve the values stored in the TinyWebDB for both tags, playername1
and playername2
. That way, no matter what player this player is, the variables are populated with the player’s name. Remember that for TinyWebDB, we can only make the calls to TinyWebDB with the tags. We must actually handle the data later when it is returned from the TinyWebDB service on the Internet.
3. Typeblock a TinyWebDB1.GetValue
block and snap it in below the .StoreValue
block in the btnSaveSettings.Click
event. Typeblock a text
block and replace the text with the tag text playername1
. Snap the text
block into the tag
socket on the .GetValue
block.
4. Typeblock another TinyWebDB1.GetValue
block and snap it in next in the .Click
event handler. Typeblock a text
block and replace the text with playername2
this time. Snap it into the tag
socket on the .GetValue
block.
After storing all the user settings in the appropriate variables and databases, you need to make the main play screen appear and the Settings screen disappear:
1. Typeblock the VirtualScreen1.Visible [to]
block and snap it in under the previous .GetValue
block. Typeblock a true
block and snap it into the .Visible
block.
2. Typeblock the VirtualScreen2.Visible [to]
block and snap it in as the last block in the event handler. Typeblock a false
block and snap it into the .Visible
block.
Your completed btnSaveSettings.Click
event handler should look like Figure 7-5.
Figure 7-5: The completed btnSaveSettings.Click blocks
The btnNewGame.Click
event handler is fairly easy to set up. To start a new game, you just have to reset all the score information stored locally in variables and stored in the TinyWebDB:
1. Typeblock the btnNewGame.Click
event handler. Typeblock the varPlayerScore1 [to]
block. Snap the variable block into the event handler. Snap a numeral 0
block into the to
socket.
2. Typeblock the varPlayerScore2 [to]
block and snap it in next in the btnNewGame.Click
event handler. Snap a numeral 0
block into the to
socket.
3. Typeblock the TinyWebDB1.StoreValue
block and snap it next in the btnNewGame
event handler. Use a text
block to set the tag
to playerscore1
. Use a number block to set the value
socket to 0
.
4. Typeblock another TinyWebDB1.StoreValue
block and snap it in under the previous block. Use a text
block to set the tag
to playerscore2
. Use a number block to set the value
socket to 0
.
Now you need to reset the score display labels on the main play screen to display zero:
1. Typeblock the lblOtherPlayerScore.Text [to]
block. Snap it in after the last TinyWebDB block. Use a numeral 0
block snapped into the to
socket to set the variable to zero.
2. Typeblock the lblThisPlayerScore.Text [to]
block and snap it in next. Use a numeral 0
block to set the variable value to zero.
Your completed btnNewGame.Click
event handler should look like Figure 7-6.
Figure 7-6: The completed btnNewGame.Click blocks
Handling events on the main play screen
Now that you have handled the events on the Settings screen, it’s time to handle the events on the main play screen. There are two user events to handle on the main play screen: the Settings button, which allows the user to bring up the Settings screen, and the I Got One button, which is the main play event. Clicking the I Got One! is the digital equivalent of punching your friend on the shoulder and yelling “Punch Bug!”
To handle the Settings button, make the main play screen invisible and make the Settings screen visible.
1. Typeblock the btnSettings.Click
event handler. If necessary, move it to a clear area of your workspace. Remember to right-click on the workspace to organize and handle your blocks.
Using the “Right-click, select Collapse All Blocks, right-click again, and select Organize All Blocks” routine should become habit when you are dealing with a large number of large event handlers or long block routines.
With the btnSettings.Click
block selected, typeblock the VirtualScreen1.Visible [to]
block. Typeblock a false
block and snap it into the .Visible
block.
2. Typeblock the VirtualScreen2.Visible [to]
block and snap it in under the previous block. Typeblock a true
block and snap it into the .Visible
block.
If the user has reopened the PunchDroid application from a previous game, the txtPlayerName
text box might not have any text in it even though database calls have been used at the start of the application to populate the variable. You need to place the contents of the variable in the TextBox component so the user gets the sense of data and player persistence.
3. Typeblock the txtPlayerName.Text [to]
block and snap it under the previous .Visible
block. Typeblock the varPlayerName
global variable block and snap it into the to
socket on the text
block.
Your completed btnSettings.Click
event handler should look like Figure 7-7.
Figure 7-7: The btnSettings.Click blocks
The most important event on the main play screen is the I Got One! button that the user taps to indicate that they have just spotted whatever item the game is centered around. When the user taps the I Got One! button, the appropriate player score variable should increment and the appropriate score display label should display the new score. Also, the new score needs to be sent to the TinyWebDB. Before you increment the score, you use the event as an opportunity to send a request to the TinyWebDB for any updates to the other players score. You use the choose
block again to determine which call should be made. In reality, you could just send a call for both PlayerScore1 and PlayerScore2, but for the purpose of this project, you use the choose
block again for a little extra practice in using it:
1. Typeblock the btnGotOne.Click
event handler and drag it to a clear workspace.
2. First build the TinyWebDB call to check on the other player’s score. Typeblock the TinyWebDB1.GetValue
and snap it into the event handler. Typeblock a make text
block and snap it into the tag
of TinyWebDB1.GetValue
. The make text
block creates a single string for the tag from the text PlayerScore
and the opposite of whatever number is in varPlayerNumber
. Typeblock a text
block and replace the default text with PlayerScore. Snap it into the text
socket on the make text
block. Typeblock a choose
block and snap it into the next text
block on the make text
block.
Now build the test for the choose
block that says, “If the varPlayerNumber
value is 1
, return the numeral 2
to the make text
; otherwise, return the numeral 1
.”
3. Typeblock an equals comparison operator (=
) and snap it into the test
socket on the choose
block. Typeblock the varPlayerNumber
global variable block and snap it into the first socket on the comparison operator. Typeblock a numeral 1
number block and snap it into the second socket on the comparison operator.
4. Now typeblock a numeral 2
number block and snap it into the then-return
block.
5. Typeblock a numeral 1
block and snap it into the else-return
block on the choose
block.
Now the make text
block concatenates the text PlayerScore
and either the numeral 1
or 2
and uses it as one string for the TinyWebDB tag.
Next you need to increment the appropriate variable so that the player’s score goes up when the I Got One! button is clicked. If the varPlayerNumber
is 1, varPlayerScore1
should increment. If the varPlayerNumber
is 2, the varPlayerScore2
should go up. You can use a simple IfElse
block to increment the right variable and then store the result in the TinyWebDB:
1. Typeblock an IfElse
block and snap it into the btnGotOne.Click
event handler. Build the test condition to check if the varPlayerNumber
contains the value 1
. If it does, the first case then-do
socket should increment the varPlayerScore1
. Otherwise, the second case else-do
socket should increment the varPlayerScore2
variable.
2. With the IfElse
block selected, typeblock an equals comparison operator. Typeblock the varPlayerNumber
global variable block and snap it into the first socket on the comparison operator. Typeblock a numeral 1
number block and snap it into the second socket on the comparison operator.
Now build the then-do
case for when the test evaluates to true
. If the test is true
, increment varPlayerScore1
and send the new value to the label and the TinyWebDB.
3. Typeblock the varPlayerScore1 [to]
block and snap it into the then-do
socket on the IfElse
block. Typeblock an addition operator by typing a plus sign (+
) and pressing Enter. Snap the additive operator into the to
socket on the varPlayerScore1
block. Typeblock the varPlayerScore1
global reporting block and snap it into the first socket on the additive operator block. Typeblock a numeral 1
number block and snap it into the second socket on the additive operator block. This takes the value of varPlayerScore1
, adds one, and stores it back into the variable.
4. Now update the label with the new score. If this player is Player1, you use the lblThisPlayerScore
to display the new score.
5. Typeblock the lblThisPlayerScore.Text [to]
block and snap it in under the varPlayerScore1
incrementing block. Typeblock the varPlayerScore1
global variable reporting block and snap it into the lblThisPlayerScore.Text
block. This updates the label with the latest score.
Now store the value of the varPlayerScore1
because it has changed in TinyWebDB:
1. Typeblock the TinyWebDB1.StoreValue
block and snap it in under the label set block. Typeblock a text
block for the tag and replace the default text with PlayerScore1
. Snap the text
block into the tag
socket on the .StoreValue
block. Typeblock the varPlayerScore1
reporting block and snap it into the valueToStore
block on the .StoreValue
block. This sends the contents of the varPlayerScore1
variable to the TinyWebDB to be stored under the tag PlayerScore1
.
Your first case then-do
socket should look like Figure 7-8.
If the IfElse
block test evaluates to false
, you want to increment the Player2 score and update the label and store it as well.
2. Typeblock the varPlayerScore2 [to]
block and snap it into the else-do
socket on in the IfElse
block. Typeblock the additive (+
) block and snap it into the to
socket on the varPlayerScore2
block. Typeblock the varPlayerScore2
global variable block and snap it into the first socket on the additive block. Typeblock a numeral 1
number block and snap it into the second socket on the additive block. Again, this is the typical variable increment routine.
Now update the label with the new score. In the previous case for the then-do
socket, if the local player was Player1, the lblThisPlayerScore.Text
would be populated with the value of the varPlayerScore1
. If this player is Player2, you want to set the lblThisPlayerScore.Text
to the value of the varPlayerScore2
variable.
3. Typeblock the lblThisPlayerScore [to]
block and snap it into the else-do
socket under the varPlayerScore2
block. Typeblock the varPlayerScore2
global variable block and snap it into the lblThisPlayerScore.Text
block.
Now store the changed variable in the TinyWebDB. Typeblock the TinyWebDB1.StoreValue
block and snap it last into the else-do
socket on the IfElse
block. Typeblock a text
block for the tag and replace the text with PlayerScore2
. Snap the text
block into the tag
socket on the .StoreValue
block. Typeblock the varPlayerScore2
global variable block and snap it into the valueToStore
socket on the .StoreValue
block.
Your completed btnGotOne.Click
event handler should look like Figure 7-8.
Figure 7-8: The completed btnGotOne.Click event handler blocks
Every time you make a TinyWebDB call, the Web service eventually returns the requested tag and data. The TinyWebDB1.GotValue
event handler has two special name/value blocks associated with it. When the TinyWebDB service returns the value that has been called for, it returns it in one package made up of two pieces: the tag and the value. The first piece is represented by the tagFromWebDB1
value block. This value block contains the tag that was called for that initiated the tag/value return. If the tag that was used to initiate the call was PlayerName1
, the contents of the tagFromWebDB1
are PlayerName1
. The second piece of the return package is the actual data that was stored with the tag. This piece of the package is represented by the valueFromWebDB1
value block. If the tag that was used to initiate the call has the PlayerName1
data stored under that tag, it is returned in the valueFromWebDB1
block.
This method of handling data returning from the TinyWebDB1 service is an asynchronous service fulfillment. That means that the order you request tag/value combinations is not necessarily the order they return in. Because of delays with servers and Internet pathways, you cannot assume that data arrives in the order it was requested. The tag/value pairing allows you to open a return package and say “Aha! This is the PlayerName1
tag I requested! I want to place the value I stored with that tag in a certain variable.” When that data returns, you need to decide what data has been returned and what you want to do with it. You use a series of nested If
and IfElse
blocks for every possible tag and data pair that might be returned. So far, you have stored information in the TinyWebDB under the following four tags:
• PlayerName1
• PlayerName2
• PlayerScore1
• PlayerScore2
The player name tags test whether the returned value is the same as the name in VarPlayerName
. If it is the same, you don’t want to do anything with the data. But if the value of the returned data for a player name tag is not the same as the name stored in VarPlayerName
, lblOtherPlayerName
should be set to the value.
For the player score tags, you need to check whether the returned value is empty. App Inventor doesn’t like to do calculations on variables that have a null
value. If you set the value of one of the varPlayerScore
variables to null
, when the application tries to increment the value, the application crashes. If there is no data in the value returned from the TinyWebDB service, you want to discard the data. If there is in fact a value in the returned response, you should update the appropriate variable.
Finally you set the lblOtherPlayerScore.Text
to the appropriate score using the contents of the appropriate variable:
If the TinyWebDB1.GotValue does not have name blocks in the tagFromWebDB and valueFromWebDB sockets on the .GotValue event handler, you need to populate the sockets with name blocks from the Definitions drawer and change their names accordingly.
1. Typeblock the TinyWebDB1.GotValue
event handler. With the TinyWebDB1.GotValue
block selected, typeblock an If
block. Build the test for the If
block by typeblocking an equals comparison operator and snapping it into the test
socket of the If
block. With the comparison operator selected, typeblock the tagFromWebDB1
value block and snap it into the first socket on the comparison operator. Typeblock a text
block and replace the default text with playername1
. Snap it into the second socket on the comparison operator.
This test checks to see whether the incoming tag is the PlayerName1
tag. If it is, you need to decide what to do with the value that is connected to the tag.
If the test in the If
block evaluates to true
, you need to test to see whether the current player name stored in varPlayerName
is the same as the value coming in from the TinyWebDB service. If it is the same, you can discard it. This is information your application already knows.
You use an IfElse
block in a special way for this operation. You can use an IfElse
block to say, in essence, “If this is true, do nothing; otherwise, do something.” You do this by leaving one of the cases without any blocks to execute. If the value from the Web database is the same as the value in varPlayerName
, you do nothing with the value.
2. With the If
block selected, typeblock an IfElse
block and make sure it snaps into the If
block. Typeblock the equals comparison operator (=
) and snap it into the test
socket on the IfElse
block. Typeblock the valueFromWebDB1
block and snap it into the first socket on the comparison operator. Typeblock the varPlayerName
global variable block and snap it into the second socket on the comparison operator. This tests to see whether the contents of valueFromWebDB
and varPlayerName
are the same.
If the test evaluates to true
, you don’t want to do anything with the data, so leave the then-do
socket empty on the IfElse
block.
If the test evaluates to false
, the incoming name is the name of the other player and you want to place it the lblOtherPlayerName
label on the main play screen.
3. Typeblock the lblOtherPlayerName.Txt [to]
block and snap it into the else-do
socket of the IfElse
block. Typeblock the valueFromWebDB1
value block and snap it into the lblOtherPlayerName
block. These blocks set the label to the other player’s name.
Next you set the exact same series of blocks again, but this time for when the incoming tagFromWebDB1
is PlayerName2:
1. Typeblock an If
block and snap it in below your first If
block. With the If
block selected, typeblock the equals comparison operator (=
). Typeblock the tagFromWebDB1
value block and snap it into the first socket on the comparison operator. Typeblock a text
block and replace the default text with PlayerName2
. These blocks test to see whether the incoming tag is the PlayerName2
tag.
With the If
block selected, typeblock an IfElse
block and make sure it snaps into your second If
block.
2. Select the IfElse
block and typeblock an equals comparison operator. Typeblock the valueFromWebDB1
value block and snap it into the first socket on the comparison operator. Typeblock the varPlayerName
global variable block and snap it into the second socket on the comparison operator. Again, if the value incoming from the Web database is the same as that stored in the PlayerName
variable, you discard it.
3. Leave the then-do
socket empty on the second IfElse
block.
4. Typeblock the lblOtherPlayerName.Text [to]
block and snap it into the else-do
socket on the second IfElse
block. Typeblock the valueFromWebDB1
block and snap it into the socket on the lblOtherPlayerName.Text
block.
Your next two nested If
blocks check whether the incoming tag is the PlayerScore
tag and then check to see whether the value is empty. You could handle the incoming PlayerScore
tag/value in much the same way as you handled PlayerName
; instead, you use nested If
statements with a not
block. So instead of building the logic as, “If the value from the WebDB is empty, do nothing; otherwise, do something,” you build the logic as, “If the value from the WebDB is not empty, do this.” You see that the method you use here is a slightly neater and more graceful way to handle the situation:
1. Typeblock an If
block and snap it in as the third If
block down in your TinyWebDB1.GotValue
event handler. Build the test for the If
block by typeblocking a comparison operator and snapping it into the test
socket on the If
block. Typeblock the tagFromWebDB1
value block and snap it into the first socket on the comparison operator. Typeblock a text
block and replace the text with PlayerScore1
. Snap the text
block into the second socket on the comparison operator.
This test checks to see if the incoming tag is the PlayerScore1 tag. If it is, you need to make sure that the data content isn’t a null value. App Inventor hates doing math on a variable with a null value.
2. With your third If
block selected, typeblock another If
block and make sure it snaps into your third If
block’s then-do
socket.
You use the not
block to execute this nested If
block only when the value from the WebDB is not null.
3. Typeblock a not
block and snap it into the test
socket of your nest If
block. Typeblock an equals comparison operator and snap it into the not
block. Typeblock the valueFromWebDB1
block and snap it into the first socket on the comparison operator. Typeblock a text
block and delete the default text, leaving an empty text
block. Snap the text
block into the second socket on the comparison operator.
4. This test says, “If the valueFromWebDB1 is not null, the test is true.” If the test is true, you want to store the value in the varPlayerScore1
variable. Typeblock the varPlayerScore1 [to]
and snap it into your nested If
block. Typeblock the valueFromWebDB1
value block and snap it into the varPlayerScore1
block.
If the incoming tag is PlayerScore1
and the incoming value is not blank, the value is placed in the varPlayerScore
variable.
As you can probably see, you can write this same logic in a third way that is even tighter. You can use an And
block to chain conditions. You can create a test that says, “If this test and this test and this test are true, execute these blocks.” You can create as many and
clauses as you need. As you build this If
block, refer to Figure 7-9 for this slightly more complex but neater way to check for two things at once:
1. Typeblock a fourth If
block and snap it in below the third If
block. Typeblock an and
block and snap it into the test
socket of your fourth If
block. Typeblock an equals comparison operator and snap it into the test
socket on the and
block. It creates another test
socket for every test you put in it. With your first comparison operator selected, typeblock the tagFromWebDB1
value block . Typeblock a text
block and replace the text with PlayerScore2. Snap the text
block into the second socket on the comparison operator.
2. Select the and
block and typeblock a not
block. Make sure it snaps into the next test
socket. Typeblock an equals comparison operator (=
) and snap it into the not
block. Typeblock the valueFromWebDB1
value block and snap it into the first socket on the comparison operator. Typeblock a text
block and delete the default text. Snap the empty text
block into the second socket on the comparison operator.
Now you have a test that asks that two
conditions evaluate as true before the contained blocks are executed.
3. Typeblock the varPlayerScore2 [to]
and snap it into the then-do
socket on your fourth If
block. Typeblock the valueFromWebDB1
value block and snap it into the varPlayerScore2
block.
At this point, you have handled every possible incoming tag from the TinyWebDB component. When you are building large projects, it is sometimes helpful to keep a list of the tags/values you use throughout your application. Every time you request data from the TinyWebDB component, it has to be handled with the .GotValue
event when it arrives from the Web database.
Finally, set the OtherPlayerScore
label with the appropriate variable value:
1. Typeblock an IfElse
block and snap it into the .GotValue
block as the last block. Typeblock an equals comparison operator. Snap it into the test
socket. Typeblock the varPlayerNumber
global value block and snap it into the first socket on the comparison operator. Typeblock a numeral 1
number block and snap it into the second socket on the comparison operator. If the player number is 1
, the lblOtherPlayerScore.Text
should be set to the value of the varPlayerScore2
. If the varPlayerNumber is not 1
, the label should be set to the value of varPlayerScore1
.
2. Typeblock the lblOtherPlayerScore.Text [to]
block and snap it into the then-do
case of your IfElse
block. Typeblock the varPlayerScore2
global variable block and snap it into the lblOtherPlayerScore.Text
block.
3. Typeblock another lblOtherPlayerScore.Text [to]
block and snap it into the else-do
socket of your last IfElse
block. Typeblock the varPlayerScore1
global block and snap it into the lblOtherPlayerScore.Text
block.
Your completed TinyWebDB1.GotValue
event handler should look like Figure 7-9.
To keep your player opponents and scores up-to-date, create a clock timer event that regularly polls the TinyWebDB service to have it return an updated score. Reuse blocks you already have built to make the database call.
Locate the btnGotOne.Click
event handler on your workspace. The first block in the btnGotOne.Click
event handler is the TinyWebDB1.GetValue
block, which uses a choose
block to decide what tag to request. Click on the TinyWebDB1.GetValue
block and copy it to memory by pressing Crtl+C. Close the btnGotOne.Click
event handler. Click on an empty workspace and typeblock the Clock1.Timer
event handler. Press Ctrl+V to paste a copy of the TinyWebDB1.GetValue
block from the btnGotOne.Click
event handler.
Figure 7-9: The complete TinyWebDB1.GotValue event handler blocks
Snap the copied blocks into the Clock1.Timer
event handler. Your Clock1.Timer
event handler should look like Figure 7-10.
Based on the timer value you entered in the TimerInterval
property in the Design view, the Clock1 component periodically executes the .GetValue
for the opponent’s score. A lower TimerInterval
value means the application is more up-to-date, but repeated calls to the TinyWebDB service use up data and battery power on the phone.
Figure 7-10: The completed Clock1.Timer event handler
Installing the PunchDroid Application
You have completed the PunchDroid application. Install the application on your phone by clicking the Package for Phone button in Design view. Use the Download to this Computer option when you click the Package for Phone button to download the .APK file and send it to a friend with an Android device. The friend must have the Untrusted Install Locations setting enabled on their phone. (Setting the Allow Untrusted Install Locations option varies from Android device to device. Check your device manual or look for online instructions.) You can also test PunchDroid between your phone and the emulator. You can start the emulator by clicking the New Emulator button on the Blocks Editor. The emulator can connect to the Internet through your computer’s Internet connection.
The PunchDroid application has a lot of room for improvement. Some of the features you could include in future versions are
• Support for more players
• Checking to see whether a player number slot is taken already
• Adding sound or vibration when an opponent scores
• Adding a goal or win game target
If you've worked your way through all of the previous apps in this book, you should have enough knowledge to create some pretty incredible multiplayer games that are based on the concepts in this project but have nothing to do with the silly childhood Punch Bug game. Consider a timer-based resource management game or a location-based scavenger hunt, for example. The possibilities are limitless.