Skip to content

Instantly share code, notes, and snippets.

@jparishy
Created November 20, 2014 05:49
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jparishy/35689ff0a3d77d0e15b9 to your computer and use it in GitHub Desktop.
Save jparishy/35689ff0a3d77d0e15b9 to your computer and use it in GitHub Desktop.
//
// 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