本系列博客将系统的介绍一款蓝牙对战五子棋的开发思路与过程,其中的核心部分有两个,一部分是蓝牙通信中对战双方信息交互框架的设计与开发,一部分是五子棋游戏中棋盘逻辑与胜负断定的算法实现。本篇博客将介绍游戏中蓝牙通信类的设计思路git
在前篇的一篇博客中,咱们有详细的介绍iOS中蓝牙4.0技术的应用与系统框架CoorBluetooth.framework中提供的编程接口的用法。博客地址以下,若是读者须要更详细的了解iOS中蓝牙技术的使用,能够先阅读这篇博客:github
iOS开发之蓝牙通信:http://my.oschina.net/u/2340880/blog/548127。算法
在使用蓝牙进行应用间通信交互时,必须有一方做为中心设备,有一方做为外围设备。举一个简单的例子,经过手机蓝牙能够和刷卡设备、打印机等进行信息交互,这里的刷卡设备、打印机就充当着外围设备的角色,手机就充当着中心设备的角色。在中心设备与外围设备间,外设负责向周围广播广告告知其余设备本身的存在,中心设备接收到外设广播的广告后能够选择目标设备进行链接,固然,外设的广播的广告中会携带一些身份信息供中心设备进行识别。一旦中心设备与外设创建链接,中心设备变可使用外设提供的服务,一个外设能够提供多个服务,例如一款蓝牙打印机外设可能会提供两种服务,一种服务向中心设备发送约定信息,告知中心设备支持的打印格式,一种服务获取中心设备的数据来进行打印服务。服务是中心设备与外设机型通信的功能标识,然而具体的通信媒介则是由服务中的特征值来完成的,一个服务也能够提供多个特征值。能够这样理解,特征值是两设备进行蓝牙通信的最小通信单元,是读写数据的载体。编程
上面简单介绍了在蓝牙通信中的一些基本流程与相关概念,应用于游戏中略微有一些区别,首先咱们这款游戏应该具有既能够做为中心设备也能够做为外设的能力,所以,咱们须要将中心设备的通信模式与外设的通信模式都集成与游戏的通信框架中。游戏的双方要创建链接应该有以下几个过程:框架
1.有一方创建游戏,做为房主。async
2.由一方做为游戏的加入者,扫描附近的游戏。工具
3.外设提供的服务中应该至少有两个特征值,一个用于己方下子后通知对方设备,一个用于监听对方设备的下子操做。atom
由上面分析可知,游戏中的房主正是充当蓝牙通信中的外设,它将广播广告告知周围设备本身的存在。而游戏中的加入者则是充当着蓝牙通信中的中心设备,扫描到周围的游戏房间后进行链接加入,开始游戏。spa
建立一个命名为BlueToothTool的工具类,做为游戏的蓝牙通信类,编写其头文件以下:.net
BlueToothTool.h
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import <CoreBluetooth/CoreBluetooth.h> //这个代理用于处理接收到对方设备发送来的数据后的回调 @protocol BlueToothToolDelegate <NSObject> //获取对方数据 -(void)getData:(NSString *)data; @end @interface BlueToothTool : NSObject<CBPeripheralManagerDelegate,CBCentralManagerDelegate,CBPeripheralDelegate,UIAlertViewDelegate> //代理 @property(nonatomic,weak)id<BlueToothToolDelegate>delegate; //标记是不是游戏中的房主 @property(nonatomic,assign)BOOL isCentral; /** *获取单例对象的方法 */ +(instancetype)sharedManager; /* *做为游戏的房主创建游戏房间 */ -(void)setUpGame:(NSString *)name block:(void(^)(BOOL first))finish; /* *做为游戏的加入者查找附近的游戏 */ -(void)searchGame; /** *断块链接 */ -(void)disConnect; /* *进行写数据操做 */ -(void)writeData:(NSString *)data; @end
实现BlueToothTool.m文件以下:
#import "BlueToothTool.h" @implementation BlueToothTool { //外设管理中心 CBPeripheralManager * _peripheralManager; //外设提供的服务 CBMutableService * _ser; //服务提供的读特征值 CBMutableCharacteristic * _readChara; //服务提供的写特征值 CBMutableCharacteristic * _writeChara; //等待对方加入的提示视图 UIView * _waitOtherView; //正在扫描附近游戏的提示视图 UIView * _searchGameView; //设备中心管理对象 CBCentralManager * _centerManger; //要链接的外设 CBPeripheral * _peripheral; //要交互的外设属性 CBCharacteristic * _centerReadChara; CBCharacteristic * _centerWriteChara; //开始游戏后的回调 告知先手与后手信息 void(^block)(BOOL first); } //实现单例方法 +(instancetype)sharedManager{ static BlueToothTool *tool = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ tool = [[self alloc] init]; }); return tool; } //实现建立游戏的方法 -(void)setUpGame:(NSString *)name block:(void (^)(BOOL))finish{ block = [finish copy]; if (_peripheralManager==nil) { //初始化服务 _ser= [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00067"] primary:YES]; //初始化特征 _readChara = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00067"] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable]; _writeChara = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00068"] properties:CBCharacteristicPropertyWriteWithoutResponse value:nil permissions:CBAttributePermissionsWriteable]; //向服务中添加特征 _ser.characteristics = @[_readChara,_writeChara]; _peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)]; } //设置为房主 _isCentral=YES; //开始广播广告 [_peripheralManager startAdvertising:@{CBAdvertisementDataLocalNameKey:@"WUZIGame"}]; } //外设检测蓝牙状态 -(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{ //判断是否可用 if (peripheral.state==CBPeripheralManagerStatePoweredOn) { //添加服务 [_peripheralManager addService:_ser]; //开始广播广告 [_peripheralManager startAdvertising:@{CBAdvertisementDataLocalNameKey:@"WUZIGame"}]; }else{ //弹提示框 dispatch_async(dispatch_get_main_queue(), ^{ [self showAlert]; }); } } //开始放广告的回调 -(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{ if (_waitOtherView==nil) { _waitOtherView = [[UIView alloc]initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-100, 240, 200, 100)]; dispatch_async(dispatch_get_main_queue(), ^{ UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 100)]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.text = @"等待附近玩家加入"; [_waitOtherView addSubview:label]; _waitOtherView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4]; [[[UIApplication sharedApplication].delegate window]addSubview:_waitOtherView]; }); }else{ dispatch_async(dispatch_get_main_queue(), ^{ [_waitOtherView removeFromSuperview]; [[[UIApplication sharedApplication].delegate window]addSubview:_waitOtherView]; }); } } //添加服务后回调的方法 -(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{ if (error) { NSLog(@"添加服务失败"); } NSLog(@"添加服务成功"); } //中心设备订阅特征值时回调 -(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{ [_peripheralManager stopAdvertising]; if (_isCentral) { UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"" message:@"请选择先手后手" delegate:self cancelButtonTitle:@"我先手" otherButtonTitles:@"我后手", nil]; dispatch_async(dispatch_get_main_queue(), ^{ [_waitOtherView removeFromSuperview]; [alert show]; }); } } //收到写消息后的回调 -(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests{ dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate getData:[[NSString alloc]initWithData:requests.firstObject.value encoding:NSUTF8StringEncoding]]; }); } //弹提示框的方法 -(void)showAlert{ UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"舒适提示" message:@"请确保您的蓝牙可用" delegate:nil cancelButtonTitle:@"好的" otherButtonTitles:nil, nil]; [alert show]; } //===============================================================做为游戏加入这实现的方法======== //搜索周围游戏 -(void)searchGame{ if (_centerManger==nil) { _centerManger = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)]; }else{ [_centerManger scanForPeripheralsWithServices:nil options:nil]; if (_searchGameView==nil) { _searchGameView = [[UIView alloc]initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-100, 240, 200, 100)]; UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 100)]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.text = @"正在扫加入描附近游戏"; _searchGameView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4]; [_searchGameView addSubview:label]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; }else{ [_searchGameView removeFromSuperview]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; } } //设置为游戏加入方 _isCentral = NO; } //设备硬件检测状态回调的方法 可用后开始扫描设备 -(void)centralManagerDidUpdateState:(CBCentralManager *)central{ if (_centerManger.state==CBCentralManagerStatePoweredOn) { [_centerManger scanForPeripheralsWithServices:nil options:nil]; if (_searchGameView==nil) { dispatch_async(dispatch_get_main_queue(), ^{ _searchGameView = [[UIView alloc]initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/2-100, 240, 200, 100)]; UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 200, 100)]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.text = @"正在扫加入描附近游戏"; _searchGameView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4]; [_searchGameView addSubview:label]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; }); }else{ dispatch_async(dispatch_get_main_queue(), ^{ [_searchGameView removeFromSuperview]; [[[UIApplication sharedApplication].delegate window]addSubview:_searchGameView]; }); } }else{ dispatch_async(dispatch_get_main_queue(), ^{ [self showAlert]; }); } } //发现外设后调用的方法 -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{ //获取设备的名称 或者广告中的相应字段来配对 NSString * name = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey]; if ([name isEqualToString:@"WUZIGame"]) { //保存此设备 _peripheral = peripheral; //进行链接 [_centerManger connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey:@YES}]; } } //链接外设成功的回调 -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@"链接成功"); //设置代理与搜索外设中的服务 [peripheral setDelegate:self]; [peripheral discoverServices:nil]; dispatch_async(dispatch_get_main_queue(), ^{ [_searchGameView removeFromSuperview]; }); } //链接断开 -(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@"链接断开"); [_centerManger connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey:@YES}]; } //发现服务后回调的方法 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ for (CBService *service in peripheral.services) { //发现服务 比较服务的UUID if ([service.UUID isEqual:[CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00067"]]) { NSLog(@"Service found with UUID: %@", service.UUID); //查找服务中的特征值 [peripheral discoverCharacteristics:nil forService:service]; break; } } } //开发服务中的特征值后回调的方法 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ for (CBCharacteristic *characteristic in service.characteristics) { //发现特征 比较特征值得UUID 来获取所须要的 if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00067"]]) { //保存特征值 _centerReadChara = characteristic; //监听特征值 [_peripheral setNotifyValue:YES forCharacteristic:_centerReadChara]; } if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00068"]]) { //保存特征值 _centerWriteChara = characteristic; } } } //所监听的特征值更新时回调的方法 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { //更新接收到的数据 NSLog(@"%@",[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]); //要在主线程中刷新 dispatch_async(dispatch_get_main_queue(), ^{ [self.delegate getData:[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]]; }); } -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{ //告诉开发者前后手信息 if (buttonIndex==0) { if (_isCentral) { block(1); }else{ block(0); } }else{ if (_isCentral) { block(0); }else{ block(1); } } } //断开链接 -(void)disConnect{ if (!_isCentral) { [_centerManger cancelPeripheralConnection:_peripheral]; [_peripheral setNotifyValue:NO forCharacteristic:_centerReadChara]; } } //写数据 -(void)writeData:(NSString *)data{ if (_isCentral) { [_peripheralManager updateValue:[data dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:_readChara onSubscribedCentrals:nil]; }else{ [_peripheral writeValue:[data dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:_centerWriteChara type:CBCharacteristicWriteWithoutResponse]; } } @end
附录:游戏的源码已经放在git上,时间比较仓促,只用了一下午来写,其中还有许多细节与bug没有进行调整,有须要的能够做为参考:
git地址:https://github.com/ZYHshao/BlueGame。
专一技术,热爱生活,交流技术,也作朋友。
——珲少 QQ群:203317592