Created
November 20, 2014 05:49
-
-
Save jparishy/35689ff0a3d77d0e15b9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.swift | |
// Tetris | |
// | |
// Created by Julius Parishy on 11/19/14. | |
// Copyright (c) 2014 Julius Parishy. All rights reserved. | |
// | |
import UIKit | |
let DefaultWorldWidth = 20 | |
let DefaultWorldHeight = 10 | |
let DefaultSquareWidth: CGFloat = 40.0 | |
let DefaultSquareHeight: CGFloat = 40.0 | |
struct World { | |
let width: Int | |
let height: Int | |
let squareWidth: CGFloat | |
let squareHeight: CGFloat | |
let currentTetromino: Tetromino? | |
let tetrominos: [Tetromino] | |
let delay: NSTimeInterval | |
init(width: Int = DefaultWorldWidth, height: Int = DefaultWorldHeight, squareWidth: CGFloat = DefaultSquareWidth, squareHeight: CGFloat = DefaultSquareHeight, currentTetromino: Tetromino? = nil, tetrominos: [Tetromino] = [Tetromino](), delay: NSTimeInterval = 1.0) { | |
self.width = width | |
self.height = height | |
self.squareWidth = squareWidth | |
self.squareHeight = squareHeight | |
self.currentTetromino = currentTetromino | |
self.tetrominos = tetrominos | |
self.delay = delay | |
} | |
func render() -> CGImageRef { | |
let size = CGSizeMake(self.squareWidth * CGFloat(self.width), self.squareHeight * CGFloat(self.height)) | |
UIGraphicsBeginImageContextWithOptions(size, true, UIScreen.mainScreen().scale) | |
let context = UIGraphicsGetCurrentContext() | |
CGContextSetFillColorWithColor(context, UIColor.lightGrayColor().CGColor) | |
CGContextFillRect(context, CGRectMake(0.0, 0.0, size.width, size.height)) | |
let renderTetromino: (Tetromino) -> Void = { | |
tetromino in | |
CGContextSetFillColorWithColor(context, tetromino.color()) | |
for index in tetromino.globalIndices { | |
let x: CGFloat = CGFloat(index.x) * self.squareWidth | |
let y: CGFloat = CGFloat(index.y) * self.squareHeight | |
let rect = CGRectMake(x, y, self.squareWidth, self.squareHeight) | |
CGContextFillRect(context, rect) | |
} | |
} | |
if let ct = self.currentTetromino { | |
renderTetromino(ct) | |
} | |
for tetromino in self.tetrominos { | |
renderTetromino(tetromino) | |
} | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image.CGImage | |
} | |
func withNewTetromino(tetromino: Tetromino) -> World { | |
let tetrominos = [ tetromino ] | |
return World(width: self.width, height: self.height, squareWidth: self.squareWidth, squareHeight: self.squareHeight, currentTetromino: self.currentTetromino, tetrominos: tetrominos) | |
} | |
func update(rotateLeft: Bool, rotateRight: Bool, moveLeft: Bool, moveRight: Bool) -> World { | |
if let currentTetromino = self.currentTetromino?.next(self, moveLeft: moveLeft, moveRight: moveRight, rotateLeft: rotateLeft, rotateRight: rotateRight) { | |
return World(width: self.width, height: self.height, squareWidth: self.squareWidth, squareHeight: self.squareHeight, currentTetromino: currentTetromino, tetrominos: self.tetrominos) | |
} else { | |
if let currentTetromino = self.currentTetromino { | |
let nextTetromino = Tetromino.random(self) | |
let tetrominos = self.tetrominos + [ currentTetromino ] | |
return World(width: self.width, height: self.height, squareWidth: self.squareWidth, squareHeight: self.squareHeight, currentTetromino: nil, tetrominos: tetrominos) | |
} else { | |
let nextTetromino = Tetromino.random(self) | |
return World(width: self.width, height: self.height, squareWidth: self.squareWidth, squareHeight: self.squareHeight, currentTetromino: nextTetromino, tetrominos: self.tetrominos) | |
} | |
} | |
} | |
} | |
struct Index { | |
let x: Int | |
let y: Int | |
} | |
func rotate(v4s: [Index], m: [[Int]]) -> [Index] { | |
return v4s.map() { | |
v4 in | |
/* | |
[ 0, -1 ] [ 1 ] [ 0*1 + -1*1] = [ -1 ] | |
[ 1, 0 ] * [ 1 ] = [ 1*1 + 0*1] = [ 1 ] | |
*/ | |
let x = (v4.x * m[0][0]) + (v4.y * m[0][1]) | |
let y = (v4.x * m[1][0]) + (v4.y * m[1][1]) | |
return Index(x: x, y: y) | |
} | |
} | |
struct Tetromino { | |
let rootIndex: Index | |
let indices: [Index] | |
let type: Type | |
enum Type { | |
case L1 | |
case L2 | |
case S1 | |
case S2 | |
case T | |
case Square | |
case Line | |
} | |
func color() -> CGColor { | |
switch self.type { | |
case .L1: | |
return UIColor.redColor().CGColor | |
case .L2: | |
return UIColor.purpleColor().CGColor | |
case .S1: | |
return UIColor.yellowColor().CGColor | |
case .S2: | |
return UIColor.orangeColor().CGColor | |
case .T: | |
return UIColor.magentaColor().CGColor | |
case .Square: | |
return UIColor.greenColor().CGColor | |
case .Line: | |
return UIColor.blueColor().CGColor | |
} | |
} | |
func transformed(moveDown: Bool, moveLeft: Bool, moveRight: Bool, rotateLeft: Bool, rotateRight: Bool) -> Tetromino { | |
typealias TransformTetromino = (Tetromino) -> (Tetromino) | |
let moveDownFunc: TransformTetromino = { | |
t in | |
let rootIndex = Index(x: t.rootIndex.x, y: t.rootIndex.y + 1) | |
return Tetromino(rootIndex: rootIndex, indices: t.indices, type: t.type) | |
} | |
let moveLeftFunc: TransformTetromino = { | |
t in | |
let rootIndex = Index(x: t.rootIndex.x - 1, y: t.rootIndex.y) | |
return Tetromino(rootIndex: rootIndex, indices: t.indices, type: t.type) | |
} | |
let moveRightFunc: TransformTetromino = { | |
t in | |
let rootIndex = Index(x: t.rootIndex.x + 1, y: t.rootIndex.y) | |
return Tetromino(rootIndex: rootIndex, indices: t.indices, type: t.type) | |
} | |
let rotateLeftFunc: TransformTetromino = { | |
t in | |
let m = [ [0, -1], [1, 0] ] | |
let indices: [Index] = rotate(t.indices, m) | |
return Tetromino(rootIndex: t.rootIndex, indices: indices, type: t.type) | |
} | |
let rotateRightFunc: TransformTetromino = { | |
t in | |
let rotates = [TransformTetromino](count: 3, repeatedValue: rotateLeftFunc) | |
return rotates.reduce(t) { return $1($0) } | |
} | |
let funcs = [ moveDownFunc, moveLeftFunc, moveRightFunc, rotateLeftFunc, rotateRightFunc ] | |
let ops = [ (moveDown, 0), (moveLeft, 1), (moveRight, 2), (rotateLeft, 3), (rotateRight, 4) ] | |
return ops.reduce(self) { | |
(t, op) in | |
if op.0 { | |
let f: (Tetromino) -> (Tetromino) = funcs[op.1] | |
return f(t) | |
} else { | |
return t | |
} | |
} | |
} | |
func valid(world: World) -> Bool { | |
let tetrominos = world.tetrominos | |
let first = self.globalIndices[0].y | |
let maxY: Int = self.globalIndices.reduce(first, combine: { return ($1.y > $0 ? $1.y : $0) }) | |
let exists: ([Tetromino], Index) -> Bool = { | |
(ts, index) in | |
let arrayOfIndices: [[Index]] = ts.map() { return $0.globalIndices } | |
let all = arrayOfIndices.reduce([Index]()) { return $0 + $1 } | |
return all.reduce(false) { (exists, i) in return (exists || (i.x == index.x && i.y == index.y)) } | |
} | |
for index in self.globalIndices { | |
if exists(tetrominos, Index(x: index.x, y: maxY)) { | |
return false | |
} else if maxY == world.height { | |
return false | |
} | |
} | |
return true | |
} | |
func next(world: World, moveLeft: Bool, moveRight: Bool, rotateLeft: Bool, rotateRight: Bool) -> Tetromino? { | |
let next = self.transformed(true, moveLeft: moveLeft, moveRight: moveRight, rotateLeft: rotateLeft, rotateRight: rotateRight) | |
if next.valid(world) == false { | |
return nil | |
} | |
return next | |
} | |
var globalIndices: [Index] { | |
return self.indices.map() { | |
index in | |
return Index(x: self.rootIndex.x + index.x, y: self.rootIndex.y + index.y) | |
} | |
} | |
static func randomType() -> Type { | |
let types: [Type] = [ .L1, .L2, .S1, .S2, .Square, .Line ] | |
let number = Int(arc4random_uniform(UInt32(types.count))) | |
return types[number] | |
} | |
static func baseIndicesForType(type: Type) -> [Index] { | |
let x = 0 | |
switch type { | |
case .L1: | |
return [ | |
Index(x: x, y: 0), | |
Index(x: x + 1, y: 0), | |
Index(x: x, y: 1), | |
Index(x: x, y: 2), | |
] | |
case .L2: | |
return [ | |
Index(x: x, y: 0), | |
Index(x: x, y: 1), | |
Index(x: x, y: 2), | |
Index(x: x + 1, y: 2), | |
] | |
case .S1: | |
return [ | |
Index(x: x, y: 0), | |
Index(x: x + 1, y: 0), | |
Index(x: x + 1, y: 1), | |
Index(x: x + 2, y: 1), | |
] | |
case .S2: | |
return [ | |
Index(x: x, y: 1), | |
Index(x: x + 1, y: 1), | |
Index(x: x + 1, y: 0), | |
Index(x: x + 2, y: 0), | |
] | |
case .T: | |
return [ | |
Index(x: x, y: 0), | |
Index(x: x + 1, y: 0), | |
Index(x: x + 2, y: 0), | |
Index(x: x + 1, y: 1), | |
] | |
case .Square: | |
return [ | |
Index(x: x, y: 0), | |
Index(x: x + 1, y: 0), | |
Index(x: x, y: 1), | |
Index(x: x + 1, y: 1), | |
] | |
case .Line: | |
return [ | |
Index(x: x, y: 0), | |
Index(x: x + 1, y: 0), | |
Index(x: x + 2, y: 0), | |
Index(x: x + 3, y: 0), | |
] | |
} | |
} | |
static func random(world: World) -> Tetromino { | |
let type = self.randomType() | |
let indices = self.baseIndicesForType(type) | |
let randomX = Int(arc4random_uniform(UInt32(world.width))) | |
let rootIndex = Index(x: randomX, y: 0) | |
return Tetromino(rootIndex: rootIndex, indices: indices, type: type) | |
} | |
} | |
class WorldWrapper : NSObject { | |
let world: World | |
init(world: World) { | |
self.world = world | |
super.init() | |
} | |
} | |
class ViewController: UIViewController { | |
var moveLeft: Bool = false | |
var moveRight: Bool = false | |
var rotateLeft: Bool = false | |
var rotateRight: Bool = false | |
var gameView = UIView() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
self.gameView.frame = CGRectMake(0, 0, CGFloat(DefaultWorldWidth)*DefaultSquareWidth, CGFloat(DefaultWorldHeight)*DefaultSquareHeight) | |
self.gameView.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)) | |
self.view.addSubview(self.gameView) | |
let world = World() | |
tick(nil) | |
} | |
func tick(timer: NSTimer?) { | |
let currentWorld: World = (timer?.userInfo as? WorldWrapper)?.world ?? World() | |
let nextWorld = currentWorld.update(self.rotateLeft, rotateRight: self.rotateRight, moveLeft: self.moveLeft, moveRight: self.moveRight) | |
let next = WorldWrapper(world: nextWorld) | |
self.gameView.layer.contents = nextWorld.render() | |
NSTimer.scheduledTimerWithTimeInterval(currentWorld.delay, target: self, selector: "tick:", userInfo: next, repeats: false) | |
self.moveLeft = false | |
self.moveRight = false | |
self.rotateLeft = false | |
self.rotateRight = false | |
} | |
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) { | |
super.touchesEnded(touches, withEvent: event) | |
let location = touches.anyObject()?.locationInView(self.view) | |
for touch in touches { | |
let t = touch as UITouch | |
let l = t.locationInView(self.view) | |
if l.y < CGRectGetMidY(self.view.frame) { | |
self.rotateLeft = l.x < CGRectGetMidX(self.view.frame) | |
self.rotateRight = l.x > CGRectGetMidX(self.view.frame) | |
} else { | |
self.moveLeft = l.x < CGRectGetMidX(self.view.frame) | |
self.moveRight = l.x > CGRectGetMidX(self.view.frame) | |
} | |
} | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
// Dispose of any resources that can be recreated. | |
} | |
override func prefersStatusBarHidden() -> Bool { | |
return true | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment