logo头像

学如逆水行舟

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

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

一、引言

上一篇博客我们介绍了在开发一款蓝牙对战五子棋游戏中核心的蓝牙通讯框架的设计与编写,本篇博客将来完成独立的棋盘逻辑与胜负判定算法。上篇博客地址如下:

五子棋游戏中和核心通讯类设计:http://my.oschina.net/u/2340880/blog/644432

二、棋盘中独立棋格的设计

我们知道,五子棋游戏的棋盘是由横纵交叉的两组平行线组成,每一个横纵线的交点即是棋盘上可以落子的点。因此,在设计棋盘前,我们可以先来设计创建棋盘上每一个独立的落子点,这里称之为棋格,在iOS中,可以使用UIButton类来进行棋格的设计。

创建一个类,命名为TipButton作为棋格类,实现其头文件如下:

TipButton.h

1
2
3
4
5
6
7
8
9
10
11
#import <UIKit/UIKit.h>
@interface TipButton : UIButton
//标记此瓦片是否已经落子 0 空 1 己方落子 2 敌方落子
@property(nonatomic,assign)int hasChess;
//落子 BOOL类型的参数 决定是己方还是敌方
-(void)dropChess:(BOOL)isMine;
//设置白子或者黑子
@property(nonatomic,assign)BOOL isWhite;
//瓦片编号
@property(nonatomic,assign)int index;
@end

实现.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
#import "TipButton.h"
@implementation TipButton
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self creatView];
    }
    return self;
}
-(void)creatView{
    //创建横竖两条线
    UIView * line1 = [[UIView alloc]initWithFrame:CGRectMake(self.frame.size.width/2-0.25, 0, 0.5, self.frame.size.height)];
    line1.backgroundColor = [UIColor grayColor];
    [self addSubview:line1];
    
    UIView * line2 = [[UIView alloc]initWithFrame:CGRectMake(0, self.frame.size.height/2-0.25, self.frame.size.width, 0.5)];
    line2.backgroundColor = [UIColor grayColor];
    [self addSubview:line2];
}

-(void)dropChess:(BOOL)isMine{
    UIView * view = [[UIView alloc]initWithFrame:CGRectMake(self.frame.size.width/2-5, self.frame.size.height/2-5, 10, 10)];
    view.layer.masksToBounds = YES;
    view.layer.cornerRadius = 5;
    UIColor * myColor;
    UIColor * otherColor;
    if (_isWhite) {
        myColor = [UIColor whiteColor];
        otherColor = [UIColor blackColor];
    }else{
        myColor = [UIColor blackColor];
        otherColor = [UIColor whiteColor];
    }
    if (isMine) {
        view.backgroundColor = myColor;
        self.hasChess = 1;
    }else{
        view.backgroundColor = otherColor;
        self.hasChess = 2;
    }
    [self addSubview:view];
   
}
@end

三、游戏棋盘的设计

创建一个继承于UIView的类,作为五子棋游戏的棋盘,命名为GameView实现如下:

GameView.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <UIKit/UIKit.h>
#import "TipButton.h"
#import "BlueToothTool.h"
//用于处理用户下子后的逻辑
@protocol GameViewDelegate<NSObject>
-(void)gameViewClick:(NSString *)index;
@end
@interface GameView : UIView<UIAlertViewDelegate>
//存放所有棋格
@property(nonatomic,strong)NSMutableArray<TipButton *> * tipArray;
@property(nonatomic,weak)id<GameViewDelegate>delegate;
//进行下子
-(void)setTipIndex:(int)index;
@end

GameView.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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#import "GameView.h"
@implementation GameView
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _tipArray = [[NSMutableArray alloc]init];
        [self creatView];
    }
    return self;
}
//创建表格视图 横16 竖20
-(void)creatView{
    self.layer.borderColor = [UIColor colorWithRed:222/255.0 green:222/255.0 blue:222/255.0 alpha:1].CGColor;
    self.layer.borderWidth = 0.5;
    CGFloat width = self.frame.size.width/12;
    CGFloat height = self.frame.size.height/20;
    //排列布局
    for (int i=0; i<240; i++) {
        TipButton * btn = [[TipButton alloc]initWithFrame:CGRectMake(width*(i%12), height*(i/12), width, height)];
        [btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];
        btn.isWhite = NO;
        btn.index=i;
        [self addSubview:btn];
        [_tipArray addObject:btn];
    }
}
-(void)click:(TipButton *)btn{
    if (btn.hasChess==0) {
        //下子
        [btn dropChess:YES];
        //进行胜负判定
        [self cheak];
        [self.delegate gameViewClick:[NSString stringWithFormat:@"%d",btn.index]];
    }
}
//进行胜负判定
-(void)cheak{
    //判定己方是否胜利
    if ([self cheakMine]) {
        UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"您胜利啦" message:@"" delegate:self cancelButtonTitle:@"好的" otherButtonTitles:nil, nil];
        [alert show];
    }
    //判断对方是否胜利
    if ([self cheakOther]) {
        UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"您失败了" message:@"" delegate:self cancelButtonTitle:@"好的" otherButtonTitles:nil, nil];
        [alert show];
    }
}
-(void)setTipIndex:(int)index{
    //下子
    for (TipButton * btn in _tipArray) {
        if (btn.index==index) {
            [btn dropChess:NO];
            [self cheak];
        }
    }
}
-(BOOL)cheakOther{
    //遍历所有棋子
    for (int i=0; i<_tipArray.count; i++) {
        TipButton * tip = _tipArray[i];
        //获取是否是己方棋子
        if (tip.hasChess==2) {
            //进行五子判定逻辑
            //横向
            if ( [self cheak1HasMineOrOther:NO index:i]) {
                return YES;
            }
            //左上到右下的对角线
            if ( [self cheak2HasMineOrOther:NO index:i]) {
                return YES;
            }
            //纵向
            if ( [self cheak3HasMineOrOther:NO index:i]) {
                return YES;
            }
            //右上到左下的对角线
            if ( [self cheak4HasMineOrOther:NO index:i]) {
                return YES;
            }
        }
    }
    return NO;

}

-(BOOL)cheakMine{
    //遍历所有棋子
    for (int i=0; i<_tipArray.count; i++) {
        TipButton * tip = _tipArray[i];
        //获取是否是己方棋子
        if (tip.hasChess==1) {
            //进行五子判定逻辑
            //横向
            if ( [self cheak1HasMineOrOther:YES index:i]) {
                return YES;
            }
            //左上到右下的对角线
            if ( [self cheak2HasMineOrOther:YES index:i]) {
                return YES;
            }
            //纵向
            if ( [self cheak3HasMineOrOther:YES index:i]) {
                return YES;
            }
            //右上到左下的对角线
            if ( [self cheak4HasMineOrOther:YES index:i]) {
                return YES;
            }
        }
    }
    return NO;
}


-(BOOL)cheak1HasMineOrOther:(BOOL)mine index:(int)index{
    int mineOrOther = 0;
    if (mine) {
        mineOrOther = 1;
    }else{
        mineOrOther = 2;
    }
    int count=1;
    //左侧右侧同时进行可以增加效率
    //左侧
    count = count +[self algorithmic1:index param:mineOrOther num:4];
    //右侧
    count = count +[self algorithmic2:index param:mineOrOther num:4];
    if (count>=5) {
        return YES;
    }else{
        return NO;
    }
}

-(BOOL)cheak2HasMineOrOther:(BOOL)mine index:(int)index{
    int mineOrOther = 0;
    if (mine) {
        mineOrOther = 1;
    }else{
        mineOrOther = 2;
    }
    int count=1;
    //左上右下同时进行可以增加效率
    //左上
    count = count +[self algorithmic3:index param:mineOrOther num:4];
    //右下
    count = count +[self algorithmic4:index param:mineOrOther num:4];
    if (count>=5) {
        return YES;
    }else{
        return NO;
    }
}

-(BOOL)cheak3HasMineOrOther:(BOOL)mine index:(int)index{
    int mineOrOther = 0;
    if (mine) {
        mineOrOther = 1;
    }else{
        mineOrOther = 2;
    }
    int count=1;
    //纵向
    //向上
    count = count +[self algorithmic5:index param:mineOrOther num:4];
    //向下
    count = count +[self algorithmic6:index param:mineOrOther num:4];
    if (count>=5) {
        return YES;
    }else{
        return NO;
    }
}
-(BOOL)cheak4HasMineOrOther:(BOOL)mine index:(int)index{
    int mineOrOther = 0;
    if (mine) {
        mineOrOther = 1;
    }else{
        mineOrOther = 2;
    }
    int count=1;
    //纵向
    //向上
    count = count +[self algorithmic7:index param:mineOrOther num:4];
    //向下
    count = count +[self algorithmic8:index param:mineOrOther num:4];
    
    NSLog(@"%d",count);
    if (count>=5) {
        return YES;
    }else{
        return NO;
    }
}

/*
 左侧递归进行查找 index 棋子编号 param 对比值 num 递归次数
 */
-(int)algorithmic1:(int)index param:(int)param num:(int)num{
    if (num>0) {
        int tem = 4-(num-1);
            //左侧有子
        if (index-tem>=0) {
            //左侧无换行
            if(((index-tem)%12)!=11){
                if (_tipArray[index-tem].hasChess==param) {
                   return  [self algorithmic1:index param:param num:num-1];
                }else{
                    return 4-num;
                }
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}
/*
 右侧递归进行查找 index 棋子编号 param 对比值 num 递归次数
 */
-(int)algorithmic2:(int)index param:(int)param num:(int)num{
    
    if (num>0) {
        int tem = 4-(num-1);
        //右侧有子
        if (index+tem<240) {
            //右侧无换行
            if(((index+tem)%12)!=11){
                if (_tipArray[index+tem].hasChess==param) {
                    return  [self algorithmic2:index param:param num:num-1];
                }else{
                    return 4-num;
                }
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}

/*
 左上递归进行查找 index 棋子编号 param 对比值 num 递归次数
 */
-(int)algorithmic3:(int)index param:(int)param num:(int)num{
    if (num>0) {
        int tem = 4-(num-1);
        //左上有子
        if ((index-(tem*12)-tem)>=0) {
            //右侧无换行
            if(((index-(tem*12)-tem)%12)!=11){
                if (_tipArray[(index-(tem*12)-tem)].hasChess==param) {
                    return  [self algorithmic3:index param:param num:num-1];
                }else{
                    return 4-num;
                }
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}

-(int)algorithmic4:(int)index param:(int)param num:(int)num{
    if (num>0) {
        int tem = 4-(num-1);
        //左上有子
        if ((index+(tem*12)+tem)<240) {
            //右侧无换行
            if(((index+(tem*12)+tem)%12)!=0){
                if (_tipArray[(index+(tem*12)+tem)].hasChess==param) {
                    return  [self algorithmic4:index param:param num:num-1];
                }else{
                    return 4-num;
                }
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}

-(int)algorithmic5:(int)index param:(int)param num:(int)num{
    if (num>0) {
        int tem = 4-(num-1);
        //上有子
        if ((index-(tem*12))>=0) {
            if (_tipArray[(index-(tem*12))].hasChess==param) {
                return  [self algorithmic5:index param:param num:num-1];
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}

-(int)algorithmic6:(int)index param:(int)param num:(int)num{
    if (num>0) {
        int tem = 4-(num-1);
        //上有子
        if ((index+(tem*12))<240) {
            if (_tipArray[(index+(tem*12))].hasChess==param) {
                return  [self algorithmic6:index param:param num:num-1];
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}
-(int)algorithmic7:(int)index param:(int)param num:(int)num{
    if (num>0) {
        int tem = 4-(num-1);
        //左上有子
        if ((index-(tem*12)+tem)>=0) {
            //右侧无换行
            if(((index-(tem*12)+tem)%12)!=0){
                if (_tipArray[(index-(tem*12)+tem)].hasChess==param) {
                    return  [self algorithmic7:index param:param num:num-1];
                }else{
                    return 4-num;
                }
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}

-(int)algorithmic8:(int)index param:(int)param num:(int)num{
    if (num>0) {
        int tem = 4-(num-1);
        //左上有子
        if ((index+(tem*12)-tem)<240) {
            //右侧无换行
            if(((index+(tem*12)-tem)%12)!=11){
                if (_tipArray[(index+(tem*12)-tem)].hasChess==param) {
                    return  [self algorithmic8:index param:param num:num-1];
                }else{
                    return 4-num;
                }
            }else{
                return 4-num;
            }
        }else{
            return 4-num;
        }
    }else{
        //递归了四次
        return 4-num;
    }
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [[BlueToothTool sharedManager]disConnect];
    [(UIViewController *)[self.superview nextResponder] dismissViewControllerAnimated:YES completion:nil];
}
@end

关于胜负判定的算法逻辑,这里采用了向各个方向进行递归查找的方式,这里有一点需要主要,在4个方向进行递归查找时,理论上每个方向只需要单面递归即可,但是代码中采用了双面递归在进行累加的方式,这样的设计可以遍历更少的棋子判定出胜负情况。

四、整合通讯与游戏逻辑

创建一个继承于UIViewController的类作为游戏视图控制器,实现如下:

GameViewController.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
#import "GameViewController.h"
#import "GameView.h"
#import "BlueToothTool.h"
@interface GameViewController ()<BlueToothToolDelegate,GameViewDelegate>
{
    UIView * _bgView;
    UILabel * _tipLabel;
    GameView * _view;
}
@end

@implementation GameViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor brownColor];
    //创建游戏视图
    _view = [[GameView alloc]initWithFrame:CGRectMake(20, 40, (self.view.frame.size.width-40), (self.view.frame.size.width-40)/12*20)];
    _view.delegate=self;
    [self.view addSubview:_view];
    //创建背景视图
    _bgView = [[UIView alloc]initWithFrame:self.view.frame];
    _bgView.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.1];
    UIButton * btn = [UIButton buttonWithType:UIButtonTypeSystem];
    btn.frame = CGRectMake(self.view.frame.size.width/2-50, 150, 100, 30);
    UIButton * btn2 = [UIButton buttonWithType:UIButtonTypeSystem];
    btn2.frame = CGRectMake(self.view.frame.size.width/2-50, 250, 100, 30);
    [btn setTitle:@"创建游戏" forState:UIControlStateNormal];
    [btn2 setTitle:@"扫描附近游戏" forState:UIControlStateNormal];
    btn.backgroundColor = [UIColor orangeColor];
    btn2.backgroundColor = [UIColor orangeColor];
    [btn addTarget:self action:@selector(creatGame) forControlEvents:UIControlEventTouchUpInside];
    [btn2 addTarget:self action:@selector(searchGame) forControlEvents:UIControlEventTouchUpInside];
    [_bgView addSubview:btn];
    [_bgView addSubview:btn2];
    
    [self.view addSubview:_bgView];
    //设置蓝牙通讯类代理
    [BlueToothTool sharedManager].delegate=self;
    //创建提示标签
    _tipLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width,40)];
    [self.view addSubview:_tipLabel];
    _tipLabel.textAlignment = NSTextAlignmentCenter;
}
-(void)creatGame{
    [[BlueToothTool sharedManager]setUpGame:@"" block:^(BOOL first) {
        [_bgView removeFromSuperview];
        if (first) {
            _tipLabel.text = @"请您下子";
            //进行发送下子信息
        }else{
            _tipLabel.text = @"请等待对方下子";
            self.view.userInteractionEnabled = NO;
            [self gameViewClick:@"-1"];
        }
    }];
}
-(void)searchGame{
    [[BlueToothTool sharedManager]searchGame];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
-(void)getData:(NSString *)data{
    if (_bgView.superview) {
        [_bgView removeFromSuperview];
    }
    if ([data integerValue]==-1) {
        _tipLabel.text = @"请您下子";
         self.view.userInteractionEnabled = YES;
        return;
    }
    _tipLabel.text = @"请您下子";
    [_view setTipIndex:[data intValue]];
    self.view.userInteractionEnabled = YES;
}

-(void)gameViewClick:(NSString *)index{
    _tipLabel.text = @"请等待对方下子";
    [[BlueToothTool sharedManager]writeData:index];
    self.view.userInteractionEnabled = NO;
}
@end

游戏运行的主要界面如下图所示:

附录:游戏的源码已经放在git上,时间比较仓促,只用了一下午来写,其中还有许多细节与bug没有进行调整,有需要的可以作为参考:

git地址:https://github.com/ZYHshao/BlueGame

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

——珲少 QQ群:203317592