added "Hold" functionality; revamped scoring system; added popup banner for 2+ lines removed at once; fixed bug at end game where shapes still present were not cleared away
This commit is contained in:
parent
e69d8eda4f
commit
0a374b852d
|
@ -15,13 +15,6 @@
|
||||||
<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"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="NEXT" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kjl-Kl-IpX">
|
|
||||||
<rect key="frame" x="257" y="44" width="40" height="21"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<fontDescription key="fontDescription" name="Avenir-Heavy" family="Avenir" pointSize="15"/>
|
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5g3-e3-yLO">
|
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5g3-e3-yLO">
|
||||||
<rect key="frame" x="310" y="538" width="84" height="100"/>
|
<rect key="frame" x="310" y="538" width="84" height="100"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
|
||||||
|
@ -60,6 +53,20 @@
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
</view>
|
</view>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="NEXT" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kjl-Kl-IpX">
|
||||||
|
<rect key="frame" x="257" y="44" width="40" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" name="Avenir-Heavy" family="Avenir" pointSize="15"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="HOLD" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="BOe-2c-WvE">
|
||||||
|
<rect key="frame" x="257" y="204" width="50" height="21"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<fontDescription key="fontDescription" name="Avenir-Heavy" family="Avenir" pointSize="15"/>
|
||||||
|
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" systemColor="systemPurpleColor" red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" systemColor="systemPurpleColor" red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
|
|
|
@ -37,6 +37,45 @@ enum BlockColor: Int, CustomStringConvertible, CaseIterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rectangles in block coordinates
|
||||||
|
class BlockRect {
|
||||||
|
var left: Int
|
||||||
|
var top: Int
|
||||||
|
var right: Int
|
||||||
|
var bottom: Int
|
||||||
|
|
||||||
|
init(left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
self.left = left
|
||||||
|
self.top = top
|
||||||
|
self.right = right
|
||||||
|
self.bottom = bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(x: Int, y: Int) {
|
||||||
|
self.init(left: x, top: y, right: x, bottom: y)
|
||||||
|
}
|
||||||
|
|
||||||
|
var width: Int {
|
||||||
|
return right - left + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var height: Int {
|
||||||
|
return bottom - top + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
static func +(lhs: BlockRect, rhs: BlockRect) -> BlockRect {
|
||||||
|
return BlockRect(left: min(lhs.left, rhs.left), top: min(lhs.top, rhs.top), right: max(lhs.right, rhs.right), bottom: max(lhs.bottom, rhs.bottom))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func +=(lhs: BlockRect, rhs: BlockRect) -> BlockRect {
|
||||||
|
lhs.left = min(lhs.left, rhs.left)
|
||||||
|
lhs.top = min(lhs.top, rhs.top)
|
||||||
|
lhs.right = max(lhs.right, rhs.right)
|
||||||
|
lhs.bottom = max(lhs.bottom, rhs.bottom)
|
||||||
|
return lhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Represents a single block in the game.
|
// Represents a single block in the game.
|
||||||
class Block: Hashable, CustomStringConvertible {
|
class Block: Hashable, CustomStringConvertible {
|
||||||
// Constants
|
// Constants
|
||||||
|
|
|
@ -23,6 +23,7 @@ class GameScene: SKScene {
|
||||||
var lastTick: NSDate?
|
var lastTick: NSDate?
|
||||||
|
|
||||||
var scaleFactor: CGFloat!
|
var scaleFactor: CGFloat!
|
||||||
|
var holdControlRect: CGRect!
|
||||||
|
|
||||||
var textureCache = Dictionary<String, SKTexture>()
|
var textureCache = Dictionary<String, SKTexture>()
|
||||||
var textureAtlas: SKTextureAtlas?
|
var textureAtlas: SKTextureAtlas?
|
||||||
|
@ -62,6 +63,14 @@ class GameScene: SKScene {
|
||||||
shapeLayer.addChild(gameBoard)
|
shapeLayer.addChild(gameBoard)
|
||||||
gameLayer.addChild(shapeLayer)
|
gameLayer.addChild(shapeLayer)
|
||||||
|
|
||||||
|
// Calculate area of "hold" piece for hit-testing
|
||||||
|
let holdBBox = Shape.boundingBox(baseColumn: HoldColumn, baseRow: HoldRow)
|
||||||
|
let holdWidth = CGFloat(holdBBox.width) * BlockSize
|
||||||
|
let holdHeight = CGFloat(holdBBox.height) * BlockSize
|
||||||
|
let holdX = LayerPosition.x + (CGFloat(HoldColumn) * BlockSize)
|
||||||
|
let holdY = LayerPosition.y + (CGFloat(HoldRow) * BlockSize)
|
||||||
|
holdControlRect = CGRect(x: holdX * scaleFactor, y: holdY * scaleFactor, width: holdWidth * scaleFactor, height: holdHeight * scaleFactor)
|
||||||
|
|
||||||
// Set the theme music to play infinitely.
|
// 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)))
|
||||||
}
|
}
|
||||||
|
@ -163,9 +172,19 @@ class GameScene: SKScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate the removed lines "exploding" and the remaining blocks dropping into place.
|
// 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>>, fadingShapes: Array<Shape>, completion: @escaping () -> ()) {
|
||||||
var longestDuration: TimeInterval = 0
|
var longestDuration: TimeInterval = 0
|
||||||
|
|
||||||
|
// Animate the shapes fading out
|
||||||
|
for fadeShape in fadingShapes {
|
||||||
|
for block in fadeShape.blocks {
|
||||||
|
let sprite = block.sprite!
|
||||||
|
let fadeOutAction: SKAction = SKAction.fadeOut(withDuration: TimeInterval(0.1))
|
||||||
|
fadeOutAction.timingMode = .easeIn
|
||||||
|
sprite.run(SKAction.sequence([fadeOutAction, SKAction.removeFromParent()]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Animate the falling blocks dropping into place
|
// 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() {
|
||||||
|
@ -212,4 +231,28 @@ class GameScene: SKScene {
|
||||||
}
|
}
|
||||||
run(SKAction.sequence([SKAction.wait(forDuration: longestDuration), SKAction.run(completion)]))
|
run(SKAction.sequence([SKAction.wait(forDuration: longestDuration), SKAction.run(completion)]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animate the multiple-line clearance banner.
|
||||||
|
func animateClearanceBanner(bannerName: String?) {
|
||||||
|
guard let name = bannerName else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var texture = textureCache[name]
|
||||||
|
if texture == nil {
|
||||||
|
texture = textureAtlas?.textureNamed(name)
|
||||||
|
textureCache[name] = texture
|
||||||
|
}
|
||||||
|
let sprite = SKSpriteNode(texture: texture)
|
||||||
|
|
||||||
|
sprite.position = CGPoint(x: LayerPosition.x + (CGFloat(NumColumns / 2) * BlockSize), y: LayerPosition.y - (CGFloat(NumRows / 2) * BlockSize))
|
||||||
|
sprite.zPosition = 20
|
||||||
|
sprite.alpha = 0
|
||||||
|
shapeLayer.addChild(sprite)
|
||||||
|
|
||||||
|
// Animate a quick fade-in followed by a slow fade out
|
||||||
|
let fadeInAction: SKAction = SKAction.fadeAlpha(to: 0.9, duration: 0.05)
|
||||||
|
let fadeOutAction: SKAction = SKAction.fadeOut(withDuration: 1.5)
|
||||||
|
fadeOutAction.timingMode = .easeIn
|
||||||
|
sprite.run(SKAction.sequence([fadeInAction, fadeOutAction, SKAction.removeFromParent()]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import UIKit
|
||||||
import SpriteKit
|
import SpriteKit
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
|
|
||||||
|
let ClearanceAnimation = [nil, "double", "triple", "privyet"]
|
||||||
|
|
||||||
class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizerDelegate {
|
class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizerDelegate {
|
||||||
var scene: GameScene!
|
var scene: GameScene!
|
||||||
var privyet: Privyet!
|
var privyet: Privyet!
|
||||||
|
@ -31,7 +33,8 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
scene.scaleMode = .aspectFill
|
scene.scaleMode = .aspectFill
|
||||||
|
|
||||||
bucketRect = scene.rectForBucket()
|
bucketRect = scene.rectForBucket()
|
||||||
print("Computed bucket rectangle = \(bucketRect!)")
|
//print("Computed bucket rectangle = \(bucketRect!)")
|
||||||
|
//print("Computed hold rectangle = \(scene.holdControlRect!)")
|
||||||
|
|
||||||
scene.tick = didTick
|
scene.tick = didTick
|
||||||
|
|
||||||
|
@ -52,6 +55,8 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
let tapLoc = sender.location(in: view)
|
let tapLoc = sender.location(in: view)
|
||||||
if bucketRect.contains(tapLoc) {
|
if bucketRect.contains(tapLoc) {
|
||||||
privyet.rotateShape()
|
privyet.rotateShape()
|
||||||
|
} else if scene.holdControlRect.contains(tapLoc) {
|
||||||
|
privyet.holdShape()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +141,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
view.isUserInteractionEnabled = false
|
view.isUserInteractionEnabled = false
|
||||||
scene.stopTicking()
|
scene.stopTicking()
|
||||||
scene.playSound(sound: "Sounds/gameover.mp3")
|
scene.playSound(sound: "Sounds/gameover.mp3")
|
||||||
scene.animateCollapsingLines(linesToRemove: privyet.removeAllBlocks(), fallenBlocks: privyet.removeAllBlocks()) {
|
scene.animateCollapsingLines(linesToRemove: privyet.removeAllBlocks(), fallenBlocks: privyet.removeAllBlocks(), fadingShapes: privyet.removeAllShapes()) {
|
||||||
privyet.beginGame()
|
privyet.beginGame()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,20 +166,52 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
|
||||||
scene.playSound(sound: "Sounds/drop.mp3")
|
scene.playSound(sound: "Sounds/drop.mp3")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called when the current shape is put "on hold"
|
||||||
|
func gameShapePutOnHold(privyet: Privyet, firstHold: Bool) {
|
||||||
|
guard let held = privyet.heldShape else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scene.stopTicking()
|
||||||
|
view.isUserInteractionEnabled = false
|
||||||
|
scene.redrawShape(shape: held) {
|
||||||
|
if (firstHold) {
|
||||||
|
self.nextShape() // act like the previous shape settled
|
||||||
|
} else {
|
||||||
|
self.scene.redrawShape(shape: privyet.fallingShape!) {
|
||||||
|
self.scene.startTicking()
|
||||||
|
self.view.isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scene.playSound(sound: "Sounds/zap.mp3")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: called when a shape lands, used to keep from showing the "special" banner more than once
|
||||||
|
func internalDidLand(privyet: Privyet, showBanner: Bool) {
|
||||||
|
let removedLines = privyet.removeCompletedLines()
|
||||||
|
let linesCount = removedLines.linesRemoved.count
|
||||||
|
if linesCount > 0 {
|
||||||
|
self.scoreLabel.text = "\(privyet.score)"
|
||||||
|
if (showBanner) {
|
||||||
|
scene.animateClearanceBanner(bannerName: ClearanceAnimation[linesCount - 1])
|
||||||
|
}
|
||||||
|
scene.animateCollapsingLines(linesToRemove: removedLines.linesRemoved, fallenBlocks: removedLines.fallenBlocks, fadingShapes: []) {
|
||||||
|
self.internalDidLand(privyet: privyet, showBanner: (linesCount <= 1) && showBanner)
|
||||||
|
}
|
||||||
|
self.scene.playSound(sound: "Sounds/bomb.mp3")
|
||||||
|
if showBanner && linesCount == 4 {
|
||||||
|
self.scene.playSound(sound: "Sounds/privyet.mp3")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextShape()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Called when a shape "lands" on top of existing blocks or at the bottom of the bucket
|
// 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
|
||||||
let removedLines = privyet.removeCompletedLines()
|
internalDidLand(privyet: privyet, showBanner: true)
|
||||||
if removedLines.linesRemoved.count > 0 {
|
|
||||||
self.scoreLabel.text = "\(privyet.score)"
|
|
||||||
scene.animateCollapsingLines(linesToRemove: removedLines.linesRemoved, fallenBlocks: removedLines.fallenBlocks) {
|
|
||||||
self.gameShapeDidLand(privyet: privyet)
|
|
||||||
}
|
|
||||||
self.scene.playSound(sound: "Sounds/bomb.mp3")
|
|
||||||
} else {
|
|
||||||
nextShape()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when a shape moves on the screen
|
// Called when a shape moves on the screen
|
||||||
|
|
|
@ -17,8 +17,12 @@ let StartingRow = 0
|
||||||
let PreviewColumn = 12
|
let PreviewColumn = 12
|
||||||
let PreviewRow = 3
|
let PreviewRow = 3
|
||||||
|
|
||||||
let PointsPerLine = 10
|
let HoldColumn = 12
|
||||||
let LevelThreshold = 500
|
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 {
|
protocol PrivyetDelegate {
|
||||||
// Invoked when the current round of Privyet ends
|
// Invoked when the current round of Privyet ends
|
||||||
|
@ -36,6 +40,9 @@ protocol PrivyetDelegate {
|
||||||
// invoked when the falling shape has changed its location after being dropped
|
// invoked when the falling shape has changed its location after being dropped
|
||||||
func gameShapeDidDrop(privyet: Privyet)
|
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
|
// invoked when the game has reached a new level
|
||||||
func gameDidLevelUp(privyet: Privyet)
|
func gameDidLevelUp(privyet: Privyet)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +51,7 @@ class Privyet {
|
||||||
var bucket: Array2D<Block>
|
var bucket: Array2D<Block>
|
||||||
var nextShape: Shape?
|
var nextShape: Shape?
|
||||||
var fallingShape: Shape?
|
var fallingShape: Shape?
|
||||||
|
var heldShape: Shape?
|
||||||
var delegate: PrivyetDelegate?
|
var delegate: PrivyetDelegate?
|
||||||
|
|
||||||
var score = 0
|
var score = 0
|
||||||
|
@ -52,6 +60,7 @@ class Privyet {
|
||||||
init() {
|
init() {
|
||||||
fallingShape = nil
|
fallingShape = nil
|
||||||
nextShape = nil
|
nextShape = nil
|
||||||
|
heldShape = nil
|
||||||
bucket = Array2D<Block>(columns: NumColumns, rows: NumRows)
|
bucket = Array2D<Block>(columns: NumColumns, rows: NumRows)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +163,7 @@ class Privyet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance score and game level as appropriate
|
// Advance score and game level as appropriate
|
||||||
let pointsEarned = removedLines.count * PointsPerLine * level
|
let pointsEarned = PointsPerLine[removedLines.count - 1] * max(level / 2, 1)
|
||||||
score += pointsEarned
|
score += pointsEarned
|
||||||
if score >= level * LevelThreshold {
|
if score >= level * LevelThreshold {
|
||||||
level += 1
|
level += 1
|
||||||
|
@ -201,6 +210,21 @@ class Privyet {
|
||||||
return allBlocks
|
return allBlocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes all stored shapes from their slots (at end of game). Returns all the removed shapes.
|
||||||
|
func removeAllShapes() -> Array<Shape> {
|
||||||
|
var allShapes = Array<Shape>()
|
||||||
|
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.
|
// 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 {
|
||||||
|
@ -272,4 +296,22 @@ class Privyet {
|
||||||
}
|
}
|
||||||
delegate?.gameShapeDidMove(privyet: self)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,21 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final func getBoundingBoxForOrientation(baseColumn: Int, baseRow: Int, orientation: Orientation) -> BlockRect {
|
||||||
|
guard let offsets = blockRowColumnPositions[orientation] else {
|
||||||
|
return BlockRect(x: baseColumn, y: baseRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
let blockRects = offsets.map { (offset) -> BlockRect in
|
||||||
|
return BlockRect(x: baseColumn + offset.columnDiff, y: baseRow + offset.rowDiff)
|
||||||
|
}
|
||||||
|
return blockRects[0] + blockRects[1] + blockRects[2] + blockRects[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
final func getBoundingBox(baseColumn: Int, baseRow: Int) -> BlockRect {
|
||||||
|
return getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .Zero) + getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .Ninety) + getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .OneEighty) + getBoundingBoxForOrientation(baseColumn: baseColumn, baseRow: baseRow, orientation: .TwoSeventy)
|
||||||
|
}
|
||||||
|
|
||||||
// Rotate the blocks in this shape to a new orientation.
|
// 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 {
|
||||||
|
@ -193,6 +208,17 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
rotateBlocks(orientation: orientation)
|
rotateBlocks(orientation: orientation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final func exchangePositions(other: Shape) {
|
||||||
|
var tmp = other.column
|
||||||
|
other.column = self.column
|
||||||
|
self.column = tmp
|
||||||
|
tmp = other.row
|
||||||
|
other.row = self.row
|
||||||
|
self.row = tmp
|
||||||
|
other.rotateBlocks(orientation: other.orientation)
|
||||||
|
rotateBlocks(orientation: orientation)
|
||||||
|
}
|
||||||
|
|
||||||
// Generates a random shape.
|
// Generates a random shape.
|
||||||
final class func random(startingColumn: Int, startingRow: Int) -> Shape {
|
final class func random(startingColumn: Int, startingRow: Int) -> Shape {
|
||||||
switch Int.random(in: 0 ..< NumShapeTypes) {
|
switch Int.random(in: 0 ..< NumShapeTypes) {
|
||||||
|
@ -212,4 +238,14 @@ class Shape: Hashable, CustomStringConvertible {
|
||||||
return ZShape(column: startingColumn, row: startingRow)
|
return ZShape(column: startingColumn, row: startingRow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class func boundingBox(baseColumn: Int, baseRow: Int) -> BlockRect {
|
||||||
|
return SquareShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
TShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
LineShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
LShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
JShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
SShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow) +
|
||||||
|
ZShape(column: 0, row: 0).getBoundingBox(baseColumn: baseColumn, baseRow: baseRow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
Privyet/Sounds/privyet.mp3
Executable file
BIN
Privyet/Sounds/privyet.mp3
Executable file
Binary file not shown.
BIN
Privyet/Sounds/zap.mp3
Executable file
BIN
Privyet/Sounds/zap.mp3
Executable file
Binary file not shown.
BIN
Privyet/Sprites.atlas/double.png
Normal file
BIN
Privyet/Sprites.atlas/double.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
Privyet/Sprites.atlas/double@2x.png
Normal file
BIN
Privyet/Sprites.atlas/double@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
Privyet/Sprites.atlas/privyet.png
Normal file
BIN
Privyet/Sprites.atlas/privyet.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
BIN
Privyet/Sprites.atlas/privyet@2x.png
Normal file
BIN
Privyet/Sprites.atlas/privyet@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
BIN
Privyet/Sprites.atlas/triple.png
Normal file
BIN
Privyet/Sprites.atlas/triple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
Privyet/Sprites.atlas/triple@2x.png
Normal file
BIN
Privyet/Sprites.atlas/triple@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
Loading…
Reference in New Issue
Block a user