privyet/Privyet/Privyet.swift

276 lines
8.7 KiB
Swift
Raw Normal View History

//
// 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 bucket: Array2D<Block>
var nextShape: Shape?
var fallingShape: Shape?
var delegate: PrivyetDelegate?
var score = 0
var level = 1
init() {
fallingShape = nil
nextShape = nil
bucket = Array2D<Block>(columns: NumColumns, rows: NumRows)
}
// Starts the game by initializing game data
func beginGame() {
if (nextShape == nil) {
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
}
delegate?.gameDidBegin(privyet: self)
}
// Advances the current falling shape and the "next" shape. Returns both these shapes as a tuple.
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)
}
// Returns true if the falling shape is in an "illegal" placement (outside bucket bounds or "colliding"
// with existing blocks in the bucket).
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 bucket[block.column, block.row] != nil {
return true
}
}
return false
}
// "Settle" the falling shape by transferring its blocks to the bucket.
func settleShape() {
guard let shape = fallingShape else {
return
}
for block in shape.blocks {
bucket[block.column, block.row] = block
}
fallingShape = nil
delegate?.gameShapeDidLand(privyet: self)
}
// Returns true if the falling block is "landing" (at the bottom of the bucket or directly above existing blocks)
func detectTouch() -> Bool {
guard let shape = fallingShape else {
return false
}
for bottomBlock in shape.bottomBlocks {
if bottomBlock.row == NumRows - 1 || bucket[bottomBlock.column, bottomBlock.row + 1] != nil {
return true
}
}
return false
}
// Ends the current game
func endGame() {
score = 0
level = 1
delegate?.gameDidEnd(privyet: self)
}
// Detect and remove completed lines. Returns a tuple of two arrays of arrays:
// linesRemoved - The blocks that will be removed as a result of completed lines.
// fallenBlocks - The blocks that will drop to a lower line in the bucket when the above blocks are removed.
// If no lines are to be removed, returns a pair of empty lists.
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 = bucket[column, row] else {
continue
}
rowOfBlocks.append(block)
}
if rowOfBlocks.count == NumColumns {
// filled line detected, remove it
removedLines.append(rowOfBlocks)
for block in rowOfBlocks {
bucket[block.column, block.row] = nil
}
}
}
if removedLines.count == 0 {
return ([], []) // no lines filled
}
// Advance score and game level as appropriate
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 = bucket[column, row] else {
continue
}
var newRow = row
while (newRow < NumRows - 1 && bucket[column, newRow + 1] == nil) {
newRow += 1
}
block.row = newRow
bucket[column, row] = nil
bucket[column, newRow] = block
fallenBlocksArray.append(block)
}
if fallenBlocksArray.count > 0 {
fallenBlocks.append(fallenBlocksArray)
}
}
return (removedLines, fallenBlocks)
}
// Removes all blocks from the bucket (at end of game). Returns all the removed blocks.
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 = bucket[column, row] else {
continue
}
rowOfBlocks.append(block)
bucket[column, row] = nil
}
allBlocks.append(rowOfBlocks)
}
return allBlocks
}
// Drop the falling shape down as far as it will go.
func dropShape() {
guard let shape = fallingShape else {
return
}
while detectIllegalPlacement() == false {
shape.lowerShapeByOneRow()
}
shape.raiseShapeByOneRow()
delegate?.gameShapeDidDrop(privyet: self)
}
// Let the falling shape fall by one row.
func letShapeFall() {
guard let shape = fallingShape else {
return
}
shape.lowerShapeByOneRow()
if detectIllegalPlacement() {
shape.raiseShapeByOneRow()
if detectIllegalPlacement() {
endGame() // shape was in illegal place to start, this ends the game
} else {
settleShape() // shape can land
}
} else {
delegate?.gameShapeDidMove(privyet: self)
if detectTouch() {
settleShape()
}
}
}
// Rotate the falling shape clockwise by 90 degrees.
func rotateShape() {
guard let shape = fallingShape else {
return
}
shape.rotateClockwise()
guard detectIllegalPlacement() == false else {
shape.rotateCounterClockwise()
return
}
delegate?.gameShapeDidMove(privyet: self)
}
// Move the falling shape left by one column.
func moveShapeLeft() {
guard let shape = fallingShape else {
return
}
shape.shiftLeftByOneColumn()
guard detectIllegalPlacement() == false else {
shape.shiftRightByOneColumn()
return
}
delegate?.gameShapeDidMove(privyet: self)
}
// Move the falling shape right by one column.
func moveShapeRight() {
guard let shape = fallingShape else {
return
}
shape.shiftRightByOneColumn()
guard detectIllegalPlacement() == false else {
shape.shiftLeftByOneColumn()
return
}
delegate?.gameShapeDidMove(privyet: self)
}
}