iOS 滑块拼图游戏(Puzzle8)
一、准备工作
先了解一个定义和定理
定义:在一个1,2,...,n的排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。——这是北大《高等代数》上的定义。
定理:交换一个排列中的两个数,则排列的奇偶性发生改变。
二、项目结构
三、实现过程
以3*3拼图为例进行分析
1、随机打乱拼图
1)初始化从0-8的数组initializeNums
NSMutableArray *initializeNums = [NSMutableArray array];//初始化0-n数字
for (int i = 0; i < _puzzleCount; i++) {
[initializeNums addObject:@(i)];
}
2)从initializeNums
随机抽取数字add到数组randomNums
,得到随机数组
NSMutableArray *randomNums = [NSMutableArray array];//随机数组
for (int i = 0; i < _puzzleCount; i++) {
int randomNum = arc4random() % initializeNums.count;
[randomNums addObject:initializeNums[randomNum]];
[initializeNums removeObjectAtIndex:randomNum];
}
3)判断拼图是否可还原
①
图1
,通过移动要还原到的拼图状态
②图2
,随机打乱的拼图状态
③图3
,将图2
中的空白块移动到拼图右下角的拼图状态,用来计算打乱的拼图是否可以还原
④ 空白块处相当于数字8
⑤ 我们的目的是把打乱拼图如图2
通过移动(空白块与相邻数字块位置交换)还原到图1
状态
⑥ 不是每个随机打乱的拼图都能还原到图1
状态(根据定义和定理有50%概率随机打乱的拼图不能还原)
⑦ 根据定义
和定理
,图1
的逆序数为0,为偶排列。所以只有图3
也为偶排列,图2
才有可能还原到图1
状态
图1
图2
如何计算图3
的逆序数
① 先计算
图2
的逆序数
② 再计算图2
到图3
变换步数
③ 将两者相加即得图3
逆序数
图3
判断图2
是否可还原代码:
//判断是否可还原拼图
inverCount = 0;
int curNum = 0;
int nextNum = 0;
for (int i = 0; i < _puzzleCount; i++) {
curNum = [randomNums[i] intValue];
if (curNum == _puzzleCount - 1) {
inverCount += _difficulty - 1 - (i / _difficulty);
inverCount += _difficulty - 1 - (i % _difficulty);
}
for (int j = i + 1; j < _puzzleCount; j++) {
nextNum = [randomNums[j] intValue];
if (curNum > nextNum) {
inverCount++;
}
}
}
if (!(inverCount % 2)) {//对2求余,余0,逆序数为偶数,即偶排列;否则,为奇排列
return randomNums;
}
获得随机可还原的数组randomNums
:
- (NSMutableArray *)getNewAvailableRandomNums {
//随机数字
int inverCount = 0;
while (1) {
NSMutableArray *initializeNums = [NSMutableArray array];//初始化0-n数字
for (int i = 0; i < _puzzleCount; i++) {
[initializeNums addObject:@(i)];
}
NSMutableArray *randomNums = [NSMutableArray array];//随机数组
for (int i = 0; i < _puzzleCount; i++) {
int randomNum = arc4random() % initializeNums.count;
[randomNums addObject:initializeNums[randomNum]];
[initializeNums removeObjectAtIndex:randomNum];
}
//判断是否可还原拼图
inverCount = 0;
int curNum = 0;
int nextNum = 0;
for (int i = 0; i < _puzzleCount; i++) {
curNum = [randomNums[i] intValue];
if (curNum == _puzzleCount - 1) {
inverCount += _difficulty - 1 - (i / _difficulty);
inverCount += _difficulty - 1 - (i % _difficulty);
}
for (int j = i + 1; j < _puzzleCount; j++) {
nextNum = [randomNums[j] intValue];
if (curNum > nextNum) {
inverCount++;
}
}
}
if (!(inverCount % 2)) {//对2求余,余0,逆序数为偶数,即偶排列;否则,为奇排列
return randomNums;
}
}
}
2、初始化拼图UI (九宫格)
代码:
- (void)customUI {
CGFloat puzzleBgViewX = 0;
CGFloat puzzleBgViewY = 64 + 20;
CGFloat puzzleBgViewW = [UIScreen mainScreen].bounds.size.width;
CGFloat puzzleBgViewH = puzzleBgViewW;
_puzzleBgView = [[UIView alloc] initWithFrame:CGRectMake(puzzleBgViewX, puzzleBgViewY, puzzleBgViewW, puzzleBgViewH)];
_puzzleBgView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:_puzzleBgView];
CGFloat puzzleBtnX = 0;
CGFloat puzzleBtnY = 0;
CGFloat puzzleBtnW = puzzleBgViewW / _difficulty - kPuzzleBtnGap * 2;
CGFloat puzzleBtnH = puzzleBtnW;
for (int i = 0; i < _puzzleCount; i++) {
puzzleBtnX = i % _difficulty * (puzzleBtnW + kPuzzleBtnGap * 2) + kPuzzleBtnGap;
puzzleBtnY = i / _difficulty * (puzzleBtnH + kPuzzleBtnGap * 2) + kPuzzleBtnGap;
UIButton *puzzleBtn = [UIButton buttonWithType:UIButtonTypeCustom];
puzzleBtn.frame = CGRectMake(puzzleBtnX, puzzleBtnY, puzzleBtnW, puzzleBtnH);
puzzleBtn.tag = i;
puzzleBtn.clipsToBounds = YES;
[_puzzleBgView addSubview:puzzleBtn];
int puzzleValue = [self.randomNums[i] intValue];
if (puzzleValue == _puzzleCount - 1) {
puzzleBtn.backgroundColor = [UIColor clearColor];
_maxPuzzleBtn = puzzleBtn;
} else {
[puzzleBtn setTitle:[NSString stringWithFormat:@"%d", puzzleValue + 1] forState:UIControlStateNormal];
puzzleBtn.backgroundColor = [UIColor colorWithRed:0x4A / 255.0 green:0xC2 / 255.0 blue:0xFB / 255.0 alpha:1];
[puzzleBtn addTarget:self action:@selector(puzzleBtnAction:) forControlEvents:UIControlEventTouchUpInside];
}
}
}
3、滑块移动逻辑
点击空白块周围数字块,数字块移到空白块区域(其实就是空白块和数字块交换)
图4
① index
:数字块对应位置如图4
② _difficulty
: 拼图列数或行数
③ 点击数字块依次判断其上
下
左
右
是否有空白块
④ 找到空白块,将点击数字块与空白块位置交换,实现数字块移动效果
以数字块3(index = 4)为例分析
上
:upIndex = index - _difficulty
判断是否在九宫格里&&其位置对应的值是否是8,即空白块。
upIndex >= 0 && [self.randomNums[upIndex] intValue] == _puzzleCount - 1
下
:downIndex = index + _difficulty
判断是否在九宫格里&&其位置对应的值是否是8,即空白块。
if (downIndex <= _puzzleCount - 1 && [self.randomNums[downIndex] intValue] == _puzzleCount - 1
左
:leftIndex = index - 1
判断是否在九宫格里&&其位置对应的值是否是8,即空白块
index % _difficulty > 0 && [self.randomNums[leftIndex] intValue] == _puzzleCount - 1
右
:rightIndex = index + 1
判断是否在九宫格里&&其位置对应的值是否是8,即空白块
index % _difficulty < _difficulty - 1 && [self.randomNums[rightIndex] intValue] == _puzzleCount - 1
代码:
- (void)puzzleBtnAction:(UIButton *)puzzleBtn {
NSInteger index = puzzleBtn.tag;
//上
NSInteger upIndex = index - _difficulty;
if (upIndex >= 0 && [self.randomNums[upIndex] intValue] == _puzzleCount - 1) {
CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = upIndex;
self.randomNums[upIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}];
[self isWin];
return;
}
//下
NSInteger downIndex = index + _difficulty;
if (downIndex <= _puzzleCount - 1 && [self.randomNums[downIndex] intValue] == _puzzleCount - 1) {
CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = downIndex;
self.randomNums[downIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}];
[self isWin];
return;
}
//左
NSInteger leftIndex = index - 1;
if (index % _difficulty > 0 && [self.randomNums[leftIndex] intValue] == _puzzleCount - 1) {
CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = leftIndex;
self.randomNums[leftIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}];
[self isWin];
return;
}
//右
NSInteger rightIndex = index + 1;
if (index % _difficulty < _difficulty - 1 && [self.randomNums[rightIndex] intValue] == _puzzleCount - 1) {
CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = rightIndex;
self.randomNums[rightIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}];
[self isWin];
return;
}
}
*4、另一种打乱拼图的方法
思路:将
图1
经过有限次数随机移动达到打乱拼图的目的,这样打乱的拼图肯定是可还原的。
代码:
- (NSMutableArray *)getNewAvailableRandomNums2 {
NSMutableArray *randomNums = [NSMutableArray array];//随机数组 - 初始化0-n数字
for (int i = 0; i < _puzzleCount; i++) {
[randomNums addObject:@(i)];
}
int randCount = _puzzleCount * _puzzleCount;
int randDirection = 0; //0 上 1 下 2 左 3 右
BOOL aliableDirection = NO;
int blankIndex = 8;
int index = 0;
while (randCount--) {
aliableDirection = NO;
randDirection = arc4random() % 4;
while (1) {
switch (randDirection) {
case 0:
if (blankIndex / _difficulty > 0) {
index = blankIndex - _difficulty;
aliableDirection = YES;
}
break;
case 1:
if (blankIndex / _difficulty < _difficulty - 1) {
index = blankIndex + _difficulty;
aliableDirection = YES;
}
break;
case 2:
if (blankIndex % _difficulty > 0) {
index = blankIndex - 1;
aliableDirection = YES;
}
break;
case 3:
if (blankIndex % _difficulty < _difficulty - 1) {
index = blankIndex + 1;
aliableDirection = YES;
}
break;
default:
break;
}
if (aliableDirection == YES) {
break;
}
randDirection = (randDirection + 1) % 4;
}
randomNums[blankIndex] = @([randomNums[index] intValue]);
randomNums[index] = @(8);
blankIndex = index;
}
return randomNums;
}
四、其他细节功能
1、难度选择 3*3(低), 4*4(中), 5*5(高)
2、自定义图片拼图(相机和相册)
3、图片拼图提示
4、步数统计
5、最佳记录
6、移动提示音设置
具体请下载demo查看
五、实现效果
效果图
六、参考
1、不可还原拼图
2、回忆经典,讲述滑块游戏背后的数学故事
3、吴昊品游戏核心算法 Round 17 —— 吴昊教你玩拼图游戏(15 puzzle)
iOS 滑块拼图游戏(Puzzle8)
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
iOS 滑块拼图游戏(Puzzle8)的更多相关文章
- 拼图游戏 v1.1
我一直对拼图游戏比较有兴趣,市面上卖的所谓“1000块拼图”也玩过不少,不过玩那个太占地方,后来也不再买了,同时也就萌生了在电脑上玩拼图的想法. 现在虽然有很多拼图游戏,但能大多数只能支持几十或几百块 ...
- 拼图游戏源码-swift版项目源码
作者fanyinan,源码PuzzleProject,公司的项目中需要一个拼图游戏,之前有手动拼图和随机打乱的功能,近期又由于个(xian)人(zhe)爱(dan)好(teng)自己加入了自动拼图功能 ...
- Android群英传-拼图游戏puzzle-代码设计和实现
上个周末,3个小时总体上读完了<Android群英传>,本周主要在研究代码层次的设计和实现. 编译安装在手机上,玩了几把,结合代码,一周时间才掌握了整体的思路. 大部分时间,其实花在了 ...
- 利用Vue.js实现拼图游戏
之前写过一篇<基于Vue.js的表格分页组件>的文章,主要介绍了Vue组件的编写方法,有兴趣的可以访问这里进行阅读:http://www.cnblogs.com/luozhihao/p/5 ...
- JavaScript拼图游戏
今天是2016年最后一天上班了.最近几天都比较休闲,有时间空闲下来写写文档之类的. 2016过得真是快.感觉没做什么就过去了.想到之前想坚持每个月写一写博客都没坚持到.希望2017年可以吧. 无聊之余 ...
- SDL制作拼图游戏
看完教程第三集后,好像自己能用这个来写一个简单的拼图游戏,第一次写出个带界面的游戏,好有成就感. 图片是自己慢慢截左上部分8个脸. #include <stdio.h> #include ...
- 拼图游戏(js,C#,java三种语言)
<html> <head> <meta charset="utf-8"> <style type="text/css" ...
- JavaScript写一个拼图游戏
拼图游戏的代码400行, 有点多了, 在线DEMO的地址是:打开: 因为使用canvas,所以某些浏览器是不支持的: you know: 为什么要用canvas(⊙o⊙)? 因为图片是一整张jpg或 ...
- atitit.html5 拼图游戏的解决之道.
atitit.html5 拼图游戏的解决之道. 1. 拼图游戏的操作(点击法and 拖动法) 1 1. 支持键盘上.下.左.右键移动: 1 2. 支持点击空白模块中的上下左右箭头移动: 1 3. 支持 ...
随机推荐
- android 内存分哪些区
韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha 313134555@qq.com android 内存分哪些区 内存分哪些区 ============ 内存分为的5大区 1.栈区 ...
- 洛谷P1113 杂务
题目描述 John的农场在给奶牛挤奶前有很多杂务要完成,每一项杂务都需要一定的时间来完成它.比如:他们要将奶牛集合起来,将他们赶进牛棚,为奶牛清洗乳房以及一些其它工作.尽早将所有杂务完成是必要的,因为 ...
- noip 2016 day2 t1组合数问题
题目描述 组合数表示的是从n个物品中选出m个物品的方案数.举个例子,从(1,2,3) 三个物品中选择两个物品可以有(1,2),(1,3),(2,3)这三种选择方法.根据组合数的定 义,我们可以给出计算 ...
- 2017icpc 乌鲁木齐网络赛
A .Banana Bananas are the favoured food of monkeys. In the forest, there is a Banana Company that pr ...
- BZOJ 2120 数颜色(带修改莫队)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2120 [题目大意] 给出一颜色序列,每次可以修改一个位置的颜色或者询问一个区间不同颜色 ...
- [UOJ347]通道
锟题x1 以下用$d_k(x,y)$表示$x,y$在第树$k$上的距离,$h_k(x)$表示$x$在树$k$上的深度 先做两棵树,即最大化$d_1(x,y)+d_2(x,y)=h_1(x)+h_1(y ...
- NHibernate之一级缓存(第十篇)
NHibernate的一级缓存,名词好像很牛B,很难.实际上就是ISession缓存.存储在ISession的运行周期内.而二级缓存则存储在ISessionFactory内. 一.ISession一级 ...
- 在u-boot中添加命令
转:http://www.embedu.org/Column/Column464.htm 作者:曾宏安,华清远见嵌入式学院讲师. u-boot是嵌入式系统中广泛使用的一种bootloader.它不仅支 ...
- 详解MySQL性能优化(二)
http://www.jb51.net/article/70530.htm 七.MySQL数据库Schema设计的性能优化高效的模型设计 适度冗余-让Query尽两减少Join 大字段垂直分拆-sum ...
- Unicode中的BOM
BOM简述 BOM是byte order mark的缩写,在UTF-16和UTF-32中需要使用BOM来区分字节的顺序,因为我们目前的CPU有两种系列,一种是大端模式,一种是小端模式(我们常用的电脑手 ...