前言
最近寫了一個 iOS小游戲,純屬一時興起玲昧。動機:那天看到妹妹在朋友圈發(fā)了一組圖片窟蓝,正好是九宮格的形狀罪裹,突然間就覺得這些圖片不就像是一個拼圖游戲嗎?如果可以直接移動玩拼圖,那也挺酷哇状共。擼起袖子就是干套耕!做出來的效果就是這樣的:
基本思路
首先我選取了一張大的原始圖片,這張圖片用來裁成一定數(shù)量的小方塊(不用數(shù)學語言嚴謹描述了峡继,影響閱讀性)冯袍,最好是選取的圖片可以讓每個小方塊圖片都有一定的辨識度。原圖片右下角的一個小方塊丟棄作為可移動的空白空間碾牌。每一個小方塊都給她編上一個獨一無二的號碼康愤。這個編號可以用來校驗拼圖是否完成。
方塊布局是使用UICollectionView來搭建的小染,難點在于拼圖的移動翘瓮,實際上我是把圖塊的移動處理成了圖塊位置的交換,只要點擊你想要移動的圖塊裤翩,這個圖塊就會瞬移到空白位置资盅,這樣來說在游戲體驗上移動更靈敏,效率更高踊赠!
開玩時呵扛,將圖塊順序打亂。
核心算法
判斷當前點擊的圖塊是否可移動
-(void)calculateIndexOfMoveable {
//記錄空白塊的索引筐带,緊靠空白塊的方塊才可以移動今穿,實際上就是與空白塊交換位置。初始化時的空白塊統(tǒng)一在右下角伦籍。
//計算當前可移動的方塊
// 白色塊所在行row = indexOfWhite / totalCols
// 白色塊所在列col = indexOfWhite % totalCols
left = indexOfWhite - 1
right = indexOfWhite + 1;
up = indexOfWhite - totalCols;
down = indexOfWhite + totalCols;
// 但是要排除一些四周情況下的索引
if ([self indexOfCol: left] > [self indexOfCol: indexOfWhite]) {
//left 排除
left = -1;
}
if ([self indexOfCol: right] < [self indexOfCol: indexOfWhite]) {
//right 排除
right = -1;
}
if (up < 0) {
//up 排除
up = -1;
}
if (down > totalCols*totalRows-1) {
//down 排除
down = -1;
}
}
-(NSInteger)indexOfRow:(NSInteger)index {
return index / totalCols;
}
-(NSInteger)indexOfCol:(NSInteger)index {
return index % totalCols;
}
上面的 calculateIndexOfMoveable方法可以優(yōu)化成如下四個方法:
-(NSInteger)calculateIndexOfMoveable_left {
left = indexOfWhite - 1;
return [self indexOfCol: left] > [self indexOfCol: indexOfWhite] ? -1 : left;
}
-(NSInteger)calculateIndexOfMoveable_right {
right = indexOfWhite + 1;
return [self indexOfCol: right] < [self indexOfCol: indexOfWhite] ? -1 : right;
}
-(NSInteger)calculateIndexOfMoveable_up {
return (indexOfWhite - totalCols) < 0 ? -1 : indexOfWhite - totalCols;
}
-(NSInteger)calculateIndexOfMoveable_down {
return (indexOfWhite + totalCols) > (totalCols*totalRows-1) ? -1 : indexOfWhite + totalCols;
}
我這里定義了兩個數(shù)組蓝晒,一個是圖片小方塊的數(shù)組,一個是圖片塊對應(yīng)的編號數(shù)組帖鸦。這兩個數(shù)組必須保持同步更新芝薇。也可以把圖片小方塊與其對應(yīng)的編號作為一個模型類的屬性。也可以建立一個字典作儿,將編號與圖片映射洛二。
初始化圖片塊數(shù)組:
-(NSMutableArray *)dataSource {
if (!_dataSource) {
_dataSource = [NSMutableArray array];
CGFloat x,y,w,h;
w = (self.oringinalImg.image.size.width/totalCols)/[UIScreen mainScreen].scale;
h = (self.oringinalImg.image.size.height/totalRows)/[UIScreen mainScreen].scale;
for (int i=0; i<totalRows; i++) {
for (int j=0; j<totalCols; j++) {
x = j*w;
y = i*h;
CGRect rect = CGRectMake(x,y,w,h);
if ((i==totalRows-1) && (j== totalCols-1)) {
[_dataSource addObject: [[UIImage alloc] init] ];
} else {
[_dataSource addObject: [self ct_imageFromImage:self.oringinalImg.image inRect: rect]];
}
}
}
}
return _dataSource;
}
初始化圖片塊對應(yīng)的編號數(shù)組:
-(NSMutableArray *)startIndexs {
if (!_startIndexs) {
_startIndexs = [NSMutableArray array];
for (int i = 0; i < totalCols*totalRows; i++) {
_startIndexs[i] = @(i);
};
}
return _startIndexs;
}
裁剪圖片的具體方法:
/**
* 從圖片中按指定的位置大小截取圖片的一部分
*
* @param image UIImage image 原始的圖片
* @param rect CGRect rect 要截取的區(qū)域
*
* @return UIImage
*/
- (UIImage *)ct_imageFromImage:(UIImage *)image inRect:(CGRect)rect {
//把像素rect 轉(zhuǎn)化為點rect(如無轉(zhuǎn)化則按原圖像素取部分圖片)
CGFloat scale = [UIScreen mainScreen].scale;
CGFloat x= rect.origin.x*scale,y=rect.origin.y*scale,w=rect.size.width*scale,h=rect.size.height*scale;
CGRect dianRect = CGRectMake(x, y, w, h);
//截取部分圖片并生成新圖片
CGImageRef sourceImageRef = [image CGImage];
CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, dianRect);
UIImage *newImage = [UIImage imageWithCGImage:newImageRef scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
return newImage;
}
兩個數(shù)組同步隨機亂序的方法,完成后兩個數(shù)組在相同的索引位置其對應(yīng)關(guān)系仍保持不變攻锰。
- (void)randomArray {
//兩個數(shù)組同步打亂順序,早知道這么麻煩我就用模型將索引值綁定image了晾嘶。/(ㄒoㄒ)/~~
NSMutableArray *newDatasourceArr = [NSMutableArray array];
NSMutableArray *newStartIndexArr = [NSMutableArray array];
int m = (int)self.dataSource.count;
for (int i=0; i<m; i++) {
int t = arc4random() % (self.dataSource.count);
newDatasourceArr[i] = self.dataSource[t];
newStartIndexArr[i] = self.startIndexs[t];
self.dataSource[t] = [self.dataSource lastObject];
self.startIndexs[t] = [self.startIndexs lastObject];
[self.dataSource removeLastObject];
[self.startIndexs removeLastObject];
}
self.dataSource = newDatasourceArr;
self.startIndexs = newStartIndexArr;
}
12.17修改更新:關(guān)于打亂圖序,我這種隨機打亂順序的做法欠妥娶吞,試玩幾次后發(fā)現(xiàn)有些情況我總是還原不了垒迂,回憶上學時玩過的一款游戲沒有出現(xiàn)過這樣的情況。這時候我開始懷疑并不是所有的序列都可以進行還原妒蛇。而我卻忽略了娇斑,這非常不應(yīng)該策添。
打亂后還需要驗證當前狀態(tài)是否有解。根據(jù)相關(guān)定理毫缆,如果打亂后的排列與原始排列的逆序數(shù)奇偶性相同唯竹,則是可還原的(證明比較簡單 參考鏈接——不可還原的拼圖)。如果拼圖的版塊是隨機打亂的苦丁,那么只有50%概率是可以被還原的浸颓。我這里統(tǒng)一將空格設(shè)置在末尾最后一個,可以忽略掉旺拉,不影響逆序數(shù)产上。
方案二:讓程序隨機移動數(shù)次,這樣肯定是能夠還原的蛾狗。這個“數(shù)次”也值得商榷晋涣,要盡可能亂,又不能太多次了沉桌。但是我這個游戲設(shè)定的打亂后空格統(tǒng)一在最后一格谢鹊,還需要調(diào)整空格位置,同樣用到剛才的逆序數(shù)相關(guān)定理留凭,將空格與當前最后一個格子交換佃扼,現(xiàn)在排列奇偶性改變,還需要隨機將非空格的兩個格子進行交換一次蔼夜。這樣就可以了兼耀。
方案三:對于m*n的拼圖,從拼圖板塊中任取三塊做輪換求冷,通過[(m*n)/3]^2次輪換瘤运,即可實現(xiàn)相當“亂”的打亂效果。所謂三輪換匠题,實質(zhì)就是兩次交換:如123拯坟,1與2交換后,這時候狀態(tài)213梧躺,再3與2交換似谁,這時候狀態(tài)312傲绣。體現(xiàn)在拼圖上很好實驗掠哥,把包含空格的2*2格子進行各種移動變換,就對應(yīng)了3輪換秃诵。
還有個功能就是可以自定義幾行幾列续搀,難點是需要動態(tài)更新相關(guān)數(shù)據(jù),值得注意的是本例中cell是復用的菠净,大小禁舷、內(nèi)容需要根據(jù)需要即時調(diào)整彪杉。
最后奉獻上demo https://github.com/imsz5460/-puzzlegame 歡迎大家找bug,并提出優(yōu)化意見牵咙,謝謝派近!