Skip to content

Instantly share code, notes, and snippets.

@tfausak
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tfausak/0f31dd5583c4bc961a7d to your computer and use it in GitHub Desktop.
Save tfausak/0f31dd5583c4bc961a7d to your computer and use it in GitHub Desktop.
2048 implemented in Swift via Haskell.
// http://taylor.fausak.me/2014/04/28/cloning-2048-in-haskell/
import func Cocoa.rand
/*
Utilities
*/
// TODO: Can this be generalized to allow anything addable?
func combine(array: Int[]) -> Int[] {
if array.count < 2 {
return array
}
return [array[0] + array[1]] + array[2..array.endIndex]
}
func compact<T>(array: T?[]) -> T[] {
var result: T[] = []
for optional in array {
if let element = optional {
result.append(element)
}
}
return result
}
func group<T: Equatable>(var array: T[]) -> T[][] {
if array.isEmpty {
return []
}
let first = array.removeAtIndex(0)
var result = [first]
while !array.isEmpty && first == array[0] {
result.append(array.removeAtIndex(0))
}
return [result] + group(array)
}
// TODO: Can this be an infinite generator?
func iterate<T>(function: (T -> T), value: T, times: Int) -> T {
if times < 1 {
return value
}
return iterate(function, function(value), times - 1)
}
func log_2(var number: Int) -> Int {
var result = 0
while number > 1 {
++result
number /= 2
}
return result
}
func pad<T>(var array: Array<T>, value: T, var count: Int) -> Array<T> {
count = count - array.count
if count < 0 {
count = 0
}
array.extend(Array(count: count, repeatedValue: value))
return array
}
// TODO: There's got to be a better way.
func random(limit: Int) -> Int {
return Int(rand()) % limit
}
func split(string: String, separator: Character) -> String[] {
var strings: String[] = []
var buffer = ""
for character in string {
if character == separator {
strings.append(buffer)
buffer = ""
} else {
buffer += character
}
}
if !buffer.isEmpty {
strings.append(buffer)
}
return strings
}
// TODO: Can this work for ragged or empty multi-dimensional arrays?
func transpose<T>(matrix: T[][]) -> T[][] {
if matrix.isEmpty || matrix[0].isEmpty {
return matrix
}
let width = matrix.count
let height = matrix[0].count
let value = matrix[0][0]
var result: T[][] = []
for _ in 0..height {
result.append(Array(count: width, repeatedValue: value))
}
for (i, row) in enumerate(matrix) {
assert(row.count == height)
for (j, cell) in enumerate(row) {
result[j][i] = cell
}
}
return result
}
/*
Direction
*/
enum Direction {
case Left
case Down
case Right
case Up
var description: String {
switch self {
case .Left: return "←"
case .Down: return "↓"
case .Right: return "→"
case .Up: return "↑"
}
}
}
func directions() -> Direction[] {
return [
Direction.Left,
Direction.Down,
Direction.Right,
Direction.Up
]
}
/*
Point
*/
typealias Point = (x: Int, y: Int)
/*
Tile
*/
typealias Tile = Int?
func empty() -> Tile {
return nil
}
// TODO: Can this be generalized into "toString" or something?
func render(tile: Tile) -> String {
if let value = tile {
return String(value)
} else {
return " "
}
}
func parse(string: String) -> Tile {
if let value = string.toInt() {
return value
} else {
return nil
}
}
func rank(tile: Tile) -> Int {
if let value = tile {
return log_2(value)
} else {
return 0
}
}
func score(tile: Tile) -> Int {
if let value = tile {
return value * (rank(tile) - 1)
} else {
return 0
}
}
func randomTile() -> Tile {
if random(10) == 0 {
return 4
} else {
return 2
}
}
/*
Line
*/
typealias Line = Tile[]
func empty(count: Int) -> Line {
return Array(count: count, repeatedValue: empty())
}
func render(line: Line) -> String {
return "\t".join(line.map(render))
}
func parse(string: String) -> Line {
return split(string, "\t").map(parse)
}
func score(line: Line) -> Int {
return line.map(score).reduce(0, +)
}
// TODO: My functional roots are showing.
func shift(line: Line) -> Line {
let a: Int[] = compact(line)
let b: Int[][] = group(a)
let c: Int[][] = b.map(combine)
let d: Int[] = c.reduce([], +)
let e: Line = d.map { $0 } // TODO: This is dumb.
let f: Line = pad(e, nil, line.count)
return f
}
// TODO: Is there a way to directly compare arrays for equality?
func canShift(line: Line) -> Bool {
return render(line) != render(shift(line))
}
func emptyIndexes(line: Line) -> Int[] {
var result: Int[] = []
for (index, tile) in enumerate(line) {
if !tile {
result.append(index)
}
}
return result
}
func set(var line: Line, tile: Tile, index: Int) -> Line {
assert(index < line.count)
line[index] = tile
return line
}
func randomEmptyIndex(line: Line) -> Int? {
let indexes = emptyIndexes(line)
if indexes.isEmpty {
return nil
}
return indexes[random(indexes.count)]
}
func randomlySet(line: Line, tile: Tile) -> Line {
if let index = randomEmptyIndex(line) {
return set(line, tile, index)
} else {
return line
}
}
/*
Grid
*/
typealias Grid = Line[]
// TODO: This is dumb, but using "repeatedValue" doesn't generate unique arrays.
func empty(width: Int, height: Int) -> Grid {
var result: Grid = []
for _ in 0..height {
result.append(empty(width))
}
return result
}
func render(grid: Grid) -> String {
return "\n".join(grid.map(render))
}
func parse(string: String) -> Grid {
return split(string, "\n").map(parse)
}
func score(grid: Grid) -> Int {
return grid.map(score).reduce(0, +)
}
func shift(grid: Grid) -> Grid {
return grid.map(shift)
}
// TODO: See (other) canShift.
func canShift(grid: Grid) -> Bool {
return grid.filter { !canShift($0) }.isEmpty
}
// TODO: Why can't this be shortened to `.map(reverse)`?
func rotate(grid: Grid) -> Grid {
return transpose(grid).map { $0.reverse() }
}
func rotateTo(grid: Grid, direction: Direction) -> Grid {
var times: Int
switch direction {
case .Left: times = 0
case .Down: times = 1
case .Right: times = 2
case .Up: times = 3
}
return iterate(rotate, grid, times)
}
func rotateFrom(grid: Grid, direction: Direction) -> Grid {
var times: Int
switch direction {
case .Left: times = 0
case .Down: times = 3
case .Right: times = 2
case .Up: times = 1
}
return iterate(rotate, grid, times)
}
func move(grid: Grid, direction: Direction) -> Grid {
return rotateFrom(shift(rotateTo(grid, direction)), direction)
}
// TODO: See canShift.
func canMove(grid: Grid, direction: Direction) -> Bool {
return render(grid) != render(move(grid, direction))
}
func emptyPoints(grid: Grid) -> Point[] {
var result: Point[] = []
for (y, line) in enumerate(grid) {
result.extend(emptyIndexes(line).map { (x: $0, y: y) })
}
return result
}
func set(var grid: Grid, tile: Tile, point: Point) -> Grid {
assert(point.y < grid.count)
grid[point.y] = set(grid[point.y], tile, point.x)
return grid
}
func randomEmptyPoint(grid: Grid) -> Point? {
let points = emptyPoints(grid)
if points.isEmpty {
return nil
}
return points[random(points.count)]
}
func randomlySet(grid: Grid, tile: Tile) -> Grid {
if let point = randomEmptyPoint(grid) {
return set(grid, tile, point)
} else {
return grid
}
}
func randomlyAdd(grid: Grid) -> Grid {
let tile: Tile = randomTile()
return randomlySet(grid, tile)
}
func randomlyAdd(grid: Grid, count: Int) -> Grid {
if count < 1 {
return grid
} else {
return randomlyAdd(randomlyAdd(grid), count - 1)
}
}
func canMove(grid: Grid) -> Bool {
return !directions().filter { canMove(grid, $0) }.isEmpty
}
func moves(grid: Grid) -> Direction[] {
return directions().filter { canMove(grid, $0) }
}
/*
Settings
*/
struct Settings {
var gridHeight = 4
var gridWidth = 4
var maximumTile = 2048
var initialTileCount = 2
}
/*
Game
*/
struct Game {
var grid: Grid
var settings: Settings
var description: String {
let s = score(grid)
let g = render(grid)
let ms = " ".join(moves(grid).map { $0.description })
return "Score: \(s)\n\(g)\nMoves: \(ms)"
}
init() {
self.init(settings: Settings())
}
init(settings: Settings) {
self.settings = settings
grid = randomlyAdd(empty(settings.gridWidth, settings.gridHeight), settings.initialTileCount)
}
func hasWon() -> Bool {
for line in grid {
for tile in line {
if tile > settings.maximumTile {
return true
}
}
}
return false
}
// TODO: Calling this "move" breaks overloading.
mutating func go(direction: Direction) {
grid = move(grid, direction)
}
}
/*
Playground
*/
var game = Game()
println(game.description)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment