cleaned up some stuff and added more documentation to the existing code
This commit is contained in:
parent
f0d0dd0051
commit
3a15dae884
|
@ -14,7 +14,8 @@
|
||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" systemColor="systemOrangeColor" red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.0" green="0.16078431372549018" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import SpriteKit
|
||||||
|
|
||||||
let NumberOfColors: UInt32 = 6
|
let NumberOfColors: UInt32 = 6
|
||||||
|
|
||||||
|
// Colors that may be applied to blocks (whcih controls the sprite image they use)
|
||||||
enum BlockColor: Int, CustomStringConvertible {
|
enum BlockColor: Int, CustomStringConvertible {
|
||||||
case Blue = 0, Orange, Purple, Red, Teal, Yellow
|
case Blue = 0, Orange, Purple, Red, Teal, Yellow
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ enum BlockColor: Int, CustomStringConvertible {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Represents a single block in the game.
|
||||||
class Block: Hashable, CustomStringConvertible {
|
class Block: Hashable, CustomStringConvertible {
|
||||||
// Constants
|
// Constants
|
||||||
let color: BlockColor
|
let color: BlockColor
|
||||||
|
|
|
@ -54,15 +54,17 @@ class GameScene: SKScene {
|
||||||
shapeLayer.addChild(gameBoard)
|
shapeLayer.addChild(gameBoard)
|
||||||
gameLayer.addChild(shapeLayer)
|
gameLayer.addChild(shapeLayer)
|
||||||
|
|
||||||
|
// Set the theme music to play infinitely.
|
||||||
run(SKAction.repeatForever(SKAction.playSoundFileNamed("Sounds/theme.mp3", waitForCompletion: true)))
|
run(SKAction.repeatForever(SKAction.playSoundFileNamed("Sounds/theme.mp3", waitForCompletion: true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convenience function to play a sound file.
|
||||||
func playSound(sound: String) {
|
func playSound(sound: String) {
|
||||||
run(SKAction.playSoundFileNamed(sound, waitForCompletion: false))
|
run(SKAction.playSoundFileNamed(sound, waitForCompletion: false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called once per frame by the framework, before it's rendered.
|
||||||
override func update(_ currentTime: TimeInterval) {
|
override func update(_ currentTime: TimeInterval) {
|
||||||
// Called before each frame is rendered
|
|
||||||
guard let lastTick = lastTick else {
|
guard let lastTick = lastTick else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -71,23 +73,26 @@ class GameScene: SKScene {
|
||||||
self.lastTick = NSDate()
|
self.lastTick = NSDate()
|
||||||
tick?()
|
tick?()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Starts the game tick going.
|
||||||
func startTicking() {
|
func startTicking() {
|
||||||
lastTick = NSDate()
|
lastTick = NSDate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stops the game tick.
|
||||||
func stopTicking() {
|
func stopTicking() {
|
||||||
lastTick = nil
|
lastTick = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert bucket row/column coordinates to display coordinates.
|
||||||
func pointForColumn(column: Int, row: Int) -> CGPoint {
|
func pointForColumn(column: Int, row: Int) -> CGPoint {
|
||||||
let x = LayerPosition.x + (CGFloat(column) * BlockSize) + (BlockSize / 2)
|
let x = LayerPosition.x + (CGFloat(column) * BlockSize) + (BlockSize / 2)
|
||||||
let y = LayerPosition.y - ((CGFloat(row) * BlockSize) + (BlockSize / 2))
|
let y = LayerPosition.y - ((CGFloat(row) * BlockSize) + (BlockSize / 2))
|
||||||
return CGPoint(x: x, y: y)
|
return CGPoint(x: x, y: y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the "preview" shape to the current scene. This also "rezzes in" a shape's block sprites.
|
||||||
func addPreviewShapeToScene(shape: Shape, completion: @escaping () -> ()) {
|
func addPreviewShapeToScene(shape: Shape, completion: @escaping () -> ()) {
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
var texture = textureCache[block.spriteName]
|
var texture = textureCache[block.spriteName]
|
||||||
|
@ -115,6 +120,7 @@ class GameScene: SKScene {
|
||||||
run(SKAction.sequence([waitAction, completeAction]))
|
run(SKAction.sequence([waitAction, completeAction]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move the "preview" shape into position so it becomes the new "falling" shape.
|
||||||
func movePreviewShape(shape: Shape, completion: @escaping () -> ()) {
|
func movePreviewShape(shape: Shape, completion: @escaping () -> ()) {
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
let sprite = block.sprite!
|
let sprite = block.sprite!
|
||||||
|
@ -128,6 +134,7 @@ class GameScene: SKScene {
|
||||||
run(SKAction.sequence([waitAction, completeAction]))
|
run(SKAction.sequence([waitAction, completeAction]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redraw the falling shape by shifting the position of its component blocks.
|
||||||
func redrawShape(shape: Shape, completion: @escaping () -> ()) {
|
func redrawShape(shape: Shape, completion: @escaping () -> ()) {
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
let sprite = block.sprite!
|
let sprite = block.sprite!
|
||||||
|
@ -141,9 +148,11 @@ class GameScene: SKScene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animate the removed lines "exploding" and the remaining blocks dropping into place.
|
||||||
func animateCollapsingLines(linesToRemove: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>, completion: @escaping () -> ()) {
|
func animateCollapsingLines(linesToRemove: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>, completion: @escaping () -> ()) {
|
||||||
var longestDuration: TimeInterval = 0
|
var longestDuration: TimeInterval = 0
|
||||||
|
|
||||||
|
// Animate the falling blocks dropping into place
|
||||||
for (columnIdx, column) in fallenBlocks.enumerated() {
|
for (columnIdx, column) in fallenBlocks.enumerated() {
|
||||||
for (blockIdx, block) in column.enumerated() {
|
for (blockIdx, block) in column.enumerated() {
|
||||||
let newPosition = pointForColumn(column: block.column, row: block.row)
|
let newPosition = pointForColumn(column: block.column, row: block.row)
|
||||||
|
@ -158,6 +167,7 @@ class GameScene: SKScene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animate the "removed" blocks "exploding"
|
||||||
for rowToRemove in linesToRemove {
|
for rowToRemove in linesToRemove {
|
||||||
for block in rowToRemove {
|
for block in rowToRemove {
|
||||||
let randomRadius = CGFloat(UInt(arc4random_uniform(400) + 100))
|
let randomRadius = CGFloat(UInt(arc4random_uniform(400) + 100))
|
||||||
|
|
|
@ -43,10 +43,12 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the user taps the screen
|
||||||
@IBAction func didTap(_ sender: UITapGestureRecognizer) {
|
@IBAction func didTap(_ sender: UITapGestureRecognizer) {
|
||||||
privyet.rotateShape()
|
privyet.rotateShape()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the user slides their finger across the screen
|
||||||
@IBAction func didPan(_ sender: UIPanGestureRecognizer) {
|
@IBAction func didPan(_ sender: UIPanGestureRecognizer) {
|
||||||
let currentPoint = sender.translation(in: self.view)
|
let currentPoint = sender.translation(in: self.view)
|
||||||
if let originalPoint = panPointReference {
|
if let originalPoint = panPointReference {
|
||||||
|
@ -64,14 +66,17 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the user swipes the screen in a downward direction
|
||||||
@IBAction func didSwipe(_ sender: UISwipeGestureRecognizer) {
|
@IBAction func didSwipe(_ sender: UISwipeGestureRecognizer) {
|
||||||
privyet.dropShape()
|
privyet.dropShape()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to allow multiple gesture recognizers to be used simultaneously
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to prioritize one gesture recognizer over another
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
if gestureRecognizer is UISwipeGestureRecognizer {
|
if gestureRecognizer is UISwipeGestureRecognizer {
|
||||||
if otherGestureRecognizer is UIPanGestureRecognizer {
|
if otherGestureRecognizer is UIPanGestureRecognizer {
|
||||||
|
@ -85,10 +90,12 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called once per game tick (interval over which blocks drop)
|
||||||
func didTick() {
|
func didTick() {
|
||||||
privyet.letShapeFall()
|
privyet.letShapeFall()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advances the "next' and "falling" shapes and places them on the screen
|
||||||
func nextShape() {
|
func nextShape() {
|
||||||
let newShapes = privyet.newShape()
|
let newShapes = privyet.newShape()
|
||||||
guard let fallingShape = newShapes.fallingShape else {
|
guard let fallingShape = newShapes.fallingShape else {
|
||||||
|
@ -101,6 +108,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the game starts
|
||||||
func gameDidBegin(privyet: Privyet) {
|
func gameDidBegin(privyet: Privyet) {
|
||||||
levelLabel.text = "\(privyet.level)"
|
levelLabel.text = "\(privyet.level)"
|
||||||
scoreLabel.text = "\(privyet.score)"
|
scoreLabel.text = "\(privyet.score)"
|
||||||
|
@ -116,6 +124,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the game ends
|
||||||
func gameDidEnd(privyet: Privyet) {
|
func gameDidEnd(privyet: Privyet) {
|
||||||
view.isUserInteractionEnabled = false
|
view.isUserInteractionEnabled = false
|
||||||
scene.stopTicking()
|
scene.stopTicking()
|
||||||
|
@ -125,6 +134,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the game advances in level (gets faster)
|
||||||
func gameDidLevelUp(privyet: Privyet) {
|
func gameDidLevelUp(privyet: Privyet) {
|
||||||
levelLabel.text = "\(privyet.level)"
|
levelLabel.text = "\(privyet.level)"
|
||||||
if scene.tickLengthMillis >= 100 {
|
if scene.tickLengthMillis >= 100 {
|
||||||
|
@ -135,6 +145,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
scene.playSound(sound: "Sounds/levelup.mp3")
|
scene.playSound(sound: "Sounds/levelup.mp3")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when a shape is "dropped"
|
||||||
func gameShapeDidDrop(privyet: Privyet) {
|
func gameShapeDidDrop(privyet: Privyet) {
|
||||||
scene.stopTicking()
|
scene.stopTicking()
|
||||||
scene.redrawShape(shape: privyet.fallingShape!) {
|
scene.redrawShape(shape: privyet.fallingShape!) {
|
||||||
|
@ -143,6 +154,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
scene.playSound(sound: "Sounds/drop.mp3")
|
scene.playSound(sound: "Sounds/drop.mp3")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when a shape "lands" on top of existing blocks or at the bottom of the bucket
|
||||||
func gameShapeDidLand(privyet: Privyet) {
|
func gameShapeDidLand(privyet: Privyet) {
|
||||||
scene.stopTicking()
|
scene.stopTicking()
|
||||||
self.view.isUserInteractionEnabled = false
|
self.view.isUserInteractionEnabled = false
|
||||||
|
@ -158,6 +170,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when a shape moves on the screen
|
||||||
func gameShapeDidMove(privyet: Privyet) {
|
func gameShapeDidMove(privyet: Privyet) {
|
||||||
scene.redrawShape(shape: privyet.fallingShape!) { }
|
scene.redrawShape(shape: privyet.fallingShape!) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ protocol PrivyetDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Privyet {
|
class Privyet {
|
||||||
var blockArray: Array2D<Block>
|
var bucket: Array2D<Block>
|
||||||
var nextShape: Shape?
|
var nextShape: Shape?
|
||||||
var fallingShape: Shape?
|
var fallingShape: Shape?
|
||||||
var delegate: PrivyetDelegate?
|
var delegate: PrivyetDelegate?
|
||||||
|
@ -52,9 +52,10 @@ class Privyet {
|
||||||
init() {
|
init() {
|
||||||
fallingShape = nil
|
fallingShape = nil
|
||||||
nextShape = nil
|
nextShape = nil
|
||||||
blockArray = Array2D<Block>(columns: NumColumns, rows: NumRows)
|
bucket = Array2D<Block>(columns: NumColumns, rows: NumRows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Starts the game by initializing game data
|
||||||
func beginGame() {
|
func beginGame() {
|
||||||
if (nextShape == nil) {
|
if (nextShape == nil) {
|
||||||
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
||||||
|
@ -62,6 +63,7 @@ class Privyet {
|
||||||
delegate?.gameDidBegin(privyet: self)
|
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?) {
|
func newShape() -> (fallingShape: Shape?, nextShape: Shape?) {
|
||||||
fallingShape = nextShape
|
fallingShape = nextShape
|
||||||
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
nextShape = Shape.random(startingColumn: PreviewColumn, startingRow: PreviewRow)
|
||||||
|
@ -77,6 +79,8 @@ class Privyet {
|
||||||
return (fallingShape, nextShape)
|
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 {
|
func detectIllegalPlacement() -> Bool {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return false
|
return false
|
||||||
|
@ -84,82 +88,93 @@ class Privyet {
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
if block.column < 0 || block.column >= NumColumns || block.row < 0 || block.row >= NumRows {
|
if block.column < 0 || block.column >= NumColumns || block.row < 0 || block.row >= NumRows {
|
||||||
return true
|
return true
|
||||||
} else if blockArray[block.column, block.row] != nil {
|
} else if bucket[block.column, block.row] != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "Settle" the falling shape by transferring its blocks to the bucket.
|
||||||
func settleShape() {
|
func settleShape() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for block in shape.blocks {
|
for block in shape.blocks {
|
||||||
blockArray[block.column, block.row] = block
|
bucket[block.column, block.row] = block
|
||||||
}
|
}
|
||||||
fallingShape = nil
|
fallingShape = nil
|
||||||
delegate?.gameShapeDidLand(privyet: self)
|
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 {
|
func detectTouch() -> Bool {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for bottomBlock in shape.bottomBlocks {
|
for bottomBlock in shape.bottomBlocks {
|
||||||
if bottomBlock.row == NumRows - 1 || blockArray[bottomBlock.column, bottomBlock.row + 1] != nil {
|
if bottomBlock.row == NumRows - 1 || bucket[bottomBlock.column, bottomBlock.row + 1] != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ends the current game
|
||||||
func endGame() {
|
func endGame() {
|
||||||
score = 0
|
score = 0
|
||||||
level = 1
|
level = 1
|
||||||
delegate?.gameDidEnd(privyet: self)
|
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>>) {
|
func removeCompletedLines() -> (linesRemoved: Array<Array<Block>>, fallenBlocks: Array<Array<Block>>) {
|
||||||
var removedLines = Array<Array<Block>>()
|
var removedLines = Array<Array<Block>>()
|
||||||
for row in (1..<NumRows).reversed() {
|
for row in (1..<NumRows).reversed() {
|
||||||
var rowOfBlocks = Array<Block>()
|
var rowOfBlocks = Array<Block>()
|
||||||
for column in 0..<NumColumns {
|
for column in 0..<NumColumns {
|
||||||
guard let block = blockArray[column, row] else {
|
guard let block = bucket[column, row] else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rowOfBlocks.append(block)
|
rowOfBlocks.append(block)
|
||||||
}
|
}
|
||||||
if rowOfBlocks.count == NumColumns {
|
if rowOfBlocks.count == NumColumns {
|
||||||
|
// filled line detected, remove it
|
||||||
removedLines.append(rowOfBlocks)
|
removedLines.append(rowOfBlocks)
|
||||||
for block in rowOfBlocks {
|
for block in rowOfBlocks {
|
||||||
blockArray[block.column, block.row] = nil
|
bucket[block.column, block.row] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if removedLines.count == 0 {
|
if removedLines.count == 0 {
|
||||||
return ([], [])
|
return ([], []) // no lines filled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advance score and game level as appropriate
|
||||||
let pointsEarned = removedLines.count * PointsPerLine * level
|
let pointsEarned = removedLines.count * PointsPerLine * level
|
||||||
score += pointsEarned
|
score += pointsEarned
|
||||||
if score >= level * LevelThreshold {
|
if score >= level * LevelThreshold {
|
||||||
level += 1
|
level += 1
|
||||||
delegate?.gameDidLevelUp(privyet: self)
|
delegate?.gameDidLevelUp(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallenBlocks = Array<Array<Block>>()
|
var fallenBlocks = Array<Array<Block>>()
|
||||||
for column in 0..<NumColumns {
|
for column in 0..<NumColumns {
|
||||||
var fallenBlocksArray = Array<Block>()
|
var fallenBlocksArray = Array<Block>()
|
||||||
for row in (1..<removedLines[0][0].row).reversed() {
|
for row in (1..<removedLines[0][0].row).reversed() {
|
||||||
guard let block = blockArray[column, row] else {
|
guard let block = bucket[column, row] else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var newRow = row
|
var newRow = row
|
||||||
while (newRow < NumRows - 1 && blockArray[column, newRow + 1] == nil) {
|
while (newRow < NumRows - 1 && bucket[column, newRow + 1] == nil) {
|
||||||
newRow += 1
|
newRow += 1
|
||||||
}
|
}
|
||||||
block.row = newRow
|
block.row = newRow
|
||||||
blockArray[column, row] = nil
|
bucket[column, row] = nil
|
||||||
blockArray[column, newRow] = block
|
bucket[column, newRow] = block
|
||||||
fallenBlocksArray.append(block)
|
fallenBlocksArray.append(block)
|
||||||
}
|
}
|
||||||
if fallenBlocksArray.count > 0 {
|
if fallenBlocksArray.count > 0 {
|
||||||
|
@ -169,22 +184,24 @@ class Privyet {
|
||||||
return (removedLines, fallenBlocks)
|
return (removedLines, fallenBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes all blocks from the bucket (at end of game). Returns all the removed blocks.
|
||||||
func removeAllBlocks() -> Array<Array<Block>> {
|
func removeAllBlocks() -> Array<Array<Block>> {
|
||||||
var allBlocks = Array<Array<Block>>()
|
var allBlocks = Array<Array<Block>>()
|
||||||
for row in 0..<NumRows {
|
for row in 0..<NumRows {
|
||||||
var rowOfBlocks = Array<Block>()
|
var rowOfBlocks = Array<Block>()
|
||||||
for column in 0..<NumColumns {
|
for column in 0..<NumColumns {
|
||||||
guard let block = blockArray[column, row] else {
|
guard let block = bucket[column, row] else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rowOfBlocks.append(block)
|
rowOfBlocks.append(block)
|
||||||
blockArray[column, row] = nil
|
bucket[column, row] = nil
|
||||||
}
|
}
|
||||||
allBlocks.append(rowOfBlocks)
|
allBlocks.append(rowOfBlocks)
|
||||||
}
|
}
|
||||||
return allBlocks
|
return allBlocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop the falling shape down as far as it will go.
|
||||||
func dropShape() {
|
func dropShape() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
|
@ -196,6 +213,7 @@ class Privyet {
|
||||||
delegate?.gameShapeDidDrop(privyet: self)
|
delegate?.gameShapeDidDrop(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let the falling shape fall by one row.
|
||||||
func letShapeFall() {
|
func letShapeFall() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
|
@ -204,9 +222,9 @@ class Privyet {
|
||||||
if detectIllegalPlacement() {
|
if detectIllegalPlacement() {
|
||||||
shape.raiseShapeByOneRow()
|
shape.raiseShapeByOneRow()
|
||||||
if detectIllegalPlacement() {
|
if detectIllegalPlacement() {
|
||||||
endGame()
|
endGame() // shape was in illegal place to start, this ends the game
|
||||||
} else {
|
} else {
|
||||||
settleShape()
|
settleShape() // shape can land
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
|
@ -216,6 +234,7 @@ class Privyet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate the falling shape clockwise by 90 degrees.
|
||||||
func rotateShape() {
|
func rotateShape() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
|
@ -228,6 +247,7 @@ class Privyet {
|
||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move the falling shape left by one column.
|
||||||
func moveShapeLeft() {
|
func moveShapeLeft() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
|
@ -240,6 +260,7 @@ class Privyet {
|
||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move the falling shape right by one column.
|
||||||
func moveShapeRight() {
|
func moveShapeRight() {
|
||||||
guard let shape = fallingShape else {
|
guard let shape = fallingShape else {
|
||||||
return
|
return
|
||||||
|
@ -251,5 +272,4 @@ class Privyet {
|
||||||
}
|
}
|
||||||
delegate?.gameShapeDidMove(privyet: self)
|
delegate?.gameShapeDidMove(privyet: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import SpriteKit
|
||||||
|
|
||||||
let NumOrientations: UInt32 = 4
|
let NumOrientations: UInt32 = 4
|
||||||
|
|
||||||
|
// Represents the orientation of a shape in increments of 90 degrees
|
||||||
enum Orientation: Int, CustomStringConvertible {
|
enum Orientation: Int, CustomStringConvertible {
|
||||||
case Zero = 0, Ninety, OneEighty, TwoSeventy
|
case Zero = 0, Ninety, OneEighty, TwoSeventy
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ enum Orientation: Int, CustomStringConvertible {
|
||||||
return Orientation(rawValue:Int(arc4random_uniform(NumOrientations)))!
|
return Orientation(rawValue:Int(arc4random_uniform(NumOrientations)))!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the orientation resulting from a rotation in either direction.
|
||||||
static func rotate(orientation: Orientation, clockwise: Bool) -> Orientation {
|
static func rotate(orientation: Orientation, clockwise: Bool) -> Orientation {
|
||||||
var rotated = orientation.rawValue + (clockwise ? 1 : -1)
|
var rotated = orientation.rawValue + (clockwise ? 1 : -1)
|
||||||
if rotated > Orientation.TwoSeventy.rawValue {
|
if rotated > Orientation.TwoSeventy.rawValue {
|
||||||
|
@ -51,6 +53,8 @@ let SecondBlockIdx: Int = 1
|
||||||
let ThirdBlockIdx: Int = 2
|
let ThirdBlockIdx: Int = 2
|
||||||
let FourthBlockIdx: Int = 3
|
let FourthBlockIdx: Int = 3
|
||||||
|
|
||||||
|
// Base class representing a shape consisting of four blocks. Derived classes represent the different
|
||||||
|
// tetramino shapes.
|
||||||
class Shape: Hashable, CustomStringConvertible {
|
class Shape: Hashable, CustomStringConvertible {
|
||||||
// The color of the shape
|
// The color of the shape
|
||||||
let color: BlockColor
|
let color: BlockColor
|
||||||
|
@ -65,15 +69,18 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
// Required overrides
|
// Required overrides
|
||||||
|
|
||||||
// Subclasses must override this property
|
// 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)>] {
|
var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
|
||||||
return [:]
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subclasses must override this property
|
// 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<Block>] {
|
var bottomBlocksForOrientations: [Orientation: Array<Block>] {
|
||||||
return [:]
|
return [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the blocks in this shape that are currently "on the bottom"
|
||||||
var bottomBlocks: Array<Block> {
|
var bottomBlocks: Array<Block> {
|
||||||
guard let bottomBlocks = bottomBlocksForOrientations[orientation] else {
|
guard let bottomBlocks = bottomBlocksForOrientations[orientation] else {
|
||||||
return []
|
return []
|
||||||
|
@ -110,6 +117,7 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
self.init(column: column, row: row, color: BlockColor.random(), orientation: Orientation.random())
|
self.init(column: column, row: row, color: BlockColor.random(), orientation: Orientation.random())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the blocks of the shape based on the current position, color, and orientation.
|
||||||
final func initializeBlocks() {
|
final func initializeBlocks() {
|
||||||
guard let blockRowColumnTranslations = blockRowColumnPositions[orientation] else {
|
guard let blockRowColumnTranslations = blockRowColumnPositions[orientation] else {
|
||||||
return
|
return
|
||||||
|
@ -120,6 +128,7 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate the blocks in this shape to a new orientation.
|
||||||
final func rotateBlocks(orientation: Orientation) {
|
final func rotateBlocks(orientation: Orientation) {
|
||||||
guard let blockRowColumnTranslation: Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else {
|
guard let blockRowColumnTranslation: Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else {
|
||||||
return
|
return
|
||||||
|
@ -130,34 +139,41 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate the blocks in this shape clockwise.
|
||||||
final func rotateClockwise() {
|
final func rotateClockwise() {
|
||||||
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: true)
|
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: true)
|
||||||
rotateBlocks(orientation: newOrientation)
|
rotateBlocks(orientation: newOrientation)
|
||||||
orientation = newOrientation
|
orientation = newOrientation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rotate the blocks in this shape counterclockwise.
|
||||||
final func rotateCounterClockwise() {
|
final func rotateCounterClockwise() {
|
||||||
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: false)
|
let newOrientation = Orientation.rotate(orientation: orientation, clockwise: false)
|
||||||
rotateBlocks(orientation: newOrientation)
|
rotateBlocks(orientation: newOrientation)
|
||||||
orientation = newOrientation
|
orientation = newOrientation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one row down.
|
||||||
final func lowerShapeByOneRow() {
|
final func lowerShapeByOneRow() {
|
||||||
shiftBy(columns: 0, rows: 1)
|
shiftBy(columns: 0, rows: 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one row up.
|
||||||
final func raiseShapeByOneRow() {
|
final func raiseShapeByOneRow() {
|
||||||
shiftBy(columns: 0, rows: -1)
|
shiftBy(columns: 0, rows: -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one colukmn to the right.
|
||||||
final func shiftRightByOneColumn() {
|
final func shiftRightByOneColumn() {
|
||||||
shiftBy(columns: 1, rows: 0)
|
shiftBy(columns: 1, rows: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates block positions in this shape to one column to the left.
|
||||||
final func shiftLeftByOneColumn() {
|
final func shiftLeftByOneColumn() {
|
||||||
shiftBy(columns: -1, rows: 0)
|
shiftBy(columns: -1, rows: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shifts plock positions in this shape in a relative fashion.
|
||||||
final func shiftBy(columns: Int, rows: Int) {
|
final func shiftBy(columns: Int, rows: Int) {
|
||||||
self.column += columns
|
self.column += columns
|
||||||
self.row += rows
|
self.row += rows
|
||||||
|
@ -167,12 +183,14 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moves the shape to an absolute position.
|
||||||
final func moveTo(column: Int, row: Int) {
|
final func moveTo(column: Int, row: Int) {
|
||||||
self.column = column
|
self.column = column
|
||||||
self.row = row
|
self.row = row
|
||||||
rotateBlocks(orientation: orientation)
|
rotateBlocks(orientation: orientation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates a random shape.
|
||||||
final class func random(startingColumn: Int, startingRow: Int) -> Shape {
|
final class func random(startingColumn: Int, startingRow: Int) -> Shape {
|
||||||
switch Int(arc4random_uniform(NumShapeTypes)) {
|
switch Int(arc4random_uniform(NumShapeTypes)) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user