Skip to content

Commit

Permalink
Updated the player status message to add a game won/lost flag to sync…
Browse files Browse the repository at this point in the history
…hronize new game start. A new game will not be started until both players report they have won or lost, which prevents 2 issues:

1) Action messages no longer being sent.
2) Action messages being processed at the start of the next game from the previous game.

Fixed the bug where the level song in multiplayer was based on the game difficulty instead of being selected at random.
  • Loading branch information
forteri76 committed May 14, 2014
1 parent 1057146 commit 22b71c6
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 20 deletions.
4 changes: 2 additions & 2 deletions AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jfedor.frozenbubble"
android:versionCode="26"
android:versionName="2.2">
android:versionCode="27"
android:versionName="2.3">

<supports-screens
android:smallScreens="true"
Expand Down
56 changes: 50 additions & 6 deletions src/com/efortin/frozenbubble/NetworkGameManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public class NetworkGameManager extends Thread
public static final int ACTION_BYTES = 37;
public static final int FIELD_BYTES = 112;
public static final int PREFS_BYTES = Preferences.PREFS_BYTES;
public static final int STATUS_BYTES = 13;
public static final int STATUS_BYTES = 14;

/*
* Network game management definitions.
Expand Down Expand Up @@ -224,7 +224,7 @@ public NetworkGameManager(Context myContext,
*/
localStatus = new PlayerStatus((byte) localPlayer.playerID,
(short) 0, (short) 1,
false, false, requestPrefs,
false, false, false, requestPrefs,
(short) 0, (short) 0);
}

Expand Down Expand Up @@ -552,9 +552,10 @@ public class PlayerStatus {
public short localActionID;
public short remoteActionID;
/*
* The following flag is used to manage game synchronization.
* The following flags are used to manage game synchronization.
*/
public boolean readyToPlay;
public boolean gameWonLost;
/*
* The following flags are used to request data from the remote
* player(s) - either their game preferences, or game field data.
Expand All @@ -577,6 +578,7 @@ public class PlayerStatus {
* @param localId - the local last transmitted action ID.
* @param remoteId - the remote current pending action ID.
* @param ready - player is ready to play flag.
* @param wonLost - player won or lost the game.
* @param field - request field data.
* @param prefs - request preference data.
* @param localCRC - the local player bubble grid CRC16 checksum.
Expand All @@ -586,11 +588,13 @@ public PlayerStatus(byte id,
short localId,
short remoteId,
boolean ready,
boolean wonLost,
boolean field,
boolean prefs,
short localCRC,
short remoteCRC) {
init(id, localId, remoteId, ready, field, prefs, localCRC, remoteCRC);
init(id, localId, remoteId, ready, wonLost, field, prefs,
localCRC, remoteCRC);
}

/**
Expand Down Expand Up @@ -620,6 +624,7 @@ public void copyFromStatus(PlayerStatus status) {
this.localActionID = status.localActionID;
this.remoteActionID = status.remoteActionID;
this.readyToPlay = status.readyToPlay;
this.gameWonLost = status.gameWonLost;
this.fieldRequest = status.fieldRequest;
this.prefsRequest = status.prefsRequest;
this.localChecksum = status.localChecksum;
Expand All @@ -645,6 +650,7 @@ public void copyFromBuffer(byte[] buffer, int startIndex) {
shortBytes[1] = buffer[startIndex++];
this.remoteActionID = toShort(shortBytes);
this.readyToPlay = buffer[startIndex++] == 1;
this.gameWonLost = buffer[startIndex++] == 1;
this.fieldRequest = buffer[startIndex++] == 1;
this.prefsRequest = buffer[startIndex++] == 1;
shortBytes[0] = buffer[startIndex++];
Expand Down Expand Up @@ -674,6 +680,7 @@ public void copyToBuffer(byte[] buffer, int startIndex) {
buffer[startIndex++] = shortBytes[0];
buffer[startIndex++] = shortBytes[1];
buffer[startIndex++] = (byte) ((this.readyToPlay == true)?1:0);
buffer[startIndex++] = (byte) ((this.gameWonLost == true)?1:0);
buffer[startIndex++] = (byte) ((this.fieldRequest == true)?1:0);
buffer[startIndex++] = (byte) ((this.prefsRequest == true)?1:0);
toByteArray(this.localChecksum, shortBytes);
Expand All @@ -691,6 +698,7 @@ public void copyToBuffer(byte[] buffer, int startIndex) {
* @param localId - the local last transmitted action ID.
* @param remoteId - the remote current pending action ID.
* @param ready - player is ready to play.
* @param wonLost - player won or lost the game.
* @param field - request field data
* @param prefs - request preference data
* @param localCRC - the local player bubble grid CRC16 checksum.
Expand All @@ -700,6 +708,7 @@ public void init(byte id,
short localId,
short remoteId,
boolean ready,
boolean wonLost,
boolean field,
boolean prefs,
short localCRC,
Expand All @@ -709,6 +718,7 @@ public void init(byte id,
this.localActionID = localId;
this.remoteActionID = remoteId;
this.readyToPlay = ready;
this.gameWonLost = wonLost;
this.fieldRequest = field;
this.prefsRequest = prefs;
this.localChecksum = localCRC;
Expand Down Expand Up @@ -956,6 +966,17 @@ private void copyPrefsToBuffer(Preferences prefs,
}
}

/**
* Check if the network game is finished. The game is finished when
* each player has either won or lost the game.
*/
public boolean getGameIsFinished() {
if (remoteStatus == null)
return false;
else
return localStatus.gameWonLost && remoteStatus.gameWonLost;
}

/**
* Check if the network game is ready for action. The game is ready
* to begin play when all data synchronization tasks are completed, at
Expand Down Expand Up @@ -1376,6 +1397,7 @@ else if ((gameId != MulticastManager.FILTER_OFF) &&
!localStatus.fieldRequest &&
!localStatus.readyToPlay) {
localStatus.readyToPlay = true;
localStatus.gameWonLost = false;
}
/*
* The local player status was updated. Set the status
Expand All @@ -1391,10 +1413,10 @@ else if ((gameId != MulticastManager.FILTER_OFF) &&

/*
* If the message contains a remote player game action, add it
* to the appropriate action list.
* to the remote player action list if we are ready to play.
*/
if ((msgId == MSG_ID_ACTION) && (length == (ACTION_BYTES + 2))) {
if (playerId == remotePlayer.playerID) {
if (localStatus.readyToPlay && (playerId == remotePlayer.playerID)) {
addAction(new PlayerAction(buffer, 2));
}
}
Expand All @@ -1416,6 +1438,7 @@ else if ((gameId != MulticastManager.FILTER_OFF) &&
localStatus.fieldRequest = false;
if (!localStatus.prefsRequest && !localStatus.readyToPlay) {
localStatus.readyToPlay = true;
localStatus.gameWonLost = false;
}
/*
* The local player status was updated. Set the status
Expand Down Expand Up @@ -1574,6 +1597,27 @@ public void setActionTimeout(long timeout) {
actionTxTime = System.currentTimeMillis() + timeout;
}

/**
* Set the flag to indicate that the local player won or lost the
* network game. Only set it once, otherwise calling this function
* multiple times in succession would flood the network with player
* status messages due to how an immediate local player status message
* transmission is initiated whenever the player status changes.
*/
public void setGameIsFinished() {
if (!localStatus.gameWonLost) {
localStatus.gameWonLost = true;
/*
* The local player status was updated. Set the status timeout to
* expire immediately and wake up the network manager thread.
*/
setStatusTimeout(0L);
synchronized(this) {
notify();
}
}
}

/**
* Set the game start timeout.
* @param timeout - the timeout expiration interval.
Expand Down
13 changes: 9 additions & 4 deletions src/org/jfedor/frozenbubble/FrozenBubble.java
Original file line number Diff line number Diff line change
Expand Up @@ -820,10 +820,13 @@ private void playMusic(boolean startPlaying)
{
int modNow;
/*
* Ascertain which song to play.
* Ascertain which song to play. For a single player game, the song
* is based on the current level. For a two player game, or if the
* game thread has been destroyed, the song is selected at random.
*/
if (mGameThread != null)
if ((mGameThread != null) && (numPlayers == 1)) {
modNow = mGameThread.getCurrentLevelIndex() % MODlist.length;
}
else
{
Random rand = new Random();
Expand All @@ -832,11 +835,13 @@ private void playMusic(boolean startPlaying)
/*
* Determine whether to create a music player or load the song.
*/
if (myModPlayer == null)
if (myModPlayer == null) {
myModPlayer = new ModPlayer(this, MODlist[modNow],
getMusicOn(), !startPlaying);
else
}
else {
myModPlayer.loadNewSong(MODlist[modNow], startPlaying);
}
allowUnpause = true;
}

Expand Down
35 changes: 27 additions & 8 deletions src/org/jfedor/frozenbubble/GameView.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ public void cleanUp() {
*/
public abstract void checkRemoteChecksum();
public abstract void cleanUp();
public abstract boolean getGameIsFinished();
public abstract boolean gameIsReadyForAction();
public abstract short getLatestRemoteActionId();
public abstract boolean getRemoteAction();
Expand All @@ -259,6 +260,7 @@ public abstract void sendLocalPlayerAction(int playerId,
int attackBarBubbles,
byte attackBubbles[],
double aimPosition);
public abstract void setGameIsFinished();
public abstract void setLocalChecksum(short checksum);
public abstract void setRemoteChecksum(short checksum);
public abstract void unPause();
Expand Down Expand Up @@ -2449,6 +2451,9 @@ private void updateGameState() {
gameEnum game2Result = mFrozenGame2.getGameResult();

if (game1Result != gameEnum.PLAYING) {
if (mNetworkManager != null) {
mNetworkManager.setGameIsFinished();
}
if ((game1Result == gameEnum.WON) ||
(game1Result == gameEnum.NEXT_WON)) {
mFrozenGame2.setGameResult(gameEnum.LOST);
Expand All @@ -2458,6 +2463,9 @@ private void updateGameState() {
}
}
else if (game2Result != gameEnum.PLAYING) {
if (mNetworkManager != null) {
mNetworkManager.setGameIsFinished();
}
if ((game2Result == gameEnum.WON) ||
(game2Result == gameEnum.NEXT_WON)) {
if (mHighScoreManager != null) {
Expand All @@ -2474,18 +2482,29 @@ else if (game2Result != gameEnum.PLAYING) {
}

/*
* Only start a new game when player 1 provides input when the
* opponent is the CPU, because the CPU is prone to sneaking a
* launch attempt in after the game is decided.
* When playing a network game, only start a new game when the
* current game is finished, which is indicated when both
* players have either won or lost.
*
* If the opponent in a multiplayer game is the CPU, only start
* a new game when player 1 provides input, because otherwise
* the CPU is prone to sneaking a launch attempt in after the
* game has already been decided.
*
* Otherwise, the first player to provide input initiates the
* new game.
*/
if (((game1State == gameEnum.NEXT_LOST) ||
(game1State == gameEnum.NEXT_WON)) ||
(!mRemoteInput.isCPU &&
((game2State == gameEnum.NEXT_LOST) ||
(game2State == gameEnum.NEXT_WON)))) {
boolean gameFinished = false;
if (mNetworkManager != null) {
gameFinished = mNetworkManager.getGameIsFinished();
}

if (((mNetworkManager == null) || gameFinished) &&
(((game1State == gameEnum.NEXT_LOST) ||
(game1State == gameEnum.NEXT_WON)) ||
(!mRemoteInput.isCPU &&
((game2State == gameEnum.NEXT_LOST) ||
(game2State == gameEnum.NEXT_WON))))) {
if ((game1State == gameEnum.NEXT_WON) ||
(game2State == gameEnum.NEXT_LOST)) {
numPlayer1GamesWon++;
Expand Down

0 comments on commit 22b71c6

Please sign in to comment.