Apple’s Snakes and Ladders Swift Example

All the cool kids are learning Swift this week! Not wanting to be left out I downloaded Xcode beta 6 and the iBook introduction and started following long with the code sample in Xcode’s new playground.

Swift is impressive as it fixes a number of issues with Objective-C by replacing it wholesale with a modern language that feels like a well thought-out JavaScript. I love the idea of optional values, implicit types, simple generics, and how let and var are used to create immutable and mutable objects. I’m still digesting the class vs struct vs enum idea. I’m not sure why they are just not all classes but give me time, I’m sure I’ll see the light.

The Snakes and Ladders example is a fine bit of sample code and shows off Swift’s power as a teaching language. But I had a couple of issues with it and tweaked it. See below and check out the comments for why I did what I did. You should be able to copy and paste this code into a playground and watch it run!

// Playground - noun: a place where people can play

import Cocoa

// Snakes and Ladders Game

let finalSquare = 25 // 25 squares in the game (0 to 24)

// init all squares to 0 and included a 26th square (to simplify the code)
// Note: you can use var or let to define the array board (Apple uses var, I'm using let)
// - let makes board a constant but values of a const array are still mutable
// - its the number of elements in the array that are immutable
// - using let allows the compiler to optimize looking up values in the array
let board = Int[] (count: finalSquare + 1, repeatedValue: 0)

// in 4 squares as ladders (+ values) and 4 squares as snakes (- values)
// ladders move the player forward by value in square
// snakes move the player backward by value in square
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08

var square = 0   // player position
var diceRoll = 0 // moves are determined by an 8-sided die

println("Game Starting at square \(square)")
// start the game
while square < finalSquare {
  
  // the Apple sample code used a rather useless means of rolling a dice by
  // incrementing a value.
  // I see why they did that: a more real world example requires the un-swifty 
  // C function arc4random_uniform().
  // Later on the book Apple shows you how to use a generator class to create
  // a random number but frankly, that is overkill.
  
  // I betcha Apple to introduce a Swifty library of math functions that will 
  // hide the ugliness of arc4random_uniform() with something like randInt()
  
  let diceRoll = Int(arc4random_uniform(7))
  
  // move by the rolled amount
  square += diceRoll
  
  if square < board.count {
    
    // if the player is still on the board do what the current square instructs
    let oldSquare = square // extra let created for printing the results since square's value has changed
    square += board[square]
    
    println("diceRoll = \(diceRoll), landing = \(oldSquare), instruction = \(board[oldSquare]), destination = \(square)")

  }
}

println("Game over at square \(square)")

Update!

I could not get the sweet while loop with the label "gameLoop" to work in the playground. It's no surprise that Swift has a couple of bugs. So with a bit of fiddling I got it to work with a gameOver flag. And while I was at it I created a swifty function to hide the ugly arc4Rand_uniform() call and refactored the print statements so you can follow along with the game in the console.

func randNum(limit: Int) -> Int {
  return Int(arc4random_uniform(UInt32(limit)))
}

// start the game
println("Game Starting at square \(square)")

var gameOver = false

gameLoop: while square != finalSquare {
  diceRoll = randNum(7)
  switch square + diceRoll {
  case finalSquare:
    // diceRolled moved player to finalSquare
    // game over
    print("starting \(square), ")
    print("diceRoll \(diceRoll), ")
    print("destination \(square + diceRoll), Game Over")
    println()
    
    // Swift didn't recognise the loop's label so I commented it out
    // This teams the break only breaks out of the switch
    // The gameOver flag is used to break out of the while loop
    gameOver = true
    break //gameloop
  case let newSquare where newSquare > finalSquare:
    // diceRoll moved player beyound finalSquare
    // roll again
    print("starting \(square), ")
    print("diceRoll \(diceRoll), ")
    print("destination \(square + diceRoll), Re-roll")
    println()
    
    // Swift didn't recognise the loop's label but in this case
    // it doesn't matter...
    continue //gameloop
  default:
    // valid move
    // update board
    print("starting \(square), ")
    print("diceRoll \(diceRoll), ")
    square += diceRoll
    print("destination \(square), ")
    print("instruction \(board[square]), ")
    square += board[square]
    print("result \(square)")
    println()
  }
  // if flag is set break out of the while loop
  if gameOver {
    break
  }
}