Once the device accepts the connection request, the peer immediately receives a session status change notification with a status of gkpeerstateconnected and the device is added to the player list.
To test this app, you need to run a copy of two apps: one server and one client. The simplest approach is to use the emulator as a server and a physical device as the client.
If you don't have a developer account, you won't be able to debug on the real machine, so you might want to run two emulators on the same machine. It's not a no, but it's not that simple. If you want to do this, please refer to This method on the stack overflow.
Run the app on the emulator and you'll see the same interface:
Now the app is running on the device. If the device is on the same network as the computer, you will see the following display on the emulator:
The device name is displayed on "TV", and the "Start Game" button on the simulator is displayed. "Waitingfor players!" is still displayed on the device.
Later, we will add more communication code between the server/client so that the game can be carried out.
Communicating with other Devices
Now that you've run two copies of the app, it's time to use gksession to communicate between the two.
The gksession has two methods for sending peer-to data, respectively:
-(BOOL) SendData: (nsdata*) Data topeers: (Nsarray *) peers Withdatamode: (Gksenddatamode) Modeerror: (Nserror *) error;
-(BOOL) Senddatatoallpeers: (NSData *) data withdatamode: (Gksenddatamode) Modeerror: (Nserror *) error
Two methods are used to send nsdata to one or more endpoints, respectively. For this project, we will use the first method. The advantage of the first method is that you can send messages to yourself. Although this is somewhat magical, there is a benefit that the server can send data to trigger its own response, just like a client.
The server may have multiple peers (including itself), but the client will only have one peer: the server.
In either case, this method sends the message to all endpoints.
A NSData object can accommodate a variety of data, so the NSString command needs to be packaged as a nsdata send, and in turn when the data is received, in order to print out debugging information.
In atviewcontroller.m: Finally, add the following methods:
#pragma mark-peer Communication - (void)sendtoallpeers:(nsstring *)command { Nserror *Error = nil; [self.gksession senddata: [command datausingencoding:nsutf8stringencoding] Topeers:self.peersToNames.allKeys withdatamode:gksenddatareliable Error:&error]; if (error) { NSLog(@"error sending command%@ to peers:%@", command, error); } } |
As the method name shows, this method sends a nsstring to all connected endpoints. NSString datausingencoding: Method converts a string to a null-terminated UTF8 byte-encoded nsdata.
After receiving, Gksession delegate method ReceiveData:fromPeer:inSession:context: will be called. It is still empty and requires you to implement the logic.
Add code to the ReceiveData:fromPeer:inSession:context: method:
NSString *commandreceived = [[nsstring alloc] initwithdata:data Encoding:nsutf8stringencoding]; NSLog(@"Command%@ received from%@ (%@)", commandreceived, Peer, Self.peerstonames [peer]); |
Encode the data back to NSString and print.
When testing, you can send a command and then test the results in the console.
At the end of the Startgame method (in the atviewcontroller.m file), add:
[self sendtoallpeers:@"TEST COMMAND"]; |
This will invoke the Startgame method when the user taps the Start Game button. This causes the server to send a Test command command to all endpoints.
Compile and run the app, run it first in the emulator, and then run it on the device. When the device starts, the console will have output, and you should ensure that the information is output from the device.
When the app on the emulator starts, tap the Start Game button. You can see the following output displayed in the console:
Isn't it super simple? Now that you're ready to send the message, the rest of the job is to add new commands and get the game running.
Add game logic
This is a knowledge game, you need to prepare some questions and answers. I found a CS (computer science) exam paper from Georgiatech, called Trivia Databasestarter, which is super simple and does not add a bunch of messy license terms.
I converted it from CSV format to a well-formed plist, where questions.plist in the project contained a two-dimensional array. In each array in the array, the first element is the question, and the correct answer is the 2nd element (do not peek!), then the wrong answer.
Open atviewcontroller.m to add the following properties:
@property (nonatomic, strong) Nsmutablearray *questions; @property (nonatomic, strong) nsmutabledictionary *peerstopoints; @property (nonatomic, assign) Nsinteger Currentquestionanswer; @property (nonatomic, assign) Nsinteger currentquestionanswersreceived; @property (nonatomic, assign) Nsinteger maxpoints; |
These attributes are described as follows:
- questions– store questions and answers. It is mutable, because whenever a question is asked, the question is removed from the array. This way you don't repeat the question, and you can easily tell when the game is over.
- peerstopoints– current results. Stores the score for each endpoint.
- currentquestionanswer– the index of the correct answer to the current question.
- How many answers did currentquestionanswersreceived– receive.
- maxpoints– the current highest score, which is used when locating the winner in the Peertopoints array.
All the properties are ready and it's time to add the code to start the game. Delete the original line of code in Startgame, and then add the following code:
if (! self.gamestarted) { self.gamestarted = YES; Self.maxpoints = 0; self.questions = [[ nsarray arraywithcontentsoffile: [[ nsbundle mainbundle pathforresource: @ "questions" oftype< Span style= "color: #002200;" >: @ "plist" ]] mutablecopy "; Self.peerstopoints = [[nsmutabledictionary alloc] initwithcapacity: Self.peersToNames.count]; for (nsstring *peerid in Self.peerstonames) { Self.peerstopoints[peerid] = @0; } } |
If the game is not already started, set gamestarted to YES and maxpoints to zero. Then load the question from the plist file. You need to use the Mutablecopy method to get a mutable array so that you can remove the question from the array. Then initialize the peerstopoints array, and everyone's score is initialized to 0.
There are no commands, so the game is ready to start, but it doesn't really start. This is our follow-up work.
First, add the following constants to the atviewcontroller.m :
| static nsstring * const kcommandquestion = Span style= "color: #11740A;" >@ ; static nsstring * const kcommandendquestion = @ "endquestion" ; static nsstring * const kcommandanswer = @ ; |
You'll see how to use them later.
Add the following method after the Startgame method:
- (void)startquestion { //1 int Questionindex = arc4random_uniform((int) [self.questions Count ]); Nsmutablearray *Questionarray = [self.questions[questionindex] mutablecopy] ; [self.questions removeobjectatindex:questionindex]; //2 NSString *Question = Questionarray[0]; [questionarray removeobjectatindex:0]; //3 Nsmutablearray *Answers = [[Nsmutablearray alloc] initwithcapacity: [ Questionarray Count]]; Self.currentquestionanswer = -1; self.currentquestionanswersreceived = 0; while ([questionarray count] > 0) { //4 int Answerindex = arc4random_uniform((int) [Questionarray Count]); if (answerindex = = 0 && self.currentquestionanswer = = - 1 ) { Self.currentquestionanswer = [answers Count]; } [answers addobject:questionarray[answerindex]]; [questionarray removeobjectatindex:answerindex]; } //5 [Self sendtoallpeers: [kcommandquestion stringbyappendingstring: [nsstr ing stringwithformat:@"%lu", (unsigned long) [ answers Count]] []; [self.scene startquestionwithanswercount: [answers Count]]; [self.mirroredscene startquestion:question withanswers:answers]; } |
The process of starting the quiz is as follows:
- First, a question is randomly selected from a question that has never been completed. Questionarray saves a copy of the currently selected question and removes the chosen question from the array of questions.
- The question content is located in the first element of Questionarray, followed by an alternative answer. First remove the question content and save it, then remove it from the Questionarray. Now, the Questionarray contains all the answers, the first of which is the correct answer.
- Initializes a mutable array to store the post-order answer, and then resets several properties.
- In the loop, the answer is randomly extracted. If it is the first answer (that is, the correct answer), save the index to Currentquestionanswer. The options for random selection are then added to the answers array and removed from the Questionarray.
- Finally, send the "question:" Command and the number of alternative answers to all endpoints. For example, "Question:4". Then update the interface, send the question content and the alternate answer to the second display window after the random order.
Next, at the end of the Startgame method, add the IF block:
Start the game and view the server's display.