SlideShare a Scribd company logo
Swift
(via swift-2048)
Austin Zheng
Who am I?
• My name is Austin Zheng
• I work at LinkedIn as an iOS developer
• Before that I wrote firmware for an embedded
systems startup in Redwood City
What Is 2048?
• iOS clone of web game “2048” by
Gabriele Cirulli
• In turn based on iOS game “Threes”
by Asher Vollmer
• Slide in a direction to combine like
tiles
• 2+2 = 4, 4+4 = 8
• Make a ‘2048’ tile, or fill up the
board and lose
(2048 demo)
Architecture
(very high level)
View
Game Logic
(Model)
View Controller
Actions
View Commands
(forwarded to view)
Backing
Store
Backing Store
Backing Store (Old)
@interface F3HGameModel ()
@property (nonatomic, strong) NSMutableArray *gameState;
@property (nonatomic) NSUInteger dimension;
//...
@end
Backing Store
struct SquareGameboard<T> {
var boardArray: [T]; let dimension: Int
!
init(dimension d: Int, initialValue: T) {
dimension = d
boardArray = [T](count:d*d, repeatedValue:initialValue)
}
!
subscript(row: Int, col: Int) -> T {
get {
return boardArray[row*dimension + col]
}
set {
boardArray[row*dimension + col] = newValue
}
}
}
Structs
• Like classes, they can have properties and
methods.
• Unlike classes, structs can’t inherit from other
structs.
• Unlike classes, structs are value types
Generics
struct SquareGameboard<T> {
let dimension: Int
var boardArray: [T]
!
init(dimension d: Int, initialValue: T) {
dimension = d
boardArray = [T](count:d*d, repeatedValue:initialValue)
}
}
Subscripts
subscript(row: Int, col: Int) -> T {
get {
return boardArray[row*dimension + col]
}
set {
boardArray[row*dimension + col] = newValue
}
}
gameboard[x, y] = TileObject.Empty
!
let someTile = gameboard[x, y]
What, exactly, are we
storing?
TileModel (Old)
// This is an Objective-C class which represents a tile
@interface F3HTileModel : NSObject
@property (nonatomic) BOOL empty;
@property (nonatomic) NSUInteger value;
@end
TileObject
enum TileObject {
case Empty
case Tile(value: Int)
}
!
let anEmptyTile = TileObject.Empty
let eightTile = TileObject.Tile(value: 8)
let anotherTile = TileObject.Tile(value: 2)
Swift Enums
• They can do everything C or Objective-C enums
can…
• They can also do everything structs in Swift can
do - methods and properties…
• Optionally, you can have an enum value store
associated data. (variants, tagged unions, sum
types, case classes)
Game Logic
22
2
22
2
2
22
2
22
2
2
22
2
22
2
2
func performMove(direction: MoveDirection) -> Bool {
func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] {
var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0))
for i in 0..<self.dimension {
switch direction {
case .Up: buffer[i] = (i, currentRowOrColumn)
case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn)
case .Left: buffer[i] = (currentRowOrColumn, i)
case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1)
}
}
return buffer
}
!
for i in 0..<dimension {
let coords = coordinateGenerator(i)
let tiles = coords.map() { (c: (Int, Int)) -> TileObject in
let (x, y) = c
return self.gameboard[x, y]
}
let orders = merge(tiles)
// ...
}
// ...
}
func performMove(direction: MoveDirection) -> Bool {
func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] {
var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0))
for i in 0..<self.dimension {
switch direction {
case .Up: buffer[i] = (i, currentRowOrColumn)
case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn)
case .Left: buffer[i] = (currentRowOrColumn, i)
case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1)
}
}
return buffer
}
!
for i in 0..<dimension {
let coords = coordinateGenerator(i)
let tiles = coords.map() { (c: (Int, Int)) -> TileObject in
let (x, y) = c
return self.gameboard[x, y]
}
let orders = merge(tiles)
// ...
}
// ...
}
func performMove(direction: MoveDirection) -> Bool {
func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] {
var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0))
for i in 0..<self.dimension {
switch direction {
case .Up: buffer[i] = (i, currentRowOrColumn)
case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn)
case .Left: buffer[i] = (currentRowOrColumn, i)
case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1)
}
}
return buffer
}
!
for i in 0..<dimension {
let coords = coordinateGenerator(i)
let tiles = coords.map() { (c: (Int, Int)) -> TileObject in
let (x, y) = c
return self.gameboard[x, y]
}
let orders = merge(tiles)
// ...
}
// ...
}
func performMove(direction: MoveDirection) -> Bool {
func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] {
var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0))
for i in 0..<self.dimension {
switch direction {
case .Up: buffer[i] = (i, currentRowOrColumn)
case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn)
case .Left: buffer[i] = (currentRowOrColumn, i)
case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1)
}
}
return buffer
}
!
for i in 0..<dimension {
let coords = coordinateGenerator(i)
let tiles = coords.map() { (c: (Int, Int)) -> TileObject in
let (x, y) = c
return self.gameboard[x, y]
}
let orders = merge(tiles)
// ...
}
// ...
}
func performMove(direction: MoveDirection) -> Bool {
func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] {
var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0))
for i in 0..<self.dimension {
switch direction {
case .Up: buffer[i] = (i, currentRowOrColumn)
case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn)
case .Left: buffer[i] = (currentRowOrColumn, i)
case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1)
}
}
return buffer
}
!
for i in 0..<dimension {
let coords = coordinateGenerator(i)
let tiles = coords.map() { (c: (Int, Int)) -> TileObject in
let (x, y) = c
return self.gameboard[x, y]
}
let orders = merge(tiles)
// ...
}
// ...
}
func performMove(direction: MoveDirection) -> Bool {
func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] {
var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0))
for i in 0..<self.dimension {
switch direction {
case .Up: buffer[i] = (i, currentRowOrColumn)
case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn)
case .Left: buffer[i] = (currentRowOrColumn, i)
case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1)
}
}
return buffer
}
!
for i in 0..<dimension {
let coords = coordinateGenerator(i)
let tiles = coords.map() { (c: (Int, Int)) -> TileObject in
let (x, y) = c
return self.gameboard[x, y]
}
let orders = merge(tiles)
// ...
}
// ...
}
A single row…
• Condense the row to remove any space - some
might move, some might stay still
• Collapse two adjacent tiles of equal value into a
single tile with double the value
• Convert our intermediate representation into
‘Actions’ that the view layer can easily act upon
Tracking changes?
• We want to know when a tile is moved, when it
stays still, when it’s combined
• Let’s posit an ActionToken
• An ActionToken lives in an array. Its position in the
array is the final position of the tile it represents
• An ActionToken also tracks the state of the tile or
tiles that undertook the action it describes
ActionToken (old)
typedef enum {
F3HMergeTileModeNoAction = 0,
F3HMergeTileModeMove,
F3HMergeTileModeSingleCombine,
F3HMergeTileModeDoubleCombine
} F3HMergeTileMode;
!
@interface F3HMergeTile : NSObject
@property (nonatomic) F3HMergeTileMode mode;
@property (nonatomic) NSInteger originalIndexA;
@property (nonatomic) NSInteger originalIndexB;
@property (nonatomic) NSInteger value;
!
+ (instancetype)mergeTile;
@end
ActionToken
enum ActionToken {
case NoAction(source: Int, value: Int)
case Move(source: Int, value: Int)
case SingleCombine(source: Int, value: Int)
case DoubleCombine(source: Int, second: Int, value: Int)
}
Game Logic
• Condense - remove spaces between tiles
42
2 4
func condense(group: [TileObject]) -> [ActionToken] {
var tokenBuffer = [ActionToken]()
for (idx, tile) in enumerate(group) {
switch tile {
case let .Tile(value) where tokenBuffer.count == idx:
tokenBuffer.append(ActionToken.NoAction(source: idx, value: value))
case let .Tile(value):
tokenBuffer.append(ActionToken.Move(source: idx, value: value))
default:
break
}
}
return tokenBuffer;
}
func condense(group: [TileObject]) -> [ActionToken] {
var tokenBuffer = [ActionToken]()
for (idx, tile) in enumerate(group) {
switch tile {
case let .Tile(value) where tokenBuffer.count == idx:
tokenBuffer.append(ActionToken.NoAction(source: idx, value: value))
case let .Tile(value):
tokenBuffer.append(ActionToken.Move(source: idx, value: value))
default:
break
}
}
return tokenBuffer;
}
Swift ‘switch’
• At its most basic, works like the C or Objective-C
switch statement
• But it can do far more!
• One example: take the values out of an enum
• Cases can be qualified by ‘where’ clauses
• Has to be comprehensive, and no default
fallthrough
Game Logic
• Collapse - perform necessary merges
4 84
4 2 82
func collapse(group: [ActionToken]) -> [ActionToken] {
func quiescentTileStillQuiescent(inputPosition: Int, outputLength: Int, originalPosition: Int) -> Bool {
return (inputPosition == outputLength) && (originalPosition == inputPosition)
}
!
var tokenBuffer = [ActionToken]()
var skipNext = false
for (idx, token) in enumerate(group) {
if skipNext {
skipNext = false
continue
}
switch token {
case .SingleCombine:
assert(false, "Cannot have single combine token in input")
case .DoubleCombine:
assert(false, "Cannot have double combine token in input")
case let .NoAction(s, v)
where (idx < group.count-1
&& v == group[idx+1].getValue()
&& quiescentTileStillQuiescent(idx, tokenBuffer.count, s)):
let nv = v + group[idx+1].getValue()
skipNext = true
tokenBuffer.append(ActionToken.SingleCombine(source: next.getSource(), value: nv))
case let t where (idx < group.count-1 && t.getValue() == group[idx+1].getValue()):
let next = group[idx+1]
let nv = t.getValue() + group[idx+1].getValue()
skipNext = true
tokenBuffer.append(ActionToken.DoubleCombine(source: t.getSource(), second: next.getSource(), value: nv))
case let .NoAction(s, v) where !quiescentTileStillQuiescent(idx, tokenBuffer.count, s):
tokenBuffer.append(ActionToken.Move(source: s, value: v))
case let .NoAction(s, v):
tokenBuffer.append(ActionToken.NoAction(source: s, value: v))
case let .Move(s, v):
tokenBuffer.append(ActionToken.Move(source: s, value: v))
default:
break
}
}
return tokenBuffer
}
Game Logic
• Convert - create ‘move orders’ for the view
enum MoveOrder {
case SingleMoveOrder(source: Int,
destination: Int,
value: Int,
wasMerge: Bool)
case DoubleMoveOrder(firstSource: Int,
secondSource: Int,
destination: Int,
value: Int)
}
func convert(group: [ActionToken]) -> [MoveOrder] {
var moveBuffer = [MoveOrder]()
for (idx, t) in enumerate(group) {
switch t {
case let .Move(s, v):
moveBuffer.append(MoveOrder.SingleMoveOrder(source: s,
destination: idx, value: v, wasMerge: false))
case let .SingleCombine(s, v):
moveBuffer.append(MoveOrder.SingleMoveOrder(source: s,
destination: idx, value: v, wasMerge: true))
case let .DoubleCombine(s1, s2, v):
moveBuffer.append(MoveOrder.DoubleMoveOrder(firstSource: s1,
secondSource: s2, destination: idx, value: v))
default:
break
}
}
return moveBuffer
}
Views
func insertTile(pos: (Int, Int), value: Int) {
let (row, col) = pos
let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding)
let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding)
let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0
let tile = TileView(position: CGPointMake(x, y),
width: tileWidth, value: value, radius: r, delegate: provider)
tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale,
tilePopStartScale))
!
addSubview(tile)
bringSubviewToFront(tile)
UIView.animateWithDuration(tileExpandTime,
delay: tilePopDelay,
options: UIViewAnimationOptions.TransitionNone,
animations: { () -> Void in
// Make the tile 'pop'
tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale,
self.tilePopMaxScale))
},
completion: { (finished: Bool) -> Void in
// Shrink the tile after it 'pops'
UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in
tile.layer.setAffineTransform(CGAffineTransformIdentity)
})
})
}
func insertTile(pos: (Int, Int), value: Int) {
let (row, col) = pos
let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding)
let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding)
let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0
let tile = TileView(position: CGPointMake(x, y),
width: tileWidth, value: value, radius: r, delegate: provider)
tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale,
tilePopStartScale))
!
addSubview(tile)
bringSubviewToFront(tile)
UIView.animateWithDuration(tileExpandTime,
delay: tilePopDelay,
options: UIViewAnimationOptions.TransitionNone,
animations: { () -> Void in
// Make the tile 'pop'
tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale,
self.tilePopMaxScale))
},
completion: { (finished: Bool) -> Void in
// Shrink the tile after it 'pops'
UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in
tile.layer.setAffineTransform(CGAffineTransformIdentity)
})
})
}
func insertTile(pos: (Int, Int), value: Int) {
let (row, col) = pos
let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding)
let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding)
let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0
let tile = TileView(position: CGPointMake(x, y),
width: tileWidth, value: value, radius: r, delegate: provider)
tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale,
tilePopStartScale))
!
addSubview(tile)
bringSubviewToFront(tile)
UIView.animateWithDuration(tileExpandTime,
delay: tilePopDelay,
options: UIViewAnimationOptions.TransitionNone,
animations: { () -> Void in
// Make the tile 'pop'
tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale,
self.tilePopMaxScale))
},
completion: { (finished: Bool) -> Void in
// Shrink the tile after it 'pops'
UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in
tile.layer.setAffineTransform(CGAffineTransformIdentity)
})
})
}
func insertTile(pos: (Int, Int), value: Int) {
let (row, col) = pos
let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding)
let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding)
let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0
let tile = TileView(position: CGPointMake(x, y),
width: tileWidth, value: value, radius: r, delegate: provider)
tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale,
tilePopStartScale))
!
addSubview(tile)
bringSubviewToFront(tile)
UIView.animateWithDuration(tileExpandTime,
delay: tilePopDelay,
options: UIViewAnimationOptions.TransitionNone,
animations: { () -> Void in
// Make the tile 'pop'
tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale,
self.tilePopMaxScale))
},
completion: { (finished: Bool) -> Void in
// Shrink the tile after it 'pops'
UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in
tile.layer.setAffineTransform(CGAffineTransformIdentity)
})
})
}
Selectors
UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(upButtonTapped)];
!
upSwipe.numberOfTouchesRequired = 1;
upSwipe.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:upSwipe];
!
!
!
- (void)upButtonTapped {
[self.model performMoveInDirection:F3HMoveDirectionUp
completionBlock:^(BOOL changed) {
if (changed) [self followUp]; }];
}
Selectors
UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(upButtonTapped)];
!
upSwipe.numberOfTouchesRequired = 1;
upSwipe.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:upSwipe];
!
!
!
- (void)upButtonTapped {
[self.model performMoveInDirection:F3HMoveDirectionUp
completionBlock:^(BOOL changed) {
if (changed) [self followUp]; }];
}
Selectors
UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(upButtonTapped)];
!
upSwipe.numberOfTouchesRequired = 1;
upSwipe.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:upSwipe];
!
!
!
- (void)upButtonTapped {
[self.model performMoveInDirection:F3HMoveDirectionUp
completionBlock:^(BOOL changed) {
if (changed) [self followUp]; }];
}
Selectors
UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:@selector(upButtonTapped)];
!
upSwipe.numberOfTouchesRequired = 1;
upSwipe.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:upSwipe];
!
!
!
- (void)upButtonTapped {
[self.model performMoveInDirection:F3HMoveDirectionUp
completionBlock:^(BOOL changed) {
if (changed) [self followUp]; }];
}
Selectors in Swift
let upSwipe = UISwipeGestureRecognizer(target: self, action: Selector("up:"))
upSwipe.numberOfTouchesRequired = 1
upSwipe.direction = UISwipeGestureRecognizerDirection.Up
view.addGestureRecognizer(upSwipe)
@objc(up:)
func upCommand(r: UIGestureRecognizer!) {
assert(model != nil)
let m = model!
m.queueMove(MoveDirection.Up,
completion: { (changed: Bool) -> () in
if changed {
self.followUp()
}
})
}
Selectors in Swift
let upSwipe = UISwipeGestureRecognizer(target: self, action: Selector("up:"))
upSwipe.numberOfTouchesRequired = 1
upSwipe.direction = UISwipeGestureRecognizerDirection.Up
view.addGestureRecognizer(upSwipe)
@objc(up:)
func upCommand(r: UIGestureRecognizer!) {
assert(model != nil)
let m = model!
m.queueMove(MoveDirection.Up,
completion: { (changed: Bool) -> () in
if changed {
self.followUp()
}
})
}
Selectors in Swift
let upSwipe = UISwipeGestureRecognizer(target: self, action: Selector("up:"))
upSwipe.numberOfTouchesRequired = 1
upSwipe.direction = UISwipeGestureRecognizerDirection.Up
view.addGestureRecognizer(upSwipe)
@objc(up:)
func upCommand(r: UIGestureRecognizer!) {
assert(model != nil)
let m = model!
m.queueMove(MoveDirection.Up,
completion: { (changed: Bool) -> () in
if changed {
self.followUp()
}
})
}
Questions?
Swift, via "swift-2048"

More Related Content

What's hot (20)

PDF
Cyrille Martraire: Monoids, Monoids Everywhere! at I T.A.K.E. Unconference 2015
Mozaic Works
 
PPT
Advance features of C++
vidyamittal
 
PPTX
Scala best practices
Alexander Zaidel
 
PDF
Scala.io
Steve Gury
 
PDF
Hidden Gems in Swift
Netguru
 
KEY
Exploring Canvas
Kevin Hoyt
 
PDF
Internal workshop es6_2015
Miguel Ruiz Rodriguez
 
PPT
Lecture on graphics
Rafi_Dar
 
PPTX
My favorite slides
Mitchell Wand
 
KEY
Pointer Events in Canvas
deanhudson
 
PDF
Introducción a Elixir
Svet Ivantchev
 
PPTX
Stamps - a better way to object composition
Vasyl Boroviak
 
DOCX
Computer Graphics Lab File C Programs
Kandarp Tiwari
 
PDF
Coq setoid 20110129
tmiya
 
ODP
Creating masterpieces with raphael
Pippi Labradoodle
 
PDF
Computer graphics lab manual
Ankit Kumar
 
DOCX
Computer graphics lab assignment
Abdullah Al Shiam
 
DOC
COMPUTER GRAPHICS LAB MANUAL
Vivek Kumar Sinha
 
PDF
Basics of Computer graphics lab
Priya Goyal
 
Cyrille Martraire: Monoids, Monoids Everywhere! at I T.A.K.E. Unconference 2015
Mozaic Works
 
Advance features of C++
vidyamittal
 
Scala best practices
Alexander Zaidel
 
Scala.io
Steve Gury
 
Hidden Gems in Swift
Netguru
 
Exploring Canvas
Kevin Hoyt
 
Internal workshop es6_2015
Miguel Ruiz Rodriguez
 
Lecture on graphics
Rafi_Dar
 
My favorite slides
Mitchell Wand
 
Pointer Events in Canvas
deanhudson
 
Introducción a Elixir
Svet Ivantchev
 
Stamps - a better way to object composition
Vasyl Boroviak
 
Computer Graphics Lab File C Programs
Kandarp Tiwari
 
Coq setoid 20110129
tmiya
 
Creating masterpieces with raphael
Pippi Labradoodle
 
Computer graphics lab manual
Ankit Kumar
 
Computer graphics lab assignment
Abdullah Al Shiam
 
COMPUTER GRAPHICS LAB MANUAL
Vivek Kumar Sinha
 
Basics of Computer graphics lab
Priya Goyal
 

Viewers also liked (11)

PPT
CROSS CULTURE
YAKUB90
 
PPTX
мысли современных людей
Anna-Katerina
 
PDF
Drivermanual
sreelu0906
 
PPTX
Group 22 power point
group22project
 
PDF
Drivermanual
sreelu0906
 
PPTX
One to One Coach Orientation PPT April 2014
beatarydeen
 
PPTX
تحميل كتب حقيبة ممارس القيادة المدرسية من جهاز التابلت
نصراء الكثيري
 
PDF
Introduction to devops - update 2017
gjdevos
 
PDF
Oestv 2016-12 - diaporamas pdj conjoncture
OESTV
 
PDF
Oestv 2016-12 - l'apprentissage
OESTV
 
CROSS CULTURE
YAKUB90
 
мысли современных людей
Anna-Katerina
 
Drivermanual
sreelu0906
 
Group 22 power point
group22project
 
Drivermanual
sreelu0906
 
One to One Coach Orientation PPT April 2014
beatarydeen
 
تحميل كتب حقيبة ممارس القيادة المدرسية من جهاز التابلت
نصراء الكثيري
 
Introduction to devops - update 2017
gjdevos
 
Oestv 2016-12 - diaporamas pdj conjoncture
OESTV
 
Oestv 2016-12 - l'apprentissage
OESTV
 
Ad

Similar to Swift, via "swift-2048" (20)

PDF
Functional and reactive u is gwt.create 2015
hezamu
 
PDF
Idioms in swift 2016 05c
Kaz Yoshikawa
 
PDF
C++ Programming (Please help me!! Thank you!!)Problem A Win SimU.pdf
aromalcom
 
PDF
A Few of My Favorite (Python) Things
Michael Pirnat
 
PDF
Gentle Introduction to Functional Programming
Saurabh Singh
 
KEY
Intro to Game Programming
Richard Jones
 
PDF
Computer Graphics in Java and Scala - Part 1b
Philip Schwarz
 
PDF
Javascript: repetita iuvant
Luciano Mammino
 
DOCX
NewTetrisScore.cppNewTetrisScore.cpp newTetris.cpp  Defines t.docx
curwenmichaela
 
PDF
Options and trade offs for parallelism and concurrency in Modern C++
Satalia
 
PDF
Go: It's Not Just For Google
Eleanor McHugh
 
PDF
Atomically { Delete Your Actors }
John De Goes
 
PDF
Artificial Intelligence Practical Manual.pdf
priyanshi25121980
 
PDF
Write Python for Speed
Yung-Yu Chen
 
KEY
Google Go For Ruby Hackers
Eleanor McHugh
 
PDF
Gems of GameplayKit. UA Mobile 2017.
UA Mobile
 
PDF
Real life XNA
Johan Lindfors
 
PDF
Denis Lebedev, Swift
Yandex
 
PDF
JVMLS 2016. Coroutines in Kotlin
Andrey Breslav
 
PDF
Rust concurrency tutorial 2015 12-02
nikomatsakis
 
Functional and reactive u is gwt.create 2015
hezamu
 
Idioms in swift 2016 05c
Kaz Yoshikawa
 
C++ Programming (Please help me!! Thank you!!)Problem A Win SimU.pdf
aromalcom
 
A Few of My Favorite (Python) Things
Michael Pirnat
 
Gentle Introduction to Functional Programming
Saurabh Singh
 
Intro to Game Programming
Richard Jones
 
Computer Graphics in Java and Scala - Part 1b
Philip Schwarz
 
Javascript: repetita iuvant
Luciano Mammino
 
NewTetrisScore.cppNewTetrisScore.cpp newTetris.cpp  Defines t.docx
curwenmichaela
 
Options and trade offs for parallelism and concurrency in Modern C++
Satalia
 
Go: It's Not Just For Google
Eleanor McHugh
 
Atomically { Delete Your Actors }
John De Goes
 
Artificial Intelligence Practical Manual.pdf
priyanshi25121980
 
Write Python for Speed
Yung-Yu Chen
 
Google Go For Ruby Hackers
Eleanor McHugh
 
Gems of GameplayKit. UA Mobile 2017.
UA Mobile
 
Real life XNA
Johan Lindfors
 
Denis Lebedev, Swift
Yandex
 
JVMLS 2016. Coroutines in Kotlin
Andrey Breslav
 
Rust concurrency tutorial 2015 12-02
nikomatsakis
 
Ad

Recently uploaded (20)

PDF
How to Hire AI Developers_ Step-by-Step Guide in 2025.pdf
DianApps Technologies
 
PDF
Adobe Premiere Pro Crack / Full Version / Free Download
hashhshs786
 
PDF
Empower Your Tech Vision- Why Businesses Prefer to Hire Remote Developers fro...
logixshapers59
 
PPTX
Change Common Properties in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PDF
Generic or Specific? Making sensible software design decisions
Bert Jan Schrijver
 
PDF
MiniTool Power Data Recovery 8.8 With Crack New Latest 2025
bashirkhan333g
 
PDF
NEW-Viral>Wondershare Filmora 14.5.18.12900 Crack Free
sherryg1122g
 
PDF
Download Canva Pro 2025 PC Crack Full Latest Version
bashirkhan333g
 
PPTX
Help for Correlations in IBM SPSS Statistics.pptx
Version 1 Analytics
 
PPTX
AEM User Group: India Chapter Kickoff Meeting
jennaf3
 
PPTX
Homogeneity of Variance Test Options IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PPTX
Agentic Automation: Build & Deploy Your First UiPath Agent
klpathrudu
 
PPTX
Foundations of Marketo Engage - Powering Campaigns with Marketo Personalization
bbedford2
 
PPTX
Comprehensive Risk Assessment Module for Smarter Risk Management
EHA Soft Solutions
 
PDF
IObit Driver Booster Pro 12.4.0.585 Crack Free Download
henryc1122g
 
PDF
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
PPTX
Coefficient of Variance in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
PPTX
OpenChain @ OSS NA - In From the Cold: Open Source as Part of Mainstream Soft...
Shane Coughlan
 
PDF
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
PDF
Digger Solo: Semantic search and maps for your local files
seanpedersen96
 
How to Hire AI Developers_ Step-by-Step Guide in 2025.pdf
DianApps Technologies
 
Adobe Premiere Pro Crack / Full Version / Free Download
hashhshs786
 
Empower Your Tech Vision- Why Businesses Prefer to Hire Remote Developers fro...
logixshapers59
 
Change Common Properties in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
Generic or Specific? Making sensible software design decisions
Bert Jan Schrijver
 
MiniTool Power Data Recovery 8.8 With Crack New Latest 2025
bashirkhan333g
 
NEW-Viral>Wondershare Filmora 14.5.18.12900 Crack Free
sherryg1122g
 
Download Canva Pro 2025 PC Crack Full Latest Version
bashirkhan333g
 
Help for Correlations in IBM SPSS Statistics.pptx
Version 1 Analytics
 
AEM User Group: India Chapter Kickoff Meeting
jennaf3
 
Homogeneity of Variance Test Options IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
Agentic Automation: Build & Deploy Your First UiPath Agent
klpathrudu
 
Foundations of Marketo Engage - Powering Campaigns with Marketo Personalization
bbedford2
 
Comprehensive Risk Assessment Module for Smarter Risk Management
EHA Soft Solutions
 
IObit Driver Booster Pro 12.4.0.585 Crack Free Download
henryc1122g
 
SciPy 2025 - Packaging a Scientific Python Project
Henry Schreiner
 
Coefficient of Variance in IBM SPSS Statistics Version 31.pptx
Version 1 Analytics
 
OpenChain @ OSS NA - In From the Cold: Open Source as Part of Mainstream Soft...
Shane Coughlan
 
MiniTool Partition Wizard 12.8 Crack License Key LATEST
hashhshs786
 
Digger Solo: Semantic search and maps for your local files
seanpedersen96
 

Swift, via "swift-2048"

  • 2. Who am I? • My name is Austin Zheng • I work at LinkedIn as an iOS developer • Before that I wrote firmware for an embedded systems startup in Redwood City
  • 3. What Is 2048? • iOS clone of web game “2048” by Gabriele Cirulli • In turn based on iOS game “Threes” by Asher Vollmer • Slide in a direction to combine like tiles • 2+2 = 4, 4+4 = 8 • Make a ‘2048’ tile, or fill up the board and lose
  • 5. Architecture (very high level) View Game Logic (Model) View Controller Actions View Commands (forwarded to view) Backing Store
  • 7. Backing Store (Old) @interface F3HGameModel () @property (nonatomic, strong) NSMutableArray *gameState; @property (nonatomic) NSUInteger dimension; //... @end
  • 8. Backing Store struct SquareGameboard<T> { var boardArray: [T]; let dimension: Int ! init(dimension d: Int, initialValue: T) { dimension = d boardArray = [T](count:d*d, repeatedValue:initialValue) } ! subscript(row: Int, col: Int) -> T { get { return boardArray[row*dimension + col] } set { boardArray[row*dimension + col] = newValue } } }
  • 9. Structs • Like classes, they can have properties and methods. • Unlike classes, structs can’t inherit from other structs. • Unlike classes, structs are value types
  • 10. Generics struct SquareGameboard<T> { let dimension: Int var boardArray: [T] ! init(dimension d: Int, initialValue: T) { dimension = d boardArray = [T](count:d*d, repeatedValue:initialValue) } }
  • 11. Subscripts subscript(row: Int, col: Int) -> T { get { return boardArray[row*dimension + col] } set { boardArray[row*dimension + col] = newValue } } gameboard[x, y] = TileObject.Empty ! let someTile = gameboard[x, y]
  • 12. What, exactly, are we storing?
  • 13. TileModel (Old) // This is an Objective-C class which represents a tile @interface F3HTileModel : NSObject @property (nonatomic) BOOL empty; @property (nonatomic) NSUInteger value; @end
  • 14. TileObject enum TileObject { case Empty case Tile(value: Int) } ! let anEmptyTile = TileObject.Empty let eightTile = TileObject.Tile(value: 8) let anotherTile = TileObject.Tile(value: 2)
  • 15. Swift Enums • They can do everything C or Objective-C enums can… • They can also do everything structs in Swift can do - methods and properties… • Optionally, you can have an enum value store associated data. (variants, tagged unions, sum types, case classes)
  • 20. func performMove(direction: MoveDirection) -> Bool { func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] { var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0)) for i in 0..<self.dimension { switch direction { case .Up: buffer[i] = (i, currentRowOrColumn) case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn) case .Left: buffer[i] = (currentRowOrColumn, i) case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1) } } return buffer } ! for i in 0..<dimension { let coords = coordinateGenerator(i) let tiles = coords.map() { (c: (Int, Int)) -> TileObject in let (x, y) = c return self.gameboard[x, y] } let orders = merge(tiles) // ... } // ... }
  • 21. func performMove(direction: MoveDirection) -> Bool { func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] { var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0)) for i in 0..<self.dimension { switch direction { case .Up: buffer[i] = (i, currentRowOrColumn) case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn) case .Left: buffer[i] = (currentRowOrColumn, i) case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1) } } return buffer } ! for i in 0..<dimension { let coords = coordinateGenerator(i) let tiles = coords.map() { (c: (Int, Int)) -> TileObject in let (x, y) = c return self.gameboard[x, y] } let orders = merge(tiles) // ... } // ... }
  • 22. func performMove(direction: MoveDirection) -> Bool { func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] { var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0)) for i in 0..<self.dimension { switch direction { case .Up: buffer[i] = (i, currentRowOrColumn) case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn) case .Left: buffer[i] = (currentRowOrColumn, i) case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1) } } return buffer } ! for i in 0..<dimension { let coords = coordinateGenerator(i) let tiles = coords.map() { (c: (Int, Int)) -> TileObject in let (x, y) = c return self.gameboard[x, y] } let orders = merge(tiles) // ... } // ... }
  • 23. func performMove(direction: MoveDirection) -> Bool { func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] { var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0)) for i in 0..<self.dimension { switch direction { case .Up: buffer[i] = (i, currentRowOrColumn) case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn) case .Left: buffer[i] = (currentRowOrColumn, i) case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1) } } return buffer } ! for i in 0..<dimension { let coords = coordinateGenerator(i) let tiles = coords.map() { (c: (Int, Int)) -> TileObject in let (x, y) = c return self.gameboard[x, y] } let orders = merge(tiles) // ... } // ... }
  • 24. func performMove(direction: MoveDirection) -> Bool { func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] { var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0)) for i in 0..<self.dimension { switch direction { case .Up: buffer[i] = (i, currentRowOrColumn) case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn) case .Left: buffer[i] = (currentRowOrColumn, i) case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1) } } return buffer } ! for i in 0..<dimension { let coords = coordinateGenerator(i) let tiles = coords.map() { (c: (Int, Int)) -> TileObject in let (x, y) = c return self.gameboard[x, y] } let orders = merge(tiles) // ... } // ... }
  • 25. func performMove(direction: MoveDirection) -> Bool { func coordinateGenerator(currentRowOrColumn: Int) -> [(Int, Int)] { var buffer = Array<(Int, Int)>(count:self.dimension, repeatedValue: (0, 0)) for i in 0..<self.dimension { switch direction { case .Up: buffer[i] = (i, currentRowOrColumn) case .Down: buffer[i] = (self.dimension - i - 1, currentRowOrColumn) case .Left: buffer[i] = (currentRowOrColumn, i) case .Right: buffer[i] = (currentRowOrColumn, self.dimension - i - 1) } } return buffer } ! for i in 0..<dimension { let coords = coordinateGenerator(i) let tiles = coords.map() { (c: (Int, Int)) -> TileObject in let (x, y) = c return self.gameboard[x, y] } let orders = merge(tiles) // ... } // ... }
  • 26. A single row… • Condense the row to remove any space - some might move, some might stay still • Collapse two adjacent tiles of equal value into a single tile with double the value • Convert our intermediate representation into ‘Actions’ that the view layer can easily act upon
  • 27. Tracking changes? • We want to know when a tile is moved, when it stays still, when it’s combined • Let’s posit an ActionToken • An ActionToken lives in an array. Its position in the array is the final position of the tile it represents • An ActionToken also tracks the state of the tile or tiles that undertook the action it describes
  • 28. ActionToken (old) typedef enum { F3HMergeTileModeNoAction = 0, F3HMergeTileModeMove, F3HMergeTileModeSingleCombine, F3HMergeTileModeDoubleCombine } F3HMergeTileMode; ! @interface F3HMergeTile : NSObject @property (nonatomic) F3HMergeTileMode mode; @property (nonatomic) NSInteger originalIndexA; @property (nonatomic) NSInteger originalIndexB; @property (nonatomic) NSInteger value; ! + (instancetype)mergeTile; @end
  • 29. ActionToken enum ActionToken { case NoAction(source: Int, value: Int) case Move(source: Int, value: Int) case SingleCombine(source: Int, value: Int) case DoubleCombine(source: Int, second: Int, value: Int) }
  • 30. Game Logic • Condense - remove spaces between tiles 42 2 4
  • 31. func condense(group: [TileObject]) -> [ActionToken] { var tokenBuffer = [ActionToken]() for (idx, tile) in enumerate(group) { switch tile { case let .Tile(value) where tokenBuffer.count == idx: tokenBuffer.append(ActionToken.NoAction(source: idx, value: value)) case let .Tile(value): tokenBuffer.append(ActionToken.Move(source: idx, value: value)) default: break } } return tokenBuffer; }
  • 32. func condense(group: [TileObject]) -> [ActionToken] { var tokenBuffer = [ActionToken]() for (idx, tile) in enumerate(group) { switch tile { case let .Tile(value) where tokenBuffer.count == idx: tokenBuffer.append(ActionToken.NoAction(source: idx, value: value)) case let .Tile(value): tokenBuffer.append(ActionToken.Move(source: idx, value: value)) default: break } } return tokenBuffer; }
  • 33. Swift ‘switch’ • At its most basic, works like the C or Objective-C switch statement • But it can do far more! • One example: take the values out of an enum • Cases can be qualified by ‘where’ clauses • Has to be comprehensive, and no default fallthrough
  • 34. Game Logic • Collapse - perform necessary merges 4 84 4 2 82
  • 35. func collapse(group: [ActionToken]) -> [ActionToken] { func quiescentTileStillQuiescent(inputPosition: Int, outputLength: Int, originalPosition: Int) -> Bool { return (inputPosition == outputLength) && (originalPosition == inputPosition) } ! var tokenBuffer = [ActionToken]() var skipNext = false for (idx, token) in enumerate(group) { if skipNext { skipNext = false continue } switch token { case .SingleCombine: assert(false, "Cannot have single combine token in input") case .DoubleCombine: assert(false, "Cannot have double combine token in input") case let .NoAction(s, v) where (idx < group.count-1 && v == group[idx+1].getValue() && quiescentTileStillQuiescent(idx, tokenBuffer.count, s)): let nv = v + group[idx+1].getValue() skipNext = true tokenBuffer.append(ActionToken.SingleCombine(source: next.getSource(), value: nv)) case let t where (idx < group.count-1 && t.getValue() == group[idx+1].getValue()): let next = group[idx+1] let nv = t.getValue() + group[idx+1].getValue() skipNext = true tokenBuffer.append(ActionToken.DoubleCombine(source: t.getSource(), second: next.getSource(), value: nv)) case let .NoAction(s, v) where !quiescentTileStillQuiescent(idx, tokenBuffer.count, s): tokenBuffer.append(ActionToken.Move(source: s, value: v)) case let .NoAction(s, v): tokenBuffer.append(ActionToken.NoAction(source: s, value: v)) case let .Move(s, v): tokenBuffer.append(ActionToken.Move(source: s, value: v)) default: break } } return tokenBuffer }
  • 36. Game Logic • Convert - create ‘move orders’ for the view enum MoveOrder { case SingleMoveOrder(source: Int, destination: Int, value: Int, wasMerge: Bool) case DoubleMoveOrder(firstSource: Int, secondSource: Int, destination: Int, value: Int) }
  • 37. func convert(group: [ActionToken]) -> [MoveOrder] { var moveBuffer = [MoveOrder]() for (idx, t) in enumerate(group) { switch t { case let .Move(s, v): moveBuffer.append(MoveOrder.SingleMoveOrder(source: s, destination: idx, value: v, wasMerge: false)) case let .SingleCombine(s, v): moveBuffer.append(MoveOrder.SingleMoveOrder(source: s, destination: idx, value: v, wasMerge: true)) case let .DoubleCombine(s1, s2, v): moveBuffer.append(MoveOrder.DoubleMoveOrder(firstSource: s1, secondSource: s2, destination: idx, value: v)) default: break } } return moveBuffer }
  • 38. Views
  • 39. func insertTile(pos: (Int, Int), value: Int) { let (row, col) = pos let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding) let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding) let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0 let tile = TileView(position: CGPointMake(x, y), width: tileWidth, value: value, radius: r, delegate: provider) tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale, tilePopStartScale)) ! addSubview(tile) bringSubviewToFront(tile) UIView.animateWithDuration(tileExpandTime, delay: tilePopDelay, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in // Make the tile 'pop' tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale, self.tilePopMaxScale)) }, completion: { (finished: Bool) -> Void in // Shrink the tile after it 'pops' UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in tile.layer.setAffineTransform(CGAffineTransformIdentity) }) }) }
  • 40. func insertTile(pos: (Int, Int), value: Int) { let (row, col) = pos let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding) let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding) let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0 let tile = TileView(position: CGPointMake(x, y), width: tileWidth, value: value, radius: r, delegate: provider) tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale, tilePopStartScale)) ! addSubview(tile) bringSubviewToFront(tile) UIView.animateWithDuration(tileExpandTime, delay: tilePopDelay, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in // Make the tile 'pop' tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale, self.tilePopMaxScale)) }, completion: { (finished: Bool) -> Void in // Shrink the tile after it 'pops' UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in tile.layer.setAffineTransform(CGAffineTransformIdentity) }) }) }
  • 41. func insertTile(pos: (Int, Int), value: Int) { let (row, col) = pos let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding) let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding) let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0 let tile = TileView(position: CGPointMake(x, y), width: tileWidth, value: value, radius: r, delegate: provider) tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale, tilePopStartScale)) ! addSubview(tile) bringSubviewToFront(tile) UIView.animateWithDuration(tileExpandTime, delay: tilePopDelay, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in // Make the tile 'pop' tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale, self.tilePopMaxScale)) }, completion: { (finished: Bool) -> Void in // Shrink the tile after it 'pops' UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in tile.layer.setAffineTransform(CGAffineTransformIdentity) }) }) }
  • 42. func insertTile(pos: (Int, Int), value: Int) { let (row, col) = pos let x = tilePadding + CGFloat(col)*(tileWidth + tilePadding) let y = tilePadding + CGFloat(row)*(tileWidth + tilePadding) let r = (cornerRadius >= 2) ? cornerRadius - 2 : 0 let tile = TileView(position: CGPointMake(x, y), width: tileWidth, value: value, radius: r, delegate: provider) tile.layer.setAffineTransform(CGAffineTransformMakeScale(tilePopStartScale, tilePopStartScale)) ! addSubview(tile) bringSubviewToFront(tile) UIView.animateWithDuration(tileExpandTime, delay: tilePopDelay, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in // Make the tile 'pop' tile.layer.setAffineTransform(CGAffineTransformMakeScale(self.tilePopMaxScale, self.tilePopMaxScale)) }, completion: { (finished: Bool) -> Void in // Shrink the tile after it 'pops' UIView.animateWithDuration(self.tileContractTime, animations: { () -> Void in tile.layer.setAffineTransform(CGAffineTransformIdentity) }) }) }
  • 43. Selectors UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(upButtonTapped)]; ! upSwipe.numberOfTouchesRequired = 1; upSwipe.direction = UISwipeGestureRecognizerDirectionUp; [self.view addGestureRecognizer:upSwipe]; ! ! ! - (void)upButtonTapped { [self.model performMoveInDirection:F3HMoveDirectionUp completionBlock:^(BOOL changed) { if (changed) [self followUp]; }]; }
  • 44. Selectors UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(upButtonTapped)]; ! upSwipe.numberOfTouchesRequired = 1; upSwipe.direction = UISwipeGestureRecognizerDirectionUp; [self.view addGestureRecognizer:upSwipe]; ! ! ! - (void)upButtonTapped { [self.model performMoveInDirection:F3HMoveDirectionUp completionBlock:^(BOOL changed) { if (changed) [self followUp]; }]; }
  • 45. Selectors UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(upButtonTapped)]; ! upSwipe.numberOfTouchesRequired = 1; upSwipe.direction = UISwipeGestureRecognizerDirectionUp; [self.view addGestureRecognizer:upSwipe]; ! ! ! - (void)upButtonTapped { [self.model performMoveInDirection:F3HMoveDirectionUp completionBlock:^(BOOL changed) { if (changed) [self followUp]; }]; }
  • 46. Selectors UISwipeGestureRecognizer *upSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(upButtonTapped)]; ! upSwipe.numberOfTouchesRequired = 1; upSwipe.direction = UISwipeGestureRecognizerDirectionUp; [self.view addGestureRecognizer:upSwipe]; ! ! ! - (void)upButtonTapped { [self.model performMoveInDirection:F3HMoveDirectionUp completionBlock:^(BOOL changed) { if (changed) [self followUp]; }]; }
  • 47. Selectors in Swift let upSwipe = UISwipeGestureRecognizer(target: self, action: Selector("up:")) upSwipe.numberOfTouchesRequired = 1 upSwipe.direction = UISwipeGestureRecognizerDirection.Up view.addGestureRecognizer(upSwipe) @objc(up:) func upCommand(r: UIGestureRecognizer!) { assert(model != nil) let m = model! m.queueMove(MoveDirection.Up, completion: { (changed: Bool) -> () in if changed { self.followUp() } }) }
  • 48. Selectors in Swift let upSwipe = UISwipeGestureRecognizer(target: self, action: Selector("up:")) upSwipe.numberOfTouchesRequired = 1 upSwipe.direction = UISwipeGestureRecognizerDirection.Up view.addGestureRecognizer(upSwipe) @objc(up:) func upCommand(r: UIGestureRecognizer!) { assert(model != nil) let m = model! m.queueMove(MoveDirection.Up, completion: { (changed: Bool) -> () in if changed { self.followUp() } }) }
  • 49. Selectors in Swift let upSwipe = UISwipeGestureRecognizer(target: self, action: Selector("up:")) upSwipe.numberOfTouchesRequired = 1 upSwipe.direction = UISwipeGestureRecognizerDirection.Up view.addGestureRecognizer(upSwipe) @objc(up:) func upCommand(r: UIGestureRecognizer!) { assert(model != nil) let m = model! m.queueMove(MoveDirection.Up, completion: { (changed: Bool) -> () in if changed { self.followUp() } }) }