Hide
Google Play Game Services

Developing a Turn-based Multiplayer Game in Android

This guide shows you how to implement a turn-based multiplayer game using Play Game services in an Android application.

Before you begin

If you haven't already done so, you might find it helpful to review the turn-based multiplayer game concepts.

Before you start to code your turn-based multiplayer game:

Once the player is signed in and the GoogleApiClient is connected, your game can start using the turn-based multiplayer API.

Starting a match

To begin a turn-based match, your game can prompt players to select how they want to be matched to other participants or build the match using some default criteria. Play Game services uses the match configuration data from your game to return a TurnBasedMatch object to your game; this object is updated and shared asynchronously with all participants over the lifecycle of the turn-based match.

To start a match, follow these steps:

  1. In your app, gather specifications for the type of turn-based match that the player wants to play and who can participate in the match.
    • Your game can display the built-in player selection user interface (UI) provided by the SDK or use a own custom UI to gather this data. To learn how to use the player selection UI, see Selecting players with the default user interface.
    • Alternatively, you can implement a Quick Start button to let users bypass the player selection UI, and instead use some default criteria to build the match.
  2. Use a TurnBasedMatchConfig.Builder to set the match configuration data in a TurnBasedConfigObject.
  3. Call createMatch() and pass in the TurnBasedConfigObject you created. If auto-matching is specified, Play Game services will attempt to match players to an existing game; for more details, see Implementing auto-matching.

To create a turn-based match, your game can call these methods:

Method Description
getSelectOpponentsIntent() Optional. Returns an Intent for launching the default player selection UI. From this UI, the user can select other Google+ users to invite to the user's match, or request to be auto-matched with random Google+ users.
createMatch() Creates a new turn-based match object, or loads an existing match that meets user's criteria if auto-matching is requested. If auto-matching is requested, the match returned will be either a new match or an existing match where other players have already taken turns. If a new match is returned, the user must take the first turn. Your game is responsible for handling turn-taking regardless of whether a new or existing match is loaded.

Selecting players with the default user interface

Play Game services provides a default player selection UI that lets players select their friends to invite to a match or select to be auto-matched with random players. To start a match from the default player selection UI, call getSelectOpponentsIntent(), then call startActivityForResult() and pass in that Intent. If the call is successful, the game displays a default player selection UI that prompts the user to select match invitees and a minimum and maximum number of players for auto-matching. The user’s player selection criteria is returned as Intent extras in the onActivityResult() callback.

public void onStartMatchClicked(View view) {
    Intent intent =
        Games.TurnBasedMultiplayer.getSelectOpponentsIntent(mGoogleApiClient, 1, 7, true);
    startActivityForResult(intent, RC_SELECT_PLAYERS);
}

Next, override the onActivityResult() callback to construct a TurnBasedMatchConfig object using the criteria provided by the user. The TurnBasedMatchConfig object defines the characteristics of the match that the user wants to play. Play Game services uses this configuration data to determine whether to build a new match or to auto-match the user to an existing game.

public class TbmpGameActivity extends Activity {
    ...

    @Override
    public void onActivityResult(int request, int response, Intent data) {
        super.onActivityResult(request, response, data);
        ...

        if (request == RC_SELECT_PLAYERS) {
            if (response != Activity.RESULT_OK) {
                // user canceled
                return;
            }

            // Get the invitee list.
            final ArrayList<String> invitees =
                    data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);

            // Get auto-match criteria.
            Bundle autoMatchCriteria = null;
            int minAutoMatchPlayers = data.getIntExtra(
                    Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
            int maxAutoMatchPlayers = data.getIntExtra(
                    Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);
            if (minAutoMatchPlayers > 0) {
                autoMatchCriteria = RoomConfig.createAutoMatchCriteria(
                        minAutoMatchPlayers, maxAutoMatchPlayers, 0);
            } else {
                autoMatchCriteria = null;
            }

            TurnBasedMatchConfig tbmc = TurnBasedMatchConfig.builder()
                    .addInvitedPlayers(invitees)
                    .setAutoMatchCriteria(autoMatchCriteria)
                    .build();

            // Create and start the match.
            Games.TurnBasedMultiplayer
                .createMatch(mGoogleApiClient, tbmc)
                .setResultCallback(new MatchInitiatedCallback());
        }
    }
}

In the snippet, MatchInitiatedCallback is a class that implements the ResultCallback interface. You can attach this object to the GoogleApiClient so that your game is notified whenever a match is initiated. To see how the MatchInitiatedCallback is implemented, see Taking the first turn.

If you want to create a match without using the default player selection UI, your game must provide the invitee player IDs and auto-match criteria in the TurnBasedMatchConfig object that you pass to createMatch().

Optionally, you might want to ensure that only players who are interested in a specific type of game variant are auto-matched together. If there are different versions of your app, your game can also use variants to ensure that only players who are on compatible versions are auto-matched. To specify a variant when creating a turn-based match, use the setVariant() method. Your game can also use the exclusiveBitMask parameter in createAutoMatchCriteria() to pair auto-matched players who are interested in playing specific exclusive roles in a game.

Taking the first turn

If the createMatch() call is successful, Play Game services returns a InitiateMatchResult object to signal that the player can take a turn. Before proceeding, remember to initialize the game data as needed by your game. For example, you might need to initialize the starting positions for players in a strategy game or initialize the first hand for players in a card game.

To implement the first turn, follow these steps:

  1. Retrieve the TurnBasedMatch object from the InitiateMatchResult object, then call getData().
    • If the call returns a null value, this indicates that the game data is uninitialized because no player has taken a turn yet (that is, the current player is the first player in this match). Your game can initialize the game data and store it in a byte array. The size of the game data must be less than the size returned by getMaxMatchDataSize(). The data size returned is guaranteed to be at least 128 KB.
    • If the call returns a non-null value, this indicates that the game has already started and the game data is already initialized, so make sure your game does not reinitialize the data.
  2. Optionally, call takeTurn() now. If your initial state is based on a random value (for example, a card game where the player starts with a random hand) and your game does not call takeTurn() to persist the data to Google's servers, the player could keep restarting the first turn to try to get a better starting position.
  3. Let the player perform game actions, and if appropriate, update the byte array containing the game data.
  4. Save the game data to Google’s servers by calling takeTurn(). Your game can specify the next player's participant ID, or specify null to let Play Game services find a player for auto-matching. Your game can let match participants take turns even when not all invited players have joined or when there are auto-match slots still available. To get players into gameplay faster, your game should let players take their turn as soon as they join. When you call takeTurn(), Play Game services sends a notification to the pending participant and updates the turn data on all participants' devices asynchronously.
  5. When the next player takes a turn, make sure to load the initialized data by calling getData() before you let the player perform game actions.

The following snippet shows how you might initialize a match and let the first player take a turn.

public class MatchInitiatedCallback implements
        ResultCallback<TurnBasedMultiplayer.InitiateMatchResult> {

    @Override
    public void onResult(TurnBasedMultiplayer.InitiateMatchResult result) {
        // Check if the status code is not success.
        Status status = result.getStatus();
        if (status.isSuccess()) {
            showError(status.getStatusCode());
            return;
        }

        TurnBasedMatch match = result.getMatch();

        // If this player is not the first player in this match, continue.
        if (match.getData() != null) {
            showTurnUI(match);
            return;
        }

        // Otherwise, this is the first player. Initialize the game state.
        initGame(match);

        // Let the player take the first turn
        showTurnUI(match);
    }
}

Implementing auto-matching

When your game calls createMatch() and requests auto-matching, Play Game services first attempts to match the player to an existing game. If a match is found that meets the player's auto-match criteria, Play Game services automatically joins the player and any invited players as auto-matched players into the existing match. If no match is found, your game should signal that it is available for auto-matching:

  1. Prompt the initiating player to take the first turn.
  2. Optionally, after the initiating player completes their first turn, your game can also let other invited players who joined the match take their first turns.
  3. To trigger auto-matching, call takeTurn() and pass in null in the pendingParticipantId parameter.

Handling invitations

Once a player has signed in to your game, the player may receive invitations to join a turn-based match created by another player. To save coding time and provide users with a consistent UI for responding to match invitations across applications, you can use the default match inbox UI provided by the SDK. To launch the default match inbox UI, call getInboxIntent() to get an Intent; then call startActivityForResult() and pass in that Intent.

To handle invitations for turn-based matches if your game is not using the default match inbox UI, your game can call these methods:

Method Description
acceptInvitation() Call this method if the user received a match invitation and wants to accept the invitation. Calling this method changes the user's participant status to STATUS_JOINED. The user can then proceed to take a turn.
declineInvitation() Call this method if the user received a match invitation but wants to stop the match from proceeding. Calling this method effectively cancels the match and removes the match from all GoogleApiClient objects registered to this user. The user's participant status changes to STATUS_DECLINED and the match status changes to MATCH_STATUS_CANCELED. Other participants see this match under the Completed Matches category in the match list UI.
dismissInvitation() Call this method if the user received a match invitation and wants to temporarily postpone accepting the invitation. Calling this method removes the match from all GoogleApiClient objects registered to this user. The user's participant status and the match status stays unchanged. To other participants in the match, the user still appears to be invited.

Taking a turn

After the first player takes a turn, your game can let the same participant or another participant take the next turn. Generally, turn-taking involves loading the most recent match data from Play Game services, letting the participant interact with your game, and calling takeTurn() to update the match data and pass the turn to another participant.

To implement turn-taking, follow these steps:

  1. Optionally, if you want be notified whenever any participant in the match takes a turn, attach a OnTurnBasedMatchUpdateReceivedListener to your activity. Whenever the match is updated following a player's turn, your listener is notified via the onTurnBasedMatchedReceived() callback.
  2. In order for a participant to take a turn, the match must be in active state and it must be that participant's turn. Your game can use the TurnBasedMatch object to verify this.
  3. Load the most recent match data from Play Game services by calling getData() and render your game accordingly.
  4. Let the user perform game actions and, if appropriate, persist the updated game data to a byte array.
  5. Determine the pending participant who should take the next turn. This is usually dependent on the order of play specified by your game design. The pending participant can be another player in the match or the current participant whose turn it is. Your game can also pass the turn to the next player joining by auto-match, by setting the pending participant to null.
  6. Call takeTurn() to update the Play Game services with the latest game data and pass the turn to the pending participant. Play Game services returns the status of your match update operation in an UpdateMatchResult object. If the update is successful, Play Game services sends a notification to the pending participant to inform them that it's their turn.

To take a turn, use this method:

Method Description
takeTurn() Your game should only invoke this method when it is the user's turn in an active turn-based match. After invoking this method, the user sees this match under the Their Turn list in the match list UI.
  • If a participant ID is specified in the method call, that participant becomes the current player and can play a turn. The first time this happens to an invited player, the player receives an invitation notification. Subsequently, the player receives a turn notification.
  • If no pending participant ID is specified, the match state changes to MATCH_STATUS_AUTO_MATCHING. Your game can only leave the participant ID unspecified if there are player slots that are still vacant for auto-matching.
  • Your game can call takeTurn() with the current player as the pending participant. In this case, Play Game services uploads the the game data but no notification is sent.

The following snippet shows how you might implement the current player's turn:

private TurnBasedMatch mMatch;
private TextView mDataView;
...

// Call this method when a player has completed his turn and wants to
// go onto the next player, which may be himself.
public void playTurn(View view) {

    // Get the next participant in the game-defined way, possibly round-robin.
    String nextParticipantId = getNextParticipantId();

    // Get the updated state. In this example, we simply retrieve a
    // text string from the view. In your game, there may be more
    // complicated state.
    mTurnData = mDataView.getText().toString();

    // At this point, you might want to show a waiting dialog so that
    // the current player does not try to submit turn actions twice.
    showSpinner();

    // Invoke the next turn. We are converting our data to a byte array.
    Games.TurnBasedMultiplayer
        .takeTurn(mGoogleApiClient, mMatch.getMatchId(),
                 mTurnData.getBytes(Charset.forName("UTF-16")),
                 nextParticipantId)
        .setResultCallback(this);
}

Saving game state

You can use the turn-based multiplayer API to manage your game state if your game state data fits within the allowed size. The data size limit can be retrieved by calling getMaxMatchDataSize(). The data size returned is guaranteed to be at least 128 KB.

To save game state with the turn-based multiplayer API:

  1. Call takeTurn() and pass in your game state data as the matchData parameter.
  2. If the call is successful, Play Game services notifies other participants in the match about the update and makes the match data available on all participant devices.
  3. Your game can then call getData() to retrieve the updated game state.

Your game should try to save game data for a partially-finished turn whenever a player's turn is interrupted and the player has to temporarily leave the game (for example, because of an incoming phone call). To do this, override your activity's onStop() method to call takeTurn(). Make sure to specify the current player as the pending participant by using the same participant ID as in the last call to takeTurn(). If successful, the call stores the game data in Google’s servers but does not generate a new turn notification.

Completing a match

When the match has been played to completion (for example, a user has won the game), your game should call finishMatch() to upload the user’s game data and signal to the other participants that the match is over. When your game invokes this method for the first time during a match, it must be during the user's turn. The match then appears under the Completed Matches category in the user's match list UI.

Once a player calls finishMatch() for the first time in the match, your game cannot call takeTurn() again in this match. Play Game services sends a notification to all other match participants to inform them that the match is over. These participants see this match under Your Turn category in their respective match list UIs. At this point, your game can call finishMatch() for these participants to save their final game data. Invoking this method also moves the match to the Completed Matches category in the participant’s match list UI.

To complete a match, use this method:

Method Description
finishMatch() Calling this method changes the match status changes to MATCH_STATUS_COMPLETE. After finishMatch() is called for the first time in the match, Play Game services sends a notification to the other match participants to inform them that the match is over.

Leaving a match

Participants can choose to leave at any time during the match, while allowing the match to continue. To signal that a participant is leaving, your game should call either leaveMatch() or leaveMatchDuringTurn(). When a participant leaves a match, the match can still continue with other participants as long as these conditions are met:

  • There are two or more other remaining participants, or
  • There is one remaining participant and at least one empty auto-match slot available.

Otherwise, the match is canceled.

In general, when a participant leaves a match, another player cannot join and take that participant’s place. One exception is when a player who joined by auto-match and who has not taken a turn calls leaveMatchDuringTurn(). In this case, the match reverts to the MATCH_STATUS_AUTO_MATCHING state and another player can take over the place of the participant who left.

To leave a match, your game can call these methods:

Method Description
leaveMatch() Removes the match from all GoogleApiClient objects registered to this user. You should only invoke this method when it is not the user's turn yet in an active match. Calling this method changes the user's participant status to STATUS_LEFT. If only one more participant remains, the match status changes to MATCH_STATUS_CANCELED.
leaveMatchDuringTurn() Removes the match from all GoogleApiClient objects registered to the user. You should only invoke this method on the user's turn in an active match. Calling this method changes the user's participant status to STATUS_LEFT.
  • If only one more participant remains, the match status changes to MATCH_STATUS_CANCELED.
  • If a participant ID is specified in the method call, that participant becomes the current player and can play a turn. The first time this happens to an invited player, the player receives an invitation notification. Subsequently, the player receives a turn notification.
  • If no pending participant ID is specified, the match state changes to MATCH_STATUS_AUTO_MATCHING. Your game can only leave the participant ID unspecified if there are empty auto-matching slots that are still available.

Canceling a match

Your game can end a match for all participants before the match is played to normal completion, by calling cancelMatch(). After calling this method, your game cannot call takeTurn() again in this match; the match now appears under the Completed Matches category in the match list UI.

To cancel a match, use this method:

Method Description
cancelMatch() Removes the match from all GoogleApiClient objects registered to the user. You should only invoke this method on the user's turn in an active match. Calling this method changes the user's participant status to STATUS_LEFT and the match status changes to MATCH_STATUS_CANCELED.

Dismissing a match

Your game can let users dismiss a turn or invitation so that they do not have to see the match again. This hides the match from the dismisser's match list UI and causes the match to eventually expire. Other match participants can continue to play until the dismissed match expires after two weeks, or until the match is played to completion or canceled (whichever happens first). To other participants, the dismisser still appears as a participant in the match. Another player cannot take the dismisser's place.

To dismiss a match, use this method:

Method Description
dismissMatch() Removes the match from all GoogleApiClient objects registered to the user. The user's participant status is not changed.

Tracking match expiration

A match can expire if a player does not respond to a match or turn notification for two weeks (for example, after a player whose turn it is selects Dismiss in the default match list UI). You can use the following approach on the game client to identify which player caused a game to expire:

  1. Load a list of the currently signed-in player’s list of games by calling loadMatchesByStatus().
  2. Next, call getCompletedMatches() to get a TurnBasedMatchBuffer that contains a list of matches in the MATCH_TURN_STATUS_COMPLETE state.
  3. Then, filter that list for matches in the MATCH_STATUS_EXPIRED state.
  4. If a match participant has STATUS_UNRESPONSIVE state, it indicates that this player has let the game expire.

Implementing a rematch

When a match is over, a participant may want to play a rematch with the same set of invited and auto-match opponents. Your game can initiate a rematch by invoking rematch(). Your game can only invoke this method when the match state is MATCH_STATUS_COMPLETE and no other participants have requested a rematch. If the call is successful, Play Game services sends invitation notifications to all rematched opponents.

To implement a rematch, use this method:

Method Description
rematch() Creates a new turn-based match object with the same participants as the previous match. The user must then take a first turn. If another participant already requested a rematch, calling this method returns a STATUS_MATCH_ERROR_ALREADY_REMATCHED error.

If your game uses the default match inbox UI, players can also initiate rematches from that UI. When a player initiates a rematch from the match inbox UI, your game will not receive an InitiateMatchResult object. Instead, Play Game services returns a new match object as an Intent extra in the onActivityResult() callback of the match inbox activity. If your game needs to initialize game data for the new match, make sure to use the match object returned in the onActivityResult() callback.