7 people like it.

Game of 2048

Simple implementation of the popular game "2048". Can you add up the tiles and reach 2048? The game can be played in fsi or you can add the GUI which is so far missing.

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
/// This is a simple implementation of the 2048 game
/// It's playable in fsi, I might hook it up to an app later using Xamarin's tooling. 
/// But so far it was just funny to implement the rules.
namespace T2048

module Game =

    type Row = List<int>
    type Board = List<Row>
    type Direction = LEFT | RIGHT | UP | DOWN
    let NEW_TILES = [2; 4]

    let mutable theScore = 0
    let addScore s = theScore <- theScore + s

    // Here is the main logic of the game, moving the tiles and combining identical neighbours
    // We always move from right to left, other directions are performed by turning the board first
    let rec moveRow r = 
        match r with
        | [] -> []
        | 0::bs -> moveRow bs
        | [a] -> [a]
        | a::b::bs when b=0 -> moveRow(a::bs)
        | a::b::bs when a=b -> addScore(a+b) ; (a+b)::moveRow bs
        | a::b::bs -> a::moveRow(b::bs)

    let pad elem toLen bs =
        let padLen = toLen - List.length bs
        if padLen<=0 then bs else
            let newTail = [for i in 1 .. padLen -> elem]
            bs @ newTail

    let moveBoard b =
        let size = List.length b
        let moveAndPad = moveRow >> (pad 0 size)
        List.map moveAndPad b

    let reverseBoard = List.map List.rev

    let rec transposeBoard b = 
        match b with
        | [] -> []
        | []::_ -> []
        | _ ->
            let heads = List.map List.head b
            let tails = List.map List.tail b
            heads :: transposeBoard tails

    // Turn the board, move left, and turn back
    let moveDirection d =
        match d with
        | LEFT -> moveBoard
        | RIGHT -> reverseBoard >> moveBoard >> reverseBoard
        | UP -> transposeBoard >> moveBoard >> transposeBoard
        | DOWN -> transposeBoard >> reverseBoard >> moveBoard >> transposeBoard >> List.rev

    let emptySlots b =
        let slot i j v = ((i,j),v)
        let numberRow f row = List.mapi f row
        let numberBoard = List.mapi (fun i -> numberRow (slot i))
        let empties = List.choose (fun (a,b) -> if b=0 then Some(a) else None)
        b |> numberBoard |> List.concat |> empties
       
    let rnd = new System.Random()
    let oneOf vs =
        let i = rnd.Next(List.length vs)
        List.nth vs i
    
    let updateBoard coords v board =
        let (posRow,posCol) = coords
        let updateRow pos v row = List.mapi (fun i b -> if i=pos then v else b) row
        List.mapi (fun i row -> if i=posRow then updateRow posCol v row else row) board

    let addNewTile b =
        let empties = emptySlots b
        let slotCoords = oneOf empties
        let v = oneOf NEW_TILES
        updateBoard slotCoords v b

    // To detect "game over", try a direction which is 90 degress from the current one and see if any tiles can move
    let otherDirection d = 
        match d with
        | LEFT -> UP
        | RIGHT -> UP
        | UP -> LEFT
        | DOWN -> LEFT

    // Perform a move
    let nextMove b d =
        let newBoard = b |> (moveDirection d) |> addNewTile
        let gameOver = newBoard=b && (moveDirection (otherDirection d) b)=b
        (newBoard, gameOver)

    // Install a pretty printer if we are running in fsi 
    let buildString sep l = List.foldBack (fun a acc -> sprintf "%4i %s %s" a sep acc) l ""

    let printBoard (b:Board) =
        b |> List.map (buildString "|") |> List.reduce (fun a b -> a+"\n"+b)
#if INTERACTIVE
    fsi.AddPrinter printBoard
#endif

    // Here is the current state
    let mutable theBoard:Board = []

    // Start a new game
    let reset () = 
        let _ = theScore <- 0
        let _ = theBoard <- [[0;0;0;0];
                             [0;0;0;0];
                             [0;0;0;0];
                             [0;0;0;0]] |> addNewTile
        theBoard

    // Make a move and update the current state
    let move d = 
        let (newBoard, gameOver) = nextMove theBoard d
        let _ = if gameOver then printf "Game over, score=%i" theScore
        let _ = theBoard <- newBoard
        theBoard
type Row = List<int>

Full name: T2048.Game.Row
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
type Board = List<Row>

Full name: T2048.Game.Board
type Direction =
  | LEFT
  | RIGHT
  | UP
  | DOWN

Full name: T2048.Game.Direction
union case Direction.LEFT: Direction
union case Direction.RIGHT: Direction
union case Direction.UP: Direction
union case Direction.DOWN: Direction
val NEW_TILES : int list

Full name: T2048.Game.NEW_TILES
val mutable theScore : int

Full name: T2048.Game.theScore
val addScore : s:int -> unit

Full name: T2048.Game.addScore
val s : int
val moveRow : r:int list -> int list

Full name: T2048.Game.moveRow
val r : int list
val bs : int list
val a : int
val b : int
val pad : elem:'a -> toLen:int -> bs:'a list -> 'a list

Full name: T2048.Game.pad
val elem : 'a
val toLen : int
val bs : 'a list
val padLen : int
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
val newTail : 'a list
val i : int
val moveBoard : b:int list list -> int list list

Full name: T2048.Game.moveBoard
val b : int list list
val size : int
val moveAndPad : (int list -> int list)
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val reverseBoard : (int list list -> int list list)

Full name: T2048.Game.reverseBoard
val rev : list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.rev
val transposeBoard : b:'a list list -> 'a list list

Full name: T2048.Game.transposeBoard
val b : 'a list list
val heads : 'a list
val head : list:'T list -> 'T

Full name: Microsoft.FSharp.Collections.List.head
val tails : 'a list list
val tail : list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.tail
val moveDirection : d:Direction -> (int list list -> int list list)

Full name: T2048.Game.moveDirection
val d : Direction
val emptySlots : b:int list list -> (int * int) list

Full name: T2048.Game.emptySlots
val slot : ('a -> 'b -> 'c -> ('a * 'b) * 'c)
val i : 'a
val j : 'b
val v : 'c
val numberRow : ((int -> 'a -> 'b) -> 'a list -> 'b list)
val f : (int -> 'a -> 'b)
val row : 'a list
val mapi : mapping:(int -> 'T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.mapi
val numberBoard : (int list list -> ((int * int) * int) list list)
val empties : (((int * int) * int) list -> (int * int) list)
val choose : chooser:('T -> 'U option) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.choose
val a : int * int
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
val concat : lists:seq<'T list> -> 'T list

Full name: Microsoft.FSharp.Collections.List.concat
val rnd : System.Random

Full name: T2048.Game.rnd
namespace System
Multiple items
type Random =
  new : unit -> Random + 1 overload
  member Next : unit -> int + 2 overloads
  member NextBytes : buffer:byte[] -> unit
  member NextDouble : unit -> float

Full name: System.Random

--------------------
System.Random() : unit
System.Random(Seed: int) : unit
val oneOf : vs:'a list -> 'a

Full name: T2048.Game.oneOf
val vs : 'a list
System.Random.Next() : int
System.Random.Next(maxValue: int) : int
System.Random.Next(minValue: int, maxValue: int) : int
val nth : list:'T list -> index:int -> 'T

Full name: Microsoft.FSharp.Collections.List.nth
val updateBoard : int * int -> v:'a -> board:'a list list -> 'a list list

Full name: T2048.Game.updateBoard
val coords : int * int
val v : 'a
val board : 'a list list
val posRow : int
val posCol : int
val updateRow : (int -> 'b -> 'b list -> 'b list)
val pos : int
val v : 'b
val row : 'b list
val b : 'b
val addNewTile : b:int list list -> int list list

Full name: T2048.Game.addNewTile
val empties : (int * int) list
val slotCoords : int * int
val v : int
val otherDirection : d:Direction -> Direction

Full name: T2048.Game.otherDirection
val nextMove : b:int list list -> d:Direction -> int list list * bool

Full name: T2048.Game.nextMove
val newBoard : int list list
val gameOver : bool
val buildString : sep:string -> l:int list -> string

Full name: T2048.Game.buildString
val sep : string
val l : int list
val foldBack : folder:('T -> 'State -> 'State) -> list:'T list -> state:'State -> 'State

Full name: Microsoft.FSharp.Collections.List.foldBack
val acc : string
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val printBoard : b:Board -> string

Full name: T2048.Game.printBoard
val b : Board
val reduce : reduction:('T -> 'T -> 'T) -> list:'T list -> 'T

Full name: Microsoft.FSharp.Collections.List.reduce
val a : string
val b : string
val mutable theBoard : Board

Full name: T2048.Game.theBoard
val reset : unit -> Board

Full name: T2048.Game.reset
val move : d:Direction -> Board

Full name: T2048.Game.move
val printf : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printf
Raw view Test code New version

More information

Link:http://fssnip.net/mw
Posted:10 years ago
Author:Tore Green
Tags: game