Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Scott Montgomerie
committed
Oct 31, 2010
0 parents
commit 0d1a9a7
Showing
19 changed files
with
2,849 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ComSmontgomerieBluetooth.h | ||
ComSmontgomerieBluetooth.m |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/** | ||
* Your Copyright Here | ||
* | ||
* Appcelerator Titanium is Copyright (c) 2009-2010 by Appcelerator, Inc. | ||
* and licensed under the Apache Public License (version 2) | ||
*/ | ||
#import "TiModule.h" | ||
#import <GameKit/GameKit.h> | ||
|
||
@interface ComSmontgomerieBluetoothModule : TiModule <GKPeerPickerControllerDelegate, GKSessionDelegate, UIAlertViewDelegate> | ||
{ | ||
NSInteger gameState; | ||
NSInteger peerStatus; | ||
|
||
// networking | ||
GKSession *gameSession; | ||
int gameUniqueID; | ||
int gamePacketNumber; | ||
NSString *gamePeerId; | ||
NSDate *lastHeartbeatDate; | ||
|
||
UIAlertView *connectionAlert; | ||
} | ||
|
||
@property(nonatomic) NSInteger gameState; | ||
@property(nonatomic) NSInteger peerStatus; | ||
|
||
@property(nonatomic, retain) GKSession *gameSession; | ||
@property(nonatomic, copy) NSString *gamePeerId; | ||
@property(nonatomic, retain) NSDate *lastHeartbeatDate; | ||
@property(nonatomic, retain) UIAlertView *connectionAlert; | ||
|
||
- (void)invalidateSession:(GKSession *)session; | ||
|
||
- (void)sendNetworkPacket:(GKSession *)session packetID:(int)packetID withData:(void *)data ofLength:(int)length reliable:(BOOL)howtosend; | ||
- (void)startPicker; | ||
- (id)startPicker: (id) args; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,343 @@ | ||
/** | ||
* Your Copyright Here | ||
* | ||
* Appcelerator Titanium is Copyright (c) 2009-2010 by Appcelerator, Inc. | ||
* and licensed under the Apache Public License (version 2) | ||
*/ | ||
#import "ComSmontgomerieBluetoothModule.h" | ||
#import "TiBase.h" | ||
#import "TiHost.h" | ||
#import "TiUtils.h" | ||
|
||
|
||
// GameKit Session ID for app | ||
#define kTankSessionID @"gktank" | ||
|
||
#define kMaxTankPacketSize 1024 | ||
|
||
typedef enum { | ||
NETWORK_ACK, // no packet | ||
NETWORK_COINTOSS, // decide who is going to be the server | ||
NETWORK_MOVE_EVENT, // send position | ||
NETWORK_FIRE_EVENT, // send fire | ||
NETWORK_HEARTBEAT // send of entire state at regular intervals | ||
} packetCodes; | ||
|
||
// | ||
// various states the game can get into | ||
// | ||
typedef enum { | ||
kStateStartGame, | ||
kStatePicker, | ||
kStateMultiplayer, | ||
kStateMultiplayerCointoss, | ||
kStateMultiplayerReconnect | ||
} gameStates; | ||
|
||
|
||
@implementation ComSmontgomerieBluetoothModule | ||
|
||
@synthesize peerStatus, gameState; | ||
@synthesize gameSession, gamePeerId, lastHeartbeatDate, connectionAlert; | ||
|
||
#pragma mark Internal | ||
|
||
// this is generated for your module, please do not change it | ||
-(id)moduleGUID | ||
{ | ||
return @"98126e9f-5aed-4d9f-96c0-b1faf3ab7960"; | ||
} | ||
|
||
// this is generated for your module, please do not change it | ||
-(NSString*)moduleId | ||
{ | ||
return @"com.smontgomerie.bluetooth"; | ||
} | ||
|
||
#pragma mark Lifecycle | ||
|
||
-(void)startup | ||
{ | ||
// this method is called when the module is first loaded | ||
// you *must* call the superclass | ||
[super startup]; | ||
|
||
NSLog(@"[INFO] %@ loaded",self); | ||
} | ||
|
||
-(void)shutdown:(id)sender | ||
{ | ||
// this method is called when the module is being unloaded | ||
// typically this is during shutdown. make sure you don't do too | ||
// much processing here or the app will be quit forceably | ||
|
||
// you *must* call the superclass | ||
[super shutdown:sender]; | ||
} | ||
|
||
#pragma mark Cleanup | ||
|
||
-(void)dealloc | ||
{ | ||
self.lastHeartbeatDate = nil; | ||
if(self.connectionAlert.visible) { | ||
[self.connectionAlert dismissWithClickedButtonIndex:-1 animated:NO]; | ||
} | ||
self.connectionAlert = nil; | ||
|
||
// cleanup the session | ||
[self invalidateSession:self.gameSession]; | ||
self.gameSession = nil; | ||
self.gamePeerId = nil; | ||
|
||
// release any resources that have been retained by the module | ||
[super dealloc]; | ||
} | ||
|
||
#pragma mark Internal Memory Management | ||
|
||
-(void)didReceiveMemoryWarning:(NSNotification*)notification | ||
{ | ||
// optionally release any resources that can be dynamically | ||
// reloaded once memory is available - such as caches | ||
[super didReceiveMemoryWarning:notification]; | ||
} | ||
|
||
#pragma mark Listener Notifications | ||
|
||
-(void)_listenerAdded:(NSString *)type count:(int)count | ||
{ | ||
if (count == 1 && [type isEqualToString:@"my_event"]) | ||
{ | ||
// the first (of potentially many) listener is being added | ||
// for event named 'my_event' | ||
} | ||
} | ||
|
||
-(void)_listenerRemoved:(NSString *)type count:(int)count | ||
{ | ||
if (count == 0 && [type isEqualToString:@"my_event"]) | ||
{ | ||
// the last listener called for event named 'my_event' has | ||
// been removed, we can optionally clean up any resources | ||
// since no body is listening at this point for that event | ||
} | ||
} | ||
|
||
#pragma Public APIs | ||
|
||
-(id)example:(id)args | ||
{ | ||
// example method | ||
return @"hello world"; | ||
} | ||
|
||
-(id)exampleProp | ||
{ | ||
// example property getter | ||
return @"hello world 2"; | ||
} | ||
|
||
-(void)exampleProp:(id)value | ||
{ | ||
// example property setter | ||
} | ||
|
||
#pragma mark - | ||
#pragma mark Peer Picker Related Methods | ||
|
||
-(void)startPicker | ||
{ | ||
[self startPicker: nil]; | ||
} | ||
|
||
-(id)startPicker: (id) args { | ||
NSLog(@"Showing picker.."); | ||
|
||
GKPeerPickerController* picker; | ||
|
||
self.gameState = kStatePicker; // we're going to do Multiplayer! | ||
|
||
picker = [[GKPeerPickerController alloc] init]; // note: picker is released in various picker delegate methods when picker use is done. | ||
picker.delegate = self; | ||
[picker show]; // show the Peer Picker | ||
|
||
return nil; | ||
} | ||
|
||
#pragma mark GKPeerPickerControllerDelegate Methods | ||
|
||
- (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker { | ||
// Peer Picker automatically dismisses on user cancel. No need to programmatically dismiss. | ||
|
||
// autorelease the picker. | ||
picker.delegate = nil; | ||
[picker autorelease]; | ||
|
||
// invalidate and release game session if one is around. | ||
if(self.gameSession != nil) { | ||
[self invalidateSession:self.gameSession]; | ||
self.gameSession = nil; | ||
} | ||
|
||
// go back to start mode | ||
self.gameState = kStateStartGame; | ||
} | ||
|
||
/* | ||
* Note: No need to implement -peerPickerController:didSelectConnectionType: delegate method since this app does not support multiple connection types. | ||
* - see reference documentation for this delegate method and the GKPeerPickerController's connectionTypesMask property. | ||
*/ | ||
|
||
// | ||
// Provide a custom session that has a custom session ID. This is also an opportunity to provide a session with a custom display name. | ||
// | ||
- (GKSession *)peerPickerController:(GKPeerPickerController *)picker sessionForConnectionType:(GKPeerPickerConnectionType)type { | ||
GKSession *session = [[GKSession alloc] initWithSessionID:kTankSessionID displayName:nil sessionMode:GKSessionModePeer]; | ||
return [session autorelease]; // peer picker retains a reference, so autorelease ours so we don't leak. | ||
} | ||
|
||
- (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session { | ||
// Remember the current peer. | ||
self.gamePeerId = peerID; // copy | ||
|
||
// Make sure we have a reference to the game session and it is set up | ||
self.gameSession = session; // retain | ||
self.gameSession.delegate = self; | ||
[self.gameSession setDataReceiveHandler:self withContext:NULL]; | ||
|
||
// Done with the Peer Picker so dismiss it. | ||
[picker dismiss]; | ||
picker.delegate = nil; | ||
[picker autorelease]; | ||
|
||
// Start Multiplayer game by entering a cointoss state to determine who is server/client. | ||
self.gameState = kStateMultiplayerCointoss; | ||
} | ||
|
||
#pragma mark - | ||
#pragma mark Session Related Methods | ||
|
||
// | ||
// invalidate session | ||
// | ||
- (void)invalidateSession:(GKSession *)session { | ||
if(session != nil) { | ||
[session disconnectFromAllPeers]; | ||
session.available = NO; | ||
[session setDataReceiveHandler: nil withContext: NULL]; | ||
session.delegate = nil; | ||
} | ||
} | ||
|
||
#pragma mark Data Send/Receive Methods | ||
|
||
/* | ||
* Getting a data packet. This is the data receive handler method expected by the GKSession. | ||
* We set ourselves as the receive data handler in the -peerPickerController:didConnectPeer:toSession: method. | ||
*/ | ||
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context { | ||
static int lastPacketTime = -1; | ||
unsigned char *incomingPacket = (unsigned char *)[data bytes]; | ||
int *pIntData = (int *)&incomingPacket[0]; | ||
// | ||
// developer check the network time and make sure packers are in order | ||
// | ||
int packetTime = pIntData[0]; | ||
int packetID = pIntData[1]; | ||
if(packetTime < lastPacketTime && packetID != NETWORK_COINTOSS) { | ||
return; | ||
} | ||
|
||
lastPacketTime = packetTime; | ||
switch( packetID ) { | ||
case NETWORK_COINTOSS: | ||
{ | ||
// coin toss to determine roles of the two players | ||
// int coinToss = pIntData[2]; | ||
|
||
|
||
// after 1 second fire method to hide the label | ||
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(hideGameLabel:) userInfo:nil repeats:NO]; | ||
} | ||
break; | ||
case NETWORK_MOVE_EVENT: | ||
{ | ||
|
||
} | ||
break; | ||
case NETWORK_FIRE_EVENT: | ||
{ | ||
} | ||
break; | ||
case NETWORK_HEARTBEAT: | ||
{ | ||
// update heartbeat timestamp | ||
self.lastHeartbeatDate = [NSDate date]; | ||
|
||
// if we were trying to reconnect, set the state back to multiplayer as the peer is back | ||
if(self.gameState == kStateMultiplayerReconnect) { | ||
if(self.connectionAlert && self.connectionAlert.visible) { | ||
[self.connectionAlert dismissWithClickedButtonIndex:-1 animated:YES]; | ||
} | ||
self.gameState = kStateMultiplayer; | ||
} | ||
} | ||
break; | ||
default: | ||
// error | ||
break; | ||
} | ||
} | ||
|
||
- (void)sendNetworkPacket:(GKSession *)session packetID:(int)packetID withData:(void *)data ofLength:(int)length reliable:(BOOL)howtosend { | ||
// the packet we'll send is resued | ||
static unsigned char networkPacket[kMaxTankPacketSize]; | ||
const unsigned int packetHeaderSize = 2 * sizeof(int); // we have two "ints" for our header | ||
|
||
if(length < (kMaxTankPacketSize - packetHeaderSize)) { // our networkPacket buffer size minus the size of the header info | ||
int *pIntData = (int *)&networkPacket[0]; | ||
// header info | ||
pIntData[0] = gamePacketNumber++; | ||
pIntData[1] = packetID; | ||
// copy data in after the header | ||
memcpy( &networkPacket[packetHeaderSize], data, length ); | ||
|
||
NSData *packet = [NSData dataWithBytes: networkPacket length: (length+8)]; | ||
if(howtosend == YES) { | ||
[session sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId] withDataMode:GKSendDataReliable error:nil]; | ||
} else { | ||
[session sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId] withDataMode:GKSendDataUnreliable error:nil]; | ||
} | ||
} | ||
} | ||
|
||
#pragma mark GKSessionDelegate Methods | ||
|
||
// we've gotten a state change in the session | ||
- (void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state { | ||
if(self.gameState == kStatePicker) { | ||
return; // only do stuff if we're in multiplayer, otherwise it is probably for Picker | ||
} | ||
|
||
if(state == GKPeerStateDisconnected) { | ||
// We've been disconnected from the other peer. | ||
|
||
// Update user alert or throw alert if it isn't already up | ||
NSString *message = [NSString stringWithFormat:@"Could not reconnect with %@.", [session displayNameForPeer:peerID]]; | ||
if((self.gameState == kStateMultiplayerReconnect) && self.connectionAlert && self.connectionAlert.visible) { | ||
self.connectionAlert.message = message; | ||
} | ||
else { | ||
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Lost Connection" message:message delegate:self cancelButtonTitle:@"End Game" otherButtonTitles:nil]; | ||
self.connectionAlert = alert; | ||
[alert show]; | ||
[alert release]; | ||
} | ||
|
||
// go back to start mode | ||
self.gameState = kStateStartGame; | ||
} | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/** | ||
* This is a generated file. Do not edit or your changes will be lost | ||
*/ | ||
|
||
@interface ComSmontgomerieBluetoothModuleAssets : NSObject | ||
{ | ||
} | ||
- (NSData*) moduleAsset; | ||
@end |
Oops, something went wrong.