游戲規(guī)則:
- 點擊某一方塊,當該方塊的上下左右四個方向同顏色方塊可連續(xù)(大于等于2)即可消除。
- 方塊消除后涎才,上面的方塊往下掉。
- 中間整列都空的話力九,旁邊的列往中間靠攏耍铜。
- 如果沒有可消除的方塊,游戲結束跌前。
效果如下:
流程:
- 使用隨機方法產生方塊的顏色棕兼,然后創(chuàng)建背景色與之相對應的方塊。
- 判斷游戲是否結束抵乓。
- 用戶點擊方塊伴挚,判斷該方塊的上下左右方向上是否存在同顏色的方塊靶衍。有,把方塊保存起來茎芋,進入第四步颅眶,沒有,不做任何響應田弥,并等待用戶點擊涛酗。
- 清除方塊,向下移動方塊偷厦。存在中間某列空了商叹,兩側的列往中間移動。跳轉到第二步只泼。
- 用戶點擊重新開始按鈕剖笙,跳轉到第一步。
實現:
先來看一下demo的文件結構:
其中:TwoDimentionalBrain是demo的邏輯處理请唱,DiamondsImageView是繼承UIImageView, 是帶有點擊功能的方塊枯途。
DiamondsImageView 類
class DiamondsImageView: UIImageView {
var backgroundType : backgroundType = .clear
var itemIndex : Int = 0
//起始位置和將要移動時的位置,動畫效果
var currentLocation : CGPoint?
var toLocation : CGPoint?
typealias returnIndexAndType = (Int, backgroundType) -> ()
var returnTuple : returnIndexAndType?
}
方塊在demo中是使用一個二維數組存儲籍滴,itemIndex代表該數組的index(i * row + column), backgroundType表示該方塊的顏色值酪夷。
enum backgroundType {
case yellow
case blue
case red
case green
case clear
}
而returnTuple是一個block, 主要是在點擊方塊時,在單擊響應方法里將該方塊的的itemIndex和backgroundType傳遞給viewController.
func touchInside(_ sender: UITapGestureRecognizer) {
//點擊空白方塊孽惰,則不響應
if backgroundType == .clear {
print("不能點擊")
return
}
//點擊顏色方塊晚岭,將方塊信息傳遞給viewController
if let tuple = returnTuple {
tuple(itemIndex, backgroundType)
}
}
TwoDimentionalBrain 結構體
struct TwoDimentionalBrain {
//存儲方塊顏色
private var sourceDataArray = [[backgroundType]]()
//存儲消除方塊的單個分數值(count, value)
private let scoreArray = [(0, 5), (5, 8), (10, 10), (13, 12), (15, 13), (100, 15)]
//存儲需要清除的方塊 1:清除, 0:不清除
private var clearArray = [(Int, Int)]()
//存儲需要移動的方塊列
private var emptyColumnArray = Array<Int>(repeating: 0, count: ColumnCount)
//存儲游戲分數
var score = 0;
//同列需要往下掉的方塊勋功,傳遞給viewController
typealias exchangeRowInColumn = (Int, Int, Int) -> ()
var itemMoveDown: exchangeRowInColumn?
//需要整列移動的方塊坦报,傳遞給viewController
typealias exchangeColumn = (Int, Int) -> ()
var itemChangeColumn: exchangeColumn?
}
隨機產生一行的顏色排列,返回的數組添加到sourceDataArray里面去狂鞋。
private mutating func setOneArray() -> [backgroundType] {
var array = [backgroundType]()
for _ in 0..<ColumnCount {
let data = arc4random() % 4
switch data {
case 0:
array.append(.yellow)
case 1:
array.append(.blue)
case 2:
array.append(.red)
case 3:
array.append(.green)
default:
array.append(.clear)
}
}
return array
}
mutating func setSourceDataArray() {
//先清空
sourceDataArray = [[backgroundType]]()
for _ in 0..<RowCount {
let array = setOneArray()
sourceDataArray.append(array)
}
}
用遞歸方法尋找點擊方塊的上下左右同顏色的方塊片择,用尋找左側方向來示例:
首先需要判斷當前方塊的左側方塊顏色是否一致。一致骚揍,則繼續(xù)在該方向尋找字管;否則,該方向的尋找結束信不。
private mutating func findSameTypeWithRound(row: Int, column: Int) {
let isLeft = isLeftSame(row: row, column: column)
//如果顏色一致嘲叔,則繼續(xù)往左邊尋找相同顏色的方塊
if isLeft {
findSameTypeWithRound(row: row, column: column - 1)
}
}
判斷當前方塊的左側方塊顏色是否一致的方法如下:
private mutating func isLeftSame(row: Int, column: Int) -> Bool {
if column <= 0 || isVisited(row: row, column: column - 1) {
//如果已經是最左邊或者已經訪問過了
return false
}
if sourceDataArray[row][column - 1] == .clear {
//如果已經是空白方塊
return false
}
if sourceDataArray[row][column - 1] == sourceDataArray[row][column] {
//左側方塊和當前方塊的顏色一致,則將左側方塊的行與列坐標添加到clearArray
clearArray.append((row, column - 1))
return true
}
return false
}
其他方向的尋找類似抽活,就不多陳述了硫戈。如果顏色一致,則添加到clearArray下硕,在四個方向都尋找結束之后丁逝,根據clearArray的數據計算分數和對方塊進行消除汁胆。
if clearArray.count < 2 {
clearArray = [(Int, Int)]()
return;
}
for (row, column) in clearArray {
//將需要消除的方塊的顏色設置為透明
sourceDataArray[row][column] = .clear
}
//計算分數
let count = clearArray.count
for (item, value) in scoreArray {
if count >= item {
score += value * count;
break;
}
}
方塊往下掉:遍歷sourceDataArray數組,如果方塊的顏色不是.clear 就在該方塊所在列的下一行遞歸尋找.clear的方塊霜幼,直到碰到有顏色的方塊或者是邊界沦泌。使用count存儲兩者的行數的間隔:
private func getUnClearUpCount(row: Int, column: Int) -> Int {
if row < 0 {
//遇到邊界
return -1
}
var count = 0
//遍歷該列下方的所有方塊
for index in 0...row {
if sourceDataArray[row - index][column] == .clear {
//遇到空白方塊,則間隔+1
count = count + 1
}
else {
//遇到有顏色的方塊辛掠,則返回
return count
}
}
return count
}
private mutating func moveDown() {
for row in 0..<RowCount {
for column in 0..<ColumnCount {
if sourceDataArray[row][column] != .clear {
//尋找同列的下方是否有空的方塊可以進行移動
let count = getUnClearUpCount(row: row - 1, column: column)
if count > 0 {
//有谢谦,進行移動。
sourceDataArray[row - count][column] = sourceDataArray[row][column]
sourceDataArray[row][column] = .clear
if let itemMD = itemMoveDown {
//傳遞給viewController itemMD(所在列, 原來的行, 需要移動到的行)
itemMD(column, row, row - count)
}
}
}
}
}
}
如果消除了之后發(fā)現某列空了萝衩,則需要判斷兩側方塊是否需要整列往中間移動回挽。使用二分法,從中間let centerColumn = ColumnCount / 2 分界猩谊。左邊部分:從centerColumn ~ 0 進行遍歷千劈,如果當前列不空,并且右側有空的牌捷,則整列向右移動墙牌;右邊部分:從centerColumn + 1 到右側邊界ColumnCount。如果當前列不空暗甥,并且左側有空的喜滨,則整列往左移動。
let centerColumn = ColumnCount / 2
//存儲需要移動兩列的列數間隔
var count = 0
for column in centerColumn + 1..<ColumnCount {
if emptyColumnArray[column] == 1 {
//如果是空的話撤防,就加一
count += 1
}
else if (count > 0) {
//如果當前列不空虽风,并且左側有空的,則整列移動
for i in 0..<ColumnCount - centerColumn {
if column + i < ColumnCount {
moveColumnToAnother(fromColumn: column + i, toColumn: column - count + i)
}
}
count = 0
}
else {
//當前列不空寄月,并且左側沒有空的辜膝,則什么都不做
}
整列移動的方法如下:
private mutating func moveColumnToAnother(fromColumn: Int, toColumn: Int) {
for row in 0..<sourceDataArray.count {
sourceDataArray[row][toColumn] = sourceDataArray[row][fromColumn]
sourceDataArray[row][fromColumn] = .clear
emptyColumnArray[fromColumn] = 1
emptyColumnArray[toColumn] = 0
//傳遞給viewController
if let changeColumn = itemChangeColumn {
//changeColumn(當前的列, 移動后的列)
changeColumn(fromColumn, toColumn)
}
}
}
遍歷所有的方塊,如果沒有連續(xù)的同顏色方塊漾肮,則游戲結束厂抖。
mutating func isGameOver() -> Bool {
//判斷是否已經結束游戲了
for row in 0..<RowCount {
for column in 0..<ColumnCount {
if sourceDataArray[row][column] == .clear {
//遇到空白方塊,不執(zhí)行下面的內容克懊,繼續(xù)下一次循環(huán)
continue;
}
//先清空忱辅,并把當前的方塊添加到消除數組
clearArray = [(Int, Int)]()
clearArray.append((row, column))
//在四個方向上尋找同顏色的方塊
findSameTypeWithRound(row: row, column: column)
if clearArray.count >= 2 {
//存在連續(xù)的同顏色方塊,游戲繼續(xù)
return false
}
}
}
return true
}
viewController
//顯示游戲得分
@IBOutlet var scoreLabel: UILabel!
//存儲方塊的數組
private var imageArray = [[DiamondsImageView]]()
//游戲邏輯的引用
private var diamondsBrain = TwoDimentionalBrain()
在頁面加載完成時保檐,創(chuàng)建隨機顏色的方塊耕蝉。并且實現TwoDimentionalBrain結構體中兩個移動方塊的block崔梗。
override func viewDidLoad() {
super.viewDidLoad()
//獲取隨機方塊顏色
diamondsBrain.setSourceDataArray()
//方塊移動的實現方法
weak var weakSelf = self
diamondsBrain.itemMoveDown = {(column, fromRow, toRow) in
weakSelf?.imageViewMoveDown(column: column, fromRow: fromRow, toRow: toRow)
}
diamondsBrain.itemChangeColumn = {(fromColunm, toColumn) in
weakSelf?.imageViewExchangeColumn(fromColumn: fromColunm, toColumn: toColumn)
}
//創(chuàng)建方塊
createImageView()
if diamondsBrain.isGameOver() {
print("Game Over!")
}
}
//同列的兩個方塊進行交換
private func imageViewMoveDown(column: Int, fromRow: Int, toRow: Int) {
let fromImage = imageArray[fromRow][column]
let toImage = imageArray[toRow][column]
exchangeImage(fromImage: fromImage, toImage: toImage)
imageArray[fromRow][column] = toImage
imageArray[toRow][column] = fromImage
}
//交換兩列的方塊
private func imageViewExchangeColumn(fromColumn: Int, toColumn: Int) {
for row in 0..<RowCount {
if imageArray[row][fromColumn].backgroundType == .clear {
return
}
else {
let fromImage = imageArray[row][fromColumn]
let toImage = imageArray[row][toColumn]
exchangeImage(fromImage: fromImage, toImage: toImage)
imageArray[row][fromColumn] = toImage
imageArray[row][toColumn] = fromImage
}
}
}
使用UIView動畫夜只,展示兩個方塊交換的過程(已經清楚的方塊設置成空白,所以只看到有顏色的方塊在移動)蒜魄。
private func exchangeImage(fromImage: DiamondsImageView, toImage: DiamondsImageView) {
UIView.animate(withDuration: 0.2, animations: {
let origin = fromImage.frame.origin
fromImage.frame.origin = toImage.frame.origin
toImage.frame.origin = origin
})
let index = fromImage.itemIndex
fromImage.itemIndex = toImage.itemIndex
toImage.itemIndex = index
}
創(chuàng)建方塊時扔亥,根據diamondsBrain的soureDataArray的存儲元素決定方塊的顏色场躯,而itemIndex由數組的行和列決定 itemIndex = row * ColumnCount + column,并且接收方塊點擊的block旅挤,進行相關的處理踢关。
private func createImageView() {
//根據sourceDataArray的顏色創(chuàng)建方塊
let dataArray = diamondsBrain.getSourceArray()
for (row, itemArray) in dataArray.enumerated() {
var rowImageArray = [DiamondsImageView]()
for (column, item) in itemArray.enumerated() {
let originX = space + CGFloat(column) * (width + ImageSpace)
let originY = height - CGFloat(row + 1) * (width + ImageSpace)
let rect = CGRect(x: originX, y: originY, width: width, height: width)
let imageView = DiamondsImageView(frame: rect)
imageView.backgroundType = item
imageView.itemIndex = row * ColumnCount + column
weak var weakSelf = self
imageView.returnTuple = {(index, type) in
let column = index % ColumnCount
let row = index / ColumnCount
weakSelf?.clearItem(row: row, column: column)
}
rowImageArray.append(imageView)
self.view.addSubview(imageView)
}
imageArray.append(rowImageArray)
}
updateUI()
}
在用戶點擊了方塊之后,根據傳過來的itemIndex確定點擊的方塊的行和列粘茄,然后調用diamondsBrain.getClearItem方法消除方塊签舞。消除方塊之后再判斷游戲是否結束。
private func clearItem(row: Int, column: Int) {
diamondsBrain.getClearItem(row: row, column: column)
let dataArray = diamondsBrain.getSourceArray()
for (row, itemArray) in dataArray.enumerated() {
let rowImageArray = imageArray[row]
for (column, item) in itemArray.enumerated() {
rowImageArray[column].backgroundType = item
}
}
updateUI()
if diamondsBrain.isGameOver() {
print("Game Over!")
}
}
根據方塊backgroundTyped對方塊的背景顏色進行賦值
private func updateUI() {
let score = diamondsBrain.score;
scoreLabel.text = String.init(stringInterpolationSegment: score)
for (_, itemArray) in imageArray.enumerated() {
for (_, item) in itemArray.enumerated() {
switch item.backgroundType {
case .green:
item.backgroundColor = UIColor.green
case .red:
item.backgroundColor = UIColor.red
case .blue:
item.backgroundColor = UIColor.blue
case .yellow:
item.backgroundColor = UIColor.yellow
case .clear:
item.backgroundColor = UIColor.clear
}
}
}
}
重新開始游戲時柒瓣,使用隨機方法產生方塊顏色儒搭,然后對ImageArray的方塊的背景色進行賦值。
@IBAction func resetGame(_ sender: UIButton) {
diamondsBrain.setSourceDataArray()
diamondsBrain.score = 0;
let dataArray = diamondsBrain.getSourceArray()
for (row, itemArray) in dataArray.enumerated() {
var rowImageArray = imageArray[row]
for (column, item) in itemArray.enumerated() {
let originX = space + CGFloat(column) * (width + ImageSpace)
let originY = height - CGFloat(row + 1) * (width + ImageSpace)
let rect = CGRect(x: originX, y: originY, width: width, height: width)
let imageView = rowImageArray[column]
imageView.frame = rect
imageView.backgroundType = item
imageView.itemIndex = row * ColumnCount + column
rowImageArray[column] = imageView
}
imageArray[row] = rowImageArray
}
updateUI()
}
感覺都在用代碼說話芙贫,包涵一下搂鲫,貼個demo放在百度云ClearGame 喜歡的可以去下載。