Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott Montgomerie committed Oct 31, 2010
0 parents commit 0d1a9a7
Show file tree
Hide file tree
Showing 19 changed files with 2,849 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Classes/.gitignore
@@ -0,0 +1,2 @@
ComSmontgomerieBluetooth.h
ComSmontgomerieBluetooth.m
39 changes: 39 additions & 0 deletions Classes/ComSmontgomerieBluetoothModule.h
@@ -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
343 changes: 343 additions & 0 deletions Classes/ComSmontgomerieBluetoothModule.m
@@ -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
9 changes: 9 additions & 0 deletions Classes/ComSmontgomerieBluetoothModuleAssets.h
@@ -0,0 +1,9 @@
/**
* This is a generated file. Do not edit or your changes will be lost
*/

@interface ComSmontgomerieBluetoothModuleAssets : NSObject
{
}
- (NSData*) moduleAsset;
@end

0 comments on commit 0d1a9a7

Please sign in to comment.