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

一、引言

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

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

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

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

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

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

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

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

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

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

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

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

BlueToothTool.h

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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文件如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#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。

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

  1. iOS简易蓝牙对战五子棋游戏设计思路之二——核心棋盘逻辑与胜负判定算法

    2019独角兽企业重金招聘Python工程师标准>>> iOS简易蓝牙对战五子棋游戏设计思路之二--核心棋盘逻辑与胜负判定算法 一.引言 上一篇博客我们介绍了在开发一款蓝牙对战五子棋 ...

  2. 【181018】VC++ 网络对战五子棋游戏(服务端+用户端)

    VC++ 网络对战五子棋游戏(服务端+客户端),编译后先开启服务器端,服务端管理着各个用户之间的数据传递,用户端是多个的.就像游戏大厅一样.用户登录了后服务端将向用户端发送当前所有在线玩家列表数据.由 ...

  3. ML之LGBMRegressor(Competition):2018年全国大学生计算机技能应用大赛《住房月租金预测大数据赛》——设计思路以及核心代码—191017再次更新

    ML之LGBMRegressor(Competition):2018年全国大学生计算机技能应用大赛<住房月租金预测大数据赛>--设计思路以及核心代码-191017再次更新 目录 竞赛相关信 ...

  4. Android 蓝牙对战五子棋项目实现(含人机对战功能)

    上周花了一周时间做的课程设计的项目,实现的功能如下: 基本功能: (1) 该APP能够通过蓝牙自动搜索周围其他使用了该APP的手机,用户可选择其中某一个APP发起对战的要求,被发起方可以同意或者拒绝: ...

  5. C语言实现双人对战五子棋游戏

    在编写五子棋游戏前首先对整个项目进行分析: 1.五子棋的界面绘制及显示 2.对输入的数据进行写入 3.判断输入的数据多对应的位置上是否可以下棋其中包括检测此位置是否为空及是否超出下棋的有效位置(越界超 ...

  6. java实现游戏对战过程_【Java实战】Java实现简易坦克对战小游戏

    //此程序用来实现一个简易的坦克对战小游戏! //Version:1.0 // @Author:Yongchun_zha package cha09; import javax.swing.*; im ...

  7. 【Java实战】Java实现简易坦克对战小游戏

    摘要:前期学习了Java入门的相关基础,现在通过这个小项目来熟悉巩固所学.该程序主要实现了一个简易的坦克对战小游戏,提供UI界面 //此程序用来实现一个简易的坦克对战小游戏! //Version:1. ...

  8. Java练手项目实战——五子棋游戏实现思路及源码

    目录 一.实现功能 二.实现思路 1.窗体设计 2.棋盘设计 3.棋子坐标存储设计 4.游戏状态设计 5.开始游戏与重新开始游戏的实现 6.悔棋的实现 7.认输的实现 8.游戏结束的判断,即是否有同色 ...

  9. fifo页面置换算法设计思路_千万级并发!如何设计一个多级缓存系统?

    什么是一个多级缓存系统?它有什么用?我们又如何设计一个多级缓存系统? 图片来自 Pexels 所谓多级缓存系统,就是指在一个系统的不同的架构层级进行数据缓存,以提升访问效率. 我们都知道,一个缓存系统 ...

最新文章

  1. Silverlight HLSL实现背景滚动
  2. 第一次使用MsChart运行提示:ChartImg.axd 执行子请求时出错。
  3. div实现返回符,倒三角,椭圆+小知识收集
  4. VTK:图像映射到颜色用法实战
  5. A Simple Math Problem
  6. HTTP详解(转载)
  7. java随机生成数字代码,详解系列文章
  8. 【iOS】TouchDown、TouchDownRepeat 和 TouchCancel 的区别
  9. php 重写sessionid,PHP 重置 phpsessionid
  10. GEE学习笔记3-导出table数据
  11. unable to remove repository reference  (must force) - container is using its referenced image
  12. 2021蓝桥杯省赛c++A
  13. 2016奇虎360研发工程师内推笔试编程题 - 题解
  14. 计算机专业保研面试备考:高等数学
  15. CDH安装服务和下载parcels包
  16. FFmpeg 音视频截取
  17. 给生命一段独处的时光
  18. 虚拟机NAT模式的网络设置
  19. 读里尔克的后感 文/奥斯·科特林
  20. H5canvas(绘制矩形,绘制圆形,绘制线段,绘制文字,绘制曲线)

热门文章

  1. 手动卸载删除ActiveX控件。
  2. git打patch__2019.08.07
  3. php基础从入门到面向对象(大合集)
  4. Linux与安卓安全对抗
  5. 华为云IOT Android应用开发详细教程
  6. 投影机融合的工作报告
  7. 国外15款开源免费软件
  8. Adobe Dreamweaver CS6 绿色版
  9. 逆向破解之160个CrackMe —— 008-009
  10. drupal安装教程 linux,linux:搭建Drupal