Making a turn based game on iOS that relies on GameKit and Game Center would have been a pretty straight forward affair if only the documentation was consistent, clear and without errors. Sadly the documentation is quite bad making things feel so much more complicated than they are.
In this article I’ll first provide you with some background, then a overview, before diving into the details of using the turn based exchanges available with GameKit. When you’re done reading you will be able to confidently and efficiently make your own turn based game.
To experiment with these APIs yourself, consider downloading the minimal project I created to explore exchanges myself.
For several weeks I struggled to figure out how exactly to use the GameKit APIs. Eventually I got everything working except turn based exchanges. No matter what I tried there was no way to resolve completed exchanges elegantly. So I decided to create a minimal project to highlight my problem before submitting a support ticket to Apple. After having waited two weeks I got a reply that sadly didn’t help at all (like so many other developers even the Apple engineer incorrectly interpreted the documentation). With even Apple engineers not knowing APIs or understanding the documentation I was getting quite frustrated.
Coincidentally the WWDC 2020 was just about to start at this time, so I decided to seek help at a lab session. Assuming they would need more than a single session to solve my issue I decided to book two 1-on-1 labs.
I spent the first lab explaining the issue I had with turn based exchanges. The Apple engineer then did some investigation, and in my follow-up lab he could inform me that they discovered a bug in the backend that prevented the turn holder from getting the notification needed to resolve completed exchanges. Only the requester would be notified, which obviously is only helpful when the turn holder is also the requester. In other words, turn based exchanges have presumably never worked as designed — my understanding of the APIs were correct, I had been fighting bugs that were not my own.
So now that Apple is aware of the bug, I was told they will update my thread on the Apple Developer forum when exchanges have been fixed. Meanwhile we should develop our game as if exchanges works properly, and simply workaround the error we receive when trying to mutate the match data while there are completed exchanges. So this is what I’ll be doing in this article.
To get started I think it’s best to establish a shared taxonomy. Apple uses several different terms for the same entities, so lets get some clarity:
- Participant; this is an authenticated Game Center player that has joined a match, or a player placeholder before a real human has taken the seat in the match. For you as a developer it doesn’t matter if the participant is a human or not; in either case you can initiate an exchange, update the game and end a turn.
- Turn holder; this is the participant that is currently resolving a turn; also called “current participant”. Only the turn holder is permitted to update the match data.
- Match Update; you use updates when the turn holder performs an action that mutates the match data without ending the turn. You push the mutated match data to the server, which triggers a turn event for all the other participants ensuring everyone has the current match state.
- Exchange; also called an “exchange request”. It sounds like a trade, but can in fact be used for almost any kind of communication between two or more players. An exchange is an event that includes a localised string, data, a deadline and a state (more about the states below). When you create the exchange you specify who should receive it. The replies optionally returned from the receivers are retained by the exchange and used to figure out how the match data is affected.
- Requester; also called “initiator” of the exchange. This is any participant in a match that creates an exchange.
- Recipient; a participant that receives an exchange request. Despite what the documentation is saying, you are not required to include the turn holder as a recipient. You can let the requester freely select any number of participants to be the recipients of the request.
- Exchange reply; recipients can ignore or chose to reply to a received exchange request. If no reply is sent, the requester will have to wait for the exchange to time-out. Keep in mind, you don’t necessarily need to involve the player when determining if the recipient should reply or ignore the request. A reply can contain a localisable string and/or some data. A reply can be used to acknowledge, accept or decline the received exchange; it can be a counter-bid in a negotiation; a bid in an auction; a chat message between arbitrary players, or anything else you find useful.
Turn Based Exchanges
You use an exchange to pass data between two or more players. An exchange can be cancelled by the requester up until it has been resolved.
An exchange is created, passed to the recipients and finally returned to the requester all the while its state transitions as follows:
A single exchange can be used to pass a message to an opponent, or a series of exchanges can be used to resolve an auction or negotiate a trade. The outcome of an exchange can be stored in the match data however you like; as a separate structure, or discarded after having updated the match data. How you resolve exchanges is completely up to you and the needs of your game — however keep in mind that it’s always the turn holder that resolves it, so combining the exchange and replies should result in exactly one outcome, with no need for additional input.
- When the exchange request API is called by the requester it is sent to Apple who creates the exchange and forwards it to all recipients. The state of the distributed exchange is active. Active exchanges will not prevent turn holder from performing an update or ending the turn. They can all be found in the activeExchanges array of the turn based match instance.
- The participants that did not directly receive the request, but that is loading the game at this point, will also be able to present whatever is needed to the player by parsing and processing the exchanges in the activeExchanges array. You could eg. show that a trade is happening.
- The state is updated (by Apple) to completed after all recipients have either replied to the exchange request or the request has timed out. In other words, don’t expect to get a reply from all recipients. This state-shift triggers a notification event received by both the turn holder and the requester. If the turn holder is also the requester I assume the participant’s device will still only receive one event (this is unknown due to the mentioned bug on the server).
- The job of the requester is at this point over, however you may want to use the received exchange and associated replies to present the outcome of the exchange.
- The turn holder however, will use the exchange data and all the replies (actual replies or time-outs from the recipients) to work out how the exchange affects the current match data. After this is done the turn holder needs to update the server with the resulting match data to transition the state from ‘completed’ to ‘resolved’. Until the exchange is successfully resolved the turn holder will get an error if trying to update the match data or end the turn.
- Other participants loading the game at this point will also be able to present whatever is needed by parsing and processing the exchanges in the completedExchanges array.
- Apple receives the updated match data, which means the completed exchange is now resolved. At this point the exchange is removed from the match, before the match with the updated match data is distributed as a turn event to all participants. This notification may be received possibly by all but the turn holder (as the turn holder already knows the outcome of the exchange and has the resolved match data).
Turns and Updates
The endTurn and updateMatch APIs works as needed, and are by nature very straight forward.
The turn holder can do actions that end their turn or optionally actions that only updates the match data (without ending the turn). Of course your game doesn’t have to offer updates, it may only need turn ending actions.
When a player ends their turn you determine who will be the next turn holder. However, in case the next turn holder doesn’t resolve their turn before it times out, you also queue up all the other participants – adding current participant last.
You always add the current participant last because the last participant in the queue will not time-out. This ensures there is at least one player that holds the game and ends it when everyone else have failed to take a turn.
Turn Based Exchanges are awesome – at least they will be when Apple fixes the bug. You can use exchanges for almost any in and out-of-turn communication. So go an be creative!
And that’s all folks! If this was helpful, I’m glad. You can reach me on Twitter if you have any comments or thoughts.