O slideshow foi denunciado.
Seu SlideShare está sendo baixado. ×

Swift, via "swift-2048"

Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Anúncio
Próximos SlideShares
Computer graphics lab manual
Computer graphics lab manual
Carregando em…3
×

Confira estes a seguir

1 de 51 Anúncio

Mais Conteúdo rRelacionado

Diapositivos para si (20)

Anúncio

Semelhante a Swift, via "swift-2048" (20)

Mais recentes (20)

Anúncio

Swift, via "swift-2048"

  1. 1. Swift (via swift-2048) Austin Zheng
  2. 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. 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
  4. 4. (2048 demo)
  5. 5. Architecture (very high level) View Game Logic (Model) View Controller Actions View Commands (forwarded to view) Backing Store
  6. 6. Backing Store
  7. 7. Backing Store (Old) @interface F3HGameModel () @property (nonatomic, strong) NSMutableArray *gameState; @property (nonatomic) NSUInteger dimension; //... @end
  8. 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. 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. 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. 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. 12. What, exactly, are we storing?
  13. 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. 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. 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)
  16. 16. Game Logic
  17. 17. 22 2 22 2 2
  18. 18. 22 2 22 2 2
  19. 19. 22 2 22 2 2
  20. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 30. Game Logic • Condense - remove spaces between tiles 42 2 4
  31. 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. 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. 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. 34. Game Logic • Collapse - perform necessary merges 4 84 4 2 82
  35. 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. 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. 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. 38. Views
  39. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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() } }) }
  50. 50. Questions?

×