// // 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 = 3 let HoldColumn = 12 let HoldRow = 10 // number of points for single (1 line), double (2 lines), triple (3 lines), Privyet (4 lines) let PointsPerLine = [1, 2, 5, 10] let LevelThreshold = 50 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 current shape is put on hold func gameShapePutOnHold(privyet: Privyet, firstHold: Bool) // invoked when the game has reached a new level func gameDidLevelUp(privyet: Privyet) } class Privyet { var bucket: Array2D var nextShape: Shape? var fallingShape: Shape? var heldShape: Shape? var delegate: PrivyetDelegate? var score = 0 var level = 1 init() { fallingShape = nil nextShape = nil heldShape = nil bucket = Array2D(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>, fallenBlocks: Array>) { var removedLines = Array>() for row in (1..() for column in 0..= level * LevelThreshold { level += 1 delegate?.gameDidLevelUp(privyet: self) } var fallenBlocks = Array>() for column in 0..() for row in (1.. 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> { var allBlocks = Array>() for row in 0..() for column in 0.. Array { var allShapes = Array() for shape in [fallingShape, nextShape, heldShape] { guard let s = shape else { continue } allShapes.append(s) } fallingShape = nil nextShape = nil heldShape = nil return allShapes } // 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) } // Places current shape "on hold." func holdShape() { guard let shape = fallingShape else { return } guard let held = heldShape else { shape.moveTo(column: HoldColumn, row: HoldRow) heldShape = shape fallingShape = nil delegate?.gameShapePutOnHold(privyet: self, firstHold: true) return } shape.exchangePositions(other: held) fallingShape = held heldShape = shape delegate?.gameShapePutOnHold(privyet: self, firstHold: false) } }