Compare commits

...

2 Commits

17 changed files with 289 additions and 51 deletions

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "panelback.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "panelback@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -15,54 +15,58 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rZI-CB-OQn">
<rect key="frame" x="310" y="315" width="84" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="whitebg" translatesAutoresizingMaskIntoConstraints="NO" id="2kD-q4-jVs">
<rect key="frame" x="0.0" y="0.0" width="84" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SCORE" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="InR-1s-zXh">
<rect key="frame" x="7" y="20" width="70" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="15"/>
<color key="textColor" red="0.13333333333333333" green="0.6470588235294118" blue="0.82745098039215681" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="999" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Zja-c9-zBQ">
<rect key="frame" x="0.0" y="45" width="84" height="39"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Avenir-Light" family="Avenir" pointSize="35"/>
<color key="textColor" red="0.1333333333" green="0.64705882349999999" blue="0.82745098039999998" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</view>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5g3-e3-yLO">
<rect key="frame" x="310" y="482" width="84" height="100"/>
<rect key="frame" x="310" y="538" width="84" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="whitebg" translatesAutoresizingMaskIntoConstraints="NO" id="nRI-Ry-bjG">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="panelback" translatesAutoresizingMaskIntoConstraints="NO" id="nRI-Ry-bjG">
<rect key="frame" x="0.0" y="0.0" width="84" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LEVEL" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Boc-bL-hFe">
<rect key="frame" x="7" y="20" width="70" height="21"/>
<rect key="frame" x="7" y="51" width="70" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="15"/>
<color key="textColor" red="0.54509803921568623" green="0.45490196078431372" blue="0.76078431372549016" alpha="1" colorSpace="calibratedRGB"/>
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="999" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="G8x-d8-J4Q">
<rect key="frame" x="0.0" y="45" width="84" height="39"/>
<rect key="frame" x="0.0" y="70" width="84" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Avenir-Light" family="Avenir" pointSize="35"/>
<color key="textColor" red="0.54509803921568623" green="0.45490196078431372" blue="0.76078431372549016" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="20"/>
<color key="textColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SCORE" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="InR-1s-zXh">
<rect key="frame" x="7" y="4" width="70" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Avenir-Black" 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="999" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Zja-c9-zBQ">
<rect key="frame" x="0.0" y="14" width="84" height="39"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="Avenir-Black" family="Avenir" pointSize="20"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</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>
<color key="backgroundColor" systemColor="systemPurpleColor" red="0.68627450980000004" green="0.32156862749999998" blue="0.87058823529999996" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
@ -99,10 +103,10 @@
</connections>
</swipeGestureRecognizer>
</objects>
<point key="canvasLocation" x="-64" y="86"/>
<point key="canvasLocation" x="-65.217391304347828" y="85.714285714285708"/>
</scene>
</scenes>
<resources>
<image name="whitebg" width="84" height="100"/>
<image name="panelback" width="84" height="100"/>
</resources>
</document>

View File

@ -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.
class Block: Hashable, CustomStringConvertible {
// Constants

View File

@ -22,6 +22,9 @@ class GameScene: SKScene {
var tickLengthMillis = TickLengthLevelOne
var lastTick: NSDate?
var scaleFactor: CGFloat!
var holdControlRect: CGRect!
var textureCache = Dictionary<String, SKTexture>()
var textureAtlas: SKTextureAtlas?
@ -41,7 +44,7 @@ class GameScene: SKScene {
//print("Background is sized \(background.size)")
// Scale computations
let scaleFactor = min(size.width / background.size.width, size.height / background.size.height)
scaleFactor = CGFloat(min(size.width / background.size.width, size.height / background.size.height))
background.position = CGPoint(x: 0, y: 0)
background.anchorPoint = CGPoint(x: 0, y: 1.0)
@ -60,6 +63,14 @@ class GameScene: SKScene {
shapeLayer.addChild(gameBoard)
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.
run(SKAction.repeatForever(SKAction.playSoundFileNamed("Sounds/theme.mp3", waitForCompletion: true)))
}
@ -98,6 +109,12 @@ class GameScene: SKScene {
return CGPoint(x: x, y: y)
}
func rectForBucket() -> CGRect {
let width = CGFloat(NumColumns) * BlockSize
let height = CGFloat(NumRows) * BlockSize
return CGRect(x: (LayerPosition.x + (BlockSize / 2)) * scaleFactor, y: (LayerPosition.y + (BlockSize / 2)) * scaleFactor, width: width * scaleFactor, height: height * scaleFactor)
}
// Add the "preview" shape to the current scene. This also "rezzes in" a shape's block sprites.
func addPreviewShapeToScene(shape: Shape, completion: @escaping () -> ()) {
for block in shape.blocks {
@ -155,9 +172,19 @@ 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>>, fadingShapes: Array<Shape>, completion: @escaping () -> ()) {
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
for (columnIdx, column) in fallenBlocks.enumerated() {
for (blockIdx, block) in column.enumerated() {
@ -204,4 +231,28 @@ class GameScene: SKScene {
}
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()]))
}
}

View File

@ -10,9 +10,12 @@ import UIKit
import SpriteKit
import GameplayKit
let ClearanceAnimation = [nil, "double", "triple", "privyet"]
class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizerDelegate {
var scene: GameScene!
var privyet: Privyet!
var bucketRect: CGRect!
var panPointReference: CGPoint?
@IBOutlet weak var scoreLabel: UILabel!
@ -29,6 +32,10 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
scene = GameScene(size: skView.bounds.size)
scene.scaleMode = .aspectFill
bucketRect = scene.rectForBucket()
//print("Computed bucket rectangle = \(bucketRect!)")
//print("Computed hold rectangle = \(scene.holdControlRect!)")
scene.tick = didTick
privyet = Privyet()
@ -45,7 +52,12 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
// Called when the user taps the screen
@IBAction func didTap(_ sender: UITapGestureRecognizer) {
privyet.rotateShape()
let tapLoc = sender.location(in: view)
if bucketRect.contains(tapLoc) {
privyet.rotateShape()
} else if scene.holdControlRect.contains(tapLoc) {
privyet.holdShape()
}
}
// Called when the user slides their finger across the screen
@ -129,7 +141,7 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
view.isUserInteractionEnabled = false
scene.stopTicking()
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()
}
}
@ -154,20 +166,52 @@ class GameViewController: UIViewController, PrivyetDelegate, UIGestureRecognizer
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
func gameShapeDidLand(privyet: Privyet) {
scene.stopTicking()
self.view.isUserInteractionEnabled = false
let removedLines = privyet.removeCompletedLines()
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()
}
internalDidLand(privyet: privyet, showBanner: true)
}
// Called when a shape moves on the screen

View File

@ -15,10 +15,14 @@ let StartingColumn = 4
let StartingRow = 0
let PreviewColumn = 12
let PreviewRow = 1
let PreviewRow = 3
let PointsPerLine = 10
let LevelThreshold = 500
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
@ -36,6 +40,9 @@ protocol PrivyetDelegate {
// 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)
}
@ -44,6 +51,7 @@ class Privyet {
var bucket: Array2D<Block>
var nextShape: Shape?
var fallingShape: Shape?
var heldShape: Shape?
var delegate: PrivyetDelegate?
var score = 0
@ -52,6 +60,7 @@ class Privyet {
init() {
fallingShape = nil
nextShape = nil
heldShape = nil
bucket = Array2D<Block>(columns: NumColumns, rows: NumRows)
}
@ -154,7 +163,7 @@ class Privyet {
}
// 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
if score >= level * LevelThreshold {
level += 1
@ -201,6 +210,21 @@ class Privyet {
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.
func dropShape() {
guard let shape = fallingShape else {
@ -272,4 +296,22 @@ class Privyet {
}
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)
}
}

View File

@ -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.
final func rotateBlocks(orientation: Orientation) {
guard let blockRowColumnTranslation: Array<(columnDiff: Int, rowDiff: Int)> = blockRowColumnPositions[orientation] else {
@ -193,6 +208,17 @@ class Shape: Hashable, CustomStringConvertible {
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.
final class func random(startingColumn: Int, startingRow: Int) -> Shape {
switch Int.random(in: 0 ..< NumShapeTypes) {
@ -212,4 +238,14 @@ class Shape: Hashable, CustomStringConvertible {
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

Binary file not shown.

BIN
Privyet/Sounds/zap.mp3 Executable file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB