iOS简易蓝牙对战五子棋游戏设计思路之一——核心蓝牙通讯类的设计

iOS简易蓝牙对战五子棋游戏设计思路之一——核心蓝牙通讯类的设计

一、引言

    本系列博客将系统的介绍一款蓝牙对战五子棋的开发思路与过程,其中的核心部分有两个,一部分是蓝牙通讯中对战双方信息交互框架的设计与开发,一部分是五子棋游戏中棋盘逻辑与胜负判定的算法实现。本篇博客将介绍游戏中蓝牙通讯类的设计思路

二、设计通讯类的核心想法

   在前篇的一篇博客中,我们有详细的介绍iOS中蓝牙4.0技术的应用与系统框架CoorBluetooth.framework中提供的编程接口的用法。博客地址如下,如果读者需要更详细的了解iOS中蓝牙技术的使用,可以先阅读这篇博客:

iOS开发之蓝牙通讯:http://my.oschina.net/u/2340880/blog/548127

    在使用蓝牙进行应用间通讯交互时,必须有一方作为中心设备,有一方作为外围设备。举一个简单的例子,通过手机蓝牙可以和刷卡设备、打印机等进行信息交互,这里的刷卡设备、打印机就充当着外围设备的角色,手机就充当着中心设备的角色。在中心设备与外围设备间,外设负责向周围广播广告告知其他设备自己的存在,中心设备接收到外设广播的广告后可以选择目标设备进行连接,当然,外设的广播的广告中会携带一些身份信息供中心设备进行识别。一旦中心设备与外设建立连接,中心设备变可以使用外设提供的服务,一个外设可以提供多个服务,例如一款蓝牙打印机外设可能会提供两种服务,一种服务向中心设备发送约定信息,告知中心设备支持的打印格式,一种服务获取中心设备的数据来进行打印服务。服务是中心设备与外设机型通讯的功能标识,然而具体的通讯媒介则是由服务中的特征值来完成的,一个服务也可以提供多个特征值。可以这样理解,特征值是两设备进行蓝牙通讯的最小通讯单元,是读写数据的载体。

   上面简单介绍了在蓝牙通讯中的一些基本流程与相关概念,应用于游戏中略微有一些区别,首先我们这款游戏应该具备既可以作为中心设备也可以作为外设的能力,因此,我们需要将中心设备的通讯模式与外设的通讯模式都集成与游戏的通讯框架中。游戏的双方要建立连接应该有如下几个过程:

1.有一方建立游戏,作为房主。

2.由一方作为游戏的加入者,扫描附近的游戏。

3.外设提供的服务中应该至少有两个特征值,一个用于己方下子后通知对方设备,一个用于监听对方设备的下子操作。

    由上面分析可知,游戏中的房主正是充当蓝牙通讯中的外设,它将广播广告告知周围设备自己的存在。而游戏中的加入者则是充当着蓝牙通讯中的中心设备,扫描到周围的游戏房间后进行连接加入,开始游戏。

三、游戏中蓝牙通讯类的设计

   创建一个命名为BlueToothTool的工具类,作为游戏的蓝牙通讯类,编写其头文件如下:

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

专注技术,热爱生活,交流技术,也做朋友。 ——珲少

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏快乐八哥

在Orchard中使用Image Gallery模块

     作为ASP.NET MVC领域一款优秀的开源CMS,Orchard值得所有.NET Web开发人员学习和研究,然后二次开发,最后在其基础上创新。也是遵...

21570
来自专栏SDNLAB

云数据中心网络虚拟化——大二层技术巡礼之L2 Fabric技术传输隧道

NVo3是L2 over IP/MPLS,利用IP网络的智能传输作为大二层虚拟交换机的背板走线,其好处是利用现有的设备即可完成隧道的传输。不过同时这也带来了一定...

48660
来自专栏飞总聊IT

大数据那些事(11):复活的LSM-Tree--BigTable的\b系统实现(修)

修正一些小错误。 BigTable是一个非常复杂的系统,发表的论文面面俱到,但是每个方面都写得并不是很清楚。所幸Google开源了LevelDB这个Key-Va...

40250
来自专栏黑泽君的专栏

励志公式和python小体验

13840
来自专栏陈满iOS

iOS网络视频下载与播放:两种视频URL格式(m3u8 & mp4)(AVFoundation框架篇·以网易视频为例)

分析网易新闻的视频接口时,单个视频数据其实会包含了两种视频URL格式地址,一个MP4视频URL,一个m3u8视频URL。

4.2K30
来自专栏机器人网

教你DIY一个撩妹装X神器——仿生金刚狼爪

当初金刚狼电影刚上映的时候,大街上就开始卖各种塑料的、绑在手上的狼爪玩具,不过这些玩具通常廉价,狼爪不能伸缩,可以伸缩的也大多需求手动开关,这就大大降低的玩具的...

40950
来自专栏阮一峰的网络日志

键盘之争:QWERTY还是Dvorak

上图是现在通用的QWERTY键盘,以键盘第一排字母的左边6个字母而得名。这种键盘是1868年由Christopher Sholes申请专利,后来在全世界占据了主...

34580
来自专栏信安之路

CTF初识与深入

这段时间一直在忙活CTF相关的东西,从参赛者到出题人,刷过一些题,也初步了解了出题人的逻辑;这篇文章就简单地讲一下CTF如何入门以及如何深入的学习、利用CTF这...

20700
来自专栏知晓程序

海量高清二次元壁纸!快来把老婆抱回家吧

今天,知晓程序(微信号 zxcx0101)要给广大动漫迷们,推荐一款名叫「Soda 壁纸」小程序,导航用的是颜文字,图库里全是高清壁纸,十分带感。

11020
来自专栏静晴轩

Sublime Text 最新注册码分享

SublimeText,她作为强大而小巧,性感且快捷的编辑器,长时间以来,受众人青睐;对于个人来说,也是最常用代码编辑器,且没有之一(目前同时也会用 VsCod...

39030

扫码关注云+社区

领取腾讯云代金券