// // Shape.swift // Privyet // // Created by Amy Bowersox on 5/24/20. // Copyright © 2020 Erbosoft Metaverse Design Solutions. All rights reserved. // import Foundation import SpriteKit // Represents the orientation of a shape in increments of 90 degrees enum Orientation: Int, CustomStringConvertible, CaseIterable { case Zero = 0, Ninety, OneEighty, TwoSeventy var description: String { switch self { case .Zero: return "0" case .Ninety: return "90" case .OneEighty: return "180" case .TwoSeventy: return "270" } } static func random() -> Orientation { return Orientation.allCases.shuffled().first! } // Returns the orientation resulting from a rotation in either direction. static func rotate(orientation: Orientation, clockwise: Bool) -> Orientation { var rotated = orientation.rawValue + (clockwise ? 1 : -1) if rotated > Orientation.TwoSeventy.rawValue { rotated = Orientation.Zero.rawValue } else if rotated < 0 { rotated = Orientation.TwoSeventy.rawValue } return Orientation(rawValue:rotated)! } } // The number of total shape varieties let NumShapeTypes = 7 // Shape indexes let FirstBlockIdx: Int = 0 let SecondBlockIdx: Int = 1 let ThirdBlockIdx: Int = 2 let FourthBlockIdx: Int = 3 // Base class representing a shape consisting of four blocks. Derived classes represent the different // tetramino shapes. class Shape: Hashable, CustomStringConvertible { // The blocks comprising the shape var blocks = Array() // The current orientation of the shape var orientation: Orientation // The column and row representing the shape's anchor point var column, row: Int // Required overrides // Subclasses must override this property // The color of the shape var color: BlockColor? { return nil } // Subclasses must override this property // Returns the relative positions of the shape blocks based on the shape orientation var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] { return [:] } // Subclasses must override this property // Returns the blocks in a shape that are "on the bottom" based on the shape orientation var bottomBlocksForOrientations: [Orientation: Array] { return [:] } // Returns the blocks in this shape that are currently "on the bottom" var bottomBlocks: Array { guard let bottomBlocks = bottomBlocksForOrientations[orientation] else { return [] } return bottomBlocks } // Equatable static func == (lhs: Shape, rhs: Shape) -> Bool { return lhs.row == rhs.row && lhs.column == rhs.column } // Hashable func hash(into hasher: inout Hasher) { blocks.forEach {b in hasher.combine(b.hashValue) } } // CustomStringConvertible var description: String { guard let color = color else { return " block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])" } return "\(color) block facing \(orientation): \(blocks[FirstBlockIdx]), \(blocks[SecondBlockIdx]), \(blocks[ThirdBlockIdx]), \(blocks[FourthBlockIdx])" } init(column: Int, row: Int, orientation: Orientation) { self.column = column self.row = row self.orientation = orientation initializeBlocks() } convenience init(column: Int, row: Int) { self.init(column: column, row: row, orientation: Orientation.random()) } // Initialize the blocks of the shape based on the current position, color, and orientation. final func initializeBlocks() { guard let blockRowColumnTranslations = blockRowColumnPositions[orientation] else { return } blocks = blockRowColumnTranslations.map { (diff) -> Block in return Block(column: column + diff.columnDiff, row: row + diff.rowDiff, color: color!) } } // Rotate the blocks in this shape to a new orientation. final func rotateBlocks(orientation: Orientation) { guard let blockRowColumnTranslation: Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else { return } for (idx, diff) in blockRowColumnTranslation.enumerated() { blocks[idx].column = column + diff.columnDiff blocks[idx].row = row + diff.rowDiff } } // Rotate the blocks in this shape clockwise. final func rotateClockwise() { let newOrientation = Orientation.rotate(orientation: orientation, clockwise: true) rotateBlocks(orientation: newOrientation) orientation = newOrientation } // Rotate the blocks in this shape counterclockwise. final func rotateCounterClockwise() { let newOrientation = Orientation.rotate(orientation: orientation, clockwise: false) rotateBlocks(orientation: newOrientation) orientation = newOrientation } // Updates block positions in this shape to one row down. final func lowerShapeByOneRow() { shiftBy(columns: 0, rows: 1) } // Updates block positions in this shape to one row up. final func raiseShapeByOneRow() { shiftBy(columns: 0, rows: -1) } // Updates block positions in this shape to one colukmn to the right. final func shiftRightByOneColumn() { shiftBy(columns: 1, rows: 0) } // Updates block positions in this shape to one column to the left. final func shiftLeftByOneColumn() { shiftBy(columns: -1, rows: 0) } // Shifts plock positions in this shape in a relative fashion. final func shiftBy(columns: Int, rows: Int) { self.column += columns self.row += rows for block in blocks { block.column += columns block.row += rows } } // Moves the shape to an absolute position. final func moveTo(column: Int, row: Int) { self.column = column self.row = row rotateBlocks(orientation: orientation) } // Generates a random shape. final class func random(startingColumn: Int, startingRow: Int) -> Shape { switch Int.random(in: 0 ..< NumShapeTypes) { case 0: return SquareShape(column: startingColumn, row: startingRow) case 1: return LineShape(column: startingColumn, row: startingRow) case 2: return TShape(column: startingColumn, row: startingRow) case 3: return LShape(column: startingColumn, row: startingRow) case 4: return JShape(column: startingColumn, row: startingRow) case 5: return SShape(column: startingColumn, row: startingRow) default: return ZShape(column: startingColumn, row: startingRow) } } }