256 lines
7.3 KiB
Swift
256 lines
7.3 KiB
Swift
|
//
|
||
|
// Privyet.swift
|
||
|
// Privyet
|
||
|
//
|
||
|
// Created by Amy Bowersox on 5/24/20.
|
||
|
// Copyright © 2020 Erbosoft Metaverse Design Solutions. All rights reserved.
|
||
|
//
|
||
|
|
||
|
import Foundation
|
||
|
|
||
|
let NumColumns = 10
|
||
|
let NumRows = 20
|
||
|
|
||
|
let StartingColumn = 4
|
||
|
let StartingRow = 0
|
||
|
|
||
|
let PreviewColumn = 12
|
||
|
let PreviewRow = 1
|
||
|
|
||
|
let PointsPerLine = 10
|
||
|
let LevelThreshold = 500
|
||
|
|
||
|
protocol PrivyetDelegate {
|
||
|
// Invoked when the current round of Privyet ends
|
||
|
func gameDidEnd(privyet: Privyet)
|
||
|
|
||
|
// Invoked after a game has begun
|
||
|
func gameDidBegin(privyet: Privyet)
|
||
|
|
||
|
// Invoked when the falling shape has become part of the game board
|
||
|
func gameShapeDidLand(privyet: Privyet)
|
||
|
|
||
|
// Invoked when the falling shape has changed its location
|
||
|
func gameShapeDidMove(privyet: Privyet)
|
||
|
|
||
|
// invoked when the falling shape has changed its location after being dropped
|
||
|
func gameShapeDidDrop(privyet: Privyet)
|
||
|
|
||
|
// invoked when the game has reached a new level
|
||
|
func gameDidLevelUp(privyet: Privyet)
|
||
|
}
|
||
|
|
||
|
class Privyet {
|
||
|
var blockArray: Array2D<Block>
|
||
|
var nextShape: Shape?
|
||
|
var fallingShape: Shape?
|
||
|
var delegate: PrivyetDelegate?
|
||
|
|
||
|
var score = 0
|
||
|
var level = 1
|
||
|
|
||
|
init() {
|
||
|
fallingShape = nil
|
||
|
nextShape = nil
|
||
|
blockArray = Array2D<Block>(columns: NumColumns, rows: NumRows)
|
||
|
}
|
||
|
|
||
|
func beginGame() {
|
||
|
if (nextShape == nil) {
|
||
|
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
||
|
}
|
||
|
delegate?.gameDidBegin(privyet: self)
|
||
|
}
|
||
|
|
||
|
func newShape() -> (fallingShape: Shape?, nextShape: Shape?) {
|
||
|
fallingShape = nextShape
|
||
|
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
||
|
fallingShape?.moveTo(column: StartingColumn, row: StartingRow)
|
||
|
|
||
|
guard detectIllegalPlacement() == false else {
|
||
|
nextShape = fallingShape
|
||
|
nextShape!.moveTo(column: PreviewColumn, row: PreviewRow)
|
||
|
endGame()
|
||
|
return (nil, nil)
|
||
|
}
|
||
|
|
||
|
return (fallingShape, nextShape)
|
||
|
}
|
||
|
|
||
|
func detectIllegalPlacement() -> Bool {
|
||
|
guard let shape = fallingShape else {
|
||
|
return false
|
||
|
}
|
||
|
for block in shape.blocks {
|
||
|
if block.column < 0 || block.column >= NumColumns || block.row < 0 || block.row >= NumRows {
|
||
|
return true
|
||
|
} else if blockArray[block.column, block.row] != nil {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func settleShape() {
|
||
|
guard let shape = fallingShape else {
|
||
|
return
|
||
|
}
|
||
|
for block in shape.blocks {
|
||
|
blockArray[block.column, block.row] = block
|
||
|
}
|
||
|
fallingShape = nil
|
||
|
delegate?.gameShapeDidLand(privyet: self)
|
||
|
}
|
||
|
|
||
|
func detectTouch() -> Bool {
|
||
|
guard let shape = fallingShape else {
|
||
|
return false
|
||
|
}
|
||
|
for bottomBlock in shape.bottomBlocks {
|
||
|
if bottomBlock.row == NumRows - 1 || blockArray[bottomBlock.column, bottomBlock.row + 1] != nil {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func endGame() {
|
||
|
score = 0
|
||
|
level = 1
|
||
|
delegate?.gameDidEnd(privyet: self)
|
||
|
}
|
||
|
|
||
|
func removeCompletedLines() -> (linesRemoved: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>) {
|
||
|
var removedLines = Array<Array<Block>>()
|
||
|
for row in (1..<NumRows).reversed() {
|
||
|
var rowOfBlocks = Array<Block>()
|
||
|
for column in 0..<NumColumns {
|
||
|
guard let block = blockArray[column, row] else {
|
||
|
continue
|
||
|
}
|
||
|
rowOfBlocks.append(block)
|
||
|
}
|
||
|
if rowOfBlocks.count == NumColumns {
|
||
|
removedLines.append(rowOfBlocks)
|
||
|
for block in rowOfBlocks {
|
||
|
blockArray[block.column, block.row] = nil
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if removedLines.count == 0 {
|
||
|
return ([], [])
|
||
|
}
|
||
|
let pointsEarned = removedLines.count * PointsPerLine * level
|
||
|
score += pointsEarned
|
||
|
if score >= level * LevelThreshold {
|
||
|
level += 1
|
||
|
delegate?.gameDidLevelUp(privyet: self)
|
||
|
}
|
||
|
var fallenBlocks = Array<Array<Block>>()
|
||
|
for column in 0..<NumColumns {
|
||
|
var fallenBlocksArray = Array<Block>()
|
||
|
for row in (1..<removedLines[0][0].row).reversed() {
|
||
|
guard let block = blockArray[column, row] else {
|
||
|
continue
|
||
|
}
|
||
|
var newRow = row
|
||
|
while (newRow < NumRows - 1 && blockArray[column, newRow + 1] == nil) {
|
||
|
newRow += 1
|
||
|
}
|
||
|
block.row = newRow
|
||
|
blockArray[column, row] = nil
|
||
|
blockArray[column, newRow] = block
|
||
|
fallenBlocksArray.append(block)
|
||
|
}
|
||
|
if fallenBlocksArray.count > 0 {
|
||
|
fallenBlocks.append(fallenBlocksArray)
|
||
|
}
|
||
|
}
|
||
|
return (removedLines, fallenBlocks)
|
||
|
}
|
||
|
|
||
|
func removeAllBlocks() -> Array<Array<Block>> {
|
||
|
var allBlocks = Array<Array<Block>>()
|
||
|
for row in 0..<NumRows {
|
||
|
var rowOfBlocks = Array<Block>()
|
||
|
for column in 0..<NumColumns {
|
||
|
guard let block = blockArray[column, row] else {
|
||
|
continue
|
||
|
}
|
||
|
rowOfBlocks.append(block)
|
||
|
blockArray[column, row] = nil
|
||
|
}
|
||
|
allBlocks.append(rowOfBlocks)
|
||
|
}
|
||
|
return allBlocks
|
||
|
}
|
||
|
|
||
|
func dropShape() {
|
||
|
guard let shape = fallingShape else {
|
||
|
return
|
||
|
}
|
||
|
while detectIllegalPlacement() == false {
|
||
|
shape.lowerShapeByOneRow()
|
||
|
}
|
||
|
shape.raiseShapeByOneRow()
|
||
|
delegate?.gameShapeDidDrop(privyet: self)
|
||
|
}
|
||
|
|
||
|
func letShapeFall() {
|
||
|
guard let shape = fallingShape else {
|
||
|
return
|
||
|
}
|
||
|
shape.lowerShapeByOneRow()
|
||
|
if detectIllegalPlacement() {
|
||
|
shape.raiseShapeByOneRow()
|
||
|
if detectIllegalPlacement() {
|
||
|
endGame()
|
||
|
} else {
|
||
|
settleShape()
|
||
|
}
|
||
|
} else {
|
||
|
delegate?.gameShapeDidMove(privyet: self)
|
||
|
if detectTouch() {
|
||
|
settleShape()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func rotateShape() {
|
||
|
guard let shape = fallingShape else {
|
||
|
return
|
||
|
}
|
||
|
shape.rotateClockwise()
|
||
|
guard detectIllegalPlacement() == false else {
|
||
|
shape.rotateCounterClockwise()
|
||
|
return
|
||
|
}
|
||
|
delegate?.gameShapeDidMove(privyet: self)
|
||
|
}
|
||
|
|
||
|
func moveShapeLeft() {
|
||
|
guard let shape = fallingShape else {
|
||
|
return
|
||
|
}
|
||
|
shape.shiftLeftByOneColumn()
|
||
|
guard detectIllegalPlacement() == false else {
|
||
|
shape.shiftRightByOneColumn()
|
||
|
return
|
||
|
}
|
||
|
delegate?.gameShapeDidMove(privyet: self)
|
||
|
}
|
||
|
|
||
|
func moveShapeRight() {
|
||
|
guard let shape = fallingShape else {
|
||
|
return
|
||
|
}
|
||
|
shape.shiftRightByOneColumn()
|
||
|
guard detectIllegalPlacement() == false else {
|
||
|
shape.shiftLeftByOneColumn()
|
||
|
return
|
||
|
}
|
||
|
delegate?.gameShapeDidMove(privyet: self)
|
||
|
}
|
||
|
|
||
|
}
|