Files
SwiftUI/Examples/2048 Game/SwiftUI2048/Models/GameLogic.swift
Ivan Vorobei 5eaf1fbc68 Add 2048 Game
2019-06-06 22:28:01 +03:00

177 lines
5.1 KiB
Swift
Executable File

//
// GameLogic.swift
// SwiftUI2048
//
// Created by Hongyu on 6/5/19.
// Copyright © 2019 Cyandev. All rights reserved.
//
import Foundation
import SwiftUI
import Combine
final class GameLogic : BindableObject {
enum Direction {
case left
case right
case up
case down
}
typealias BlockMatrixType = BlockMatrix<IdentifiedBlock>
let didChange = PassthroughSubject<GameLogic, Never>()
fileprivate var _blockMatrix: BlockMatrixType!
var blockMatrix: BlockMatrixType {
return _blockMatrix
}
fileprivate var _globalID = 0
fileprivate var newGlobalID: Int {
_globalID += 1
return _globalID
}
init() {
newGame()
}
func newGame() {
_blockMatrix = BlockMatrixType()
generateNewBlocks()
didChange.send(self)
}
func move(_ direction: Direction) {
defer {
didChange.send(self)
}
var moved = false
let axis = direction == .left || direction == .right
for row in 0..<4 {
var rowSnapshot = [IdentifiedBlock?]()
var compactRow = [IdentifiedBlock]()
for col in 0..<4 {
// Transpose if necessary.
if let block = _blockMatrix[axis ? (col, row) : (row, col)] {
rowSnapshot.append(block)
compactRow.append(block)
}
rowSnapshot.append(nil)
}
merge(blocks: &compactRow, reverse: direction == .down || direction == .right)
var newRow = [IdentifiedBlock?]()
compactRow.forEach { newRow.append($0) }
if compactRow.count < 4 {
for _ in 0..<(4 - compactRow.count) {
if direction == .left || direction == .up {
newRow.append(nil)
} else {
newRow.insert(nil, at: 0)
}
}
}
newRow.enumerated().forEach {
if rowSnapshot[$0]?.number != $1?.number {
moved = true
}
_blockMatrix.place($1, to: axis ? ($0, row) : (row, $0))
}
}
if moved {
generateNewBlocks()
}
}
fileprivate func merge(blocks: inout [IdentifiedBlock], reverse: Bool) {
if reverse {
blocks = blocks.reversed()
}
blocks = blocks
.map { (false, $0) }
.reduce([(Bool, IdentifiedBlock)]()) { acc, item in
if acc.last?.0 == false && acc.last?.1.number == item.1.number {
var accPrefix = Array(acc.dropLast())
var mergedBlock = item.1
mergedBlock.number *= 2
accPrefix.append((true, mergedBlock))
return accPrefix
} else {
var accTmp = acc
accTmp.append((false, item.1))
return accTmp
}
}
.map { $0.1 }
if reverse {
blocks = blocks.reversed()
}
}
@discardableResult fileprivate func generateNewBlocks() -> Bool {
var blankLocations = [BlockMatrixType.Index]()
for rowIndex in 0..<4 {
for colIndex in 0..<4 {
let index = (colIndex, rowIndex)
if _blockMatrix[index] == nil {
blankLocations.append(index)
}
}
}
guard blankLocations.count >= 2 else {
return false
}
// Don't forget to sync data.
defer {
didChange.send(self)
}
// Place the first block.
var placeLocIndex = Int.random(in: 0..<blankLocations.count)
_blockMatrix.place(IdentifiedBlock(id: newGlobalID, number: 2), to: blankLocations[placeLocIndex])
// Place the second block.
guard let lastLoc = blankLocations.last else {
return false
}
blankLocations[placeLocIndex] = lastLoc
placeLocIndex = Int.random(in: 0..<(blankLocations.count - 1))
_blockMatrix.place(IdentifiedBlock(id: newGlobalID, number: 2), to: blankLocations[placeLocIndex])
return true
}
// fileprivate func forEachBlockIndices(mode: ForEachMode = .rowByRow,
// reversed: Bool = false,
// _ action: (BlockMatrixType.Index) -> ()) {
// var indices = (0..<4).map { $0 }
// if reversed {
// indices = indices.reversed()
// }
//
// for row in indices {
// for col in indices {
// if mode == .rowByRow {
// action((col, row))
// } else {
// action((row, col)) // transpose
// }
// }
// }
// }
}