Implement a 2D sliding block puzzle game where blocks with numbers are combined to add their values.

Task
2048
You are encouraged to solve this task according to the task description, using any language you may know.
Task

The rules are that on each turn the player must choose a direction (up, down, left or right) and all tiles move as far as possible in that direction, some more than others. Two adjacent tiles (in that direction only) with matching numbers combine into one bearing the sum of those numbers. A move is valid when at least one tile can be moved, if only by combination. A new tile with the value of 2 is spawned at the end of each turn at a randomly chosen empty square, if there is one. To win, the player must create a tile with the number 2048. The player loses if no valid moves are possible.

The name comes from the popular open-source implementation of this game mechanic, 2048.

Requirements:

  • "Non-greedy" movement. The tiles that were created by combining other tiles should not be combined again during the same turn (move). That is to say that moving the tile row of
 [2][2][2][2]

to the right should result in

 ......[4][4]

and not

 .........[8]
  • "Move direction priority". If more than one variant of combining is possible, move direction shall indicate which combination will take effect. For example, moving the tile row of
 ...[2][2][2]

to the right should result in

 ......[2][4]

and not

 ......[4][2]
  • Adding a new tile on a blank space. Most of the time, a new "2" is to be added and occasionally (10% of the time) - a "4".
  • Check for valid moves. The player shouldn't be able to skip their turn by trying a move that doesn't change the board.
  • Win condition.
  • Lose condition.



AArch64 Assembly

Works with: as version Raspberry Pi 3B version Buster 64 bits

<lang AArch64 Assembly> /* ARM assembly AARCH64 Raspberry PI 3B */ /* program 2048_64.s */

/*******************************************/ /* Constantes file */ /*******************************************/ /* for this file see task include a file in language AArch64 assembly*/ .include "../includeConstantesARM64.inc" .equ SIZE, 4 .equ TOTAL, 2048 .equ BUFFERSIZE, 80

.equ KEYSIZE, 8 .equ IOCTL, 0x1D // Linux syscall .equ SIGACTION, 0x86 // Linux syscall .equ SYSPOLL, 0x16 // Linux syscall .equ CREATPOLL, 0x14 // Linux syscall .equ CTLPOLL, 0x15 // Linux syscall

.equ TCGETS, 0x5401 .equ TCSETS, 0x5402 .equ ICANON, 2 .equ ECHO, 10 .equ POLLIN, 1 .equ EPOLL_CTL_ADD, 1

.equ SIGINT, 2 // Issued if the user sends an interrupt signal (Ctrl + C) .equ SIGQUIT, 3 // Issued if the user sends a quit signal (Ctrl + D) .equ SIGTERM, 15 // Software termination signal (sent by kill by default) .equ SIGTTOU, 22

/*******************************************/ /* Structures */ /********************************************/ /* structure termios see doc linux*/

   .struct  0

term_c_iflag: // input modes

   .struct  term_c_iflag + 4 

term_c_oflag: // output modes

   .struct  term_c_oflag + 4 

term_c_cflag: // control modes

   .struct  term_c_cflag + 4 

term_c_lflag: // local modes

   .struct  term_c_lflag + 4 

term_c_cc: // special characters

   .struct  term_c_cc + 40      // see length if necessary 

term_fin:

/* structure sigaction see doc linux */

   .struct  0

sa_handler:

   .struct  sa_handler + 8 

sa_mask:

   .struct  sa_mask + 8 

sa_flags:

   .struct  sa_flags + 8

sa_sigaction:

   .struct  sa_sigaction + 8

sa_fin:

/* structure poll see doc linux */

   .struct  0

poll_event: // events mask

   .struct  poll_event + 8

poll_fd: // events returned

   .struct  poll_fd  + 8

poll_fin: /*********************************/ /* Initialized data */ /*********************************/ .data szMessOK: .asciz "Bravo !! You win. \n" szMessNotOK: .asciz "You lost !! \n" szMessNewGame: .asciz "New game (y/n) ? \n" szMessErreur: .asciz "Error detected.\n" szMessErrInitTerm: .asciz "Error terminal init.\n" szMessErrInitPoll: .asciz "Error poll init.\n" szMessErreurKey: .asciz "Error read key.\n" szMessErr: .asciz "Error code hexa : @ décimal : @ \n" szCarriageReturn: .asciz "\n" szMess0: .asciz " " szMess2: .asciz " 2 " szMess4: .asciz " 4 " szMess8: .asciz " 8 " szMess16: .asciz " 16 " szMess32: .asciz " 32 " szMess64: .asciz " 64 " szMess128: .asciz " 128 " szMess256: .asciz " 256 " szMess512: .asciz " 512 " szMess1024: .asciz " 1024 " szMess2048: .asciz " 2048 " szCleax1: .byte 0x1B

                   .byte 'c'           // other console clear
                   .byte 0

szLineH: .asciz "-----------------------------\n" szLineV: .asciz "|" szLineVT: .asciz "| | | | |\n" .align 4 qGraine: .quad 123456 /*********************************/ /* UnInitialized data */ /*********************************/ .bss .align 4 sZoneConv: .skip 24 sBuffer: .skip BUFFERSIZE qTbCase: .skip 8 * SIZE * SIZE qEnd: .skip 8 // 0 loop 1 = end loop qTouche: .skip KEYSIZE // value key pressed stOldtio: .skip term_fin // old terminal state stCurtio: .skip term_fin // current terminal state stSigAction: .skip sa_fin // area signal structure stSigAction1: .skip sa_fin stSigAction2: .skip sa_fin stSigAction3: .skip sa_fin stPoll1: .skip poll_fin // area poll structure stPoll2: .skip poll_fin stevents: .skip 16 /*********************************/ /* code section */ /*********************************/ .text .global main main: // entry of program

   mov x0,#0
   bl initTerm                      // terminal init
   cmp x0,0                         // error ?
   blt 100f
   bl initPoll                      // epoll instance init
   cmp x0,0
   blt 99f
   mov x22,x0                        // save epfd

1: // begin game loop

   ldr x0,qAdrszCleax1
   bl affichageMess
   bl razTable

2:

   bl addDigit   
   cmp x0,#-1
   beq 5f                            // end game
   bl displayGame

3:

   mov x0,x22
   bl waitKey
   cmp x0,0
   beq 3b
   bl readKey
   cmp x0,#-1
   beq 99f                          // error
   bl keyMove
   cmp x0,#0
   beq 3b                            // no change -> loop
   cmp x0,#2                         // last addition = 2048 ?
   beq 4f
   cmp x0,#-1                        // quit ?
   bne 2b                            // loop
   
   b 10f

4: // last addition = 2048

   ldr x0,qAdrszMessOK
   bl affichageMess
   b 10f

5: // display message no solution

   ldr x0,qAdrszMessNotOK
   bl affichageMess

10: // display new game ?

   ldr x0,qAdrszCarriageReturn
   bl affichageMess
   ldr x0,qAdrszMessNewGame
   bl affichageMess

11:

   mov x0,x22
   bl waitKey
   cmp x0,0
   beq 11b
   bl readKey
   ldr x0,qAdrqTouche
   ldrb w0,[x0]
   cmp w0,#'y'
   beq 1b
   cmp w0,#'Y'
   beq 1b

99:

   bl restauTerm                     // terminal restaur

100: // standard end of the program

   mov x0, #0                        // return code
   mov x8, #EXIT                     // request to exit program
   svc #0                            // perform the system call

qAdrszCarriageReturn: .quad szCarriageReturn qAdrszMessNotOK: .quad szMessNotOK qAdrszMessOK: .quad szMessOK qAdrszMessNewGame: .quad szMessNewGame qAdrsZoneConv: .quad sZoneConv qAdrszCleax1: .quad szCleax1 qAdrszMessErrInitTerm: .quad szMessErrInitTerm qAdrszMessErrInitPoll: .quad szMessErrInitPoll qAdrszMessErreurKey: .quad szMessErreurKey qAdrstOldtio: .quad stOldtio qAdrstCurtio: .quad stCurtio qAdrstSigAction: .quad stSigAction qAdrstSigAction1: .quad stSigAction1 qAdrSIG_IGN: .quad 1 qAdrqEnd: .quad qEnd qAdrqTouche: .quad qTouche qAdrstevents: .quad stevents /******************************************************************/ /* raz table cases */ /******************************************************************/ razTable:

   stp x0,lr,[sp,-16]!     // save  registres
   stp x1,x2,[sp,-16]!     // save  registres
   ldr x1,qAdrqTbCase
   mov x2,#0

1:

   str xzr,[x1,x2,lsl #3]
   add x2,x2,#1
   cmp x2,#SIZE * SIZE
   blt 1b

100:

   ldp x1,x2,[sp],16       // restaur des  2 registres
   ldp x0,lr,[sp],16       // restaur des  2 registres
   ret

/******************************************************************/ /* key move */ /******************************************************************/ /* x0 contains key value */ keyMove:

   stp x1,lr,[sp,-16]!     // save  registres
   lsr x0,x0,#16
   cmp x0,#0x42                  // down arrow 
   bne 1f
   bl moveDown
   b 100f

1:

   cmp x0,#0x41                  // high arrow
   bne 2f
   bl moveUp
   b 100f

2:

   cmp x0,#0x43                  // right arrow
   bne 3f
   bl moveRight
   b 100f

3:

   cmp x0,#0x44                  // left arrow
   bne 4f
   bl moveLeft
   b 100f

4:

   ldr x0,qAdrqTouche
   ldrb w0,[x0]
   cmp w0,#'q'                   // quit game
   bne 5f
   mov x0,#-1
   b 100f

5:

   cmp w0,#'Q'                   // quit game
   bne 100f
   mov x0,#-1
   b 100f

100:

   ldp x1,lr,[sp],16       // restaur des  2 registres
   ret

/******************************************************************/ /* move left */ /******************************************************************/ /* x0 return -1 if ok */ moveLeft:

   stp x1,lr,[sp,-16]!          // save  registres
   stp x2,x3,[sp,-16]!          // save  registres
   stp x4,x5,[sp,-16]!          // save  registres
   stp x6,x7,[sp,-16]!          // save  registres
   stp x8,x9,[sp,-16]!          // save  registres
   stp x10,x11,[sp,-16]!        // save  registres
   ldr x1,qAdrqTbCase
   mov x0,#0                   // top move Ok
   mov x2,#0                   // line indice

1:

   mov x6,#0                   // counter empty case
   mov x7,#0                   // first digit
   mov x10,#0                  // last digit to add
   mov x3,#0                   // column indice

2:

   lsl x5,x2,#2                // change this if size <> 4
   add x5,x5,x3                // compute table indice
   ldr x4,[x1,x5,lsl #3]
   cmp x4,#0
   cinc x6,x6,eq               // positions vides
   beq 5f
   cmp x6,#0
   beq 3f                      // no empty left case
   mov x8,#0
   str x8,[x1,x5,lsl #3]       // raz digit
   sub x5,x5,x6
   str x4,[x1,x5,lsl #3]       // and store to left empty position
   mov x0,#1                   // move Ok

3:

   cmp x7,#0                   // first digit
   beq 4f
   cmp x10,x4                  // prec digit have to add 
   beq 4f
   sub x8,x5,#1                // prec digit 
   ldr x9,[x1,x8,lsl #3]
   cmp x4,x9                   // equal ?
   bne 4f
   mov x10,x4                  // save digit 
   add x4,x4,x9                // yes -> add
   str x4,[x1,x8,lsl #3]
   cmp x4,#TOTAL
   beq 6f
   mov x4,#0
   str x4,[x1,x5,lsl #3]
   add x6,x6,#1                // empty case + 1
   mov x0,#1                   // move Ok

4:

   add x7,x7,#1                // no first digit

5: // and loop

   add x3,x3,#1
   cmp x3,#SIZE
   blt 2b
   add x2,x2,#1
   cmp x2,#SIZE
   blt 1b
   b 100f

6:

   mov x0,#2                   // total = 2048

100:

   ldp x10,x11,[sp],16         // restaur des  2 registres
   ldp x8,x9,[sp],16           // restaur des  2 registres
   ldp x6,x7,[sp],16           // restaur des  2 registres
   ldp x4,x5,[sp],16           // restaur des  2 registres
   ldp x2,x3,[sp],16           // restaur des  2 registres
   ldp x1,lr,[sp],16           // restaur des  2 registres
   ret

/******************************************************************/ /* move right */ /******************************************************************/ /* x0 return -1 if ok */ moveRight:

   stp x1,lr,[sp,-16]!          // save  registres
   stp x2,x3,[sp,-16]!          // save  registres
   stp x4,x5,[sp,-16]!          // save  registres
   stp x6,x7,[sp,-16]!          // save  registres
   stp x8,x9,[sp,-16]!          // save  registres
   stp x10,x11,[sp,-16]!        // save  registres
   ldr x1,qAdrqTbCase
   mov x0,#0
   mov x2,#0

1:

   mov x6,#0
   mov x7,#0
   mov x10,#0
   mov x3,#SIZE-1

2:

   lsl x5,x2,#2                  // change this if size <> 4
   add x5,x5,x3
   ldr x4,[x1,x5,lsl #3]
   cmp x4,#0
   cinc x6,x6,eq               // positions vides
   beq 5f
   cmp x6,#0
   beq 3f                      // no empty right case
   mov x0,#0
   str x0,[x1,x5,lsl #3]       // raz digit
   add x5,x5,x6
   str x4,[x1,x5,lsl #3]       // and store to right empty position
   mov x0,#1

3:

   cmp x7,#0                   // first digit
   beq 4f
   add x8,x5,#1                // next digit 
   ldr x9,[x1,x8,lsl #3]
   cmp x4,x9                   // equal ?
   bne 4f
   cmp x10,x4
   beq 4f
   mov x10,x4
   add x4,x4,x9                // yes -> add
   str x4,[x1,x8,lsl #3]
   cmp x4,#TOTAL
   beq 6f
   mov x4,#0
   str x4,[x1,x5,lsl #3]
   add x6,x6,#1                // empty case + 1
   mov x0,#1

4:

   add x7,x7,#1                // no first digit

5: // and loop

   sub x3,x3,#1
   cmp x3,#0
   bge 2b
   add x2,x2,#1
   cmp x2,#SIZE
   blt 1b
   b 100f

6:

   mov x0,#2

100:

   ldp x10,x11,[sp],16         // restaur des  2 registres
   ldp x8,x9,[sp],16           // restaur des  2 registres
   ldp x6,x7,[sp],16           // restaur des  2 registres
   ldp x4,x5,[sp],16           // restaur des  2 registres
   ldp x2,x3,[sp],16           // restaur des  2 registres
   ldp x1,lr,[sp],16           // restaur des  2 registres
   ret

/******************************************************************/ /* move down */ /******************************************************************/ /* x0 return -1 if ok */ moveDown:

   stp x1,lr,[sp,-16]!          // save  registres
   stp x2,x3,[sp,-16]!          // save  registres
   stp x4,x5,[sp,-16]!          // save  registres
   stp x6,x7,[sp,-16]!          // save  registres
   stp x8,x9,[sp,-16]!          // save  registres
   stp x10,x11,[sp,-16]!        // save  registres
   ldr x1,qAdrqTbCase
   mov x0,#0
   mov x3,#0

1:

   mov x6,#0
   mov x7,#0
   mov x10,#0
   mov x2,#SIZE-1

2:

   lsl x5,x2,#2                  // change this if size <> 4
   add x5,x5,x3
   ldr x4,[x1,x5,lsl #3]
   cmp x4,#0
   cinc x6,x6,eq               // positions vides
   beq 5f
   cmp x6,#0
   beq 3f                      // no empty right case
   mov x0,#0
   str x0,[x1,x5,lsl #3]       // raz digit
   lsl x0,x6,#2
   add x5,x5,x0
   str x4,[x1,x5,lsl #3]       // and store to right empty position
   mov x0,#1

3:

   cmp x7,#0                   // first digit
   beq 4f
   add x8,x5,#SIZE                // down digit 
   ldr x9,[x1,x8,lsl #3]
   cmp x4,x9                   // equal ?
   bne 4f
   cmp x10,x4
   beq 4f
   mov x10,x4
   add x4,x4,x9                // yes -> add
   str x4,[x1,x8,lsl #3]
   cmp x4,#TOTAL
   beq 6f
   mov x4,#0
   str x4,[x1,x5,lsl #3]
   add x6,x6,#1                // empty case + 1
   mov x0,#1

4:

   add x7,x7,#1                   // no first digit

5: // and loop

   sub x2,x2,#1
   cmp x2,#0
   bge 2b
   add x3,x3,#1
   cmp x3,#SIZE
   blt 1b
   b 100f

6:

   mov x0,#2

100:

   ldp x10,x11,[sp],16         // restaur des  2 registres
   ldp x8,x9,[sp],16           // restaur des  2 registres
   ldp x6,x7,[sp],16           // restaur des  2 registres
   ldp x4,x5,[sp],16           // restaur des  2 registres
   ldp x2,x3,[sp],16           // restaur des  2 registres
   ldp x1,lr,[sp],16           // restaur des  2 registres
   ret

/******************************************************************/ /* move up */ /******************************************************************/ /* x0 return -1 if ok */ moveUp:

   stp x1,lr,[sp,-16]!          // save  registres
   stp x2,x3,[sp,-16]!          // save  registres
   stp x4,x5,[sp,-16]!          // save  registres
   stp x6,x7,[sp,-16]!          // save  registres
   stp x8,x9,[sp,-16]!          // save  registres
   stp x10,x11,[sp,-16]!        // save  registres
   ldr x1,qAdrqTbCase
   mov x0,#0
   mov x3,#0

1:

   mov x6,#0
   mov x7,#0
   mov x10,#0
   mov x2,#0

2:

   lsl x5,x2,#2                  // change this if size <> 4
   add x5,x5,x3
   ldr x4,[x1,x5,lsl #3]
   cmp x4,#0
   cinc x6,x6,eq               // positions vides
   beq 5f
   cmp x6,#0
   beq 3f                      // no empty right case
   mov x0,#0
   str x0,[x1,x5,lsl #3]       // raz digit
   lsl x0,x6,#2
   sub x5,x5,x0
   str x4,[x1,x5,lsl #3]       // and store to right empty position
   mov x0,#1

3:

   cmp x7,#0                   // first digit
   beq 4f
   sub x8,x5,#SIZE             // up digit 
   ldr x9,[x1,x8,lsl #3]
   cmp x4,x9                   // equal ?
   bne 4f
   cmp x10,x4
   beq 4f
   mov x10,x4
   add x4,x4,x9                // yes -> add
   str x4,[x1,x8,lsl #3]
   cmp x4,#TOTAL
   beq 6f
   mov x4,#0
   str x4,[x1,x5,lsl #3]
   add x6,x6,#1                // empty case + 1
   mov x0,#1

4:

   add x7,x7,#1                // no first digit

5: // and loop

   add x2,x2,#1
   cmp x2,#SIZE
   blt 2b
   add x3,x3,#1
   cmp x3,#SIZE
   blt 1b
   b 100f

6:

   mov x0,#2

100:

   ldp x10,x11,[sp],16         // restaur des  2 registres
   ldp x8,x9,[sp],16           // restaur des  2 registres
   ldp x6,x7,[sp],16           // restaur des  2 registres
   ldp x4,x5,[sp],16           // restaur des  2 registres
   ldp x2,x3,[sp],16           // restaur des  2 registres
   ldp x1,lr,[sp],16           // restaur des  2 registres
   ret

/******************************************************************/ /* add new digit on game */ /******************************************************************/ /* x0 return -1 if ok */ addDigit:

   stp x1,lr,[sp,-16]!          // save  registres
   stp x2,x3,[sp,-16]!          // save  registres
   stp x4,x5,[sp,-16]!          // save  registres
   sub sp,sp,#8 * SIZE*SIZE
   mov fp,sp
   
   mov x0,#100
   bl genereraleas
   cmp x0,#10
   mov x1,#4
   mov x5,#2
   csel x5,x5,x1,ge
  // movlt x5,#4
   //movge x5,#2
   ldr x1,qAdrqTbCase
   mov x3,#0
   mov x4,#0

1:

   ldr x2,[x1,x3,lsl 3]
   cmp x2,#0
   bne 2f
   str x3,[fp,x4,lsl 3]
   add x4,x4,#1

2:

   add x3,x3,#1
   cmp x3,#SIZE*SIZE
   blt 1b
   cmp x4,#0                 // no empty case
   beq 4f
   cmp x4,#1
   bne 3f
   ldr x2,[fp]               // one case
   str x5,[x1,x2,lsl 3]
   mov x0,#0
   b 100f

3: // multiple case

   sub x0,x4,#1
   bl genereraleas
   ldr x2,[fp,x0,lsl 3]
   str x5,[x1,x2,lsl 3]
   mov x0,#0
   b 100f

4:

   mov x0,#-1

100:

   add sp,sp,#8*  (SIZE*SIZE)  // stack alignement
   ldp x4,x5,[sp],16           // restaur des  2 registres
   ldp x2,x3,[sp],16           // restaur des  2 registres
   ldp x1,lr,[sp],16           // restaur des  2 registres
   ret

qAdrqTbCase: .quad qTbCase /******************************************************************/ /* display game */ /******************************************************************/ displayGame:

   stp x1,lr,[sp,-16]!          // save  registres
   stp x2,x3,[sp,-16]!          // save  registres
   ldr x0,qAdrszCleax1
   bl affichageMess
   ldr x0,qAdrszLineH
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess
   ldr x1,qAdrqTbCase
   mov x2,#0

1:

   ldr x0,[x1,x2,lsl #3]
   bl digitString
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess
   add x2,x2,#1
   cmp x2,#SIZE
   blt 1b
   ldr x0,qAdrszCarriageReturn
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineH
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess

2:

   ldr x0,[x1,x2,lsl #3]
   bl digitString
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess
   add x2,x2,#1
   cmp x2,#SIZE*2
   blt 2b
   ldr x0,qAdrszCarriageReturn
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineH
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess

3:

   ldr x0,[x1,x2,lsl #3]
   bl digitString
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess
   add x2,x2,#1
   cmp x2,#SIZE*3
   blt 3b
   ldr x0,qAdrszCarriageReturn
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineH
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess

4:

   ldr x0,[x1,x2,lsl #3]
   bl digitString
   bl affichageMess
   ldr x0,qAdrszLineV
   bl affichageMess
   add x2,x2,#1
   cmp x2,#SIZE*4
   blt 4b
   ldr x0,qAdrszCarriageReturn
   bl affichageMess
   ldr x0,qAdrszLineVT
   bl affichageMess
   ldr x0,qAdrszLineH
   bl affichageMess

100:

   ldp x2,x3,[sp],16           // restaur des  2 registres
   ldp x1,lr,[sp],16           // restaur des  2 registres
   ret

qAdrszLineH: .quad szLineH qAdrszLineV: .quad szLineV qAdrszLineVT: .quad szLineVT /******************************************************************/ /* digits string */ /******************************************************************/ /* x0 contains number */ /* x0 return address string */ digitString:

   stp x1,lr,[sp,-16]!          // save  registres
   cmp x0,#0
   bne 1f
   ldr x0,qAdrszMess0
   b 100f

1:

   cmp x0,#2
   bne 2f
   ldr x0,qAdrszMess2
   b 100f

2:

   cmp x0,#4
   bne 3f
   ldr x0,qAdrszMess4
   b 100f

3:

   cmp x0,#8
   bne 4f
   ldr x0,qAdrszMess8
   b 100f

4:

   cmp x0,#16
   bne 5f
   ldr x0,qAdrszMess16
   b 100f

5:

   cmp x0,#32
   bne 6f
   ldr x0,qAdrszMess32
   b 100f

6:

   cmp x0,#64
   bne 7f
   ldr x0,qAdrszMess64
   b 100f

7:

   cmp x0,#128
   bne 8f
   ldr x0,qAdrszMess128
   b 100f

8:

   cmp x0,#256
   bne 9f
   ldr x0,qAdrszMess256
   b 100f

9:

   cmp x0,#512
   bne 10f
   ldr x0,qAdrszMess512
   b 100f

10:

   cmp x0,#1024
   bne 11f
   ldr x0,qAdrszMess1024
   b 100f

11:

   cmp x0,#2048
   bne 12f
   ldr x0,qAdrszMess2048
   b 100f

12:

   ldr x1,qAdrszMessErreur                       // error message
   bl   displayError

100:

   ldp x1,lr,[sp],16           // restaur des  2 registres
   ret

qAdrszMess0: .quad szMess0 qAdrszMess2: .quad szMess2 qAdrszMess4: .quad szMess4 qAdrszMess8: .quad szMess8 qAdrszMess16: .quad szMess16 qAdrszMess32: .quad szMess32 qAdrszMess64: .quad szMess64 qAdrszMess128: .quad szMess128 qAdrszMess256: .quad szMess256 qAdrszMess512: .quad szMess512 qAdrszMess1024: .quad szMess1024 qAdrszMess2048: .quad szMess2048

//qAdrsBuffer: .quad sBuffer qAdrszMessErreur : .quad szMessErreur

/***************************************************/ /* Generation random number */ /***************************************************/ /* x0 contains limit */ genereraleas:

   stp x1,lr,[sp,-16]!            // save  registers
   stp x2,x3,[sp,-16]!            // save  registers
   ldr x1,qAdrqGraine
   ldr x2,[x1]
   ldr x3,qNbDep1
   mul x2,x3,x2
   ldr x3,qNbDep2
   add x2,x2,x3
   str x2,[x1]                    // maj de la graine pour l appel suivant 
   cmp x0,#0
   beq 100f
   udiv x3,x2,x0
   msub x0,x3,x0,x2               // résult = remainder

100: // end function

   ldp x2,x3,[sp],16              // restaur  2 registers
   ldp x1,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

/*****************************************************/ qAdrqGraine: .quad qGraine qNbDep1: .quad 0x0019660d qNbDep2: .quad 0x3c6ef35f

/******************************************************************/ /* traitement du signal */ /******************************************************************/ sighandler:

   stp x0,lr,[sp,-16]!            // save  registers
   str x1,[sp,-16]! 
   ldr x0,qAdrqEnd
   mov x1,#1                      // maj zone end
   str x1,[x0]
   ldr x1,[sp],16
   ldp x0,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

/***************************************************/ /* display error message */ /***************************************************/ /* x0 contains error code x1 : message address */ displayError:

   stp x2,lr,[sp,-16]!            // save  registers
   mov x2,x0                      // save error code
   mov x0,x1
   bl affichageMess
   mov x0,x2                      // error code
   ldr x1,qAdrsZoneConv
   bl conversion16                // conversion hexa
   ldr x0,qAdrszMessErr           // display error message
   ldr x1,qAdrsZoneConv
   bl strInsertAtCharInc               // insert result at @ character
   mov x3,x0
   mov x0,x2                      // error code
   ldr x1,qAdrsZoneConv               // result address
   bl conversion10S                // conversion decimale
   mov x0,x3
   ldr x1,qAdrsZoneConv
   bl strInsertAtCharInc               // insert result at @ character
   bl affichageMess

100:

   ldp x2,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

qAdrszMessErr: .quad szMessErr /*********************************/ /* init terminal state */ /*********************************/ initTerm:

   stp x1,lr,[sp,-16]!            // save  registers
   /* read terminal state */
   mov x0,STDIN                   // input console
   mov x1,TCGETS
   ldr x2,qAdrstOldtio
   mov x8,IOCTL                   // call system Linux
   svc 0 
   cbnz x0,98f                    // error ?

   adr x0,sighandler              // adresse routine traitement signal
   ldr x1,qAdrstSigAction         // adresse structure sigaction
   str x0,[x1,sa_handler]         // maj handler
   mov x0,SIGINT                  // signal type
   ldr x1,qAdrstSigAction
   mov x2,0
   mov x3,8
   mov x8,SIGACTION               // call system
   svc 0 

   cmp x0,0                       // error ?
   bne 98f
   mov x0,SIGQUIT
   ldr x1,qAdrstSigAction
   mov x2,0                       // NULL
   mov x8,SIGACTION               // call system 
   svc 0 
   cmp x0,0                       // error ?
   bne 98f
   mov x0,SIGTERM
   ldr x1,qAdrstSigAction
   mov x2,0                       // NULL
   mov x8,SIGACTION               // appel systeme 
   svc 0 
   cmp x0,0
   bne 98f
   //
   adr x0,qAdrSIG_IGN             // address signal igonre function
   ldr x1,qAdrstSigAction1
   str x0,[x1,sa_handler]
   mov x0,SIGTTOU                 //invalidate other process signal
   ldr x1,qAdrstSigAction1
   mov x2,0                       // NULL
   mov x8,SIGACTION               // call system 
   svc 0 
   cmp x0,0
   bne 98f
   //
   /* read terminal current state  */
   mov x0,STDIN
   mov x1,TCGETS
   ldr x2,qAdrstCurtio            // address current termio
   mov x8,IOCTL                   // call systeme 
   svc 0 
   cmp x0,0                       // error ?
   bne 98f
   mov x2,ICANON | ECHO           // no key pressed echo on display
   mvn x2,x2                      // and one key 
   ldr x1,qAdrstCurtio
   ldr x3,[x1,#term_c_lflag]
   and x3,x2,x2                   // add flags 
   str x3,[x1,#term_c_lflag]      // and store
   mov x0,STDIN                   // maj terminal current state 
   mov x1,TCSETS
   ldr x2,qAdrstCurtio
   mov x8,IOCTL                   // call system
   svc 0 
   cbz x0,100f

98: // error display

   ldr x1,qAdrszMessErrInitTerm
   bl   displayError
   mov x0,-1

100:

   ldp x1,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

qAdrstSigAction2: .quad stSigAction2 qAdrstSigAction3: .quad stSigAction3 /*********************************/ /* init instance epool */ /*********************************/ initPoll:

   stp x1,lr,[sp,-16]!            // save  registers
   ldr x0,qAdrstevents
   mov x1,STDIN                   // maj structure events
   str x1,[x0,#poll_fd]           // maj FD
   mov x1,POLLIN                  // action code
   str x1,[x0,#poll_event]
   mov x0,0
   mov x8,CREATPOLL               // create epoll instance
   svc 0
   cmp x0,0                       // error ?
   ble 98f
   mov x10,x0                     // return FD epoll instance
   mov x1,EPOLL_CTL_ADD
   mov x2,STDIN                   // Fd to we want add
   ldr x3,qAdrstevents            // structure events address
   mov x8,CTLPOLL                 // call system control epoll
   svc 0
   cmp x0,0                       // error ?
   blt 98f                       // no
   mov x0,x10                     // return FD epoll instance
   b 100f

98: // error display

   ldr x1,qAdrszMessErrInitPoll   // error message
   bl   displayError
   mov x0,-1

100:

   ldp x1,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

/*********************************/ /* wait key */ /*********************************/ /* x0 contains FD poll */ waitKey:

   stp x1,lr,[sp,-16]!            // save  registers
   ldr x11,qAdrqTouche            // key address
   str xzr,[x11]                  // raz key

1:

   ldr x1,qAdrqEnd                // if signal ctrl-c  -> end
   ldr x1,[x1]
   cbnz x1,100f

   ldr x1,qAdrstevents
   mov x2,12                      // size events
   mov x3,1                       // timeout = 1  TODO: ??
   mov x4,0
   mov x8,SYSPOLL                 // call system wait POLL
   svc 0 
   cmp x0,0                       // key pressed ?
   bge 100f

98: // error display

   ldr x1,qAdrszMessErreurKey        // error message
   bl   displayError
   mov x0,-1

100:

   ldp x1,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

/*********************************/ /* read key */ /*********************************/ /* x0 returns key value */ readKey:

   stp x1,lr,[sp,-16]!            // save  registers
   mov x0,STDIN                   // File Descriptor
   ldr x1,qAdrqTouche             // buffer address
   mov x2,KEYSIZE                 // key size
   mov x8,READ                    // read key
   svc #0
   cmp x0,0                       // error ?
   ble 98f
   ldr x2,qAdrqTouche             // key address
   ldr x0,[x2]
   b 100f

98: // error display

   ldr x1,qAdrszMessErreur        // error message
   bl   displayError
   mov x0,-1

100:

   ldp x1,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

/*********************************/ /* restaur terminal state */ /*********************************/ restauTerm:

   stp x1,lr,[sp,-16]!            // save  registers
   mov x0,STDIN                   // end then restaur begin state terminal
   mov x1,TCSETS
   ldr x2,qAdrstOldtio
   mov x8,IOCTL                   // call system  
   svc 0
   cbz x0,100f
   ldr x1,qAdrszMessErreur        // error message
   bl   displayError

100:

   ldp x1,lr,[sp],16              // restaur  2 registers
   ret                            // return to address lr x30

/********************************************************/ /* File Include fonctions */ /********************************************************/ /* for this file see task include a file in language AArch64 assembly */ .include "../includeARM64.inc" </lang>

Ada

Works with: GNAT

<lang Ada>with Ada.Text_IO; use Ada.Text_IO; with System.Random_Numbers; procedure Play_2048 is

  -- ----- Keyboard management
  type t_Keystroke is (Up, Down, Right, Left, Quit, Restart, Invalid);
  -- Redefining this standard procedure as function to allow Get_Keystroke as an expression function
  function Get_Immediate return Character is
  begin
     return Answer : Character do Ada.Text_IO.Get_Immediate(Answer);
     end return;
  end Get_Immediate;
  Arrow_Prefix : constant Character := Character'Val(224); -- works for windows
  function Get_Keystroke return t_Keystroke is
    (case Get_Immediate is
        when 'Q' | 'q' => Quit,
        when 'R' | 'r' => Restart,
        when 'W' | 'w' => Left,
        when 'A' | 'a' => Up,
        when 'S' | 's' => Down,
        when 'D' | 'd' => Right,
        -- Windows terminal
        when Arrow_Prefix => (case Character'Pos(Get_Immediate) is
                                 when 72 => Up,
                                 when 75 => Left,
                                 when 77 => Right,
                                 when 80 => Down,
                                 when others => Invalid),
        -- Unix escape sequences
        when ASCII.ESC => (case Get_Immediate is
                              when '[' => (case Get_Immediate is
                                              when 'A' => Up,
                                              when 'B' => Down,
                                              when 'C' => Right,
                                              when 'D' => Left,
                                              when others => Invalid),
                              when others => Invalid),
        when others => Invalid);
  -- ----- Game data
  function Random_Int is new System.Random_Numbers.Random_Discrete(Integer);
  type    t_List  is array (Positive range <>) of Natural;
  subtype t_Row   is t_List (1..4);
  type    t_Board is array  (1..4) of t_Row;
  Board     : t_Board;
  New_Board : t_Board;
  Blanks    : Natural;
  Score     : Natural;
  Generator : System.Random_Numbers.Generator;
  -- ----- Displaying the board
  procedure Display_Board is
     Horizontal_Rule : constant String := "+----+----+----+----+";
     function Center (Value : in String) return String is
       ((1..(2-(Value'Length-1)/2) => ' ') & -- Add leading spaces
        Value(Value'First+1..Value'Last)   & -- Trim the leading space of the raw number image
        (1..(2-Value'Length/2) => ' '));     -- Add trailing spaces
  begin
     Put_Line (Horizontal_Rule);
     for Row of Board loop
        for Cell of Row loop
           Put('|' & (if Cell = 0 then "    " else Center(Cell'Img)));
        end loop;
        Put_Line("|");
        Put_Line (Horizontal_Rule);
     end loop;
     Put_Line("Score =" & Score'Img);
  end Display_Board;
  -- ----- Game mechanics
  procedure Add_Block is
     Block_Offset : Positive := Random_Int(Generator, 1, Blanks);
  begin
     Blanks := Blanks-1;
     for Row of Board loop
        for Cell of Row loop
           if Cell = 0 then
              if Block_Offset = 1 then
                 Cell := (if Random_Int(Generator,1,10) = 1 then 4 else 2);
                 return;
              else
                 Block_Offset := Block_Offset-1;
              end if;
           end if;
        end loop;
     end loop;
  end Add_Block;
  procedure Reset_Game is
  begin
     Board  := (others => (others => 0));
     Blanks := 16;
     Score  := 0;
     Add_Block;
     Add_Block;
  end Reset_Game;
  -- Moving and merging will always be performed leftward, hence the following transforms
  function HFlip (What : in t_Row) return t_Row is
    (What(4),What(3),What(2),What(1));
  function VFlip (What : in t_Board) return t_Board is
    (HFlip(What(1)),HFlip(What(2)),HFlip(What(3)),HFlip(What(4)));
  function Transpose (What : in t_Board) return t_Board is
  begin
     return Answer : t_Board do
        for Row in t_Board'Range loop
           for Column in t_Row'Range loop
              Answer(Column)(Row) := What(Row)(Column);
           end loop;
        end loop;
     end return;
  end Transpose;
  -- For moving/merging, recursive expression functions will be used, but they
  -- can't contain statements, hence the following sub-function used by Merge
  function Add_Blank (Delta_Score : in Natural) return t_List is
  begin
     Blanks := Blanks+1;
     Score  := Score+Delta_Score;
     return (1 => 0);
  end Add_Blank;
  function Move_Row (What : in t_List) return t_List is
    (if What'Length = 1 then What
     elsif What(What'First) = 0
     then Move_Row(What(What'First+1..What'Last)) & (1 => 0)
     else (1 => What(What'First)) & Move_Row(What(What'First+1..What'Last)));
  function Merge (What : in t_List) return t_List is
    (if What'Length <= 1 or else What(What'First) = 0 then What
     elsif What(What'First) = What(What'First+1)
     then (1 => 2*What(What'First)) & Merge(What(What'First+2..What'Last)) & Add_Blank(What(What'First))
     else (1 => What(What'First)) & Merge(What(What'First+1..What'Last)));
  function Move (What : in t_Board) return t_Board is
    (Merge(Move_Row(What(1))),Merge(Move_Row(What(2))),Merge(Move_Row(What(3))),Merge(Move_Row(What(4))));

begin

  System.Random_Numbers.Reset(Generator);
  Reset_Game;
  Main_Game_Loop: loop
     Display_Board;
     case Get_Keystroke is
        when Restart => Reset_Game;
        when Quit    => exit Main_Game_Loop;
        when Left    => New_Board := Move(Board);
        when Right   => New_Board := VFlip(Move(VFlip(Board)));
        when Up      => New_Board := Transpose(Move(Transpose(Board)));
        when Down    => New_Board := Transpose(VFlip(Move(VFlip(Transpose(Board)))));
        when others  => null;
     end case;
     if New_Board = Board then
        Put_Line ("Invalid move...");
     elsif (for some Row of New_Board => (for some Cell of Row => Cell = 2048)) then
        Display_Board;
        Put_Line ("Win !");
        exit Main_Game_Loop;
     else
        Board := New_Board;
        Add_Block; -- OK since the board has changed
        if Blanks = 0
           and then (for all Row in 1..4 =>
                       (for all Column in 1..3 =>
                            (Board(Row)(Column) /= Board(Row)(Column+1))))
           and then (for all Row in 1..3 =>
                       (for all Column in 1..4 =>
                            (Board(Row)(Column) /= Board(Row+1)(Column)))) then
           Display_Board;
           Put_Line ("Lost !");
           exit Main_Game_Loop;
        end if;
     end if;
  end loop Main_Game_Loop;

end Play_2048;</lang>

Output:
+----+----+----+----+
|  2 | 16 |  2 |  2 |
+----+----+----+----+
| 64 |    |    |    |
+----+----+----+----+
|  4 |    |    |    |
+----+----+----+----+
|    |    |    |    |
+----+----+----+----+
Score = 184

ALGOL 68

<lang algol68> main:( INT side = 4; INT right = 1, up = 2, left = 3, down = 4; []CHAR direction letters = "ruld"; []STRING direction descriptions = ("right", "up", "left", "down");

MODE BOARD = REF[,]INT; MODE CELL = REF INT;

OP = = (BOARD a, BOARD b) BOOL:

 (FOR i TO side DO FOR j TO side DO IF a[i,j] /= b[i,j] THEN mismatch FI OD OD;
 TRUE EXIT
 mismatch: FALSE);

PROC traverse board = (BOARD board, PROC(CELL)VOID callback) VOID:

 FOR i FROM 1 LWB board TO 1 UPB board DO
   FOR j FROM 2 LWB board TO 2 UPB board DO
     callback(board[i,j])
 OD OD;

PROC count blanks = (BOARD board) INT:

 (INT count := 0;
  traverse board(board, (CELL c)VOID: IF c = 0 THEN count +:= 1 FI);
  count);

PROC nth blank = (BOARD board, INT nth) CELL:

 (CELL result;
  INT count := 0;
  traverse board(board, (CELL c)VOID:
                          (IF c = 0 THEN count +:= 1 FI;
                           IF count = nth THEN
                             result := c; return
                           FI));
  return: result);

PROC add new number = (BOARD board) VOID:

 (INT nblanks = count blanks(board);
  INT number   := (random >= .9 | 4 | 2);
  INT position := ENTIER (random * nblanks) + 1;
  nth blank(board, position) := number);

PROC shift = (REF[]INT row, BOOL to the end) VOID:

 (INT from = (to the end | UPB row | LWB row),
      to   = (to the end | LWB row | UPB row),
      dir  = (to the end | -1 | 1);
  FOR i FROM from + dir BY dir TO to  DO
    IF row[i] /= 0 THEN
      INT blank := 0;
      FOR j FROM i - dir BY -dir TO from WHILE row[j] = 0 DO
        blank := j
      OD;
      IF blank /= 0 THEN
        row[blank] := row[i];
        row[i] := 0
      FI
    FI
  OD);

PROC combine = (REF[]INT row, BOOL to the end) VOID:

 (INT from = (to the end | UPB row | LWB row),
      to   = (to the end | LWB row | UPB row),
      dir  = (to the end | -1 | 1);
  FOR i FROM from BY dir TO to - dir DO
    IF row[i] /= 0 AND row[i] = row[i+dir] THEN
      row[i] *:= 2;
      row[i+dir] := 0
    FI
  OD);

PROC move = (BOARD board, INT direction) VOID:

 FOR i TO side DO
   CASE direction IN
     # right # (shift(board[i,], TRUE);  combine(board[i,], TRUE);  shift(board[i,], TRUE)),
     # up    # (shift(board[,i], FALSE); combine(board[,i], FALSE); shift(board[,i], FALSE)),
     # left  # (shift(board[i,], FALSE); combine(board[i,], FALSE); shift(board[i,], FALSE)),
     # down  # (shift(board[,i], TRUE);  combine(board[,i], TRUE);  shift(board[,i], TRUE))
   ESAC
 OD;

PROC print board = (BOARD board)VOID:

 (FOR i FROM 1 LWB board TO 1 UPB board DO
    print("+");
    FOR j FROM 2 LWB board TO 2 UPB board DO print("------+") OD;
    print((new line, "|"));
    FOR j FROM 2 LWB board TO 2 UPB board DO
      print(((board[i,j] = 0 | "     " | whole(board[i,j],-5)), " |"))
    OD;
    print(new line)
  OD;
  print("+"); FOR j FROM 2 LWB board TO 2 UPB board DO print("------+") OD;
  print(new line)
 );

PROC score = (BOARD board) INT:

 (INT result := 0;
  traverse board(board, (CELL c)VOID: result +:= c);
  result);

PROC join = ([]STRING strings, STRING joiner) STRING:

 IF UPB strings > 0 THEN
   STRING result := strings[1];
   FOR i FROM 2 TO UPB strings DO result +:= joiner +:= strings[i] OD;
   result
 ELSE
   ""
 FI;

BOARD board = LOC [side,side]INT; BOARD previous = LOC [side,side]INT;

traverse board(board, (CELL c)VOID: c := 0);

  1. start with two numbers #

TO 2 DO add new number(board) OD;

  1. play! #

STRING prompt := "enter one of [" + direction letters + "] (for " + join(direction descriptions, "/") + "): "; DO

 CHAR key;
 INT dir;
 print board(board);
 print(("score: ", whole(score(board),0), new line));
 WHILE
   print(prompt);
   read((key, new line));
   NOT char in string(key, dir, direction letters)
 DO SKIP OD;
 previous := board;
 move(board, dir);
 IF count blanks(board) = 0 THEN lose FI;
 traverse board(board, (CELL c)VOID: IF c = 2048 THEN win FI);
 IF previous = board THEN
   print(("try again!", new line))
 ELSE
   add new number(board)
 FI

OD;

win: print board(board); print(("you win!", new line)) EXIT lose: print(("you lose!", new line)) ) </lang>

APPLESOFT

<lang Applesoft>PRINT "Game 2048"

10  REM  ************
20  REM  *   2024   *
30  REM  ************
40  HOME 
100 W = 2: REM    **** W=0 FOR LOOSE W=1 FOR WIN W=2 FOR PLAYING ****  
110  DIM MA(4,4)
120 FC = 16: REM   FREECELLS 
130 A$ = "":SC = 0:MT = 2
140  GOSUB 1000: DRAW THESCREEN
150  GOSUB 1500: REM  PRINT SCORE AND MAXTILE
160  GOSUB 1700: REM  BREED
170  GOSUB 2000: REM  PRINT SCORES IN THE MATRIX AND CALC FC AND MT
200  REM  ******************
210  REM  MAIN PROGRAM
220  REM  ******************
230  HTAB 38: VTAB 22
235  IF W < 2 THEN  GOTO 950: REM  ******* END GAME ********
240  WAIT  - 16384,128:A =  PEEK ( - 16384) - 128 - 68: POKE  - 16368,0
250  ON A GOTO 999,900,900,900,300,350,400,900,450
280  REM  ************************
285  REM  FOLLOWING LINES HANDLE THE UP, LEFT, RIGHT, DOWN, NOP, EXIT
290  REM  ************************  
300  GOSUB 2500: GOSUB 3500: GOSUB 2500: GOSUB 1700: GOSUB 2000: GOSUB 1
    500
310  GOTO 200
350  GOSUB 2600: GOSUB 3600: GOSUB 2600: GOSUB 1700: GOSUB 2000: GOSUB 1
    500
360  GOTO 200
400  GOSUB 2700: GOSUB 3700: GOSUB 2700: GOSUB 1700: GOSUB 2000: GOSUB 1
    500
410  GOTO 200
450  GOSUB 2800: GOSUB 3800: GOSUB 2800: GOSUB 1700: GOSUB 2000: GOSUB 1
    500
460  GOTO 200
900  GOTO 200
950  HOME : VTAB 10
960  PRINT "          ********************"
970  IF W = 1 THEN  PRINT "          *    YOU   WIN     *"
980  IF W = 0 THEN  PRINT "          *    YOU   LOOSE   *"
990  PRINT "          ********************"
995  PRINT "               SCORE  =";SC
996  PRINT "               MAXTILE=";MT
999  END 
1000  REM  DRAW FRAME + SCORE
1010  FOR I = 1 TO 5
1020  VTAB 1 + (I - 1) * 4: PRINT "---------------------"
1030  NEXT I
1040  FOR I = 1 TO 4
1050  FOR J = 1 TO 3
1060  VTAB 1 + (I - 1) * 4 + J: PRINT "|    |    |    |    |"
1070  NEXT J
1080  NEXT I
1090  HTAB 30: VTAB 3: PRINT "I";
1100  HTAB 30: VTAB 9: PRINT "M";
1110  HTAB 25: VTAB 6: PRINT "J";
1120  HTAB 35: VTAB 6: PRINT "K";
1130  HTAB 25: VTAB 12: PRINT "E = END"
1140  HTAB 25: VTAB 14: PRINT "SCORE:"
1150  HTAB 25: VTAB 16: PRINT "MAXTILE:"
1160  HTAB 1: VTAB 19: PRINT "YOU CAN SLIDE  THE NUMBERS IN THE MATRIX"
1170  HTAB 1: VTAB 20: PRINT "BY PRESSING IJKM. WHEN MATCHING NUMBERS"
1180  HTAB 1: VTAB 21: PRINT "MEET THEY COMBINE IN THE SUM"
1190  HTAB 1: VTAB 22: PRINT "TO WIN YOU HAVE TO REACH THE SUM 2048"
1200  RETURN 
1500  REM  ***************
1501  REM  PRINT SCORE + MAXTILE
1502  REM  ***************
1510  VTAB 14: HTAB 32:
1520 SC$ =  STR$ (SC):LS =  LEN (SC$)
1530  FOR I = 1 TO 7 - LS: PRINT " ";: NEXT I
1540  PRINT SC$
1550  VTAB 16: HTAB 34:
1560 MT$ =  STR$ (MT):LS =  LEN (MT$)
1570  FOR I = 1 TO 5 - LS: PRINT " ";: NEXT I
1580  PRINT MT$
1590  IF SC = 2048 THEN W = 1: REM  ******** YOU WIN ********  
1690  RETURN 
1700  REM  ****************
1701  REM  PUT A "2" IN A RANDOM EMPTY CELL
1702  REM  ****************
1708  IF FC = 0 THEN W = 0: GOTO 1800: REM  ***** YOU LOOSE *****
1710 K =  INT ( RND (1) * FC + 1)
1720 N = 0
1730  FOR I = 1 TO 4
1740  FOR J = 1 TO 4
1750  IF MA(I,J) = 0 THEN N = N + 1
1760  IF N = K THEN MA(I,J) = 2:FC = FC - 1:I = 4:J = 4
1780  NEXT J
1790  NEXT I
1800  RETURN 
2000  REM  *************
2001  REM  WRITE THE CELL CONTENT AND CALC. FREECELLS AND MAXTILE
2002  REM  *************
2005 FC = 0:MT = 0: REM   INITIALIZE FREECELLS AND MAXTILES  
2010  FOR I = 1 TO 4
2020  FOR J = 1 TO 4
2030  HTAB 2 + (J - 1) * 5: VTAB 3 + (I - 1) * 4
2040  PRINT "    ";: HTAB 2 + (J - 1) * 5
2050  IF MA(I,J) = 0 THEN FC = FC + 1: GOTO 2060
2055  PRINT MA(I,J);
2060  IF MA(I,J) > MT THEN MT = MA(I,J)
2090  NEXT J
2100  NEXT I
2190  RETURN 
2500  REM  *****************
2510  REM   COMPACT UP - KIND OF BUBBLE SORT
2520  REM  *****************
2530  FOR J = 1 TO 4
2540  FOR K = 3 TO 1 STEP  - 1
2550  FOR I = 1 TO K
2560  IF MA(I,J) = 0 THEN MA(I,J) = MA(I + 1,J):MA(I + 1,J) = 0
2570  NEXT : NEXT : NEXT 
2590  RETURN 
2600  REM  ************
2610  REM  COMPACT LEFT
2620  REM  ************
2630  FOR I = 1 TO 4
2640  FOR K = 3 TO 1 STEP  - 1
2650  FOR J = 1 TO K
2660  IF MA(I,J) = 0 THEN MA(I,J) = MA(I,J + 1):MA(I,J + 1) = 0
2670  NEXT : NEXT : NEXT 
2690  RETURN 
2700  REM   ************
2710  REM   COMPACT RIGHT
2720  REM   ************
2730  FOR I = 1 TO 4
2740  FOR K = 2 TO 4
2750  FOR J = 4 TO K STEP  - 1
2760  IF MA(I,J) = 0 THEN MA(I,J) = MA(I,J - 1):MA(I,J - 1) = 0
2770  NEXT : NEXT : NEXT 
2790  RETURN 
2800  REM   *****************  
2810  REM    COMPACT DOWN
2820  REM   *****************  
2830  FOR J = 1 TO 4
2840  FOR K = 2 TO 4
2850  FOR I = 4 TO K STEP  - 1
2860  IF MA(I,J) = 0 THEN MA(I,J) = MA(I - 1,J):MA(I - 1,J) = 0
2870  NEXT : NEXT : NEXT 
2890  RETURN 
3500  REM  ***************
3510  REM  ADD UP
3520  REM  ***************
3530  FOR J = 1 TO 4
3540  FOR I = 1 TO 3
3550  IF MA(I,J) = MA(I + 1,J) THEN MA(I,J) = MA(I,J) * 2:MA(I + 1,J) = 
    0:SC = SC + MA(I,J)
3560  NEXT : NEXT 
3590  RETURN 
3600  REM  **************
3610  REM  SUM LEFT
3620  REM  **************
3630  FOR I = 1 TO 4
3640  FOR J = 1 TO 3
3650  IF MA(I,J) = MA(I,J + 1) THEN MA(I,J) = MA(I,J) * 2:MA(I,J + 1) = 
    0:SC = SC + MA(I,J)
3660  NEXT : NEXT 
3690  RETURN 
3700  REM  **************
3710  REM  SUM RIGHT
3720  REM  **************
3730  FOR I = 1 TO 4
3740  FOR J = 4 TO 2 STEP  - 1
3750  IF MA(I,J) = MA(I,J - 1) THEN MA(I,J) = MA(I,J) * 2:MA(I,J - 1) = 
    0:SC = SC + MA(I,J)
3760  NEXT : NEXT 
3790  RETURN 
3800  REM   *************** 
3810  REM   ADD DOWN
3820  REM   ***************  
3830  FOR J = 1 TO 4
3840  FOR I = 4 TO 2 STEP  - 1
3850  IF MA(I,J) = MA(I - 1,J) THEN MA(I,J) = MA(I,J) * 2:MA(I - 1,J) = 
    0:SC = SC + MA(I,J)
3860  NEXT : NEXT 
3890  RETURN 

it runs somehow slowly but still fun to play. The only non standard basic is the input routine which reads directly the memory location (line 240) instead of using "input" od "get"


| | | | | |4 | | |2 | I | | | | |


| | | | | J K |4 |2 | | | | | | | |


M

| | | | | |2 |16 |4 | | | | | | | E = END


| | | | | SCORE: 4924 |128 |512 | | | | | | | | MAXTILE: 512


YOU CAN SLIDE THE NUMBERS IN THE MATRIX BY PRESSING IJKM. WHEN MATCHING NUMBERS MEET THEY COMBINE IN THE SUM TO WIN YOU HAVE TO REACH THE SUM 2048


</lang>

ARM Assembly

Works with: as version Raspberry Pi

<lang ARM Assembly> /* ARM assembly Raspberry PI */ /* program 2048.s */

/* REMARK 1 : this program use routines in a include file

  see task Include a file language arm assembly 
  for the routine affichageMess conversion10 
  see at end of this program the instruction include */

/* for constantes see task include a file in arm assembly */ /************************************/ /* Constantes */ /************************************/ .include "../constantes.inc" .equ STDIN, 0 @ Linux input console .equ READ, 3 @ Linux syscall .equ SIZE, 4 .equ TOTAL, 2048 .equ BUFFERSIZE, 80

.equ IOCTL, 0x36 @ Linux syscall .equ SIGACTION, 0x43 @ Linux syscall .equ SYSPOLL, 0xA8 @ Linux syscall

.equ TCGETS, 0x5401 .equ TCSETS, 0x5402 .equ ICANON, 2 .equ ECHO, 10 .equ POLLIN, 1

.equ SIGINT, 2 @ Issued if the user sends an interrupt signal (Ctrl + C) .equ SIGQUIT, 3 @ Issued if the user sends a quit signal (Ctrl + D) .equ SIGTERM, 15 @ Software termination signal (sent by kill by default) .equ SIGTTOU, 22

/*******************************************/ /* Structures */ /********************************************/ /* structure termios see doc linux*/

   .struct  0

term_c_iflag: @ input modes

   .struct  term_c_iflag + 4 

term_c_oflag: @ output modes

   .struct  term_c_oflag + 4 

term_c_cflag: @ control modes

   .struct  term_c_cflag + 4 

term_c_lflag: @ local modes

   .struct  term_c_lflag + 4 

term_c_cc: @ special characters

   .struct  term_c_cc + 20      @ see length if necessary 

term_fin:

/* structure sigaction see doc linux */

   .struct  0

sa_handler:

   .struct  sa_handler + 4 

sa_mask:

   .struct  sa_mask + 4 

sa_flags:

   .struct  sa_flags + 4 

sa_sigaction:

   .struct  sa_sigaction + 4 

sa_fin:

/* structure poll see doc linux */

   .struct  0

poll_fd: @ File Descriptor

   .struct  poll_fd + 4 

poll_events: @ events mask

   .struct  poll_events + 4 

poll_revents: @ events returned

   .struct  poll_revents + 4 

poll_fin: /*********************************/ /* Initialized data */ /*********************************/ .data szMessOK: .asciz "Bravo !! You win. \n" szMessNotOK: .asciz "You lost !! \n" szMessNewGame: .asciz "New game (y/n) ? \n" szMessErreur: .asciz "Error detected.\n" szCarriageReturn: .asciz "\n" //szMessMovePos: .asciz "\033[00;00H" szMess0: .asciz " " szMess2: .asciz " 2 " szMess4: .asciz " 4 " szMess8: .asciz " 8 " szMess16: .asciz " 16 " szMess32: .asciz " 32 " szMess64: .asciz " 64 " szMess128: .asciz " 128 " szMess256: .asciz " 256 " szMess512: .asciz " 512 " szMess1024: .asciz " 1024 " szMess2048: .asciz " 2048 " szClear1: .byte 0x1B

                   .byte 'c'           @ other console clear
                   .byte 0

szLineH: .asciz "-----------------------------\n" szLineV: .asciz "|" szLineVT: .asciz "| | | | |\n" .align 4 iGraine: .int 123456 /*********************************/ /* UnInitialized data */ /*********************************/ .bss .align 4 sZoneConv: .skip 24 sBuffer: .skip BUFFERSIZE iTbCase: .skip 4 * SIZE * SIZE iEnd: .skip 4 @ 0 loop 1 = end loop iTouche: .skip 4 @ value key pressed stOldtio: .skip term_fin @ old terminal state stCurtio: .skip term_fin @ current terminal state stSigAction: .skip sa_fin @ area signal structure stSigAction1: .skip sa_fin stPoll1: .skip poll_fin @ area poll structure stPoll2: .skip poll_fin /*********************************/ /* code section */ /*********************************/ .text .global main main: @ entry of program

1: @ begin game loop

   ldr r0,iAdrszClear1
   bl affichageMess
   bl razTable

2:

   bl addDigit   
   cmp r0,#-1
   beq 5f                            @ end game
   bl displayGame

3:

   bl readKey
   cmp r0,#-1
   beq 100f                          @ error or control-c
   bl keyMove
   cmp r0,#0
   beq 3b                            @ no change -> loop
   cmp r0,#2                         @ last addition = 2048 ?
   beq 4f
   cmp r0,#-1                        @ quit ?
   bne 2b                            @ loop
   
   b 10f

4: @ last addition = 2048

   ldr r0,iAdrszMessOK
   bl affichageMess
   b 10f

5: @ display message no solution

   ldr r0,iAdrszMessNotOK
   bl affichageMess

10: @ display new game ?

   ldr r0,iAdrszCarriageReturn
   bl affichageMess
   ldr r0,iAdrszMessNewGame
   bl affichageMess
   bl readKey
   ldr r0,iAdriTouche
   ldrb r0,[r0]
   cmp r0,#'y'
   beq 1b
   cmp r0,#'Y'
   beq 1b
   

100: @ standard end of the program

   mov r0, #0                        @ return code
   mov r7, #EXIT                     @ request to exit program
   svc #0                            @ perform the system call

iAdrszCarriageReturn: .int szCarriageReturn iAdrszMessNotOK: .int szMessNotOK iAdrszMessOK: .int szMessOK iAdrszMessNewGame: .int szMessNewGame iAdrsZoneConv: .int sZoneConv iAdrszClear1: .int szClear1 /******************************************************************/ /* raz table cases */ /******************************************************************/ razTable:

   push {r0-r2,lr}                  @ save  registers
   ldr r1,iAdriTbCase
   mov r0,#0
   mov r2,#0

1:

   str r0,[r1,r2,lsl #2]
   add r2,r2,#1
   cmp r2,#SIZE * SIZE
   blt 1b

100:

   pop {r0-r2,lr}                   @ restaur registers 
   bx lr                            @return

/******************************************************************/ /* key move */ /******************************************************************/ /* r0 contains key value */ keyMove:

   push {r1,lr}                  @ save  registers
   cmp r0,#0x42                  @ down arrow 
   bne 1f
   bl moveDown
   b 100f

1:

   cmp r0,#0x41                  @ high arrow
   bne 2f
   bl moveUp
   b 100f

2:

   cmp r0,#0x43                  @ right arrow
   bne 3f
   bl moveRight
   b 100f

3:

   cmp r0,#0x44                  @ left arrow
   bne 4f
   bl moveLeft
   b 100f

4:

   ldr r0,iAdriTouche
   ldrb r0,[r0]
   cmp r0,#'q'                   @ quit game
   bne 5f
   mov r0,#-1
   b 100f

5:

   cmp r0,#'Q'                   @ quit game
   bne 100f
   mov r0,#-1
   b 100f

100:

   pop {r1,lr}                   @ restaur registers 
   bx lr                         @return

/******************************************************************/ /* move left */ /******************************************************************/ /* r0 return -1 if ok */ moveLeft:

   push {r1-r10,lr}            @ save registers
   ldr r1,iAdriTbCase
   mov r0,#0                   @ top move Ok
   mov r2,#0                   @ line indice

1:

   mov r6,#0                   @ counter empty case
   mov r7,#0                   @ first digit
   mov r10,#0                  @ last digit to add
   mov r3,#0                   @ column indice

2:

   lsl r5,r2,#2                @ change this if size <> 4
   add r5,r5,r3                @ compute table indice
   ldr r4,[r1,r5,lsl #2]
   cmp r4,#0
   addeq r6,r6,#1              @ positions vides
   beq 5f
   cmp r6,#0
   beq 3f                      @ no empty left case
   mov r8,#0
   str r8,[r1,r5,lsl #2]       @ raz digit
   sub r5,r5,r6
   str r4,[r1,r5,lsl #2]       @ and store to left empty position
   mov r0,#1                   @ move Ok
   //sub r6,r6,#1

3:

   cmp r7,#0                   @ first digit
   beq 4f
   cmp r10,r4                  @ prec digit have to add 
   beq 4f
   sub r8,r5,#1                @ prec digit 
   ldr r9,[r1,r8,lsl #2]
   cmp r4,r9                   @ equal ?
   bne 4f
   mov r10,r4                  @ save digit 
   add r4,r4,r9                @ yes -> add
   str r4,[r1,r8,lsl #2]
   cmp r4,#TOTAL
   moveq r0,#2
   beq 100f
   mov r4,#0
   str r4,[r1,r5,lsl #2]
   add r6,r6,#1                @ empty case + 1
   mov r0,#1                   @ move Ok

4:

   add r7,r7,#1                @ no first digit

5: @ and loop

   add r3,r3,#1
   cmp r3,#SIZE
   blt 2b
   add r2,r2,#1
   cmp r2,#SIZE
   blt 1b

100:

   pop {r1-r12,lr}
   bx lr                       @ return 

/******************************************************************/ /* move right */ /******************************************************************/ /* r0 return -1 if ok */ moveRight:

   push {r1-r5,lr}                @ save registers
   ldr r1,iAdriTbCase
   mov r0,#0
   mov r2,#0

1:

   mov r6,#0
   mov r7,#0
   mov r10,#0
   mov r3,#SIZE-1

2:

   lsl r5,r2,#2                  @ change this if size <> 4
   add r5,r5,r3
   ldr r4,[r1,r5,lsl #2]
   cmp r4,#0
   addeq r6,r6,#1                @ positions vides
   beq 5f
   cmp r6,#0
   beq 3f                      @ no empty right case
   mov r0,#0
   str r0,[r1,r5,lsl #2]       @ raz digit
   add r5,r5,r6
   str r4,[r1,r5,lsl #2]       @ and store to right empty position
   mov r0,#1

3:

   cmp r7,#0                   @ first digit
   beq 4f
   add r8,r5,#1                @ next digit 
   ldr r9,[r1,r8,lsl #2]
   cmp r4,r9                   @ equal ?
   bne 4f
   cmp r10,r4
   beq 4f
   mov r10,r4
   add r4,r4,r9                @ yes -> add
   str r4,[r1,r8,lsl #2]
   cmp r4,#TOTAL
   moveq r0,#2
   beq 100f
   mov r4,#0
   str r4,[r1,r5,lsl #2]
   add r6,r6,#1                @ empty case + 1
   mov r0,#1

4:

   add r7,r7,#1                @ no first digit

5: @ and loop

   sub r3,r3,#1
   cmp r3,#0
   bge 2b
   add r2,r2,#1
   cmp r2,#SIZE
   blt 1b
   

100:

   pop {r1-r5,lr}
   bx lr                          @ return 

/******************************************************************/ /* move down */ /******************************************************************/ /* r0 return -1 if ok */ moveDown:

   push {r1-r5,lr}                @ save registers
   ldr r1,iAdriTbCase
   mov r0,#0
   mov r3,#0

1:

   mov r6,#0
   mov r7,#0
   mov r10,#0
   mov r2,#SIZE-1

2:

   lsl r5,r2,#2                  @ change this if size <> 4
   add r5,r5,r3
   ldr r4,[r1,r5,lsl #2]
   cmp r4,#0
   addeq r6,r6,#1                @ positions vides
   beq 5f
   cmp r6,#0
   beq 3f                      @ no empty right case
   mov r0,#0
   str r0,[r1,r5,lsl #2]       @ raz digit
   lsl r0,r6,#2
   add r5,r5,r0
   str r4,[r1,r5,lsl #2]       @ and store to right empty position
   mov r0,#1

3:

   cmp r7,#0                   @ first digit
   beq 4f
   add r8,r5,#SIZE                @ down digit 
   ldr r9,[r1,r8,lsl #2]
   cmp r4,r9                   @ equal ?
   bne 4f
   cmp r10,r4
   beq 4f
   mov r10,r4
   add r4,r4,r9                @ yes -> add
   str r4,[r1,r8,lsl #2]
   cmp r4,#TOTAL
   moveq r0,#2
   beq 100f
   mov r4,#0
   str r4,[r1,r5,lsl #2]
   add r6,r6,#1                @ empty case + 1
   mov r0,#1

4:

   add r7,r7,#1                   @ no first digit

5: @ and loop

   sub r2,r2,#1
   cmp r2,#0
   bge 2b
   add r3,r3,#1
   cmp r3,#SIZE
   blt 1b

100:

   pop {r1-r5,lr}
   bx lr                          @ return 

/******************************************************************/ /* move up */ /******************************************************************/ /* r0 return -1 if ok */ moveUp:

   push {r1-r5,lr}                @ save registers
   ldr r1,iAdriTbCase
   mov r0,#0
   mov r3,#0

1:

   mov r6,#0
   mov r7,#0
   mov r10,#0
   mov r2,#0

2:

   lsl r5,r2,#2                  @ change this if size <> 4
   add r5,r5,r3
   ldr r4,[r1,r5,lsl #2]
   cmp r4,#0
   addeq r6,r6,#1                @ positions vides
   beq 5f
   cmp r6,#0
   beq 3f                      @ no empty right case
   mov r0,#0
   str r0,[r1,r5,lsl #2]       @ raz digit
   lsl r0,r6,#2
   sub r5,r5,r0
   str r4,[r1,r5,lsl #2]       @ and store to right empty position
   mov r0,#1

3:

   cmp r7,#0                   @ first digit
   beq 4f
   sub r8,r5,#SIZE             @ up digit 
   ldr r9,[r1,r8,lsl #2]
   cmp r4,r9                   @ equal ?
   bne 4f
   cmp r10,r4
   beq 4f
   mov r10,r4
   add r4,r4,r9                @ yes -> add
   str r4,[r1,r8,lsl #2]
   cmp r4,#TOTAL
   moveq r0,#2
   beq 100f
   mov r4,#0
   str r4,[r1,r5,lsl #2]
   add r6,r6,#1                @ empty case + 1
   mov r0,#1

4:

   add r7,r7,#1                @ no first digit

5: @ and loop

   add r2,r2,#1
   cmp r2,#SIZE
   blt 2b
   add r3,r3,#1
   cmp r3,#SIZE
   blt 1b

100:

   pop {r1-r5,lr}
   bx lr                          @ return 

/******************************************************************/ /* add new digit on game */ /******************************************************************/ /* r0 return -1 if ok */ addDigit:

   push {r1-r5,lr}                @ save registers
   sub sp,#4 * SIZE*SIZE
   mov fp,sp
   
   mov r0,#100
   bl genereraleas
   cmp r0,#10
   movlt r5,#4
   movge r5,#2
   ldr r1,iAdriTbCase
   mov r3,#0
   mov r4,#0

1:

   ldr r2,[r1,r3,lsl #2]
   cmp r2,#0
   bne 2f
   str r3,[fp,r4,lsl #2]
   add r4,r4,#1

2:

   add r3,r3,#1
   cmp r3,#SIZE*SIZE
   blt 1b
   cmp r4,#0              @ no empty case
   moveq r0,#-1
   beq 100f
   cmp r4,#1             
   bne 3f
   ldr r2,[fp]            @ one case
   str r5,[r1,r2,lsl #2]
   mov r0,#0
   b 100f

3: @ multiple case

   sub r0,r4,#1
   bl genereraleas
   ldr r2,[fp,r0,lsl #2]
   str r5,[r1,r2,lsl #2]
   mov r0,#0
   

100:

   add sp,#4*  (SIZE*SIZE)    @ stack alignement
   pop {r1-r5,lr}
   bx lr                      @ return 

iAdriTbCase: .int iTbCase /******************************************************************/ /* display game */ /******************************************************************/ displayGame:

   push {r1-r3,lr}            @ save registers
   ldr r0,iAdrszClear1
   bl affichageMess
   ldr r0,iAdrszLineH
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess
   ldr r1,iAdriTbCase
   mov r2,#0

1:

   ldr r0,[r1,r2,lsl #2]
   bl digitString
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess
   add r2,r2,#1
   cmp r2,#SIZE
   blt 1b
   ldr r0,iAdrszCarriageReturn
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineH
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess

2:

   ldr r0,[r1,r2,lsl #2]
   bl digitString
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess
   add r2,r2,#1
   cmp r2,#SIZE*2
   blt 2b
   ldr r0,iAdrszCarriageReturn
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineH
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess

3:

   ldr r0,[r1,r2,lsl #2]
   bl digitString
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess
   add r2,r2,#1
   cmp r2,#SIZE*3
   blt 3b
   ldr r0,iAdrszCarriageReturn
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineH
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess

4:

   ldr r0,[r1,r2,lsl #2]
   bl digitString
   bl affichageMess
   ldr r0,iAdrszLineV
   bl affichageMess
   add r2,r2,#1
   cmp r2,#SIZE*4
   blt 4b
   ldr r0,iAdrszCarriageReturn
   bl affichageMess
   ldr r0,iAdrszLineVT
   bl affichageMess
   ldr r0,iAdrszLineH
   bl affichageMess

100:

   pop {r1-r3,lr}
   bx lr                       @ return 

iAdrszLineH: .int szLineH iAdrszLineV: .int szLineV iAdrszLineVT: .int szLineVT //iAdrszMessMovePos: .int szMessMovePos /******************************************************************/ /* digits string */ /******************************************************************/ /* r0 contains number */ /* r0 return address string */ digitString:

   push {r1,lr}        @ save registers
   cmp r0,#0
   bne 1f
   ldr r0,iAdrszMess0
   b 100f

1:

   cmp r0,#2
   bne 2f
   ldr r0,iAdrszMess2
   b 100f

2:

   cmp r0,#4
   bne 3f
   ldr r0,iAdrszMess4
   b 100f

3:

   cmp r0,#8
   bne 4f
   ldr r0,iAdrszMess8
   b 100f

4:

   cmp r0,#16
   bne 5f
   ldr r0,iAdrszMess16
   b 100f

5:

   cmp r0,#32
   bne 6f
   ldr r0,iAdrszMess32
   b 100f

6:

   cmp r0,#64
   bne 7f
   ldr r0,iAdrszMess64
   b 100f

7:

   cmp r0,#128
   bne 8f
   ldr r0,iAdrszMess128
   b 100f

8:

   cmp r0,#256
   bne 9f
   ldr r0,iAdrszMess256
   b 100f

9:

   cmp r0,#512
   bne 10f
   ldr r0,iAdrszMess512
   b 100f

10:

   cmp r0,#1024
   bne 11f
   ldr r0,iAdrszMess1024
   b 100f

11:

   cmp r0,#2048
   bne 12f
   ldr r0,iAdrszMess2048
   b 100f

12:

   ldr r1,iAdrszMessErreur                       @ error message
   bl   displayError

100:

   pop {r1,lr}
   bx lr                   @ return 

iAdrszMess0: .int szMess0 iAdrszMess2: .int szMess2 iAdrszMess4: .int szMess4 iAdrszMess8: .int szMess8 iAdrszMess16: .int szMess16 iAdrszMess32: .int szMess32 iAdrszMess64: .int szMess64 iAdrszMess128: .int szMess128 iAdrszMess256: .int szMess256 iAdrszMess512: .int szMess512 iAdrszMess1024: .int szMess1024 iAdrszMess2048: .int szMess2048

//iAdrsBuffer: .int sBuffer /***************************************************/ /* Generation random number */ /***************************************************/ /* r0 contains limit */ genereraleas:

   push {r1-r4,lr}         @ save registers 
   ldr r4,iAdriGraine
   ldr r2,[r4]
   ldr r3,iNbDep1
   mul r2,r3,r2
   ldr r3,iNbDep2
   add r2,r2,r3
   str r2,[r4]             @ maj de la graine pour l appel suivant 
   cmp r0,#0
   beq 100f
   add r1,r0,#1            @ divisor
   mov r0,r2               @ dividende
   bl division
   mov r0,r3               @ résult = remainder
 

100: @ end function

   pop {r1-r4,lr}          @ restaur registers
   bx lr                   @ return

/*****************************************************/ iAdriGraine: .int iGraine iNbDep1: .int 0x343FD iNbDep2: .int 0x269EC3 /***************************************************/ /* read touch */ /***************************************************/ readKey:

   push {r1-r7,lr}
   mov r5,#0
   ldr r1,iAdriTouche                            @ buffer address
   str r5,[r1]                                  @ raz 4 bytes iTouche
   /* read terminal state */
   mov r0,#STDIN                                @ input console
   mov r1,#TCGETS
   ldr r2,iAdrstOldtio
   mov r7, #IOCTL                               @ call system Linux
   svc #0 
   cmp r0,#0                                    @ error ?
   beq 1f
   ldr r1,iAdrszMessErreur                      @ error message
   bl   displayError
   mov r0,#-1
   b 100f

1:

   adr r0,sighandler                            @ adresse routine traitement signal
   ldr r1,iAdrstSigAction                       @ adresse structure sigaction
   str r0,[r1,#sa_handler]                      @ maj handler
   mov r0,#SIGINT                               @ signal type
   ldr r1,iAdrstSigAction
   mov r2,#0                                    @ NULL
   mov r7, #SIGACTION                           @ call system
   svc #0 
   cmp r0,#0                                    @ error ?
   bne 97f
   mov r0,#SIGQUIT
   ldr r1,iAdrstSigAction
   mov r2,#0                                    @ NULL
   mov r7, #SIGACTION                           @ call system 
   svc #0 
   cmp r0,#0                                    @ error ?
   bne 97f
   mov r0,#SIGTERM
   ldr r1,iAdrstSigAction
   mov r2,#0                                    @ NULL
   mov r7, #SIGACTION                           @ appel systeme 
   svc #0 
   cmp r0,#0
   bne 97f
   @
   adr r0,iSIG_IGN                              @ address signal ignore function
   ldr r1,iAdrstSigAction1
   str r0,[r1,#sa_handler]
   mov r0,#SIGTTOU                              @invalidate other process signal
   ldr r1,iAdrstSigAction1
   mov r2,#0                                    @ NULL
   mov r7,#SIGACTION                            @ call system 
   svc #0 
   cmp r0,#0
   bne 97f
   @
   /* read terminal current state  */
   mov r0,#STDIN
   mov r1,#TCGETS
   ldr r2,iAdrstCurtio                          @ address current termio
   mov r7,#IOCTL                                @ call systeme 
   svc #0 
   cmp r0,#0                                    @ error ?
   bne 97f
   mov r2,#ICANON | ECHO                        @ no key pressed echo on display
   mvn r2,r2                                    @ and one key 
   ldr r1,iAdrstCurtio
   ldr r3,[r1,#term_c_lflag]
   and r3,r2                                    @ add flags 
   str r3,[r1,#term_c_lflag]                    @ and store
   mov r0,#STDIN                                @ maj terminal current state 
   mov r1,#TCSETS
   ldr r2,iAdrstCurtio
   mov r7, #IOCTL                               @ call system
   svc #0 
   cmp r0,#0
   bne 97f
   @

2: @ loop waiting key

   ldr r0,iAdriEnd                              @ if signal ctrl-c  -> end
   ldr r0,[r0]
   cmp r0,#0
   movne r5,#-1
   bne 98f
   ldr r0,iAdrstPoll1                            @ address structure poll
   mov r1,#STDIN
   str r1,[r0,#poll_fd]                          @ maj FD
   mov r1,#POLLIN                                @ action code
   str r1,[r0,#poll_events]
   mov r1,#1                                     @ items number structure poll
   mov r2,#0                                     @ timeout = 0 
   mov r7,#SYSPOLL                               @ call system POLL
   svc #0 
   cmp r0,#0                                     @ key pressed ?
   ble 2b                                        @ no key pressed -> loop
                                                 @ read key
   mov r0,#STDIN                                 @ File Descriptor
   ldr r1,iAdriTouche                            @ buffer address
   mov r2,#BUFFERSIZE                            @ buffer size
   mov r7,#READ                                  @ read key
   svc #0
   cmp r0,#0                                     @ error ?
   bgt 98f

97: @ error detected

   ldr r1,iAdrszMessErreur                       @ error message
   bl   displayError
   mov r5,#-1

98: @ end then restaur begin state terminal

   mov r0,#STDIN
   mov r1,#TCSETS
   ldr r2,iAdrstOldtio
   mov r7,#IOCTL                                 @ call system  
   svc #0
   cmp r0,#0
   beq 99f                                       @ restaur ok
   ldr r1,iAdrszMessErreur                       @ error message
   bl   displayError
   mov r0,#-1
   b 100f

99:

   cmp r5,#0                                     @ no error or control-c ?
   ldreq r2,iAdriTouche                          @ key address
   ldreqb r0,[r2,#2]                             @ return key byte
   movne r0,r5                                   @ or error

100:

   pop {r1-r7, lr}
   bx lr

iSIG_IGN: .int 1 iAdriEnd: .int iEnd iAdrstPoll1: .int stPoll1 iAdriTouche: .int iTouche iAdrstOldtio: .int stOldtio iAdrstCurtio: .int stCurtio iAdrstSigAction: .int stSigAction iAdrstSigAction1: .int stSigAction1 iAdrszMessErreur : .int szMessErreur /******************************************************************/ /* traitement du signal */ /******************************************************************/ sighandler:

   push {r0,r1}
   ldr r0,iAdriEnd
   mov r1,#1                 @ maj zone end
   str r1,[r0]
   pop {r0,r1}
   bx lr

/***************************************************/ /* ROUTINES INCLUDE */ /***************************************************/ .include "../affichage.inc" </lang>

Output:
-----------------------------
|      |      |      |      |
|   8  |   4  |   4  |   2  |
|      |      |      |      |
-----------------------------
|      |      |      |      |
|  64  |  16  |      |      |
|      |      |      |      |
-----------------------------
|      |      |      |      |
|  16  |      |      |   2  |
|      |      |      |      |
-----------------------------
|      |      |      |      |
|   2  |      |      |      |
|      |      |      |      |
-----------------------------

AutoHotkey

<lang AutoHotkey>Grid := [], s := 16, w := h := S * 4.5 Gui, font, s%s% Gui, add, text, y1 loop, 4 { row := A_Index loop, 4 { col := A_Index if col = 1 Gui, add, button, v%row%_%col% xs y+1 w%w% h%h% -TabStop, % Grid[row,col] := 0 else Gui, add, button, v%row%_%col% x+1 yp w%w% h%h% -TabStop, % Grid[row,col] := 0 } } Gui, show,, 2048

------------------------------

Start: for row, obj in Grid for col, val in obj Grid[row,col] := 0

Grid[1,1]:=2 ShowGrid() return

------------------------------

GuiClose: ExitApp return

------------------------------
  1. IfWinActive, 2048
------------------------------

up:: move := false loop, 4 { col := A_Index Loop, 3 { row := A_Index if Grid[row, col] && (Grid[row, col] = Grid[row+1, col]) Grid[row, col] *=2 , Grid[row+1, col] := 0, move := true } }

loop, 4 { row := A_Index loop, 4 { col := A_Index loop, 4 if !Grid[row, col] loop, 3 if !Grid[row, col] && Grid[row+A_Index, col] { Grid[row, col] := Grid[row+A_Index, col] , Grid[row+A_Index, col] := 0, move := true if (Grid[row, col] = Grid[row-1, col]) Grid[row-1, col] *=2 , Grid[row, col] := 0, move := true } } } gosub, AddNew return

------------------------------

Down:: move := false loop, 4 { col := A_Index Loop, 3 { row := 5-A_Index if Grid[row, col] && (Grid[row, col] = Grid[row-1, col]) Grid[row, col] *=2 , Grid[row-1, col] := 0, move := true } }

loop, 4 { row := 5-A_Index loop, 4 { col := A_Index loop, 4 if !Grid[row, col] loop, 3 if !Grid[row, col] && Grid[row-A_Index, col] { Grid[row, col] := Grid[row-A_Index, col] , Grid[row-A_Index, col] := 0, move := true if (Grid[row, col] = Grid[row+1, col]) Grid[row+1, col] *=2 , Grid[row, col] := 0, move := true } } } gosub, AddNew return

------------------------------

Left:: move := false loop, 4 { row := A_Index Loop, 3 { col := A_Index if Grid[row, col] && (Grid[row, col] = Grid[row, col+1]) Grid[row, col] *=2 , Grid[row, col+1] := 0, move := true } }

loop, 4 { col := A_Index loop, 4 { row := A_Index loop, 4 if !Grid[row, col] loop, 3 if !Grid[row, col] && Grid[row, col+A_Index] { Grid[row, col] := Grid[row, col+A_Index] , Grid[row, col+A_Index] := 0, move := true if (Grid[row, col] = Grid[row, col-1]) Grid[row, col-1] *=2 , Grid[row, col] := 0, move := true }

} } gosub, AddNew return

------------------------------

Right:: move := false loop, 4 { row := A_Index Loop, 3 { col := 5-A_Index if Grid[row, col] && (Grid[row, col] = Grid[row, col-1]) Grid[row, col] *=2 , Grid[row, col-1] := 0, move := true } }

loop, 4 { col := 5-A_Index loop, 4 { row := A_Index loop, 4 if !Grid[row, col] loop, 3 if !Grid[row, col] && Grid[row, col-A_Index] { Grid[row, col] := Grid[row, col-A_Index] , Grid[row, col-A_Index] := 0, move := true if (Grid[row, col] = Grid[row, col+1]) Grid[row, col+1] *=2 , Grid[row, col] := 0, move := true } } } gosub, AddNew return

------------------------------
  1. IfWinActive
------------------------------

AddNew: if EndOfGame() { MsgBox Done `nPress OK to retry goto start } return

------------------------------

EndOfGame(){ global if Move AddRandom() ShowGrid() for row, obj in Grid for col, val in obj if !grid[row,col] return 0

for row, obj in Grid for col, val in obj if (grid[row,col] = grid[row+1,col]) || (grid[row,col] = grid[row-1,col]) || (grid[row,col] = grid[row,col+1]) || (grid[row,col] = grid[row,col-1]) return 0 return 1 }

------------------------------

ShowGrid(){ global Grid for row, obj in Grid for col, val in obj { GuiControl,, %row%_%col%, %val% if val GuiControl, Show, %row%_%col% else GuiControl, Hide, %row%_%col% } }

------------------------------

AddRandom(){ global Grid ShowGrid() Sleep, 200 for row, obj in Grid for col, val in obj if !grid[row,col] list .= (list?"`n":"") row "," col Sort, list, random Rnd := StrSplit(list, "`n").1 Grid[StrSplit(rnd, ",").1, StrSplit(rnd, ",").2] := 2 }

------------------------------</lang>

Batch File

<lang dos>::2048 Game Task from RosettaCode.org

Batch File Implementation

@echo off setlocal enabledelayedexpansion cls

begin_game

%== Set variables ==% set "score=0" set "won=0" set "SUP_score=0" for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do set /a "X_%%A%%B=0"

call :addtile call :addtile

%== Main game loop ==%

main_loop

set "changed=0" call :display echo( echo Keys: WASD (Slide Movement^), N (New game^), P (Exit^)

%== Get Keypress ==% set "key=" for /f "delims=" %%? in ('xcopy /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%?" set "key=%key:~-1%"

%== Process keypress ==% if /i "!key!"=="W" ( for /l %%? in (1,1,4) do call :slide X_1%%? X_2%%? X_3%%? X_4%%? ) if /i "!key!"=="A" ( for /l %%? in (1,1,4) do call :slide X_%%?1 X_%%?2 X_%%?3 X_%%?4 ) if /i "!key!"=="S" ( for /l %%? in (1,1,4) do call :slide X_4%%? X_3%%? X_2%%? X_1%%? ) if /i "!key!"=="D" ( for /l %%? in (1,1,4) do call :slide X_%%?4 X_%%?3 X_%%?2 X_%%?1 ) if /i "!key!"=="N" goto :begin_game if /i "!key!"=="P" exit /b

%== Check if the board changed ==% if %changed% neq 0 call :addtile

%== Check if already won ==% if %won% equ 1 ( set "msg=Nice one... You WON^!^!" goto :gameover )

%== Check for lose condition ==% set /a "real_blanks=blank_count-1" if %real_blanks% leq 0 ( for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do set "TRY_%%A%%B=!X_%%A%%B!" set "TRY_changed=%changed%" & set "changed=0" set "SUP_score=1" for /l %%? in (1,1,4) do call :slide TRY_%%?1 TRY_%%?2 TRY_%%?3 TRY_%%?4 for /l %%? in (1,1,4) do call :slide TRY_1%%? TRY_2%%? TRY_3%%? TRY_4%%? if !changed! equ 0 ( set "msg=No moves are possible... Game Over :(" goto :gameover ) else (set "changed=!TRY_changed!" & set "SUP_score=0") ) goto main_loop


~~~~~~~~~~~~~~~~~~~~ Sub Procedures ~~~~~~~~~~~~~~~~~~~~::

%== Game Over xD ==%

gameover

call :display echo( echo(!msg! echo( echo(Keys: N (New game^), P (Exit^)

key_loop

set "key=" for /f "delims=" %%? in ('xcopy /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%?" set "key=%key:~-1%" if /i "!key!"=="N" goto :begin_game if /i "!key!"=="P" exit /b goto :key_loop

%== The main slider of numbers in tiles ==%

slide

set "next=" set "slide_1=" set "slide_2=" for %%? in (%*) do if !%%?! neq 0 set "slide_1=!slide_1! !%%?!" for %%? in (!slide_1!) do ( set "scan=%%?" if "!scan!"=="!next!" ( set /a "next*=2" if !SUP_score! equ 0 set /a "score+=!next!" %== WINNING CONDITION!!! ==% if "!next!" equ "2048" set "won=1" set "scan=" ) set "slide_2=!slide_2! !next!" set "next=!scan!" ) set "slide_2=!slide_2! !next!" for /l %%? in (1,1,4) do set "final_%%?=0" set "cnt=0" & for %%? in (!slide_2!) do if !cnt! lss 4 ( set /a "cnt+=1" set "final_!cnt!=%%?" ) if not "!%1!!%2!!%3!!%4!"=="!final_1!!final_2!!final_3!!final_4!" set "changed=1" set "cnt=0" & for %%? in (%*) do ( set /a "cnt+=1" set /a "%%?=final_!cnt!" ) goto :EOF

%== Add number to tile ==%

addtile

set "blank_list=" set "blank_count=0" for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do ( if !X_%%A%%B! equ 0 ( set "blank_list=!blank_list!X_%%A%%B" set /a blank_count+=1 ) ) set /a "pick_tile=(%random% %% %blank_count%)*4" set /a "rnd=%random%%%10+1" set "tile_new=!blank_list:~%pick_tile%,4!" if %rnd%==5 (set !tile_new!=4) else (set !tile_new!=2) goto :EOF

%== Display the table ==%

display

cls echo 2048 Game in Batch echo( for /l %%A in (1,1,4) do ( for /l %%B in (1,1,4) do ( set "DX_%%A%%B=!X_%%A%%B!" if !tile_new!==X_%%A%%B (set "DX_%%A%%B= +!X_%%A%%B!") else ( if !X_%%A%%B! lss 1000 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! lss 100 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! lss 10 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! equ 0 set "DX_%%A%%B= " ) ) echo +----+----+----+----+ echo ^|!DX_%%A1!^|!DX_%%A2!^|!DX_%%A3!^|!DX_%%A4!^| ) echo +----+----+----+----+ echo( echo Score: %score% goto :EOF</lang>

Output:
2048 Game in Batch

+----+----+----+----+
|    |  +2|    |    |
+----+----+----+----+
|   4|    |    |    |
+----+----+----+----+
|   4|    |    |    |
+----+----+----+----+
|  16|   4|    |   2|
+----+----+----+----+

Score: 60

Keys: WASD (Slide Movement), N (New game), P (Exit)

BBC BASIC

<lang bbcbasic> SIZE = 4  : MAX = SIZE-1

     Won% = FALSE : Lost% = FALSE
     @% = 5
     DIM Board(MAX,MAX),Stuck% 3
     PROCBreed
     PROCPrint
     REPEAT
       Direction = GET-135
       IF Direction > 0 AND Direction < 5 THEN
         Moved% = FALSE
         PROCShift
         PROCMerge
         PROCShift
         IF Moved% THEN PROCBreed : !Stuck%=0 ELSE ?(Stuck%+Direction-1)=-1 : Lost% = !Stuck%=-1
         PROCPrint
       ENDIF
     UNTIL Won% OR Lost%
     IF Won% THEN PRINT "You WON! :-)" ELSE PRINT "You lost :-("
     END
     REM -----------------------------------------------------------------------------------------------------------------------
     DEF PROCPrint
     FOR i = 0 TO SIZE*SIZE-1
       IF Board(i DIV SIZE,i MOD SIZE) THEN PRINT Board(i DIV SIZE,i MOD SIZE); ELSE PRINT "    _";
       IF i MOD SIZE = MAX THEN PRINT
     NEXT
     PRINT STRING$(SIZE,"-----")
     ENDPROC
     REM ----------------------------------------------------------------------------------------------------------------------
     DEF PROCShift
     IF Direction = 2 OR Direction = 3 THEN loopend = MAX : step = -1 ELSE loopend = 0 : step = 1
     FOR row = loopend TO MAX-loopend STEP step
       zeros = 0
       FOR col = loopend TO MAX-loopend STEP step
         IF Direction < 3 THEN
           IF Board(row,col) = 0 THEN zeros += step ELSE IF zeros THEN SWAP Board(row,col),Board(row,col-zeros) : Moved% = TRUE
         ELSE
           IF Board(col,row) = 0 THEN zeros += step ELSE IF zeros THEN SWAP Board(col,row),Board(col-zeros,row) : Moved% = TRUE
         ENDIF
       NEXT
     NEXT
     ENDPROC
     REM -----------------------------------------------------------------------------------------------------------------------
     DEF PROCMerge
     IF Direction = 1 THEN loopend =   0 : rowoff =  0 : coloff =  1 : step =  1
     IF Direction = 2 THEN loopend = MAX : rowoff =  0 : coloff = -1 : step = -1
     IF Direction = 3 THEN loopend = MAX : rowoff = -1 : coloff =  0 : step = -1
     IF Direction = 4 THEN loopend =   0 : rowoff =  1 : coloff =  0 : step =  1
     FOR row = loopend TO MAX-loopend-rowoff STEP step
       FOR col = loopend TO MAX-loopend-coloff STEP step
         IF Board(row,col) THEN IF Board(row,col) = Board(row+rowoff,col+coloff) THEN
           Board(row,col) *= 2 : Board(row+rowoff,col+coloff) = 0
           Moved% = TRUE
           IF NOT Won% THEN Won% = Board(row,col)=2048
         ENDIF
       NEXT
     NEXT
     ENDPROC
     REM -----------------------------------------------------------------------------------------------------------------------
     DEF PROCBreed
     cell = RND(SIZE*SIZE)-1
     FOR i = 0 TO SIZE*SIZE-1
       z = (cell+i) MOD (SIZE*SIZE)
       IF Board(z DIV SIZE,z MOD SIZE) = 0 THEN Board(z DIV SIZE,z MOD SIZE) = 2-(RND(10)=1)*2 : EXIT FOR
     NEXT
     ENDPROC</lang>
Output:
    _    _    _    _
    _    _    _    _
    _    _    2    _
    _    _    _    _
--------------------
    _    _    _    _
    _    _    _    _
    2    _    _    _
    _    2    _    _
--------------------
    2    2    _    _
    _    _    2    _
    _    _    _    _
    _    _    _    _
--------------------
    4    2    _    _
    2    _    _    _
    _    _    _    _
    _    _    _    _
--------------------
.
.
.
.
    2    8    4    2
    4    2   16    4
   16    4    8   32
    4   32    2    4
--------------------
You lost :-(

C

Version 1

Supports limited colours through vt100 escape codes. Requires a posix machine for termios.h and unistd.h headers. Provides simplistic animations when moving and merging blocks. <lang c>

  1. include <stdio.h>
  2. include <stdlib.h>
  3. include <string.h>
  4. include <termios.h>
  5. include <time.h>
  6. include <unistd.h>
  1. define D_INVALID -1
  2. define D_UP 1
  3. define D_DOWN 2
  4. define D_RIGHT 3
  5. define D_LEFT 4

const long values[] = {

   0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048

};

const char *colors[] = {

   "39", "31", "32", "33", "34", "35", "36", "37", "91", "92", "93", "94"

};

struct gamestate_struct__ {

   int grid[4][4];
   int have_moved;
   long total_score;
   long score_last_move;
   int blocks_in_play;

} game;

struct termios oldt, newt;

void do_draw(void) {

   printf("\033[2J\033[HScore: %ld", game.total_score);
   if (game.score_last_move)
       printf(" (+%ld)", game.score_last_move);
   printf("\n");
   for (int i = 0; i < 25; ++i)
       printf("-");
   printf("\n");
   for (int y = 0; y < 4; ++y) {
       printf("|");
       for (int x = 0; x < 4; ++x) {
           if (game.grid[x][y])
               printf("\033[7m\033[%sm%*zd \033[0m|", colors[game.grid[x][y]],
                       4, values[game.grid[x][y]]);
           else
               printf("%*s |", 4, "");
       }
       printf("\n");
   }
   for (int i = 0; i < 25; ++i) {
       printf("-");
   }
   printf("\n");

}

void do_merge(int d) { /* These macros look pretty scary, but mainly demonstrate some space saving */

  1. define MERGE_DIRECTION(_v1, _v2, _xs, _xc, _xi, _ys, _yc, _yi, _x, _y) \
   do {                                                                    \
       for (int _v1 = _xs; _v1 _xc; _v1 += _xi) {                          \
           for (int _v2 = _ys; _v2 _yc; _v2 += _yi) {                      \
               if (game.grid[x][y] && (game.grid[x][y] ==                  \
                                   game.grid[x + _x][y + _y])) {           \
                   game.grid[x][y] += (game.have_moved = 1);               \
                   game.grid[x + _x][y + _y] = (0 * game.blocks_in_play--);\
                   game.score_last_move += values[game.grid[x][y]];        \
                   game.total_score += values[game.grid[x][y]];            \
               }                                                           \
           }                                                               \
       }                                                                   \
   } while (0)
   game.score_last_move = 0;
   switch (d) {
       case D_LEFT:
           MERGE_DIRECTION(x, y, 0, < 3, 1, 0, < 4, 1, 1, 0);
           break;
       case D_RIGHT:
           MERGE_DIRECTION(x, y, 3, > 0, -1, 0, < 4, 1, -1, 0);
           break;
       case D_DOWN:
           MERGE_DIRECTION(y, x, 3, > 0, -1, 0, < 4, 1, 0, -1);
           break;
       case D_UP:
           MERGE_DIRECTION(y, x, 0, < 3, 1, 0, < 4, 1, 0, 1);
           break;
   }
  1. undef MERGE_DIRECTION

}

void do_gravity(int d) {

  1. define GRAVITATE_DIRECTION(_v1, _v2, _xs, _xc, _xi, _ys, _yc, _yi, _x, _y) \
   do {                                                                    \
       int break_cond = 0;                                                 \
       while (!break_cond) {                                               \
           break_cond = 1;                                                 \
           for (int _v1 = _xs; _v1 _xc; _v1 += _xi) {                      \
               for (int _v2 = _ys; _v2 _yc; _v2 += _yi) {                  \
                   if (!game.grid[x][y] && game.grid[x + _x][y + _y]) {    \
                       game.grid[x][y] = game.grid[x + _x][y + _y];        \
                       game.grid[x + _x][y + _y] = break_cond = 0;         \
                       game.have_moved = 1;                                \
                   }                                                       \
               }                                                           \
           }                                                               \
           do_draw(); usleep(40000);                                       \
       }                                                                   \
   } while (0)
   switch (d) {
       case D_LEFT:
           GRAVITATE_DIRECTION(x, y, 0, < 3, 1, 0, < 4, 1, 1, 0);
           break;
       case D_RIGHT:
           GRAVITATE_DIRECTION(x, y, 3, > 0, -1, 0, < 4, 1, -1, 0);
           break;
       case D_DOWN:
           GRAVITATE_DIRECTION(y, x, 3, > 0, -1, 0, < 4, 1, 0, -1);
           break;
       case D_UP:
           GRAVITATE_DIRECTION(y, x, 0, < 3, 1, 0, < 4, 1, 0, 1);
           break;
   }
  1. undef GRAVITATE_DIRECTION

}

int do_check_end_condition(void) {

   int ret = -1;
   for (int x = 0; x < 4; ++x) {
       for (int y = 0; y < 4; ++y) {
           if (values[game.grid[x][y]] == 2048)
               return 1;
           if (!game.grid[x][y] ||
                 ((x + 1 < 4) && (game.grid[x][y] == game.grid[x + 1][y])) ||
                 ((y + 1 < 4) && (game.grid[x][y] == game.grid[x][y + 1])))
               ret = 0;
       }
   }
   return ret;

}

int do_tick(int d) {

   game.have_moved = 0;
   do_gravity(d);
   do_merge(d);
   do_gravity(d);
   return game.have_moved;

}

void do_newblock(void) {

   if (game.blocks_in_play >= 16) return;
   int bn = rand() % (16 - game.blocks_in_play);
   int pn = 0;
   for (int x = 0; x < 4; ++x) {
       for (int y = 0; y < 4; ++y) {
           if (game.grid[x][y])
               continue;
           if (pn == bn){
               game.grid[x][y] = rand() % 10 ? 1 : 2;
               game.blocks_in_play += 1;
               return;
           }
           else {
               ++pn;
           }
       }
   }

}

int main(void) {

   /* Initialize terminal settings */
   tcgetattr(STDIN_FILENO, &oldt);
   newt = oldt;
   newt.c_lflag &= ~(ICANON | ECHO);
   tcsetattr(STDIN_FILENO, TCSANOW, &newt);
   srand(time(NULL));
   memset(&game, 0, sizeof(game));
   do_newblock();
   do_newblock();
   do_draw();
   while (1) {
       int found_valid_key, direction, value;
       do {
           found_valid_key = 1;
           direction       = D_INVALID;
           value           = getchar();
           switch (value) {
               case 'h': case 'a':
                   direction = D_LEFT;
                   break;
               case 'l': case 'd':
                   direction = D_RIGHT;
                   break;
               case 'j': case 's':
                   direction = D_DOWN;
                   break;
               case 'k': case 'w':
                   direction = D_UP;
                   break;
               case 'q':
                   goto game_quit;
                   break;
               case 27:
                   if (getchar() == 91) {
                       value = getchar();
                       switch (value) {
                           case 65:
                               direction = D_UP;
                               break;
                           case 66:
                               direction = D_DOWN;
                               break;
                           case 67:
                               direction = D_RIGHT;
                               break;
                           case 68:
                               direction = D_LEFT;
                               break;
                           default:
                               found_valid_key = 0;
                               break;
                       }
                   }
                   break;
               default:
                   found_valid_key = 0;
                   break;
           }
       }  while (!found_valid_key);
       do_tick(direction);
       if (game.have_moved != 0){
               do_newblock();
       }
       do_draw();
       switch (do_check_end_condition()) {
           case -1:
               goto game_lose;
           case 1:
               goto game_win;
           case 0:
               break;
       }
   }
   if (0)

game_lose:

   printf("You lose!\n");
   goto game_quit;
   if (0)

game_win:

   printf("You win!\n");
   goto game_quit;
   if (0)

game_quit:

   /* Restore terminal settings */
   tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
   return 0;

} </lang>

Output:
Score: 1100 (+4)
-------------------------
|  64 |  32 |  64 |  32 |
|  32 |  16 |   2 |   8 |
|  16 |   4 |   8 |   4 |
|   4 |   2 |   4 |   2 |
-------------------------
You lose!


Version 2

<lang c>

  1. include <stdio.h>
  2. include <string.h>
  3. include <stdlib.h>
  4. include <time.h>
  5. define EMPTY_TILE 0
  6. define ROWS 4
  7. define COLUMNS 4


/*

*                                     GENERAL CONCEPT
* 
* How do you add up tiles when there is whitespace between them?
* You sort the array so that there are no empty tiles between them while stacking them all to one side
* then the addition function always adds up from left to right or up to bottom. It does not care
* about the left movements or the down movement. This can be achieved by reversing the array
* whenever the player chooses to move to the right or down, when the addition is finished
* the array gets reversed back and its like it had been added from right to left or bottom to top
* When the addition is done, the program scans for the number of empty tiles and uses that
* in its selection of the next tile to be filled. 10% of times a tile gets occupied with a 4
*
  • /


/*

* the remove_whitespace functions; it is pretty clear what they do.
* they use a bubble short algorith to move the 0's or empty tiles to the end of the array
* depending on the direction moved (without carring about going right or up
*
  • /

void remove_whitespace_horizontaly(int board[ROWS][COLUMNS], int rows, int columns) {

 int a = columns;
 int tmp;


 for (; a < COLUMNS - 1; ++a) {
   tmp = board[rows][a];
   board[rows][a] = board[rows][a+1];
   board[rows][a+1] = tmp;
 }

}

void remove_whitespace_verticaly(int board[ROWS][COLUMNS], int columns, int rows) {

 int a = rows;
 int tmp;
 for (; a < ROWS - 1; ++a) {
   tmp = board[a][columns];
   board[a][columns] = board[a+1][columns];
   board[a+1][columns] = tmp;
 }

}

/*

* the add_tiles functions. those functions do the heavy work of adding the tiles and
* taking care of special situations such as when adding two equal tiles a 0 gets generated
* they are quite difficult to understand i think (which means not that you need to be too clever
* but that i have done a poor job of creating them) and it took around 4 hours to get the 
* proper result
  • /

void add_tiles_horizontaly(int board[ROWS][COLUMNS]) {

 int a, b, flag;
 for (a = 0; a < ROWS; ++a) {
   for (b = 0, flag = 0; b < COLUMNS - 1 && flag != 4; ++b) {
     if (board[a][b] == EMPTY_TILE) {

remove_whitespace_horizontaly(board, a, b); --b; ++flag;

     }
     else {

if (board[a][b+1] == EMPTY_TILE) { board[a][b+1] = board[a][b]; board[a][b] = EMPTY_TILE; --b; } else if (board[a][b] == board[a][b+1]) { board[a][b] += board[a][b+1]; board[a][b+1] = EMPTY_TILE; }

     }
   }
 }

}

void add_tiles_verticaly(int board[ROWS][COLUMNS]) {

 int a, b, flag;
 for (a = 0; a < COLUMNS; ++a) {
   for (b = 0, flag = 0; b < ROWS-1 && flag != 4; ++b) {
     if (board[b][a] == EMPTY_TILE) {

remove_whitespace_verticaly(board, a, b); --b; ++flag;

     }
     else {

if (board[b+1][a] == EMPTY_TILE) { board[b+1][a] = board[b][a]; board[b][a] = EMPTY_TILE; --b; } else if (board[b][a] == board[b+1][a]) { board[b][a] += board[b+1][a]; board[b+1][a] = EMPTY_TILE; }

     }
   }
 }

}

/*

* ... print the board
*/

void print_board(int board[ROWS][COLUMNS]) {

 int a, b;
 for (a = 0; a < ROWS; ++a) {
   printf("\n");
   for (b = 0; b < COLUMNS; ++b) {
     printf("%5i", board[a][b]);
   }
 }
 printf("\n");

}

/*

* The reverse_board function reverses the array
* if the movement is right or down reverse the array
  • /

void reverse_board(char input[], int board[ROWS][COLUMNS]) {

 int a, b, c, tmp;
 if (!strcmp(input, "right")) {
   for (a = 0; a < ROWS; ++a) {
     for (b = 0, c = 3; b < 2; ++b, --c) {

tmp = board[a][b]; board[a][b] = board[a][c]; board[a][c] = tmp;

     }
   }
 }
 else if  (!strcmp(input, "down")) {
   for (a = 0; a < COLUMNS; ++a) {
     for (b = 0, c = 3; b < 2; ++b, --c) {

tmp = board[b][a]; board[b][a] = board[c][a]; board[c][a] = tmp;

     }
   }
 }

}

/*

* the check_board function is the one which evaluates the win or lose condition
* for each turn and at the same time providing the number of empty tiles for the random generator
* function
  • /

int check_board (int board[ROWS][COLUMNS]) {

 int a, b;
 int result = 0;
 int empty_tiles = 0;


 for (a = 0; a < ROWS; ++a)
   for (b = 0; b < COLUMNS; ++b)
     if (board[a][b] == 2048)

result = -1;

     else if (board[a][b] == EMPTY_TILE)

++empty_tiles;

 result = result == -1 ? result : empty_tiles;
 return result;

}

/*

* the generate_random functin generates a random number between 0 and the number of
* empty tiles. the generated number will assign to the Nth empty tile = (random_number) 
* the new value, it also takes care of the 10% chance for producing a 4 tile
  • /

void generate_random(int board[ROWS][COLUMNS], int empty_tiles ) {

 srand(time(NULL));
 int a, b;
 int random = 0;
 int tile = 0;
 random = rand() % empty_tiles;
 tile = (rand() % 9 == 4) ? 4 : 2;
 for (a = 0; a < ROWS; ++a)
   for (b = 0; b < COLUMNS; ++b)
     if (board[a][b] == EMPTY_TILE && random != 0)

--random;

     else if (board[a][b] == EMPTY_TILE && random == 0) {

board[a][b] = tile; return;

     }

}

/*

* infinite loop, get the movements or exit code and act accordingly 
  • /

int play_game(int board[ROWS][COLUMNS]) {

 char movement[81];
 int tiles = 0;
 
 printf("this is the 2048 game\n"					\

"The goal of this game is make a tile reach the value of 2048\n"\ "The board starts of with only one occupied tile.\n"\ "On each round a new tile gets added with the value of 2\n"\ "or at 10%% of the times with the value of 4\n"\ "If you run out of tiles you lose\n"\ "There are 4 movements you can supply to the game\n"\ "right, left, up, and down.\n"\ "For each of this movements the tiles move to the direction specified\n"\ "If two tiles have the same value the get added up just once.\n"\ "If 2 occupied tiles share the same row or column but are seperated by empty tiles\n"\ "then the occupied tiles travel along the empty tiles stacking in the direction\n"\ "they were directed\n"\ "For a more visual explanation you can check the wikipedia entry\n" " if you search for 2058 board game\n" \ "Here we go\n");

 print_board(board);
 while (1) {
   printf("(enter: left,right,up,down,exit)>> ");
   scanf("%s", movement);
   if (!strcmp(movement, "down")) {
     reverse_board(movement,board);
     add_tiles_verticaly(board);
     tiles = check_board(board);
     if (tiles == -1)

return -1;

     else if (tiles == 0)

return 0;

     generate_random(board,tiles);
     reverse_board(movement, board);
   }
   else if (!strcmp(movement, "up")) {
     add_tiles_verticaly(board);
     tiles = check_board(board);
     if (tiles == -1)

return -1;

     else if (tiles == 0)

return 0;

     generate_random(board,tiles);
   }
   else if (!strcmp(movement, "right")) {
     reverse_board(movement,board);
     add_tiles_horizontaly(board);
     tiles = check_board(board);
     if (tiles == -1)

return -1;

     else if (tiles == 0)

return 0;

     generate_random(board,tiles);
     reverse_board(movement, board);
   }
   else if (!strcmp(movement, "left")) {
     add_tiles_horizontaly(board);
     tiles = check_board(board);
     if (tiles == -1)

return -1;

     else if (tiles == 0)

return 0;

     generate_random(board,tiles);
   }
   else if (!strcmp(movement, "exit")) {
     return 1;
   }
   else {
     printf("Do not recognize this movement please type again\n");
     continue;
   }
   print_board(board);
 }

}


int main(void) {

 int play_game(int board[ROWS][COLUMNS]);
 void generate_random(int board[ROWS][COLUMNS], int empty_tiles );
 int check_board (int board[ROWS][COLUMNS]);
 void reverse_board(char input[], int board[ROWS][COLUMNS]);
 void print_board(int board[ROWS][COLUMNS]);
 void add_tiles_verticaly(int board[ROWS][COLUMNS]);
 void add_tiles_horizontaly(int board[ROWS][COLUMNS]);
 void remove_whitespace_verticaly(int board[ROWS][COLUMNS], int columns, int rows);
 void remove_whitespace_horizontaly(int board[ROWS][COLUMNS], int rows, int columns);
   
 int win_condition;
 int board[ROWS][COLUMNS] = {
   {0,0,0,0},
   {0,0,0,0},
   {0,0,0,0},
   {0,0,0,0}
 };


 generate_random(board, 16); /* initialize the board */
 win_condition = play_game(board);
 switch (win_condition) {
 case 1:
   printf("But you are not done yet!!!\n"	\

"Fine, see you another day\n");

   break;
 case 0:
   printf("Ohh noo, you run out of tiles\n"	\

"Run me agan to play some more\n" \ "Byyyeee\n");

   break;
 case -1:
   printf("WooooW you did it, Good job!!!\n"	\

"See ya later homie\n");

   break;
 }
 return 0;

} </lang>

C#

Translation of: C++

<lang csharp>using System;

namespace g2048_csharp {

   internal class Tile
   {
       public Tile()
       {
           Value = 0;
           IsBlocked = false;
       }
       public int Value { get; set; }
       public bool IsBlocked { get; set; }
   }
   internal enum MoveDirection
   {
       Up,
       Down,
       Left,
       Right
   }
   internal class G2048
   {
       public G2048()
       {
           _isDone = false;
           _isWon = false;
           _isMoved = true;
           _score = 0;
           InitializeBoard();
       }
       private void InitializeBoard()
       {
           for (int y = 0; y < 4; y++)
           {
               for (int x = 0; x < 4; x++)
               {
                   _board[x, y] = new Tile();
               }
           }
       }
       private bool _isDone;
       private bool _isWon;
       private bool _isMoved;
       private int _score;
       private readonly Tile[,] _board = new Tile[4, 4];
       private readonly Random _rand = new Random();
       public void Loop()
       {
           AddTile();
           while (true)
           {
               if (_isMoved)
               {
                   AddTile();
               }
               DrawBoard();
               if (_isDone)
               {
                   break;
               }
               WaitKey();
           }
           string endMessage = _isWon ? "You've made it!" : "Game Over!";
           Console.WriteLine(endMessage);
       }
       private void DrawBoard()
       {
           Console.Clear();
           Console.WriteLine("Score: " + _score + "\n");
           for (int y = 0; y < 4; y++)
           {
               Console.WriteLine("+------+------+------+------+");
               Console.Write("| ");
               for (int x = 0; x < 4; x++)
               {
                   if (_board[x, y].Value == 0)
                   {
                       const string empty = " ";
                       Console.Write(empty.PadRight(4));
                   }
                   else
                   {
                       Console.Write(_board[x, y].Value.ToString().PadRight(4));
                   }
                   Console.Write(" | ");
               }
               Console.WriteLine();
           }
           Console.WriteLine("+------+------+------+------+\n\n");
       }
       private void WaitKey()
       {
           _isMoved = false;
           Console.WriteLine("(W) Up (S) Down (A) Left (D) Right");
           char input;
           char.TryParse(Console.ReadKey().Key.ToString(), out input);
           switch (input)
           {
               case 'W':
                   Move(MoveDirection.Up);
                   break;
               case 'A':
                   Move(MoveDirection.Left);
                   break;
               case 'S':
                   Move(MoveDirection.Down);
                   break;
               case 'D':
                   Move(MoveDirection.Right);
                   break;
           }
           for (int y = 0; y < 4; y++)
           {
               for (int x = 0; x < 4; x++)
               {
                   _board[x, y].IsBlocked = false;
               }
           }
       }
       private void AddTile()
       {
           for (int y = 0; y < 4; y++)
           {
               for (int x = 0; x < 4; x++)
               {
                   if (_board[x, y].Value != 0) continue;
                   int a, b;
                   do
                   {
                       a = _rand.Next(0, 4);
                       b = _rand.Next(0, 4);
                   } while (_board[a, b].Value != 0);
                   double r = _rand.NextDouble();
                   _board[a, b].Value = r > 0.89f ? 4 : 2;
                   if (CanMove())
                   {
                       return;
                   }
               }
           }
           _isDone = true;
       }
       private bool CanMove()
       {
           for (int y = 0; y < 4; y++)
           {
               for (int x = 0; x < 4; x++)
               {
                   if (_board[x, y].Value == 0)
                   {
                       return true;
                   }
               }
           }
           for (int y = 0; y < 4; y++)
           {
               for (int x = 0; x < 4; x++)
               {
                   if (TestAdd(x + 1, y, _board[x, y].Value)
                       || TestAdd(x - 1, y, _board[x, y].Value)
                       || TestAdd(x, y + 1, _board[x, y].Value)
                       || TestAdd(x, y - 1, _board[x, y].Value))
                   {
                       return true;
                   }
               }
           }
           return false;
       }
       private bool TestAdd(int x, int y, int value)
       {
           if (x < 0 || x > 3 || y < 0 || y > 3)
           {
               return false;
           }
           return _board[x, y].Value == value;
       }
       private void MoveVertically(int x, int y, int d)
       {
           if (_board[x, y + d].Value != 0
               && _board[x, y + d].Value == _board[x, y].Value
               && !_board[x, y].IsBlocked
               && !_board[x, y + d].IsBlocked)
           {
               _board[x, y].Value = 0;
               _board[x, y + d].Value *= 2;
               _score += _board[x, y + d].Value;
               _board[x, y + d].IsBlocked = true;
               _isMoved = true;
           }
           else if (_board[x, y + d].Value == 0
                    && _board[x, y].Value != 0)
           {
               _board[x, y + d].Value = _board[x, y].Value;
               _board[x, y].Value = 0;
               _isMoved = true;
           }
           if (d > 0)
           {
               if (y + d < 3)
               {
                   MoveVertically(x, y + d, 1);
               }
           }
           else
           {
               if (y + d > 0)
               {
                   MoveVertically(x, y + d, -1);
               }
           }
       }
       private void MoveHorizontally(int x, int y, int d)
       {
           if (_board[x + d, y].Value != 0
               && _board[x + d, y].Value == _board[x, y].Value
               && !_board[x + d, y].IsBlocked
               && !_board[x, y].IsBlocked)
           {
               _board[x, y].Value = 0;
               _board[x + d, y].Value *= 2;
               _score += _board[x + d, y].Value;
               _board[x + d, y].IsBlocked = true;
               _isMoved = true;
           }
           else if (_board[x + d, y].Value == 0
                    && _board[x, y].Value != 0)
           {
               _board[x + d, y].Value = _board[x, y].Value;
               _board[x, y].Value = 0;
               _isMoved = true;
           }
           if (d > 0)
           {
               if (x + d < 3)
               {
                   MoveHorizontally(x + d, y, 1);
               }
           }
           else
           {
               if (x + d > 0)
               {
                   MoveHorizontally(x + d, y, -1);
               }
           }
       }
       private void Move(MoveDirection direction)
       {
           switch (direction)
           {
               case MoveDirection.Up:
                   for (int x = 0; x < 4; x++)
                   {
                       int y = 1;
                       while (y < 4)
                       {
                           if (_board[x, y].Value != 0)
                           {
                               MoveVertically(x, y, -1);
                           }
                           y++;
                       }
                   }
                   break;
               case MoveDirection.Down:
                   for (int x = 0; x < 4; x++)
                   {
                       int y = 2;
                       while (y >= 0)
                       {
                           if (_board[x, y].Value != 0)
                           {
                               MoveVertically(x, y, 1);
                           }
                           y--;
                       }
                   }
                   break;
               case MoveDirection.Left:
                   for (int y = 0; y < 4; y++)
                   {
                       int x = 1;
                       while (x < 4)
                       {
                           if (_board[x, y].Value != 0)
                           {
                               MoveHorizontally(x, y, -1);
                           }
                           x++;
                       }
                   }
                   break;
               case MoveDirection.Right:
                   for (int y = 0; y < 4; y++)
                   {
                       int x = 2;
                       while (x >= 0)
                       {
                           if (_board[x, y].Value != 0)
                           {
                               MoveHorizontally(x, y, 1);
                           }
                           x--;
                       }
                   }
                   break;
           }
       }
   }
   internal static class Program
   {
       public static void Main(string[] args)
       {
           RunGame();
       }
       private static void RunGame()
       {
           G2048 game = new G2048();
           game.Loop();
           CheckRestart();
       }
       private static void CheckRestart()
       {
           Console.WriteLine("(N) New game (P) Exit");
           while (true)
           {
               char input;
               char.TryParse(Console.ReadKey().Key.ToString(), out input);
               switch (input)
               {
                   case 'N':
                       RunGame();
                       break;
                   case 'P':
                       return;
                   default:
                       ClearLastLine();
                       break;
               }
           }
       }
       private static void ClearLastLine()
       {
           Console.SetCursorPosition(0, Console.CursorTop);
           Console.Write(new string(' ', Console.BufferWidth));
           Console.SetCursorPosition(0, Console.CursorTop - 1);
       }
   }

} </lang>

Output:
Score: 572

+------+------+------+------+
|      | 2    | 16   | 4    |
+------+------+------+------+
|      | 2    | 4    | 64   |
+------+------+------+------+
| 4    | 16   | 32   | 4    |
+------+------+------+------+
| 2    | 4    | 2    | 16   |
+------+------+------+------+


(W) Up (S) Down (A) Left (D) Right

C++

<lang cpp>

  1. include <time.h>
  2. include <iostream>
  3. include <string>
  4. include <iomanip>
  5. include <cstdlib>

typedef unsigned int uint; using namespace std; enum movDir { UP, DOWN, LEFT, RIGHT };

class tile { public:

   tile() : val( 0 ), blocked( false ) {}
   uint val;
   bool blocked;

};

class g2048 { public:

   g2048() : done( false ), win( false ), moved( true ), score( 0 ) {}
   void loop()
   {

addTile(); while( true ) { if( moved ) addTile(); drawBoard(); if( done ) break; waitKey(); } string s = "Game Over!"; if( win ) s = "You've made it!"; cout << s << endl << endl;

   }

private:

   void drawBoard()
   {

system( "cls" ); cout << "SCORE: " << score << endl << endl; for( int y = 0; y < 4; y++ ) { cout << "+------+------+------+------+" << endl << "| "; for( int x = 0; x < 4; x++ ) { if( !board[x][y].val ) cout << setw( 4 ) << " "; else cout << setw( 4 ) << board[x][y].val; cout << " | "; } cout << endl; } cout << "+------+------+------+------+" << endl << endl;

   }
   void waitKey()
   {

moved = false; char c; cout << "(W)Up (S)Down (A)Left (D)Right "; cin >> c; c &= 0x5F; switch( c ) { case 'W': move( UP );break; case 'A': move( LEFT ); break; case 'S': move( DOWN ); break; case 'D': move( RIGHT ); } for( int y = 0; y < 4; y++ ) for( int x = 0; x < 4; x++ ) board[x][y].blocked = false;

   }
   void addTile()
   {

for( int y = 0; y < 4; y++ ) for( int x = 0; x < 4; x++ ) if( !board[x][y].val ) { uint a, b; do { a = rand() % 4; b = rand() % 4; } while( board[a][b].val );

int s = rand() % 100; if( s > 89 ) board[a][b].val = 4; else board[a][b].val = 2; if( canMove() ) return; } done = true;

   }
   bool canMove()
   {

for( int y = 0; y < 4; y++ ) for( int x = 0; x < 4; x++ ) if( !board[x][y].val ) return true;

for( int y = 0; y < 4; y++ ) for( int x = 0; x < 4; x++ ) { if( testAdd( x + 1, y, board[x][y].val ) ) return true; if( testAdd( x - 1, y, board[x][y].val ) ) return true; if( testAdd( x, y + 1, board[x][y].val ) ) return true; if( testAdd( x, y - 1, board[x][y].val ) ) return true; } return false;

   }
   bool testAdd( int x, int y, uint v )
   {

if( x < 0 || x > 3 || y < 0 || y > 3 ) return false; return board[x][y].val == v;

   }
   void moveVert( int x, int y, int d )
   {

if( board[x][y + d].val && board[x][y + d].val == board[x][y].val && !board[x][y].blocked && !board[x][y + d].blocked ) { board[x][y].val = 0; board[x][y + d].val *= 2; score += board[x][y + d].val; board[x][y + d].blocked = true; moved = true; } else if( !board[x][y + d].val && board[x][y].val ) { board[x][y + d].val = board[x][y].val; board[x][y].val = 0; moved = true; } if( d > 0 ) { if( y + d < 3 ) moveVert( x, y + d, 1 ); } else { if( y + d > 0 ) moveVert( x, y + d, -1 ); }

   }
   void moveHori( int x, int y, int d )
   {

if( board[x + d][y].val && board[x + d][y].val == board[x][y].val && !board[x][y].blocked && !board[x + d][y].blocked ) { board[x][y].val = 0; board[x + d][y].val *= 2; score += board[x + d][y].val; board[x + d][y].blocked = true; moved = true; } else if( !board[x + d][y].val && board[x][y].val ) { board[x + d][y].val = board[x][y].val; board[x][y].val = 0; moved = true; } if( d > 0 ) { if( x + d < 3 ) moveHori( x + d, y, 1 ); } else { if( x + d > 0 ) moveHori( x + d, y, -1 ); }

   }
   void move( movDir d )
   {

switch( d ) { case UP: for( int x = 0; x < 4; x++ ) { int y = 1; while( y < 4 ) { if( board[x][y].val ) moveVert( x, y, -1 ); y++;} } break; case DOWN: for( int x = 0; x < 4; x++ ) { int y = 2; while( y >= 0 ) { if( board[x][y].val ) moveVert( x, y, 1 ); y--;} } break; case LEFT: for( int y = 0; y < 4; y++ ) { int x = 1; while( x < 4 ) { if( board[x][y].val ) moveHori( x, y, -1 ); x++;} } break; case RIGHT: for( int y = 0; y < 4; y++ ) { int x = 2; while( x >= 0 ) { if( board[x][y].val ) moveHori( x, y, 1 ); x--;} } }

   }
   tile board[4][4];
   bool win, done, moved;
   uint score;

}; int main( int argc, char* argv[] ) {

   srand( static_cast<uint>( time( NULL ) ) );
   g2048 g; g.loop();
   return system( "pause" );

} </lang>

Output:
SCORE: 2024

+------+------+------+------+
|    2 |    8 |   32 |  256 |
+------+------+------+------+
|      |      |    4 |   32 |
+------+------+------+------+
|      |      |    2 |    8 |
+------+------+------+------+
|      |      |      |    2 |
+------+------+------+------+

(W)Up (S)Down (A)Left (D)Right

Clojure

<lang clojure> (ns 2048

 (:require [clojure.string :as str]))
Preferences

(def textures {:wall "----+"

              :cell      "%4s|"
              :cell-edge "|"
              :wall-edge "+"})

(def directions {:w :up

                :s :down
                :a :left
                :d :right})

(def field-size {:y 4 :x 4})

Output

(defn cells->str [line]

 (str (:cell-edge textures)
      (str/join (map (partial format (:cell textures)) line))
      "\n"))

(defn walls->str [width]

 (str (:wall-edge textures)
      (str/join (repeat width (:wall textures)))
      "\n"))

(defn field->str [field]

 (let [height (count field)
       width (count (first field))]
   (str (str/join (interleave (repeat height (walls->str width))
                              (map cells->str field)))
        (walls->str width))))
Misc

(defn handle-input []

 (let [input (read)
       try-dir ((keyword input) directions)]
   (if try-dir try-dir (recur))))

(defn get-columns [field]

 (vec (for [x (range (count (first field)))]
        (vec (for [y (range (count field))]
               (get-in field [y x]))))))

(defn reverse-lines [field]

 (mapv #(vec (reverse %)) field))

(defn padding [coll n sym]

 (vec (concat coll (repeat n sym))))

(defn find-empties [field]

 (remove
   nil?
   (for [y (range (count field))
         x (range (count (nth field y)))]
     (when (= (get-in field [y x]) \space) [y x]))))

(defn random-add [field]

 (let [empties (vec (find-empties field))]
   (assoc-in field
             (rand-nth empties)
             (rand-nth (conj (vec (repeat 9 2)) 4)))))

(defn win-check [field]

 (= 2048
    (transduce
      (filter number?)
      (completing max)
      0
      (flatten field))))

(defn lose-check [field]

 (empty? (filter (partial = \space) (flatten field))))

(defn create-start-field [y x]

 (->> (vec (repeat y (vec (repeat x \space))))
      (random-add)
      (random-add)))
Algo

(defn lines-by-dir [back? direction field]

 (case direction
   :left field
   :right (reverse-lines field)
   :down (if back?
           (get-columns (reverse-lines field))
           (reverse-lines (get-columns field)))
   :up (get-columns field)))

(defn shift-line [line]

 (let [len (count line)
       line (vec (filter number? line))
       max-idx (dec (count line))]
   (loop [new [] idx 0]
     (if (> idx max-idx)
         (padding new (- len (count new)) \space)
         (if (= (nth line idx) (get line (inc idx)))
             (recur (conj new (* 2 (nth line idx))) (+ 2 idx))
             (recur (conj new (nth line idx)) (inc idx)))))))

(defn shift-field [direction field]

 (->> (lines-by-dir false direction field)
      (mapv shift-line)
      (lines-by-dir true direction)))

(defn handle-turn [field]

 (let [direction (handle-input)]
   (->> (shift-field direction field)
        (random-add))))

(defn play-2048 []

 (loop [field (create-start-field (:y field-size) (:x field-size))]
   (println (field->str field))
   (cond (win-check field) (println "You win")
         (lose-check field) (println "You lose")
         :default (recur (handle-turn field)))))

(play-2048)</lang>

Output:
+----+----+----+----+
|    |   2|    |    |
+----+----+----+----+
|   2|    |    |    |
+----+----+----+----+
|   4|    |    |    |
+----+----+----+----+
|  16|   2|    |    |
+----+----+----+----+

Common Lisp

Depends on Windows msvcrt.dll for _getch. Depends on quicklisp. Use arrow keys to make moves and press "Q" to quit. Tested with SBCL. <lang lisp>(ql:quickload '(cffi alexandria))

(defpackage :2048-lisp

 (:use :common-lisp :cffi :alexandria))

(in-package :2048-lisp)

(defvar *lib-loaded* nil)

(unless *lib-loaded*

 ;; Load msvcrt.dll to access _getch.
 (define-foreign-library msvcrt
   (:windows (:default "msvcrt")))
 (use-foreign-library msvcrt)
 (defcfun "_getch" :int)
 (setf *lib-loaded* t))

(defun read-arrow ()

 "Get an arrow key from input as UP, DOWN, LEFT, or RIGHT, otherwise

return a char of whatever was pressed."

 (let ((first-char (-getch)))
   (if (= 224 first-char)
       (case (-getch)
         (75 'left)
         (80 'down)
         (77 'right)
         (72 'up))
       (code-char first-char))))

(defmacro swap (place1 place2)

 "Exchange the values of two places."
 (let ((temp (gensym "TEMP")))
   `(cl:let ((,temp ,place1))
      (cl:setf ,place1 ,place2)
      (cl:setf ,place2 ,temp))))

(defun nflip (board &optional (left-right t))

 "Flip the elements of a BOARD left and right or optionally up and down."
 (let* ((y-len (array-dimension board 0))
        (x-len (array-dimension board 1))
        (y-max (if left-right y-len (truncate y-len 2)))
        (x-max (if left-right (truncate x-len 2) x-len)))
   (loop for y from 0 below y-max
      for y-place = (- y-len 1 y) do
        (loop for x from 0 below x-max
           for x-place = (- x-len 1 x) do
             (if left-right
                 (swap (aref board y x) (aref board y x-place))
                 (swap (aref board y x) (aref board y-place x)))))
   board))

(defun flip (board &optional (left-right t))

 "Flip the elements of a BOARD left and right or optionally up and down.

Non-destructive version."

 (nflip (copy-array board) left-right))

(defun transpose (board)

 "Transpose the elements of BOARD into a new array."
 (let* ((y-len (array-dimension board 0))
        (x-len (array-dimension board 1))
        (new-board (make-array (reverse (array-dimensions board)))))
   (loop for y from 0 below y-len do
        (loop for x from 0 below x-len do
             (setf (aref new-board x y) (aref board y x))))
   new-board))

(defun add-random-piece (board)

 "Find a random empty spot on the BOARD to add a new piece.

Return T if successful, NIL otherwise."

 (loop
    for x from 0 below (array-total-size board)
    unless (row-major-aref board x)
    count 1 into count
    and collect x into indices
    finally
      (unless (= 0 count)
        (setf (row-major-aref board (nth (random count) indices))
              (if (= 0 (random 10)) 4 2))
        (return t))))

(defun squash-line (line)

 "Reduce a sequence of numbers from left to right according to

the rules of 2048. Return the score of squashing as well."

 (let* ((squashed
         (reduce
          (lambda (acc x)
            (if (equal x (car acc))
                (cons (list (* 2 x)) (cdr acc))
                (cons x acc)))
          (nreverse (remove-if #'null line))
          :initial-value nil))
        (new-line (flatten squashed)))
   (list (append (make-list (- (length line) (length new-line))) new-line)
         (reduce #'+ (flatten (remove-if-not #'listp squashed))))))

(defun squash-board (board)

 "Reduce each row of a board from left to right according to

the rules of 2048. Return the total score of squashing the board as well."

 (let ((y-len (array-dimension board 0))
       (x-len (array-dimension board 1))
       (total 0))
   (list (make-array (array-dimensions board) :initial-contents
                     (loop for y from 0 below y-len
                        for (line score) =
                          (squash-line
                           (make-array x-len
                                       :displaced-to board
                                       :displaced-index-offset
                                       (array-row-major-index board y 0)))
                        collect line
                        do (incf total score)))
         total)))

(defun make-move (board direction)

 "Make a move in the given DIRECTION on a new board."
 ;; Move by always squashing right, but transforming the board as needed.
 (destructuring-bind (new-board score)
     (case direction
       (up (squash-board (flip (transpose board))))
       (down (squash-board (flip (transpose board) nil)))
       (left (squash-board (nflip (flip board nil))))
       (right (squash-board board)))
   (let ((new-board
          ;; Reverse the transformation.
          (case direction
            (up (transpose (nflip new-board)))
            (down (transpose (nflip new-board nil)))
            (left (nflip (nflip new-board nil)))
            (right new-board))))
     (unless (equalp board new-board)
       (add-random-piece new-board)
       (list new-board score)))))

(defun winp (board winning-tile)

 "Determine if a BOARD is in a winning state."
 (loop for x from 0 below (array-total-size board)
    for val = (row-major-aref board x)
    when (eql val winning-tile) do (return t)))

(defun game-overp (board)

 "Determine if a BOARD is in a game over state."
 ;; If a move is simulated in every direction and nothing changes,
 ;; then we can assume there are no valid moves left.
 (notany (lambda (dir) (car (make-move board dir)))
         '(up down left right)))

(defun print-divider (cells cell-size)

 "A print helper function for PRINT-BOARD."
 (dotimes (_ cells)
   (princ "+")
   (dotimes (_ cell-size)
     (princ "-")))
 (princ "+")
 (terpri))

(defun print-board (board cell-size)

 "Pretty print the given BOARD with a particular CELL-SIZE."
 (let* ((y-len (array-dimension board 0))
        (x-len (array-dimension board 1))
        (super-size (+ 2 cell-size)))
   (loop for y from 0 below y-len do
        (print-divider x-len super-size)
        (loop for x from 0 below x-len
           for val = (aref board y x)
           do
             (princ "|")
             (if val
                 (format t " ~VD " cell-size val)
                 (dotimes (_ super-size) (princ " "))))
        (princ "|")
        (terpri))
   (print-divider x-len super-size)))

(defun init-board ()

 (let ((board (make-array '(4 4) :initial-element nil)))
   (setf (row-major-aref board (random (array-total-size board))) 2)
   board))

(defun prompt-input (board score &optional (check t))

 (cond
   ((and check (winp board 2048)) (format t "You win!"))
   ((and check (game-overp board)) (format t "Game over..."))
   (t (let ((choice (read-arrow)))
        (cond
          ((symbolp choice)
           (destructuring-bind (&optional move (new-score 0))
               (make-move board choice)
             (if move
                 (prompt move (+ score new-score))
                 (prompt-input board score))))
          ((find choice "qQ")
           (format t "Quitting."))
          (t (prompt-input board score nil)))))))

(defun prompt (&optional (board (init-board)) (score 0))

 (format t "~%   Score: ~D~%" score)
 (print-board board 4)
 (prompt-input board score))</lang>
Output:
* (2048-lisp::prompt)

   Score: 0
+------+------+------+------+
|      |      |      |      |
+------+------+------+------+
|      |      |      |      |
+------+------+------+------+
|      |      |    2 |      |
+------+------+------+------+
|      |      |      |      |
+------+------+------+------+

Some time later...

   Score: 336
+------+------+------+------+
|      |    4 |   16 |   32 |
+------+------+------+------+
|      |      |    4 |   16 |
+------+------+------+------+
|      |      |   32 |      |
+------+------+------+------+
|      |    2 |      |      |
+------+------+------+------+

D

Translation of: C++

<lang d>import std.stdio, std.string, std.random; import core.stdc.stdlib: exit;

struct G2048 {

   public void gameLoop() /*@safe @nogc*/ {
       addTile;
       while (true) {
           if (moved)
               addTile;
           drawBoard;
           if (done)
               break;
           waitKey;
       }
       writeln(win ? "You win!" : "Game Over!");
   }

private:

   static struct Tile {
       uint val = 0;
       bool blocked = false;
   }
   enum moveDir { up, down, left, right }
   enum uint side = 4;
   Tile[side][side] board;
   bool win = false, done = false, moved = true;
   uint score = 0;
   void drawBoard() const /*@safe @nogc*/ {
       writeln("SCORE: ", score, "\n");
       foreach (immutable y; 0 .. side) {
           write("+------+------+------+------+\n| ");
           foreach (immutable x; 0 .. side) {
               if (board[x][y].val)
                   writef("%4d", board[x][y].val);
               else
                   writef("%4s", " ");
               write(" | ");
           }
           writeln;
       }
       "+------+------+------+------+\n".writeln;
   }
   void waitKey() /*@safe*/ {
       moved = false;
       "(W)Up (S)Down (A)Left (D)Right (Q)Quit: ".write;
       immutable c = readln.strip.toLower;
       switch (c) {
           case "w": move(moveDir.up);    break;
           case "a": move(moveDir.left);  break;
           case "s": move(moveDir.down);  break;
           case "d": move(moveDir.right); break;
           case "q": endGame;             break;
           default:                       break;
       }
       foreach (immutable y; 0 .. side)
           foreach (immutable x; 0 .. side)
               board[x][y].blocked = false;
   }
   void endGame() const {
       writeln("Game ended with score: ", score);
       exit(0);
   }
   void addTile() /*nothrow*/ @safe /*@nogc*/ {
       foreach (immutable y; 0 .. side) {
           foreach (immutable x; 0 .. side) {
               if (!board[x][y].val) {
                   uint a, b;
                   do {
                       a = uniform(0, side);
                       b = uniform(0, side);
                   } while (board[a][b].val);
                   board[a][b].val = (uniform01 > 0.89) ? side : 2;
                   if (canMove)
                       return;
               }
           }
       }
       done = true;
   }
   bool canMove() const pure nothrow @safe @nogc {
       foreach (immutable y; 0 .. side)
           foreach (immutable x; 0 .. side)
               if (!board[x][y].val)
                   return true;
       foreach (immutable y; 0 .. side) {
           foreach (immutable x; 0 .. side) {
               if (testAdd(x + 1, y, board[x][y].val) ||
                   testAdd(x - 1, y, board[x][y].val) ||
                   testAdd(x, y + 1, board[x][y].val) ||
                   testAdd(x, y - 1, board[x][y].val))
               return true;
           }
       }
       return false;
   }
   bool testAdd(in uint x, in uint y, in uint v) const pure nothrow @safe @nogc {
       if (x > 3 || y > 3)
           return false;
       return board[x][y].val == v;
   }
   void moveVertically(in uint x, in uint y, in uint d) pure nothrow @safe @nogc {
       if (board[x][y + d].val && board[x][y + d].val == board[x][y].val &&
           !board[x][y].blocked && !board[x][y + d].blocked) {
           board[x][y].val = 0;
           board[x][y + d].val *= 2;
           score += board[x][y + d].val;
           board[x][y + d].blocked = true;
           moved = true;
       } else if (!board[x][y + d].val && board[x][y].val) {
           board[x][y + d].val = board[x][y].val;
           board[x][y].val = 0;
           moved = true;
       }
       if (d > 0) {
           if (y + d < 3)
               moveVertically(x, y + d,  1);
       } else {
           if (y + d > 0)
               moveVertically(x, y + d, -1);
       }
   }
   void moveHorizontally(in uint x, in uint y, in uint d) pure nothrow @safe @nogc {
       if (board[x + d][y].val && board[x + d][y].val == board[x][y].val &&
           !board[x][y].blocked && !board[x + d][y].blocked) {
           board[x][y].val = 0;
           board[x + d][y].val *= 2;
           score += board[x + d][y].val;
           board[x + d][y].blocked = true;
           moved = true;
       } else if (!board[x + d][y].val && board[x][y].val) {
           board[x + d][y].val = board[x][y].val;
           board[x][y].val = 0;
           moved = true;
       }
       if (d > 0) {
           if (x + d < 3)
               moveHorizontally(x + d, y,  1);
       } else {
           if (x + d > 0)
               moveHorizontally(x + d, y, -1);
       }
   }
   void move(in moveDir d) pure nothrow @safe @nogc {
       final switch (d) with(moveDir) {
           case up:
               foreach (immutable x; 0 .. side)
                   foreach (immutable y; 1 .. side)
                       if (board[x][y].val)
                           moveVertically(x, y, -1);
               break;
           case down:
               foreach (immutable x; 0 .. side)
                   foreach_reverse (immutable y; 0 .. 3)
                       if (board[x][y].val)
                           moveVertically(x, y, 1);
               break;
           case left:
               foreach (immutable y; 0 .. side)
                   foreach (immutable x; 1 .. side)
                       if (board[x][y].val)
                           moveHorizontally(x, y, -1);
               break;
           case right:
               foreach (immutable y; 0 .. side)
                   foreach_reverse (immutable x; 0 .. 3)
                       if (board[x][y].val)
                           moveHorizontally(x, y, 1);
       }
   }

}

void main() /*safe*/ {

   G2048 g;
   g.gameLoop;

}</lang> The output is the same as the C++ version.

Delphi

Thanks for Rudy Velthuis [1].

Translation of: C++

<lang Delphi> program Game2048;

{$APPTYPE CONSOLE}

uses

 System.SysUtils,
 System.Math,
 Velthuis.Console;

type

 TTile = class
   Value: integer;
   IsBlocked: Boolean;
   constructor Create;
 end;
 TMoveDirection = (mdUp, mdDown, mdLeft, mdRight);
 TG2048 = class
   FisDone, FisWon, FisMoved: boolean;
   Fscore: Cardinal;
   FBoard: array[0..3, 0..3] of TTile;
   function GetLine(aType: byte): string;
 public
   constructor Create;
   destructor Destroy; override;
   procedure InitializeBoard();
   procedure FinalizeBoard();
   procedure Loop;
   procedure DrawBoard();
   procedure WaitKey();
   procedure AddTile();
   function CanMove(): boolean;
   function TestAdd(x, y, value: Integer): boolean;
   procedure MoveHorizontally(x, y, d: integer);
   procedure MoveVertically(x, y, d: integer);
   procedure Move(direction: TMoveDirection);
 end;


{ TTile }

constructor TTile.Create; begin

 Value := 0;
 IsBlocked := false;

end;

{ TG2048 }

procedure TG2048.AddTile; var

 y, x, a, b: Integer;
 r: Double;

begin

 for y := 0 to 3 do
 begin
   for x := 0 to 3 do
   begin
     if Fboard[x, y].Value <> 0 then
       continue;
     repeat
       a := random(4);
       b := random(4);
     until not (Fboard[a, b].Value <> 0);
     r := Random;
     if r > 0.89 then
       Fboard[a, b].Value := 4
     else
       Fboard[a, b].Value := 2;
     if CanMove() then
     begin
       Exit;
     end;
   end;
 end;
 FisDone := true;

end;

function TG2048.CanMove: boolean; var

 y, x: Integer;

begin

 for y := 0 to 3 do
 begin
   for x := 0 to 3 do
   begin
     if Fboard[x, y].Value = 0 then
     begin
       Exit(true);
     end;
   end;
 end;
 for y := 0 to 3 do
 begin
   for x := 0 to 3 do
   begin
     if TestAdd(x + 1, y, Fboard[x, y].Value) or TestAdd(x - 1, y, Fboard[x, y].Value)
       or TestAdd(x, y + 1, Fboard[x, y].Value) or TestAdd(x, y - 1, Fboard[x,
       y].Value) then
     begin
       Exit(true);
     end;
   end;
 end;
 Exit(false);

end;

constructor TG2048.Create; begin

 FisDone := false;
 FisWon := false;
 FisMoved := true;
 Fscore := 0;
 InitializeBoard();
 Randomize;

end;

destructor TG2048.Destroy; begin

 FinalizeBoard;
 inherited;

end;

procedure TG2048.DrawBoard; var

 y, x: Integer;
 color: byte;
 lineFragment, line: string;

begin

 ClrScr;
 HighVideo;
 writeln('Score: ', Fscore: 3, #10);
 TextBackground(White);
 TextColor(black);
 for y := 0 to 3 do
 begin
   if y = 0 then
     writeln(GetLine(0))
   else
     writeln(GetLine(1));
   Write('  '#$2551' ');
   for x := 0 to 3 do
   begin
     if Fboard[x, y].Value = 0 then
     begin
       Write('    ');
     end
     else
     begin
       color := Round(Log2(Fboard[x, y].Value));
       TextColor(14 - color);
       Write(Fboard[x, y].Value: 4);
       TextColor(Black);
     end;
     Write(' '#$2551' ');
   end;
   writeln(' ');
 end;
 writeln(GetLine(2), #10#10);
 TextBackground(Black);
 TextColor(White);

end;

procedure TG2048.FinalizeBoard; var

 y, x: integer;

begin

 for y := 0 to 3 do
   for x := 0 to 3 do
     FBoard[x, y].Free;

end;

function TG2048.GetLine(aType: byte): string; var

 fragment, line: string;
 bgChar, edChar, mdChar: char;

begin

 case aType of
   0:
     begin
       bgChar := #$2554;
       edChar := #$2557;
       mdChar := #$2566;
     end;
   1:
     begin
       bgChar := #$2560;
       edChar := #$2563;
       mdChar := #$256C;
     end;
   2:
     begin
       bgChar := #$255A;
       edChar := #$255D;
       mdChar := #$2569;
     end;
 end;
 fragment := string.create(#$2550, 6);
 line := fragment + mdChar + fragment + mdChar + fragment + mdChar + fragment;
 Result := '  '+bgChar + line + edChar + '  ';

end;

procedure TG2048.InitializeBoard; var

 y, x: integer;

begin

 for y := 0 to 3 do
   for x := 0 to 3 do
     FBoard[x, y] := TTile.Create;

end;

procedure TG2048.Loop; begin

 AddTile();
 while (true) do
 begin
   if (FisMoved) then
     AddTile();
   DrawBoard();
   if (FisDone) then
     break;
   WaitKey();
 end;
 if FisWon then
   Writeln('Youve made it!')
 else
   Writeln('Game Over!');

end;

procedure TG2048.Move(direction: TMoveDirection); var

 x, y: Integer;

begin

 case direction of
   mdUp:
     begin
       for x := 0 to 3 do
       begin
         y := 1;
         while y < 4 do
         begin
           if Fboard[x, y].Value <> 0 then
             MoveVertically(x, y, -1);
           Inc(y);
         end;
       end;
     end;
   mdDown:
     begin
       for x := 0 to 3 do
       begin
         y := 2;
         while y >= 0 do
         begin
           if Fboard[x, y].Value <> 0 then
             MoveVertically(x, y, 1);
           Dec(y);
         end;
       end;
     end;
   mdLeft:
     begin
       for y := 0 to 3 do
       begin
         x := 1;
         while x < 4 do
         begin
           if Fboard[x, y].Value <> 0 then
             MoveHorizontally(x, y, -1);
           Inc(x);
         end;
       end;
     end;
   mdRight:
     begin
       for y := 0 to 3 do
       begin
         x := 2;
         while x >= 0 do
         begin
           if Fboard[x, y].Value <> 0 then
             MoveHorizontally(x, y, 1);
           Dec(x);
         end;
       end;
     end;
 end;

end;

procedure TG2048.MoveHorizontally(x, y, d: integer); begin

 if (FBoard[x + d, y].Value <> 0) and (FBoard[x + d, y].Value = FBoard[x, y].Value)
   and (not FBoard[x + d, y].IsBlocked) and (not FBoard[x, y].IsBlocked) then
 begin
   FBoard[x, y].Value := 0;
   FBoard[x + d, y].Value := FBoard[x + d, y].Value * 2;
   Fscore := Fscore + (FBoard[x + d, y].Value);
   FBoard[x + d, y].IsBlocked := true;
   FisMoved := true;
 end
 else if ((FBoard[x + d, y].Value = 0) and (FBoard[x, y].Value <> 0)) then
 begin
   FBoard[x + d, y].Value := FBoard[x, y].Value;
   FBoard[x, y].Value := 0;
   FisMoved := true;
 end;
 if d > 0 then
 begin
   if x + d < 3 then
   begin
     MoveHorizontally(x + d, y, 1);
   end;
 end
 else
 begin
   if x + d > 0 then
   begin
     MoveHorizontally(x + d, y, -1);
   end;
 end;

end;

procedure TG2048.MoveVertically(x, y, d: integer); begin

 if (Fboard[x, y + d].Value <> 0) and (Fboard[x, y + d].Value = Fboard[x, y].Value)
   and (not Fboard[x, y].IsBlocked) and (not Fboard[x, y + d].IsBlocked) then
 begin
   Fboard[x, y].Value := 0;
   Fboard[x, y + d].Value := Fboard[x, y + d].Value * 2;
   Fscore := Fscore + (Fboard[x, y + d].Value);
   Fboard[x, y + d].IsBlocked := true;
   FisMoved := true;
 end
 else if ((Fboard[x, y + d].Value = 0) and (Fboard[x, y].Value <> 0)) then
 begin
   Fboard[x, y + d].Value := Fboard[x, y].Value;
   Fboard[x, y].Value := 0;
   FisMoved := true;
 end;
 if d > 0 then
 begin
   if y + d < 3 then
   begin
     MoveVertically(x, y + d, 1);
   end;
 end
 else
 begin
   if y + d > 0 then
   begin
     MoveVertically(x, y + d, -1);
   end;
 end;

end;

function TG2048.TestAdd(x, y, value: Integer): boolean; begin

 if (x < 0) or (x > 3) or (y < 0) or (y > 3) then
   Exit(false);
 Exit(Fboard[x, y].value = value);

end;

procedure TG2048.WaitKey; var

 y, x: Integer;

begin

 FisMoved := false;
 writeln('(W) Up (S) Down (A) Left (D) Right (ESC)Exit');
 case ReadKey of
   'W', 'w':
     Move(TMoveDirection.mdUp);
   'A', 'a':
     Move(TMoveDirection.mdLeft);
   'S', 's':
     Move(TMoveDirection.mdDown);
   'D', 'd':
     Move(TMoveDirection.mdRight);
   #27:
     FisDone := true;
 end;
 for y := 0 to 3 do
   for x := 0 to 3 do
     Fboard[x, y].IsBlocked := false;

end;

var

 Game: TG2048;

begin

 with TG2048.Create do
 begin
   Loop;
   Free;
 end;
 Writeln('Press Enter to exit');
 Readln;

end.</lang>

Output:
Score: 6472

  ╔══════╦══════╦══════╦══════╗
  ║    2 ║    8 ║    2 ║    4 ║
  ╠══════╬══════╬══════╬══════╣
  ║   16 ║   64 ║    8 ║   64 ║
  ╠══════╬══════╬══════╬══════╣
  ║    8 ║  512 ║  256 ║    2 ║
  ╠══════╬══════╬══════╬══════╣
  ║    2 ║    4 ║   16 ║    4 ║
  ╚══════╩══════╩══════╩══════╝


Game Over!
Press Enter to exit

Elixir

Works with: Elixir version 1.3

<lang elixir>defmodule Game2048 do

 @size 4
 @range 0..@size-1
 
 def play(goal \\ 2048), do: setup() |> play(goal)
 
 defp play(board, goal) do
   show(board)
   cond do
     goal in Map.values(board) ->
         IO.puts "You win!"
         exit(:normal)
     0 in Map.values(board) or combinable?(board) ->
         moved = move(board, keyin())
         if moved == board, do: play(board, goal), else: add_tile(moved) |> play(goal)
     true ->
         IO.puts "Game Over!"
         exit(:normal)
   end
 end
 
 defp setup do
   (for i <- @range, j <- @range, into: %{}, do: {{i,j},0})
   |> add_tile
   |> add_tile
 end
 
 defp add_tile(board) do
   position = blank_space(board) |> Enum.random
   tile = if :rand.uniform(10)==1, do: 4, else: 2
   %{board | position => tile}
 end
 
 defp blank_space(board) do
   for {key, 0} <- board, do: key
 end
 
 defp keyin do
   key = IO.gets("key in wasd or q: ")
   case String.first(key) do
     "w" -> :up
     "a" -> :left
     "s" -> :down
     "d" -> :right
     "q" -> exit(:normal)
     _   -> keyin()
   end
 end
 
 defp move(board, :up) do
   Enum.reduce(@range, board, fn j,acc ->
     Enum.map(@range, fn i -> acc[{i,j}] end)
     |> move_and_combine
     |> Enum.with_index
     |> Enum.reduce(acc, fn {v,i},map -> Map.put(map, {i,j}, v) end)
   end)
 end
 defp move(board, :down) do
   Enum.reduce(@range, board, fn j,acc ->
     Enum.map(@size-1..0, fn i -> acc[{i,j}] end)
     |> move_and_combine
     |> Enum.reverse
     |> Enum.with_index
     |> Enum.reduce(acc, fn {v,i},map -> Map.put(map, {i,j}, v) end)
   end)
 end
 defp move(board, :left) do
   Enum.reduce(@range, board, fn i,acc ->
     Enum.map(@range, fn j -> acc[{i,j}] end)
     |> move_and_combine
     |> Enum.with_index
     |> Enum.reduce(acc, fn {v,j},map -> Map.put(map, {i,j}, v) end)
   end)
 end
 defp move(board, :right) do
   Enum.reduce(@range, board, fn i,acc ->
     Enum.map(@size-1..0, fn j -> acc[{i,j}] end)
     |> move_and_combine
     |> Enum.reverse
     |> Enum.with_index
     |> Enum.reduce(acc, fn {v,j},map -> Map.put(map, {i,j}, v) end)
   end)
 end
 
 defp move_and_combine(tiles) do
   (Enum.filter(tiles, &(&1>0)) ++ [0,0,0,0])
   |> Enum.take(@size)
   |> case do
        [a,a,b,b] -> [a*2, b*2, 0, 0]
        [a,a,b,c] -> [a*2, b, c, 0]
        [a,b,b,c] -> [a, b*2, c, 0]
        [a,b,c,c] -> [a, b, c*2, 0]
        x         -> x
      end
 end
 
 defp combinable?(board) do
   Enum.any?(for i <- @range, j <- 0..@size-2, do: board[{i,j}]==board[{i,j+1}]) or
   Enum.any?(for j <- @range, i <- 0..@size-2, do: board[{i,j}]==board[{i+1,j}])
 end
 
 @frame   String.duplicate("+----", @size) <> "+"
 @format (String.duplicate("|~4w", @size) <> "|") |> to_charlist   # before 1.3 to_char_list
 
 defp show(board) do
   Enum.each(@range, fn i ->
     IO.puts @frame
     row = for j <- @range, do: board[{i,j}]
     IO.puts (:io_lib.fwrite @format, row) |> to_string |> String.replace(" 0|", "  |")
   end)
   IO.puts @frame
 end

end

Game2048.play 512</lang>

Output:
+----+----+----+----+
|    |   2|    |    |
+----+----+----+----+
|    |    |    |    |
+----+----+----+----+
|   2|    |    |    |
+----+----+----+----+
|    |    |    |    |
+----+----+----+----+
key in wasd or q: s
.
.
.
+----+----+----+----+
|   2|   4|   2|    |
+----+----+----+----+
|  16|    |    |    |
+----+----+----+----+
|   8|  16|  32|   2|
+----+----+----+----+
|  64| 256| 128|   4|
+----+----+----+----+
key in wasd or q: q

Elm

Works with: Elm 0.18.0

Try online [2] <lang Elm>module Main exposing (..)

import Html exposing (Html, div, p, text, button, span, h2) import Html.Attributes exposing (class, style) import Html.Events exposing (onClick) import Keyboard exposing (KeyCode) import Random import Tuple


main =

   Html.program
       { init = ( { initialModel | waitingForRandom = True }, generateRandomTiles 2 )
       , view = view
       , update = update
       , subscriptions = always (Keyboard.downs KeyPress)
       }


-- MODEL


-- tiles either have a value (2, 4, 8, ...) or are empty type alias Tile =

   Maybe Int


type alias Model =

   { score : Int
   , tiles : List Tile
   , hasLost : Bool
   , winKeepPlaying : Bool
   , waitingForRandom : Bool -- prevent user from giving input while waiting for Random Cmd to return
   }


initialModel : Model initialModel =

   { score = 0, tiles = List.repeat 16 Nothing, waitingForRandom = False, hasLost = False, winKeepPlaying = False}


-- UPDATE


type alias RandomTileInfo =

   ( Int, Int )


type Msg

   = KeyPress KeyCode
   | AddRandomTiles (List RandomTileInfo)
   | NewGame
   | KeepPlaying


-- asks the random generator to generate the information required for later adding random tiles -- generate a random position for the and value (4 10%, 2 90%) for each tile -- this uses Random.pair and Random.list to get a variable number of such pairs with one Cmd generateRandomTiles : Int -> Cmd Msg generateRandomTiles num =

   let
       randomPosition =
           Random.int 0 15
       randomValue =
           Random.int 1 10
               |> Random.map
                   (\rnd ->
                       if rnd == 10 then
                           4
                       else
                           2
                   )
       -- 10% chance
       randomPositionAndValue =
           Random.pair randomPosition randomValue
   in
       Random.list num randomPositionAndValue |> Random.generate AddRandomTiles


-- actually add a random tile to the model addRandomTile : RandomTileInfo -> List Tile -> List Tile addRandomTile ( newPosition, newValue ) tiles =

   let
       -- newPosition is a value between 0 and 15
       -- go through the list and count the amount of empty tiles we've seen.
       -- if we reached the newPosition % emptyTileCount'th empty tile, set its value to newValue
       emptyTileCount =
           List.filter ((==) Nothing) tiles |> List.length
       -- if there are less than 16 empty tiles this is the number of empty tiles we pass
       targetCount =
           newPosition % emptyTileCount
       set_ith_empty_tile tile ( countEmpty, newList ) =
           case tile of
               Just value ->
                   ( countEmpty, (Just value) :: newList )
               Nothing ->
                   if countEmpty == targetCount then
                       -- replace this empty tile with the new value
                       ( countEmpty + 1, (Just newValue) :: newList )
                   else
                       ( countEmpty + 1, Nothing :: newList )
   in
       List.foldr set_ith_empty_tile ( 0, [] ) tiles |> Tuple.second


-- core game mechanic: move numbers (to the left, -- moving to the right is equivalent to moving left on the reversed array) -- this function works on single columns/rows moveNumbers : List Tile -> ( List Tile, Int ) moveNumbers tiles =

   let
       last =
           List.head << List.reverse
   
       -- init is to last what tail is to head
       init =
          List.reverse << List.drop 1 << List.reverse
       doMove tile ( newTiles, addScore ) =
           case tile of
               -- omit empty tiles when shifting
               Nothing ->
                   ( newTiles, addScore )
               Just value ->
                   case last newTiles of
                       -- if the last already moved tile ...
                       Just (Just value2) ->
                           -- ... has the same value, add a tile with the summed value
                           if value == value2 then
                               ( (init newTiles) ++ [ Just (2 * value) ]
                               , addScore + 2 * value )
                           else
                               -- ... else just add the tile
                               ( newTiles ++ [ Just value ], addScore )
                       _ ->
                           -- ... else just add the tile
                           ( newTiles ++ [ Just value ], addScore )
       ( movedTiles, addScore ) =
           List.foldl doMove ( [], 0 ) tiles
   in
       ( movedTiles ++ List.repeat (4 - List.length movedTiles) Nothing, addScore )


update : Msg -> Model -> ( Model, Cmd Msg ) update msg model =

   case msg of
       -- new game button press
       NewGame ->
           if not model.waitingForRandom then
               ( { initialModel | waitingForRandom = True }, generateRandomTiles 2 )
           else
               ( model, Cmd.none )
       -- "keep playing" button on win screen
       KeepPlaying ->
           ( { model | winKeepPlaying = True }, Cmd.none)
       -- Random generator Cmd response
       AddRandomTiles tileInfos ->
           let
               newTiles =
                   List.foldl addRandomTile model.tiles tileInfos
           in
               ( { model | tiles = newTiles, waitingForRandom = False }, Cmd.none )


       KeyPress code ->
           let
               -- zip list and indices, apply filter, unzip
               indexedFilter func list =
                   List.map2 (,) (List.range 0 (List.length list - 1)) list
                       |> List.filter func
                       |> List.map Tuple.second
               -- the i'th row (of 4) contains elements i*4, i*4+1, i*4+2, i*4+3
               -- so all elements for which index//4 == i
               i_th_row list i =
                   indexedFilter (((==) i) << (flip (//) 4) << Tuple.first) list
               -- the i'th col (of 4) contain elements i, i+4, i+2*4, i+3*4
               -- so all elements for which index%4 == i
               i_th_col list i =
                   indexedFilter (((==) i) << (flip (%) 4) << Tuple.first) list
           
               -- rows and columns of the grid
               rows list =
                   List.map (i_th_row list) (List.range 0 3)
               cols list =
                   List.map (i_th_col list) (List.range 0 3)
               -- move each row or column and unzip the results from each call to moveNumbers
               move =
                   List.unzip << List.map moveNumbers
               moveReverse =
                   List.unzip << List.map (Tuple.mapFirst List.reverse << moveNumbers << List.reverse)
               -- concat rows back into a flat array and sum all addScores
               unrows =
                   Tuple.mapSecond List.sum << Tuple.mapFirst List.concat
               -- turn columns back into a flat array and sum all addScores
               uncols =
                   Tuple.mapSecond List.sum << Tuple.mapFirst (List.concat << cols << List.concat)
   
   
               -- when shifting left or right each row can be (reverse-) shifted separately
               -- when shifting up or down each column can be (reveerse-) shifted separately
               ( newTiles, addScore ) =
                   case code of
                       37 ->
                           -- left
                           unrows <| move <| rows model.tiles
                       38 ->
                           -- up
                           uncols <| move <| cols model.tiles
                       39 ->
                           -- right
                           unrows <| moveReverse <| rows model.tiles
                       40 ->
                           -- down
                           uncols <| moveReverse <| cols model.tiles
                       _ ->
                           ( model.tiles, 0 )
               
              
               containsEmptyTiles =
                   List.any ((==) Nothing)
                   
               containsAnySameNeighbours : List Tile -> Bool
               containsAnySameNeighbours list =
                   let
                       tail = List.drop 1 list
                       init = List.reverse <| List.drop 1 <| List.reverse list
                   in
                       List.any (uncurry (==)) <| List.map2 (,) init tail
               hasLost =
                    -- grid full
                   (not (containsEmptyTiles newTiles))
                       -- and no left/right move possible
                       && (not <| List.any containsAnySameNeighbours <| rows newTiles)
                       -- and no up/down move possible
                       && (not <| List.any containsAnySameNeighbours <| cols newTiles)
               ( cmd, waiting ) =
                   if List.all identity <| List.map2 (==) model.tiles newTiles then
                       ( Cmd.none, False )
                   else
                       ( generateRandomTiles 1, True )
               score =
                   model.score + addScore
           in
               -- unsure whether this actually happens but regardless:
               -- keep the program from accepting a new keyboard input when a new tile hasn't been spawned yet
               if model.waitingForRandom then
                   ( model, Cmd.none )
               else
                   ( { model | tiles = newTiles, waitingForRandom = waiting, score = score, hasLost = hasLost }, cmd )



-- VIEW


containerStyle : List ( String, String ) containerStyle =

   [ ( "width", "450px" )
   , ( "height", "450px" )
   , ( "background-color", "#bbada0" )
   , ( "float", "left" )
   , ( "border-radius", "6px")
   ]


tileStyle : Int -> List ( String, String ) tileStyle value =

   let
       color =
           case value of
               0 ->
                   "#776e65"
               2 ->
                   "#eee4da"
               4 ->
                   "#ede0c8"
               8 ->
                   "#f2b179"
               16 ->
                   "#f59563"
               32 ->
                   "#f67c5f"
               64 ->
                   "#f65e3b"
               128 ->
                   "#edcf72"
               256 ->
                   "#edcc61"
               512 ->
                   "#edc850"
               1024 ->
                   "#edc53f"
               2048 ->
                   "#edc22e"
               _ ->
                   "#edc22e"
   in
       [ ( "width", "100px" )
       , ( "height", "70px" )
       , ( "background-color", color )
       , ( "float", "left" )
       , ( "margin-left", "10px" )
       , ( "margin-top", "10px" )
       , ( "padding-top", "30px" )
       , ( "text-align", "center" )
       , ( "font-size", "30px" )
       , ( "font-weight", "bold" )
       , ( "border-radius", "6px")
       ]


viewTile : Tile -> Html Msg viewTile tile =

   div [ style <| tileStyle <| Maybe.withDefault 0 tile ]
       [ span [] [ text <| Maybe.withDefault "" <| Maybe.map toString tile ]
       ]


viewGrid : List Tile -> Html Msg viewGrid tiles =

   div [ style containerStyle ] <| List.map viewTile tiles


viewLost : Html Msg viewLost =

   div
       [ style containerStyle ]
       [ div
           [ style [ ( "text-align", "center" ) ] ]
           [ h2 [] [ text "You lost!" ]
           ]
       ]

viewWin : Html Msg viewWin =

   div
       [ style containerStyle ]
       [ div
           [ style [ ( "text-align", "center" ) ] ]
           [ h2 [] [ text "Congratulations, You won!" ]
           , button
               [ style [ ( "margin-bottom", "16px" ), ( "margin-top", "16px" ) ], onClick KeepPlaying ]
               [ text "Keep playing" ]
           ]
       ]


view : Model -> Html Msg view model =

   div [ style [ ( "width", "450px" ) ] ]
       [ p [ style [ ( "float", "left" ) ] ] [ text <| "Your Score: " ++ toString model.score ]
       , button
           [ style [ ( "margin-bottom", "16px" ), ( "margin-top", "16px" ), ( "float", "right" ) ], onClick NewGame ]
           [ text "New Game" ]
       , if model.hasLost then
           viewLost
         else if List.any ((==) (Just 2048)) model.tiles && not model.winKeepPlaying then
           viewWin
         else
           viewGrid model.tiles
       ]

</lang>

F#

The following code can be executed as is using F# Interactive from system command line ("fsi.exe 2048.fsx") but not from Visual Studio F# Interactive window due to the way it access keyboard (the System.Console.ReadKey() function).

<lang fsharp> // the board is represented with a list of 16 integers let empty = List.init 16 (fun _ -> 0) let win = List.contains 2048

// a single movement (a hit) consists of stacking and then possible joining let rec stack = function

   | 0 :: t -> stack t @ [0]
   | h :: t -> h :: stack t
   | [] -> []

let rec join = function

   | a :: b :: c when a = b -> (a + b :: join c) @ [0]
   | a :: b -> a :: join b
   | [] -> []

let hit = stack >> join let hitBack = List.rev >> hit >> List.rev let rows = List.chunkBySize 4 let left = rows >> List.map hit >> List.concat let right = rows >> List.map hitBack >> List.concat let up = rows >> List.transpose >> List.map hit >> List.transpose >> List.concat let down = rows >> List.transpose >> List.map hitBack >> List.transpose >> List.concat

let lose g = left g = g && right g = g && up g = g && down g = g

// spawn a 2 or occasionally a 4 at a random unoccupied position let random = System.Random() let spawnOn g =

   let newTileValue = if random.Next 10 = 0 then 4 else 2
   let numZeroes = List.filter ((=) 0) >> List.length
   let newPosition = g |> numZeroes |> random.Next
   let rec insert what where = function
       | 0 :: tail when numZeroes tail = where -> what :: tail
       | h :: t -> h :: insert what where t
       | [] -> []
   insert newTileValue newPosition g

let show =

   let line = List.map (sprintf "%4i") >> String.concat " "
   rows >> List.map line >> String.concat "\n" >> printf "\n%s\n"

// use an empty list as a sign of user interrupt let quit _ = [] let quitted = List.isEmpty

let dispatch = function | 'i' -> up | 'j' -> left | 'k' -> down | 'l' -> right | 'q' -> quit | _ -> id let key() = System.Console.ReadKey().KeyChar |> char let turn state =

   show state
   let nextState = (key() |> dispatch) state
   if nextState <> state && not (quitted nextState) then spawnOn nextState else nextState

let play() =

   let mutable state = spawnOn empty
   while not (win state || lose state || quitted state) do state <- turn state
   if quitted state then printfn "User interrupt" else
       show state
       if win state then printf "You win!"
       if lose state then printf "You lose!"

play() </lang>

Factor

Can be loaded and run as a module by copying the code to a file and executing "factor 2048.factor".

For every step prints an ASCII representation of the board on the console. Controlled by feeding the program lines with a single character:

  • W - up
  • S - down
  • A - left
  • D - right
  • Q - exit the game


<lang> USE: accessors FROM: arrays => <array> array ; FROM: assocs => assoc-filter keys zip ; FROM: combinators => case cleave cond ; FROM: combinators.short-circuit => 1|| 1&& 2&& ; FROM: continuations => cleanup ; FROM: formatting => printf sprintf ; FROM: fry => '[ _ ; FROM: grouping => all-equal? clump group ; FROM: io => bl flush nl readln write ; FROM: kernel => = 2bi 2dup 2drop and bi bi* bi@ boa boolean clone equal? dip drop dup if if* keep loop nip not over swap throw tri unless when with xor ; FROM: math => integer times * + > >= ; FROM: math.functions => ^ ; FROM: math.parser => hex> ; FROM: math.order => +lt+ +gt+ +eq+ ; FROM: random => random sample ; FROM: sequences => <iota> <repetition> any? all? append concat each first flip head if-empty interleave length map pop push reduce reverse second set-nth tail ; FROM: sorting => sort ; FROM: vectors => <vector> ; IN: 2048-game


ERROR: invalid-board ;

SYMBOL: left SYMBOL: right SYMBOL: up SYMBOL: down

TUPLE: tile { level integer }

TUPLE: board { width integer } { height integer } { tiles array }

M: tile equal?

   {
       [ and ] ! test for f
       [ [ level>> ] bi@ = ]
   }
   2&&
valid-board? ( w h -- ? )
   * 0 > ! board with 0 tiles does not have a meaningful representation
<board> ( w h -- board )
   [ valid-board? [ invalid-board throw ] unless ]
   [ 2dup * f <array> board boa ] 2bi
<tile> ( n -- tile )
   tile boa

! 1 in 10 tile starts as 4

new-tile ( -- tile )
   10 random 0 = [ 2 ] [ 1 ] if
   <tile>

<PRIVATE

space-left? ( board -- ? )
   tiles>> [ f = ] any?
rows>> ( board -- seq )
   dup tiles>>
   [ drop { } ] [ swap width>> group ] if-empty
rows<< ( seq board -- )
   [ concat ] dip tiles<<
columns>> ( board -- seq )
   rows>> flip
columns<< ( seq board -- )
   [ flip concat ] dip tiles<<
change-rows ( board quote -- board )
   over [ rows>> swap call( seq -- seq ) ] [ rows<< ] bi 
inline
change-columns ( board quote -- board )
   over [ columns>> swap call( seq -- seq ) ] [ columns<< ] bi 
inline
can-move-left? ( seq -- ? )
   {   
       ! one element seq cannot move
       [ length 1 = not ]
       ! empty seq cannot move
       [ [ f = ] all? not ] 
       [ 2 clump 
           [
               {
                   ! test for identical adjescent tiles
                   [ [ first ] [ second ] bi [ and ] [ = ] 2bi and ] 
                   ! test for empty space on the left and tile on the right
                   [ [ first ] [ second ] bi [ xor ] [ drop f = ] 2bi and ]
               } 1||
           ] any?
       ]
   } 1&&
can-move-direction? ( board direction -- ? )
   {
       { left  [ rows>>    [         can-move-left? ] any? ] }
       { right [ rows>>    [ reverse can-move-left? ] any? ] }
       { up    [ columns>> [         can-move-left? ] any? ] }
       { down  [ columns>> [ reverse can-move-left? ] any? ] } 
   } case
can-move-any? ( board -- ? )
   { left right up down } [ can-move-direction? ] with any?
empty-indices ( seq -- seq )
   [ length <iota> ] keep zip
   [ nip f = ] assoc-filter keys
pick-random ( seq -- elem )
   1 sample first

! create a new tile on an empty space

add-tile ( board -- )
   [ new-tile swap [ empty-indices pick-random ] keep [ set-nth ] keep ] change-tiles drop

! combines equal tiles justified right or does nothing

combine-tiles ( tile1 tile2 -- tile3 tile4 )
   2dup { [ and ] [ = ] } 2&& 
   [ drop [ 1 + ] change-level f swap ] when
justify-left ( seq -- seq )
   [   
       {
           { [ dup  f = ] [ 2drop +lt+ ] }
           { [ over f = ] [ 2drop +gt+ ] }
           [ 2drop +eq+ ]
       } cond
   ] sort
collapse ( seq -- seq )
   justify-left
     
   ! combine adjescent
   dup length <vector> 
   [ over
       [ swap [ push ] keep ]
       [   
           {
               [ pop combine-tiles ]
               [ push ]
               [ push ] 
           } cleave
       ] if-empty
   ] reduce
   
   ! fill in the gaps after combination
   justify-left

! draws an object GENERIC: draw ( obj -- )

PRIVATE>

! a single tile is higher than 2048 (level 10)

won? ( board -- ? )
   tiles>> [ dup [ level>> 11 >= ] when ] any? 

! if there is no space left and no neightboring tiles are the same, end the board

lost? ( board -- ? )
   {
       [ space-left? ]
       [ won? ]
       [ can-move-any? ] 
   } 1|| not
serialize ( board -- str )
   [ width>> ]
   [ height>> ]
   [ tiles>>
       [ dup f = [ drop 0 ] [ level>> ] if "%02x" sprintf ] map concat
   ] tri
   "%02x%02x%s" sprintf
deserialize ( str -- board )
   [ 2 head hex> ] [ 2 tail ] bi
   [ 2 head hex> ] [ 2 tail ] bi
   2 group [ hex> dup 0 = [ drop f ] [ tile boa ] if ] map
   board boa
move ( board direction -- )
   {
       { left  [ [ [         collapse         ] map ] change-rows    ] }
       { right [ [ [ reverse collapse reverse ] map ] change-rows    ] }
       { up    [ [ [         collapse         ] map ] change-columns ] }
       { down  [ [ [ reverse collapse reverse ] map ] change-columns ] }
   } case drop


get-input ( -- line )
   readln
parse-input ( line -- direction/f )
   {
       { "a" [ left  ] }
       { "d" [ right ] }
       { "w" [ up    ] }
       { "s" [ down  ] }
       { "q" [ f ] }
       [ "Wrong input: %s\n" printf flush
         get-input parse-input ] 
   } case

<PRIVATE

init ( board -- )
   '[ _ add-tile ] 2 swap times

M: tile draw ( tile -- )

   level>> 2 swap ^ "% 4d" printf

M: boolean draw ( _ -- )

   drop 4 [ bl ] times
horizontal-line ( board -- )
   width>>
   " " write
   "+------" <repetition> concat
   write "+ " write nl
separator ( -- )
   " | " write

M: board draw ( board -- )

   [ horizontal-line ] keep
   [ rows>> ]
   [ 
      '[ _ horizontal-line ]
       [   separator
           [ separator ] [ draw ] interleave
           separator nl
       ] interleave
   ]
   [ horizontal-line ]
   tri
   flush
update ( board -- f )
   {
       [ 
           get-input parse-input [
               {
                   [ can-move-direction? ] 
                   [ over [ move ] [ add-tile ] bi* t ]
               } 2&& drop t
           ] [ drop f ] if*
       ]
       [ can-move-any? ] 
   } 1&&
exit ( board -- )
   { 
       { [ dup lost? ] [ "You lost! Better luck next time." write nl ] }
       { [ dup won?  ] [ "You won! Congratulations!" write nl ] }
       [ "Bye!" write nl ]
   } cond drop

PRIVATE>

start-2048 ( -- )
   4 4 <board>
   [ 
       ! Initialization
       [ init ]
       [ draw ]
       ! Event loop
       [ [ dup [ update ] [ draw ] bi ] loop ] tri
   ]
   ! Cleanup
   [ exit ]
   [ ]
   cleanup

MAIN: start-2048 </lang>

Forth

Translation of: C
Works with: gforth version 0.7.3

Just like my implementation of 15 Puzzle Game, this uses Vim's h/j/k/l for movement.

<lang forth>\ in Forth, you do many things on your own. This word is used to define 2D arrays

2D-ARRAY ( height width )

CREATE DUP , * CELLS ALLOT DOES> ( y x baseaddress ) ROT ( x baseaddress y ) OVER @ ( x baseaddress y width ) * ( x baseaddress y*width ) ROT ( baseaddress y*width x ) + 1+ CELLS +

require random.fs HERE SEED !

0 CONSTANT D-INVALID 1 CONSTANT D-UP 2 CONSTANT D-DOWN 3 CONSTANT D-LEFT 4 CONSTANT D-RIGHT

4 CONSTANT NROWS 4 CONSTANT NCOLS

NROWS NCOLS * CONSTANT GRIDSIZE NROWS NCOLS 2D-ARRAY GRID CREATE HAVE-MOVED CELL ALLOT CREATE TOTAL-SCORE CELL ALLOT CREATE MOVE-SCORE CELL ALLOT

DIE-DIRECTIONCONST ." Unknown direction constant:" . BYE ;
ESC #ESC EMIT ;
CLS

ESC ." [2J" ESC ." [H"

GRID-VALUE 1 SWAP LSHIFT ;
DASHES 0 ?DO [CHAR] - EMIT LOOP ;
DRAW ( -- )

CLS ." Score: " TOTAL-SCORE @ 0 U.R MOVE-SCORE @ ?DUP IF ." (+" 0 U.R ." )" THEN CR 25 DASHES CR

NROWS 0 ?DO ." |" NCOLS 0 ?DO J I GRID @ ?DUP IF GRID-VALUE 4 U.R ELSE 4 SPACES THEN ." |" LOOP CR LOOP

25 DASHES CR

COUNT-FREE-SPACES ( -- free-spaces )

0 ( count ) NROWS 0 ?DO NCOLS 0 ?DO J I GRID @ 0= IF 1+ THEN LOOP LOOP

GET-FREE-SPACE ( index -- addr )

0 0 GRID SWAP ( curr-addr index ) 0 0 GRID @ 0<> IF 1+ THEN 0 ?DO ( find the next free space index times ) BEGIN CELL+ DUP @ 0= UNTIL LOOP

NEW-BLOCK ( -- )

COUNT-FREE-SPACES DUP 0<= IF DROP EXIT THEN RANDOM GET-FREE-SPACE 10 RANDOM 0= IF 2 ELSE 1 THEN SWAP !

2GRID ( a-y a-x b-y b-x -- a-addr b-addr ) GRID -ROT GRID SWAP ;
CAN-MERGE ( dest-addr other-addr -- can-merge? )

@ SWAP @ ( other-val dest-val ) DUP 0<> -ROT = AND

CAN-GRAVITY ( dest-addr other-addr -- can-gravity? )

@ SWAP @ ( other-val dest-val ) 0= SWAP 0<> AND

TRY-MERGE ( dest-y dest-x other-y other-x -- )

2GRID ( dest-addr other-addr ) 2DUP CAN-MERGE IF TRUE HAVE-MOVED ! 0 SWAP ! ( dest-addr ) DUP @ 1+ DUP ( dest-addr dest-val dest-val ) ROT ! ( dest-val ) GRID-VALUE DUP ( score-diff score-diff ) MOVE-SCORE +! TOTAL-SCORE +! ELSE 2DROP THEN

TRY-GRAVITY ( did-something-before operator dest-y dest-x other-y other-x -- did-something-after operator )

2GRID ( ... dest-addr other-addr ) 2DUP CAN-GRAVITY IF TRUE HAVE-MOVED ! DUP @ ( ... dest-addr other-addr other-val ) ROT ( ... other-addr other-val dest-addr ) ! ( ... other-addr ) 0 SWAP ! NIP TRUE SWAP ELSE 2DROP THEN

TRY-LOST? ( lost-before operator dy dx oy ox -- lost-after operator )

2GRID CAN-MERGE INVERT ( lost-before operator lost-now ) ROT AND SWAP ( lost-after operator )

MOVEMENT-LOOP ( direction operator -- ) CASE

SWAP D-UP OF NROWS 1- 0 ?DO NCOLS 0 ?DO J I J 1+ I 4 PICK EXECUTE LOOP LOOP ENDOF D-DOWN OF 1 NROWS 1- ?DO NCOLS 0 ?DO J I J 1- I 4 PICK EXECUTE LOOP -1 +LOOP ENDOF D-LEFT OF NCOLS 1- 0 ?DO NROWS 0 ?DO I J I J 1+ 4 PICK EXECUTE LOOP LOOP ENDOF D-RIGHT OF 1 NCOLS 1- ?DO NROWS 0 ?DO I J I J 1- 4 PICK EXECUTE LOOP -1 +LOOP ENDOF DIE-DIRECTIONCONST ENDCASE DROP ;

MERGE ( move -- ) ['] TRY-MERGE MOVEMENT-LOOP ;
GRAVITY-ONCE ( move -- success? ) FALSE SWAP ['] TRY-GRAVITY MOVEMENT-LOOP ;
GRAVITY ( move -- )

BEGIN DUP GRAVITY-ONCE INVERT UNTIL DROP

MOVE-LOST? ( move -- lost? ) TRUE SWAP ['] TRY-LOST? MOVEMENT-LOOP ;
END-IF-LOST ( -- lost? )

COUNT-FREE-SPACES 0= IF TRUE 5 1 DO I MOVE-LOST? AND LOOP IF ." You lose!" CR BYE THEN THEN

END-IF-WON ( -- )

NROWS 0 ?DO NCOLS 0 ?DO J I GRID @ GRID-VALUE 2048 = IF ." You win!" CR BYE THEN LOOP LOOP

TICK ( move -- )

FALSE HAVE-MOVED ! 0 MOVE-SCORE ! DUP GRAVITY DUP MERGE GRAVITY HAVE-MOVED @ IF NEW-BLOCK DRAW THEN END-IF-WON END-IF-LOST

GET-MOVE ( -- move )

BEGIN KEY CASE #EOF OF BYE ENDOF #ESC OF BYE ENDOF [CHAR] q OF BYE ENDOF [CHAR] Q OF BYE ENDOF [CHAR] k OF D-UP TRUE ENDOF [CHAR] K OF D-UP TRUE ENDOF [CHAR] j OF D-DOWN TRUE ENDOF [CHAR] J OF D-DOWN TRUE ENDOF [CHAR] h OF D-LEFT TRUE ENDOF [CHAR] H OF D-LEFT TRUE ENDOF [CHAR] l OF D-RIGHT TRUE ENDOF [CHAR] L OF D-RIGHT TRUE ENDOF FALSE SWAP ENDCASE UNTIL

INIT ( -- )

NROWS 0 ?DO NCOLS 0 ?DO 0 J I GRID ! LOOP LOOP

0 TOTAL-SCORE ! 0 MOVE-SCORE ! NEW-BLOCK NEW-BLOCK DRAW

MAIN-LOOP ( -- )

BEGIN GET-MOVE TICK AGAIN

INIT MAIN-LOOP</lang>

Output:
Score: 20136 (+2048)
-------------------------
|     |     |     |     |
|   2 |     |     |     |
|     |   8 |   2 |     |
|2048 |   4 |   8 |   2 |
-------------------------
You win!

Fortran

The Plan

The primary objective was to achieve the processing without generating similar code for each of the four different move directions or alternatively for the two lots of related directions - left/right and up/down. The various directions each involve variations on a loop of the form DO L = 1,N and this can easily be generalised as DO L = first,last,increment with a set of suitable values for each direction. Although Fortran encompasses complex arithmetic so that one could play about with vector arithmetic (notably, multiplying by (0,1) rotates by ninety degrees counterclockwise), alas, this is not provided for integer type variables, and in any case, the (x,y) orientation of Cartesian coordinates is not the same as the (row,column) orientation usual for arrays and character-style output, so to reduce mental strain complex arithmetic is not attempted and screen layout rules. However, an echo remains in that the directions are listed in the (x,y) style counter-clockwise: right, up, left, down.

Further thought shows that a move in a selected direction also involves a direction at right angles. To reduce vague generality, suppose the move direction is "right". All squares in a row are to be shoved rightwards, and this is to be repeated for each row: a series perpendicular to the move direction. Indeed, since rows do not interact in this move each row could be processed in parallel, but an ordinary sequential loop will do. It could run in any order so only two sorts of directions need be handled, but to reduce the mental strain, all four are distinct. Thus, there is a two-level process: the outer loop steps through the collection of rows, and the inner loop deals with the movement in each row. The outer loop is controlled by arrays RC1, RCn, RCi for first, last, increment to step along the rows (or columns): RC. And for the inner loop perpendicular to that so CR for column (or row) there are arrays CR1, CRn, CRi - all this is intended to be read as DO L = 1,N but with extra verbiage because the loop might be DO L = N,1,-1 instead.

Holding firmly to the one-dimensional aspect of the row's processing, the actual processing can be seen to be simple. For instance, step along an array comparing each element to its predecessor, as in A(I) and A(I - 1), or, (avoiding index arithmetic) maintain two indices: CI and PI for current and previous index. Start CI at element one, and run the loop as DO L = 2,N with on each iteration PI taking the value of CI, and CI being calculated afresh. Except that the loop has verbiage: DO L = (first + increment),last,increment.

But in fact, the board is represented as a two dimensional array. Fortran does not offer a special "index" type of variable so that if this was a two-element entity with the value (1,2), A(this) would be equivalent to A(1,2) One must write out the indices, as in A(this(1),this(2)) On the other hand, F90 introduced array arithmetic and related statements, so one can declare CIJ to be a two-element array, and introduce array arithmetic similar to complex number arithmetic to juggle indices. Further, instead of using simple variables and IF-statements or the like to select amongst the directions, this is done by using array WAY, and its associate YAW to obtain a perpendicular direction. That is, for direction W, WAY(W) selects either (0,1) or (1,0) so that RC*WAY(W) switches the value of RC between the first or second dimension, and YAW is the other way around.

Except that WAY and YAW are two dimensional arrays (rather than a one-dimensional array of complex number pairs, alas) so that the expression is in fact RC*WAY(W,1:2) and the calculations for both indices are done together. Because Fortran uses the "column-major" ordering of elements in storage, successive elements of a multi-dimensional array have the leftmost index varying most rapidly so that the order is WAY(1,1), WAY(2,1), WAY(3,1), WAY(4,1), WAY(1,2), etc and statements such as DATA or PARAMETER whereby values can be listed employ that ordering. So that the list of values for WAY and YAW can be aligned in the source code with the similar lists for the arrays specifying the loop parameters for each direction, the ordering is WAY(4,2) rather than WAY(2,4) even though this means that the two values for a given direction are not in adjacent storage, unlike the two parts of a complex number.

Arrays WAY and YAW are reminiscent of "truth tables" in Boolean logic, and it is tempting to imagine that YAW = ¬WAY, but alas, a NOT operation applied to an integer variable will flip not just the lowest bit. Trying a .NOT. operation on LOGICAL variables instead will work as desired, except that their integer interpretations may not be as hoped for. Yet another ploy might be based on W being even/odd or odd/even, and similar trickery might be applied to the other arrays of constants, but, enough. The devious juggling of arrays is traditional in Fortran.

Source

The initial attempt at showing the board relied rather heavily on FORMAT tricks, in particular the use of the <n> facility whereby the value of an integer expression can be inserted into a format statement's coding on-the-fly, as in the following. <lang Fortran>

       WRITE (MSG,1)		!Roll forth a top/bottom boundary. No corner characters (etc.), damnit.
   1   FORMAT ("|",<NC>(<W>("-"),"|"))	!Heavy reliance on runtime values in NC and W. But see FORMAT 22.
   2     FORMAT ("|",<NC>(<W>(" "),"|"))	!No horizontal markings within a tile. See FORMAT 1.
         WRITE (MSG,22) ((" ",L1  = 1,W),"|",C = 1,NC)	!Compare to FORMAT 2.
  22     FORMAT ("|",666A1)				!A constant FORMAT, a tricky WRITE.
   4     FORMAT ("|",<NC - 1>(<W>("-"),"+"),<W>("-"),"|")	!With internal + rather than |.</lang> 

This sort of thing is not necessarily accepted by all compilers, so instead the next stage was to convert to using complicated WRITE statements. If one regarded the various sizes (the values of NR, NC, W in the source) as truly fixed, literal constants could be used throughout. This would however mean that they would appear without explanation, and if one eventually attempted to recode with different values, mistakes would be likely. Thus below, FORMAT 3 has (<NC>(A1,I<W>),A1) and if the <> scheme were unavailable, you'd have to use (4(A1,I6),A1) instead, not too troublesome a change. Or, the text of the format sequence could be written to a CHARACTER variable, as demonstrated in Multiplication_tables#Traditional_approach. Yet another approach might be (666(A1,I6)) which relies on the addendum A1 happening to be the same as the start of the (A1,I6) pair, but there is still the appearance of the literal constant six instead of <W>, and if there were to be any change to be made, it would have to be remembered... <lang Fortran> SUBROUTINE SHOW(NR,NC,BOARD) !Mess about.

      INTEGER NR,NC		!Number of rows and columns.
      INTEGER BOARD(NR,NC)	!The board. Actual storage is transposed!
      INTEGER R,C 		!Steppers.
      INTEGER L,L1		!Fingers.
      INTEGER W		!A width.
      PARAMETER (W = 6)	!Six will suffice for 2048, even 524288.
      CHARACTER*(NC*(W + 1) + 1) ALINE
      CHARACTER*1 TL,TR,BL,BR	!Corner characters: top left, etc. Code page 850, and 437?
      CHARACTER*1 LR,RL,TD,BU	!Side joining: Left rightwards, right leftwards, top downwards, bottom upwards.
      CHARACTER*1 VL,HL,XX	!Vertical and horizontal lines, line crossing.
      PARAMETER (TL=CHAR(218),TR=CHAR(191),BL=CHAR(192),BR=CHAR(217))	!Works for the "code page" 437, and 850.
      PARAMETER (LR=CHAR(195),RL=CHAR(180),TD=CHAR(194),BU=CHAR(193))	!Try the DOS command CHCP to see which is in use.
      PARAMETER (VL=CHAR(179),HL=CHAR(196),XX=CHAR(197))		!Attempts to change the code page no longer work...
      INTEGER MSG		!I/O unit number.
      COMMON/IODEV/ MSG	!I talk to the trees...
       WRITE (MSG,1) TL,((HL,L = 1,W),TD,C = 1,NC - 1),(HL,L = 1,W),TR	!Write the top edge, with downwards ticks.
   1   FORMAT (666A1)		!Surely long enough.
       DO R = 1,NR		!Chug down the rows.
         WRITE (MSG,1) VL,((" ",L=1,W),VL,C = 1,NC - 1),(" ",L=1,W),VL	!Space vertically to make the tile look less rectangular.
         WRITE (ALINE,3) (VL,BOARD(R,C),C = 1,NC),VL	!The columns of the row. Usage is BOARD(row,col) despite storage adjacency.
   3     FORMAT (<NC>(A1,I<W>),A1)	!Fixed sizes might suffice.
         DO C = 1,NC			!Now inspect each cell along the line.
           L1  = 1 + (C - 1)*(W + 1) + 1	!Locate the first interior character.
           IF (BOARD(R,C).LE.0) THEN		!Should this one be blank?
             ALINE(L1 + W - 1:L1 + W - 1) = " "	!Yes. Scrub the lone zero at the end of the span.
            ELSE				!Non blank, but, aligned right.
             L = L1					!So, look for the first digit.
             DO WHILE(ALINE(L:L).EQ." ")		!There is surely one digit to be found.
               L = L + 1					!Not yet. Advance.
             END DO					!End with L fingering the first digit.
             IF (L.GT.L1) ALINE(L1 + (L - L1 + 1)/2:L1 + W - 1) =	!Halve (approx.) the spare space at the start.
    &                     ALINE(L:L1 + W - 1)		!The first digit to the last digit.
           END IF				!So much for that line segment.
         END DO			!On to the next column.
         WRITE (MSG,"(A)") ALINE	!Roll the fancy line, all in one go.
         WRITE (MSG,1) VL,((" ",L=1,W),VL,C = 1,NC - 1),(" ",L=1,W),VL	!More vertical space.
         IF (R.LT.NR) WRITE (MSG,1) LR,((HL,L = 1,W),XX,C = 1,NC - 1),	!Write an internal horizontal seam.
    &                                   (HL,L = 1,W),RL		!Starting and ending with a horizontal tick.
       END DO			!On to the next row.
       WRITE (MSG,1) BL,((HL,L = 1,W),BU,C = 1,NC - 1),(HL,L = 1,W),BR	!Write the bottom edge, witrh upwards ticks.
     END SUBROUTINE SHOW	!That was nice.
     PROGRAM PUZZLE	!Some severe array juggling may indeed cause puzzlement.
     INTEGER NR,NC,N			!Describes the shape of the board.
     PARAMETER (NR = 4, NC = 4, N = NR*NC)	!Determines the shape of the board.
     INTEGER BOARD(NR,NC)		!Thus transpose furrytran's column-major usage. Beware!!!
     INTEGER BORED(N)			!This allows for consecutive comparisons.
     EQUIVALENCE (BOARD,BORED)		!Because the arrays are in the same place.
     INTEGER BIJ,PB,CB			!Juggles with the values of some  squares.
     INTEGER STARTVALUE,STARTTILES,TARGET	!Document the starting value.
     PARAMETER (TARGET = 2048,STARTVALUE = 2,STARTTILES = 2)	!Why not start with one?
     INTEGER SCORE			!Count them all.
     INTEGER I,IT,TRY			!Odds and ends.
     INTEGER LIST(N)			!A list.
     CHARACTER*1 WAYS(4),WAYC(4)	!In two dimensions, there are four possible ways to move.
     CHARACTER*4 WAYI			!There is no equivalent of INDEX for searching arrays.
     EQUIVALENCE (WAYS,WAYI)		!But this enables two interpretations of the same storage.
     PARAMETER (WAYC = (/"R","U","L","D"/))	!These are the names for the available directions.
     INTEGER W,M,RC,CR,CIJ(2),PIJ(2),WAY(4,2),YAW(4,2)	!Directions in array index terms.
     INTEGER RC1(4),RCN(4),RCI(4), CR1(4),CRN(4),CRI(4)	!Loop control for the directions..
     PARAMETER (RC1 = (/ 1, 1,NR,NC/), CR1 = (/ 1,NR,NC, 1/))	!Start values of the first and second loops.
     PARAMETER (RCN = (/NR,NC, 1, 1/), CRN = (/NC, 1, 1,NR/))	!End values.
     PARAMETER (RCI = (/+1,+1,-1,-1/), CRI = (/+1,-1,-1,+1/))	!Incrementing or decrementing accordingly.
     PARAMETER (WAY = (/ 1, 0, 1, 0,            0, 1, 0, 1/))	!The first loop is either the row, or the column.
     PARAMETER (YAW = (/ 0, 1, 0, 1,            1, 0, 1, 0/))	!The second loop is the other way around.
     REAL VALUE			!Humph. Yet another interface to a "random" number generator.
     CHARACTER*1 C		!A monocharacter response is anticipated.
     INTEGER MSG,KBD		!I/O unit numbers.
     COMMON/IODEV/ MSG,KBD	!Pass the word.
     KBD = 5	!Standard input. (Keyboard -> Display screen)
     MSG = 6	!Standard output. (Display screen)
     WRITE (MSG,1) TARGET,NR,NC,STARTVALUE	!Announce.
   1 FORMAT ("To play '",I0,"' with ",I0," rows and ",I0," columns.",/,
    1"On each move, choose a direction (Up, Down, Left, Right)",/
    2 "by typing the single letter U, D, L, R, or, a space to quit."/
    3 "All squares will be pushed as far as possible that way.",/
    4 "Those meeting with the same number will form one square",/
    5 "with the sum of the numbers, and one becomes blank.",/
    6 "After each move, a random blank square becomes ",I0,/)
     WRITE (MSG,2)	!Now for some annoyance.
   2 FORMAT ("An integer to start the 'random' number generator: ",$)	!Not starting a new line.
     READ (KBD,*) TRY	!Could use a time-of-day in microseconds, or similar.
     CALL SEED(TRY)	!But this enables reproducibility. And cheating.

Concoct a board layout.

  10 BOARD = 0			!Clear for action.
     DO I = 1,STARTTILES	!Place the initial tiles, with their starting values.
  11   CALL RANDOM(VALUE)		!0 <= VALUE < 1.
       IT = VALUE*N + 1		!1 <= IT <= N. Don't round up!
       IF (BORED(IT).NE.0) GO TO 11	!Oops! Flounder towards another tile.
       BORED(IT) = STARTVALUE		!The beginning.
     END DO			!On to the next.
     SCORE = STARTVALUE*STARTTILES	!Save some mental arithmetic.
     TRY = 0		!No moves made yet.

Consider possible moves. Think in (x,y) but convert those thimks to (row,column). Eurghf.

  20 TRY = TRY + 1		!Here we go again.
     CALL SHOW(NR,NC,BOARD)	!The current state.
     WAYS = ""			!No moveable directions are known.
     DO 21 W = 1,4		!One way or another, consider each possible direction.
       DO RC = RC1(W),RCN(W),RCI(W)	!W = 1 = +x: consider each successive row.
         CIJ = RC*WAY(W,1:2) + CR1(W)*YAW(W,1:2)	!Finger the first position.
         DO CR = CR1(W) + CRI(W),CRN(W),CRI(W)		!W = 1; along the columns of the row.
           PIJ = CIJ					!Retain the previous position.
           CIJ = RC*WAY(W,1:2) + CR*YAW(W,1:2)		!Convert (RC,CR) to either (RC,CR) or (CR,RC).
           BIJ = BOARD(CIJ(1),CIJ(2))			!Grab the current position's board state.
           IF ((BOARD(PIJ(1),PIJ(2)).GT.0   .AND. BIJ.EQ.0)		!A non-empty tile to move to an empty one?
    1      .OR.(BOARD(PIJ(1),PIJ(2)).EQ.BIJ .AND. BIJ.GT.0)) THEN	!Or, there is a pair, BOARD(CIJ) = BOARD(PIJ),
             WAYS(W) = WAYC(W)					!Then this direction is available.
             GO TO 21						!No need to seek further opportunities for its use.
           END IF					!So much for the current position.
         END DO				!Advance the scan along direction W.
       END DO				!Advance to the next (row or column) at right angles to W.
  21 CONTINUE			!Try another way.

Cast forth an invitation, and obtain a choice.

  30 WRITE (MSG,31) TRY,SCORE,WAYS	!Summary.
  31 FORMAT ("Move",I4,", score ",I0,". Moves ",4A1,$)	!The $, of course, says "don't end the line".
     IF (ALL(WAYS.EQ." ")) GO TO 600	!A gridlock?
     WRITE (MSG,32)			!Nope. Invite a selection.
  32 FORMAT (" ... Your move: ",$)	!Awaits input, with a new line after pressing "enter".
     IF (COUNT(WAYS.NE." ").EQ.1) THEN	!Or, perhaps it is a choice you can't refuse.
       W = MAXLOC(ABS(ICHAR(WAYS) - ICHAR(" ")),DIM = 1)	!One element's value differes from " "...
       WRITE (MSG,33) WAYS(W)			!Sieze control!
  33   FORMAT (A1," is the only possibility!")	!Just don't ask for input.
      ELSE				!But often, the human can decide.
       READ (KBD,"(A)") C			!Just one character. The first one typed.
       IF (C.LE." ") STOP "Oh well."		!Bored, already?
       I = INDEX("ruld",C)			!A lowercase letter may be presented.
       IF (I.GT.0) C = "RULD"(I:I)		!So, convert to uppercase, if worthy.
       W = INDEX(WAYI,C)			!What is it? There is no search of elements of the array WAYS.
       IF (W.LE.0) THEN			!Perhaps it is blocked.
         WRITE (MSG,34) C				!Alas.
  34     FORMAT ("Not a possible move! ",A)		!Just so.
         GO TO 30					!Try again.
       END IF					!So much for suspicion.
     END IF				!A move has been chosen.

Complete the selected move. Carefully avoid enabling cascades, so 1122 is pulled right to ..24, not .222 then ..42.

  40 M = MOD(W + 1,4) + 1		!W is the direction of movement, its inverse, M, faces arrivals.
     DO RC = RC1(M),RCN(M),RCI(M)	!Loop through the (rows/columns) at right angles to the selected anti-way.
       PIJ = RC*WAY(M,1:2) + CR1(M)*YAW(M,1:2)	!Finger the first square, which may be empty.
       PB = BOARD(PIJ(1),PIJ(2))		!Load it into my two-element buffer: PB and CB.
       IF (PB.NE.0) BOARD(PIJ(1),PIJ(2)) = 0	!It may be returned to the board somewhere else.
       DO CR = CR1(M) + CRI(M),CRN(M),CRI(M)	!Step along the (column/row) of the selected anti-direction.
         CIJ = RC*WAY(M,1:2) + CR*YAW(M,1:2)		!Convert (RC,CR) to either CIJ = (RC,CR) or CIJ = (CR,RC).
         CB = BOARD(CIJ(1),CIJ(2))			!Inspect this square.
         IF (CB.EQ.0) CYCLE				!From nothing comes nothing.
         BOARD(CIJ(1),CIJ(2)) = 0			!The board's value now lives precariously in CB.
         IF (PB.EQ.0) THEN				!A waiting hole? (And, CB is not empty)
           PB = CB						!Yes. Fill it. More may follow, after spaces.
         ELSE						!Otherwise, two non-zero values are in hand.
           IF (PB.EQ.CB) THEN					!If they match,
             PB = PB + CB						!Combine the new with the old.
             CB = 0							!The new one is gone.
           END IF						!So much for matches.
           BOARD(PIJ(1),PIJ(2)) = PB				!Roll the trailing value.
           PIJ = PIJ + CRI(M)*YAW(M,1:2)			!Advance the finger.
           PB = CB						!Shuffle along one.
         END IF					!So much for that square.
       END DO					!On to the next one along.
       IF (PB.GT.0) BOARD(PIJ(1),PIJ(2)) = PB	!A tail end value?
     END DO				!On to the next set.

Choose a random blank square.

  50 IT = 0		!None have been located. (There is surely one, as a move was possible)
     DO I = 1,N	!Step through all the possible squares.
       IF (BORED(I).LE.0) THEN	!Empty?
         IT = IT + 1			!Yes. Augment my list.
         LIST(IT) = I			!Recording available squares.
       END IF			!So much for that square.
     END DO		!On to the next.
     IF (IT.GT.1) THEN	!If a choice s available,
       CALL RANDOM(VALUE)	!Concoct another: 0 <= VALUE < 1.
       IT = VALUE*IT + 1	!And thus with integer truncation, choose an empty square.
     END IF		!So much for choices.
     BORED(LIST(IT)) = STARTVALUE	!Fill the square.
     SCORE = SCORE + STARTVALUE	!Might as well keep count.

Check for success.

  60 IF (ALL(BORED.LT.TARGET)) GO TO 20!Hi ho.
     WRITE (MSG,61)			!A success message.
  61 FORMAT (I0," has been reached!")	!No fancy colours nor flashing lights, nor even bells.
     GO TO 20				!Carry on, anyway.

Curses!

 600 WRITE (MSG,601)		!Alas.
 601 FORMAT ("None! Oh dear.")	!Nothing more can be done.
     END	!That was fun.

</lang>

Output

As usual, the aspect ratio of the display here differs from the "console"-type display on the computer monitor, so the square is rather oblong, and the vertical bars do not join. Rather to my surprise the special characters for the "corner" and crossing glyphs do display correctly. If the console display is copied to a text editor (UltraEdit in my case) they are translated to + signs for the crossing and corners! Further confusion is provided by any attempt to type in the character codes (ALT-218, etc.) as some (but not all) codes are translated by UltraEdit or the keyboard interface into other character codes. All-in-all, it is simpler to employ CHAR(218) in the source as plain text with no fiddling.

Input is a bit annoying, as Fortran doesn't offer an interface to the asynchronous keyboard routines (such as KeyPressed and ReadKey in Turbo Pascal, etc.) and the arrow keys are pre-empted for editing the input being typed, notably the up-arrow key recovers the text of the previous line typed. So, one must press an ordinary key and then signify the completion of your input by pressing the "enter" key. Other keys could be allowed, such as SWAZ or KIJM and the like (or UPEJ for a Dvorak keyboard) for "right", "up", "left" and "down", but you would still have to press the enter key as well.

To play '2048' with 4 rows and 4 columns.
On each move, choose a direction (Up, Down, Left, Right)
by typing the single letter U, D, L, R, or, a space to quit.
All squares will be pushed as far as possible that way.
Those meeting with the same number will form one square
with the sum of the numbers, and one becomes blank.
After each move, a random blank square becomes 2

An integer to start the 'random' number generator: 12345
┌──────┬──────┬──────┬──────┐
│      │      │      │      │
│      │   2  │      │      │
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
│      │      │      │      │
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
│      │      │      │      │
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
│      │      │   2  │      │
│      │      │      │      │
└──────┴──────┴──────┴──────┘
Move   1, score 4. Moves RULD ... Your move: d
┌──────┬──────┬──────┬──────┐
│      │      │      │      │
│      │      │      │      │
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
│      │      │      │      │
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
│      │      │   2  │      │
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
│      │   2  │   2  │      │
│      │      │      │      │
└──────┴──────┴──────┴──────┘
Move   2, score 6. Moves RULD ... Your move:

Go

<lang Go>package main

import ( "bufio" "fmt" "log" "math/rand" "os" "os/exec" "strconv" "strings" "text/template" "time" "unicode"

"golang.org/x/crypto/ssh/terminal" )

const maxPoints = 2048 const ( fieldSizeX = 4 fieldSizeY = 4 ) const tilesAtStart = 2 const probFor2 = 0.9

type button int

const ( _ button = iota up down right left quit )

var labels = func() map[button]rune { m := make(map[button]rune, 4) m[up] = 'W' m[down] = 'S' m[right] = 'D' m[left] = 'A' return m }() var keybinding = func() map[rune]button { m := make(map[rune]button, 8) for b, r := range labels { m[r] = b if unicode.IsUpper(r) { r = unicode.ToLower(r) } else { r = unicode.ToUpper(r) } m[r] = b } m[0x03] = quit return m }()

var model = struct { Score int Field [fieldSizeY][fieldSizeX]int }{}

var view = func() *template.Template { maxWidth := 1 for i := maxPoints; i >= 10; i /= 10 { maxWidth++ }

w := maxWidth + 3 r := make([]byte, fieldSizeX*w+1) for i := range r { if i%w == 0 { r[i] = '+' } else { r[i] = '-' } } rawBorder := string(r)

v, err := template.New("").Parse(`SCORE: Template:.Score Template:Range .Field ` + rawBorder + ` |Template:Range . Template:If .Template:Printf "%` + strconv.Itoa(maxWidth) + `d" .Template:Else` + strings.Repeat(" ", maxWidth) + `Template:End |Template:EndTemplate:End ` + rawBorder + `

(` + string(labels[up]) + `)Up (` + string(labels[down]) + `)Down (` + string(labels[left]) + `)Left (` + string(labels[right]) + `)Right `) check(err) return v }()

func check(err error) { if err != nil { log.Panicln(err) } }

func clear() { c := exec.Command("clear") c.Stdout = os.Stdout check(c.Run()) }

func draw() { clear() check(view.Execute(os.Stdout, model)) }

func addRandTile() (full bool) { free := make([]*int, 0, fieldSizeX*fieldSizeY)

for x := 0; x < fieldSizeX; x++ { for y := 0; y < fieldSizeY; y++ { if model.Field[y][x] == 0 { free = append(free, &model.Field[y][x]) } } }

val := 4 if rand.Float64() < probFor2 { val = 2 } *free[rand.Intn(len(free))] = val

return len(free) == 1 }

type point struct{ x, y int }

func (p point) get() int { return model.Field[p.y][p.x] } func (p point) set(n int) { model.Field[p.y][p.x] = n } func (p point) inField() bool { return p.x >= 0 && p.y >= 0 && p.x < fieldSizeX && p.y < fieldSizeY } func (p *point) next(n point) { p.x += n.x; p.y += n.y }

func controller(key rune) (gameOver bool) { b := keybinding[key]

if b == 0 { return false } if b == quit { return true }

var starts []point var next point

switch b { case up: next = point{0, 1} starts = make([]point, fieldSizeX) for x := 0; x < fieldSizeX; x++ { starts[x] = point{x, 0} } case down: next = point{0, -1} starts = make([]point, fieldSizeX) for x := 0; x < fieldSizeX; x++ { starts[x] = point{x, fieldSizeY - 1} } case right: next = point{-1, 0} starts = make([]point, fieldSizeY) for y := 0; y < fieldSizeY; y++ { starts[y] = point{fieldSizeX - 1, y} } case left: next = point{1, 0} starts = make([]point, fieldSizeY) for y := 0; y < fieldSizeY; y++ { starts[y] = point{0, y} } }

moved := false winning := false

for _, s := range starts { n := s move := func(set int) { moved = true s.set(set) n.set(0) } for n.next(next); n.inField(); n.next(next) { if s.get() != 0 { if n.get() == s.get() { score := s.get() * 2 model.Score += score winning = score >= maxPoints

move(score) s.next(next) } else if n.get() != 0 { s.next(next) if s.get() == 0 { move(n.get()) } } } else if n.get() != 0 { move(n.get()) } } }

if !moved { return false }

lost := false if addRandTile() { lost = true Out: for x := 0; x < fieldSizeX; x++ { for y := 0; y < fieldSizeY; y++ { if (y > 0 && model.Field[y][x] == model.Field[y-1][x]) || (x > 0 && model.Field[y][x] == model.Field[y][x-1]) { lost = false break Out } } } }

draw()

if winning { fmt.Println("You win!") return true } if lost { fmt.Println("Game Over") return true }

return false }

func main() { oldState, err := terminal.MakeRaw(0) check(err) defer terminal.Restore(0, oldState)

rand.Seed(time.Now().Unix())

for i := tilesAtStart; i > 0; i-- { addRandTile() } draw()

stdin := bufio.NewReader(os.Stdin)

readKey := func() rune { r, _, err := stdin.ReadRune() check(err) return r }

for !controller(readKey()) { } } </lang>

Haskell

<lang haskell>import System.IO import Data.List import Data.Maybe import Control.Monad import Data.Random import Data.Random.Distribution.Categorical import System.Console.ANSI import Control.Lens

-- Logic

-- probability to get a 4 prob4 :: Double prob4 = 0.1

type Position = Int

combine, shift :: [Int]->[Int] combine (x:y:l) | x==y = (2*x) : combine l combine (x:l) = x : combine l combine [] = []

shift l = take (length l) $ combine (filter (>0) l) ++ [0,0..]

reflect :: a ->a reflect = map reverse

type Move = Position -> Position

left, right, up, down :: Move left = map shift right = reflect . left . reflect up = transpose . left . transpose down = transpose . right . transpose

progress :: Eq a => (a -> a) -> a -> Maybe a progress f pos = if pos==next_pos then Nothing else Just next_pos where next_pos= f pos

lost, win:: Position -> Bool lost pos = all isNothing [progress move pos| move<-[left,right,up,down] ]

win = any $ any (>=2048)

go :: Position -> Maybe Move -> Maybe Position go pos move = move >>= flip progress pos


{- -- Adding 2 or 4 without lens: update l i a = l1 ++ a : l2 where (l1,_:l2)=splitAt i l indicesOf l = [0..length l-1]

add a x y pos = update pos y $ update (pos !! y) x a

add2or4 :: Position -> RVar Position add2or4 pos = do

 (x,y) <-  randomElement [(x,y) | y<-indicesOf pos, x<-indicesOf (pos!!y), pos!!y!!x ==0  ]
 a <- categorical [(0.9::Double,2), (0.1,4) ]
 return $ add a x y pos

-}

-- or with lens: indicesOf :: [a] -> [ReifiedTraversal' [a] a] indicesOf l = [ Traversal $ ix i | i <- [0..length l - 1] ]

indices2Of :: a -> [ReifiedTraversal' a a] indices2Of ls = [ Traversal $ i.j | Traversal i <- indicesOf ls, let Just l = ls ^? i, Traversal j <- indicesOf l]

add2or4 :: Position -> RVar Position add2or4 pos = do

 xy <-  randomElement [ xy | Traversal xy <- indices2Of pos, pos ^? xy == Just 0 ]
 a <- categorical [(1-prob4, 2), (prob4, 4) ]
 return $  pos & xy .~ a

-- Easy, is'n it'?

-- Main loop play :: Position -> IO () play pos = do

  c <- getChar
  case go pos $ lookup c [('D',left),('C',right),('A',up),('B',down)] of
     Nothing -> play pos
     Just pos1 -> do
        pos2 <- sample $ add2or4 pos1
        draw pos2
        when (win pos2 && not (win pos)) $ putStrLn $ "You win! You may keep going."
        if lost pos2 then putStrLn "You lost!"
           else play pos2

main :: IO () main = do

 pos <- sample $ add2or4 $ replicate 4 (replicate 4 0)
 draw pos
 play pos

-- Rendering -- See https://en.wikipedia.org/wiki/ANSI_escape_code#Colors colors =

[(0,"\ESC[38;5;234;48;5;250m     ")
,(2,"\ESC[38;5;234;48;5;255m  2  ")
,(4,"\ESC[38;5;234;48;5;230m  4  ")
,(8,"\ESC[38;5;15;48;5;208m  8  ")
,(16,"\ESC[38;5;15;48;5;209m  16 ")
,(32,"\ESC[38;5;15;48;5;203m  32 ")
,(64,"\ESC[38;5;15;48;5;9m  64 ")
,(128,"\ESC[38;5;15;48;5;228m 128 ")
,(256,"\ESC[38;5;15;48;5;227m 256 ")
,(512,"\ESC[38;5;15;48;5;226m 512 ")
,(1024,"\ESC[38;5;15;48;5;221m 1024")
,(2048,"\ESC[38;5;15;48;5;220m 2048")
,(4096,"\ESC[38;5;15;48;5;0m 4096")
,(8192,"\ESC[38;5;15;48;5;0m 8192")
,(16384,"\ESC[38;5;15;48;5;0m16384")
,(32768,"\ESC[38;5;15;48;5;0m32768")
,(65536,"\ESC[38;5;15;48;5;0m65536")
,(131072,"\ESC[38;5;15;48;5;90m131072")
]

showTile x = fromJust (lookup x colors) ++ "\ESC[B\^H\^H\^H\^H\^H \ESC[A\ESC[C"

draw :: Position -> IO () draw pos = do

 setSGR [Reset]
 clearScreen
 hideCursor
 hSetEcho stdin False
 hSetBuffering stdin NoBuffering
 setSGR [SetConsoleIntensity BoldIntensity]
 putStr "\ESC[38;5;234;48;5;248m" -- set board color
 setCursorPosition 0 0
 replicateM_ 13 $ putStrLn $ replicate 26 ' '
 setCursorPosition 1 1
 putStrLn $ intercalate "\n\n\n\ESC[C" $ concatMap showTile `map` pos

</lang>

J

Solution <lang j>NB. 2048.ijs script NB. ========================================================= NB. 2048 game engine

require 'guid' ([ 9!:1) _2 (3!:4) , guids 1 NB. randomly set initial random seed

coclass 'g2048' Target=: 2048

new2048=: verb define

 Gridsz=: 4 4
 Points=: Score=: 0
 Grid=: newnum^:2 ] Gridsz $ 0

)

newnum=: verb define

 num=. 2 4 {~ 0.1 > ?0   NB. 10% chance of 4
 idx=. 4 $. $. 0 = y        NB. indicies of 0s
 if. #idx do.               NB. handle full grid
   idx=. ,/ ({~ 1 ? #) idx  NB. choose an index
   num (<idx)} y
 else. return. y
 end.

)

mskmerge=: [: >/\.&.|. 2 =/\ ,&_1 mergerow=: ((* >:) #~ _1 |. -.@]) mskmerge scorerow=: +/@(+: #~ mskmerge)

compress=: -.&0 toLeft=: 1 :'4&{.@(u@compress)"1' toRight=: 1 : '_4&{.@(u@compress&.|.)"1' toUp=: 1 : '(4&{.@(u@compress)"1)&.|:' toDown=: 1 : '(_4&{.@(u@compress&.|.)"1)&.|:'

move=: conjunction define

 Points=: +/@, v Grid
 update newnum^:(Grid -.@-: ]) u Grid

)

noMoves=: (0 -.@e. ,)@(mergerow toRight , mergerow toLeft , mergerow toUp ,: mergerow toDown) hasWon=: Target e. ,

eval=: verb define

 Score=: Score + Points
 isend=. (noMoves , hasWon) y
 msg=. isend # 'You lost!!';'You Won!!'
 if. -. isend=. +./ isend do.
   Points=: 0
   msg=. 'Score is ',(": Score)
 end.
 isend;msg

)

showGrid=: echo

NB. ========================================================= NB. Console user interface

g2048Con_z_=: conew&'g2048con'

coclass 'g2048con' coinsert 'g2048'

create=: verb define

 echo Instructions
 startnew y

)

destroy=: codestroy quit=: destroy

startnew=: update@new2048

left=: 3 :'mergerow toLeft move (scorerow toLeft)' right=: 3 :'mergerow toRight move (scorerow toRight)' up=: 3 :'mergerow toUp move (scorerow toUp)' down=: 3 :'mergerow toDown move (scorerow toDown)'

update=: verb define

 Grid=: y       NB. update global Grid
 'isend msg'=. eval y
 echo msg
 showGrid y
 if. isend do. destroy  end.
 empty

)

Instructions=: noun define

2048

Object:

  Create the number 2048 by merging numbers.

How to play:

 When 2 numbers the same touch, they merge.
 - move numbers using the commands below:
      right__grd 
      left__grd 
      up__grd 
      down__grd 
 - quit a game:
      quit__grd 
 - start a new game:
      grd=: g2048Con 

)</lang> Usage <lang j> grd=: g2048Con

Score is 0 0 0 0 2 0 2 0 0 0 0 0 0 0 0 0 0

  right__grd 

Score is 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0 2

  down__grd 

Score is 4 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 2 ...</lang>

Java

 
Works with: Java version 8

<lang java>import java.awt.*; import java.awt.event.*; import java.util.Random; import javax.swing.*;

public class Game2048 extends JPanel {

   enum State {
       start, won, running, over
   }
   final Color[] colorTable = {
       new Color(0x701710), new Color(0xFFE4C3), new Color(0xfff4d3),
       new Color(0xffdac3), new Color(0xe7b08e), new Color(0xe7bf8e),
       new Color(0xffc4c3), new Color(0xE7948e), new Color(0xbe7e56),
       new Color(0xbe5e56), new Color(0x9c3931), new Color(0x701710)};
   final static int target = 2048;
   static int highest;
   static int score;
   private Color gridColor = new Color(0xBBADA0);
   private Color emptyColor = new Color(0xCDC1B4);
   private Color startColor = new Color(0xFFEBCD);
   private Random rand = new Random();
   private Tile[][] tiles;
   private int side = 4;
   private State gamestate = State.start;
   private boolean checkingAvailableMoves;
   public Game2048() {
       setPreferredSize(new Dimension(900, 700));
       setBackground(new Color(0xFAF8EF));
       setFont(new Font("SansSerif", Font.BOLD, 48));
       setFocusable(true);
       addMouseListener(new MouseAdapter() {
           @Override
           public void mousePressed(MouseEvent e) {
               startGame();
               repaint();
           }
       });
       addKeyListener(new KeyAdapter() {
           @Override
           public void keyPressed(KeyEvent e) {
               switch (e.getKeyCode()) {
                   case KeyEvent.VK_UP:
                       moveUp();
                       break;
                   case KeyEvent.VK_DOWN:
                       moveDown();
                       break;
                   case KeyEvent.VK_LEFT:
                       moveLeft();
                       break;
                   case KeyEvent.VK_RIGHT:
                       moveRight();
                       break;
               }
               repaint();
           }
       });
   }
   @Override
   public void paintComponent(Graphics gg) {
       super.paintComponent(gg);
       Graphics2D g = (Graphics2D) gg;
       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
               RenderingHints.VALUE_ANTIALIAS_ON);
       drawGrid(g);
   }
   void startGame() {
       if (gamestate != State.running) {
           score = 0;
           highest = 0;
           gamestate = State.running;
           tiles = new Tile[side][side];
           addRandomTile();
           addRandomTile();
       }
   }
   void drawGrid(Graphics2D g) {
       g.setColor(gridColor);
       g.fillRoundRect(200, 100, 499, 499, 15, 15);
       if (gamestate == State.running) {
           for (int r = 0; r < side; r++) {
               for (int c = 0; c < side; c++) {
                   if (tiles[r][c] == null) {
                       g.setColor(emptyColor);
                       g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7);
                   } else {
                       drawTile(g, r, c);
                   }
               }
           }
       } else {
           g.setColor(startColor);
           g.fillRoundRect(215, 115, 469, 469, 7, 7);
           g.setColor(gridColor.darker());
           g.setFont(new Font("SansSerif", Font.BOLD, 128));
           g.drawString("2048", 310, 270);
           g.setFont(new Font("SansSerif", Font.BOLD, 20));
           if (gamestate == State.won) {
               g.drawString("you made it!", 390, 350);
           } else if (gamestate == State.over)
               g.drawString("game over", 400, 350);
           g.setColor(gridColor);
           g.drawString("click to start a new game", 330, 470);
           g.drawString("(use arrow keys to move tiles)", 310, 530);
       }
   }
   void drawTile(Graphics2D g, int r, int c) {
       int value = tiles[r][c].getValue();
       g.setColor(colorTable[(int) (Math.log(value) / Math.log(2)) + 1]);
       g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7);
       String s = String.valueOf(value);
       g.setColor(value < 128 ? colorTable[0] : colorTable[1]);
       FontMetrics fm = g.getFontMetrics();
       int asc = fm.getAscent();
       int dec = fm.getDescent();
       int x = 215 + c * 121 + (106 - fm.stringWidth(s)) / 2;
       int y = 115 + r * 121 + (asc + (106 - (asc + dec)) / 2);
       g.drawString(s, x, y);
   }


   private void addRandomTile() {
       int pos = rand.nextInt(side * side);
       int row, col;
       do {
           pos = (pos + 1) % (side * side);
           row = pos / side;
           col = pos % side;
       } while (tiles[row][col] != null);
       int val = rand.nextInt(10) == 0 ? 4 : 2;
       tiles[row][col] = new Tile(val);
   }
   private boolean move(int countDownFrom, int yIncr, int xIncr) {
       boolean moved = false;
       for (int i = 0; i < side * side; i++) {
           int j = Math.abs(countDownFrom - i);
           int r = j / side;
           int c = j % side;
           if (tiles[r][c] == null)
               continue;
           int nextR = r + yIncr;
           int nextC = c + xIncr;
           while (nextR >= 0 && nextR < side && nextC >= 0 && nextC < side) {
               Tile next = tiles[nextR][nextC];
               Tile curr = tiles[r][c];
               if (next == null) {
                   if (checkingAvailableMoves)
                       return true;
                   tiles[nextR][nextC] = curr;
                   tiles[r][c] = null;
                   r = nextR;
                   c = nextC;
                   nextR += yIncr;
                   nextC += xIncr;
                   moved = true;
               } else if (next.canMergeWith(curr)) {
                   if (checkingAvailableMoves)
                       return true;
                   int value = next.mergeWith(curr);
                   if (value > highest)
                       highest = value;
                   score += value;
                   tiles[r][c] = null;
                   moved = true;
                   break;
               } else
                   break;
           }
       }
       if (moved) {
           if (highest < target) {
               clearMerged();
               addRandomTile();
               if (!movesAvailable()) {
                   gamestate = State.over;
               }
           } else if (highest == target)
               gamestate = State.won;
       }
       return moved;
   }
   boolean moveUp() {
       return move(0, -1, 0);
   }
   boolean moveDown() {
       return move(side * side - 1, 1, 0);
   }
   boolean moveLeft() {
       return move(0, 0, -1);
   }
   boolean moveRight() {
       return move(side * side - 1, 0, 1);
   }
   void clearMerged() {
       for (Tile[] row : tiles)
           for (Tile tile : row)
               if (tile != null)
                   tile.setMerged(false);
   }
   boolean movesAvailable() {
       checkingAvailableMoves = true;
       boolean hasMoves = moveUp() || moveDown() || moveLeft() || moveRight();
       checkingAvailableMoves = false;
       return hasMoves;
   }
   public static void main(String[] args) {
       SwingUtilities.invokeLater(() -> {
           JFrame f = new JFrame();
           f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
           f.setTitle("2048");
           f.setResizable(true);
           f.add(new Game2048(), BorderLayout.CENTER);
           f.pack();
           f.setLocationRelativeTo(null);
           f.setVisible(true);
       });
   }

}

class Tile {

   private boolean merged;
   private int value;
   Tile(int val) {
       value = val;
   }
   int getValue() {
       return value;
   }
   void setMerged(boolean m) {
       merged = m;
   }
   boolean canMergeWith(Tile other) {
       return !merged && other != null && !other.merged && value == other.getValue();
   }
   int mergeWith(Tile other) {
       if (canMergeWith(other)) {
           value *= 2;
           merged = true;
           return value;
       }
       return -1;
   }

}</lang>

JavaScript

Uses the P5.js library. <lang JavaScript> /* Tile object: */

function Tile(pos, val, puzzle){ this.pos = pos; this.val = val; this.puzzle = puzzle; this.merging = false;

this.getCol = () => Math.round(this.pos % 4); this.getRow = () => Math.floor(this.pos / 4);

/* draw tile on a P5.js canvas: */

this.show = function(){ let padding = this.merging ? 0 : 5; let size = 0.25*width; noStroke(); colorMode(HSB, 255); fill(10*(11 - Math.log2(this.val)), 50 + 15*Math.log2(this.val), 200); rect(this.getCol()*size + padding, this.getRow()*size + padding, size - 2*padding, size - 2*padding); fill(255); textSize(0.1*width); textAlign(CENTER, CENTER); text(this.val, (this.getCol() + 0.5)*size, (this.getRow() + 0.5)*size); }

/* move tile in a given direction: */

this.move = function(dir){ let col = this.getCol() + (1 - 2*(dir < 0))*Math.abs(dir)%4; let row = this.getRow() + (1 - 2*(dir < 0))*Math.floor(Math.abs(dir)/4); let target = this.puzzle.getTile(this.pos + dir);

if (col < 0 || col > 3 || row < 0 || row > 3) { /* target position out of bounds */ return false; } else if (target){ /* tile blocked by other tile */ if(this.merging || target.merging || target.val !== this.val) return false;

/* merge with target tile (equal values):*/ target.val += this.val; target.merging = true; this.puzzle.score += target.val; this.puzzle.removeTile(this); return true; }

/* move tile: */ this.pos += dir; return true; } }

/* Puzzle object: */

function Puzzle(){ this.tiles = []; this.dir = 0; this.score = 0; this.hasMoved = false; this.validPositions = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

this.getOpenPositions = () => this.validPositions.filter(i => this.tiles.map(x => x.pos).indexOf(i) === -1); this.getTile = pos => this.tiles.filter(x => x.pos === pos)[0]; this.removeTile = tile => this.tiles.splice(this.tiles.indexOf(tile), 1); this.winCondition = () => this.tiles.some(x => x.val === 2048);

/* check for valid moves: */

this.validMoves = function(){ /* return true if there are empty spaces */ if(this.tiles.length < 16) return true;

/* otherwise check for neighboring tiles with the same value */ let res = false; this.tiles.sort((x,y) => x.pos - y.pos); for(let i = 0; i < 16; i++) res = res || ( (i%4 < 3) ? this.tiles[i].val === this.tiles[i+1].val : false ) || ( (i < 12) ? this.tiles[i].val === this.tiles[i+4].val : false ); return res; }

/* check win and lose condition: */

this.checkGameState = function(){ if(this.winCondition()){ alert('You win!'); } else if (!this.validMoves()){ alert('You Lose!'); this.restart(); } }

this.restart = function(){ this.tiles = []; this.dir = 0; this.score = 0; this.hasMoved = false; this.generateTile(); this.generateTile(); }

/* draw the board on the p5.js canvas: */

this.show = function(){ background(200); fill(255); textSize(0.05*width); textAlign(CENTER, TOP); text("SCORE: " + this.score, 0.5*width, width);

for(let tile of this.tiles) tile.show(); }

/* update the board: */

this.animate = function(){ if(this.dir === 0) return;

/* move all tiles in a given direction */ let moving = false; this.tiles.sort((x,y) => this.dir*(y.pos - x.pos)); for(let tile of this.tiles) moving = moving || tile.move(this.dir);

/* check if the move is finished and generate a new tile */ if(this.hasMoved && !moving){ this.dir = 0; this.generateTile();

for(let tile of this.tiles) tile.merging = false; } this.hasMoved = moving; }

this.generateTile = function(){ let positions = this.getOpenPositions(); let pos = positions[Math.floor(Math.random()*positions.length)]; let val = 2 + 2*Math.floor(Math.random()*1.11); this.tiles.push(new Tile(pos, val, this)); } this.generateTile(); this.generateTile();

/* process key inputs: */

this.keyHandler = function(key){ if (key === UP_ARROW) this.dir = -4 else if (key === DOWN_ARROW) this.dir = 4 else if (key === RIGHT_ARROW) this.dir = 1 else if (key === LEFT_ARROW) this.dir = -1; } }


let game;

function setup() { createCanvas(400, 420); game = new Puzzle(); }

/* game loop: */

function draw() { game.checkGameState(); game.animate(); game.show(); }

function keyPressed(){ game.keyHandler(keyCode); } </lang>

Julia

Uses the Gtk toolkit. Includes scoring, a choice of board size and toolbar buttons for Undo and New Game. <lang julia> using Gtk.ShortNames

@enum Direction2048 Right Left Up Down

"""

   shifttiles!

The adding and condensing code is for a leftward shift, so if the move is not leftward, this will rotate matrix to make move leftward, move, then undo rotation. """ function shifttiles!(b, siz, direction)

   if direction == Right
       tmpb = rot180(b); points, winner = leftshift!(tmpb, siz); tmpb = rot180(tmpb)
   elseif direction == Up
       tmpb = rotl90(b); points, winner = leftshift!(tmpb, siz); tmpb = rotr90(tmpb)
   elseif direction == Down
       tmpb = rotr90(b); points, winner = leftshift!(tmpb, siz); tmpb = rotl90(tmpb)
   else # left movement function as coded
       return leftshift!(b, siz)
   end
   for i in 1:siz, j in 1:siz
       b[i,j] = tmpb[i,j]   # copy tmpb contents back to b (modifies b)
   end
   points, winner

end


function compactleft!(b, siz, row)

   tmprow = zeros(Int, siz)
   tmppos = 1
   for j in 1:siz
       if b[row,j] != 0
           tmprow[tmppos] = b[row,j]
           tmppos += 1
       end
   end
   b[row,:] = tmprow

end

"""

   leftshift!

Work row by row. First, compact tiles to the left if possible. Second, find and replace paired tiles in the row, then re-compact. Keep score of merges and return as pointsgained. If a 2048 value tile is created, return a winner true value. """ function leftshift!(b, siz)

   pointsgained = 0
   winner = false
   for i in 1:siz
       compactleft!(b, siz, i)
       tmprow = zeros(Int, siz)
       tmppos = 1
       for j in 1:siz-1
           if b[i,j] == b[i,j+1]
               b[i,j] = 2 * b[i,j]
               b[i,j+1] = 0
               pointsgained += b[i,j]
               if b[i,j] == 2048     # made a 2048 tile, which wins game
                   winner = true
               end
           end
           if b[i,j] != 0
               tmprow[tmppos] = b[i,j]
               tmppos += 1
           end
       end
       tmprow[siz] = b[i,siz]
       b[i,:] = tmprow
       compactleft!(b, siz, i)
   end
   pointsgained, winner

end

"""

   app2048

Run game app, with boardsize (choose 4 for original game) as an argument. """ function app2048(bsize)

   win = Window("2048 Game", 400, 400) |> (Frame() |> (box = Box(:v)))
   toolbar = Toolbar()
   newgame = ToolButton("New Game")
   setproperty!(newgame, :label, "New Game")
   setproperty!(newgame, :is_important, true)
   undomove = ToolButton("Undo Move")
   setproperty!(undomove, :label, "Undo Move")
   setproperty!(undomove, :is_important, true)
   map(w->push!(toolbar,w),[newgame,undomove])
   grid = Grid()
   map(w -> push!(box, w),[toolbar, grid])
   buttons = Array{Gtk.GtkButtonLeaf,2}(bsize, bsize)
   for i in 1:bsize, j in 1:bsize
       grid[i,j] = buttons[i,j] = Button()
       setproperty!(buttons[i,j], :expand, true)
   end
   board = zeros(Int, (bsize,bsize))
   pastboardstates = []
   score = 0
   gameover = false
   condition = Condition()
   won = ""
   function update!()
       for i in 1:bsize, j in 1:bsize
           label = (board[i,j] > 0) ? board[i,j]: " "
           setproperty!(buttons[i,j], :label, label)
       end
       setproperty!(win, :title, "$won 2048 Game  (Score: $score)")
   end
   function newrandomtile!()
       blanks = Array{Tuple{Int,Int},1}()
       for i in 1:bsize, j in 1:bsize
           if board[i,j] == 0
               push!(blanks, (i,j))
           end
       end
       if length(blanks) == 0
           gameover = true
       else
           i,j = rand(blanks)
           board[i,j] = (rand() > 0.8) ? 4 : 2
       end
   end
   function initialize!(w)
       won = ""
       gameover = false
       for i in 1:bsize, j in 1:bsize
           board[i,j] = 0
           setproperty!(buttons[i,j], :label, " ")
       end
       newrandomtile!()
       update!()
   end
   function undo!(w)
       if gameover == false
           board = pop!(pastboardstates)
           update!()
       end
   end
   function keypress(w, event)
       presses = Dict(37 => Up,    # code rotated 90 degrees
                      38 => Left,  # because of Gtk coordinates
                      39 => Down,  # y is downward positive
                      40 => Right)
       keycode = event.hardware_keycode
       if haskey(presses, keycode) && gameover == false
           push!(pastboardstates, copy(board))
           newpoints, havewon = shifttiles!(board, bsize, presses[keycode])
           score += newpoints
           if havewon && won != "Winning"
               won = "Winning"
               info_dialog("You have won the game.")
           end
           newrandomtile!()
           update!()
           if gameover
               info_dialog("Game over.\nScore: $score")
           end
       end
   end
   endit(w) = notify(condition)
   initialize!(win)
   signal_connect(initialize!, newgame, :clicked)
   signal_connect(undo!,undomove, :clicked)
   signal_connect(endit, win, :destroy)
   signal_connect(keypress, win, "key-press-event")    
   showall(win)
   wait(condition)

end


const boardsize = 4 app2048(boardsize) </lang>

Kotlin

Stateless with focus on clarity rather than conciseness.

<lang scala>import java.io.BufferedReader import java.io.InputStreamReader

const val positiveGameOverMessage = "So sorry, but you won the game." const val negativeGameOverMessage = "So sorry, but you lost the game."

fun main(args: Array<String>) {

   val grid = arrayOf(
           arrayOf(0, 0, 0, 0),
           arrayOf(0, 0, 0, 0),
           arrayOf(0, 0, 0, 0),
           arrayOf(0, 0, 0, 0)
   )
   val gameOverMessage = run2048(grid)
   println(gameOverMessage)

}

fun run2048(grid: Array<Array<Int>>): String {

   if (isGridSolved(grid)) return positiveGameOverMessage
   else if (isGridFull(grid)) return negativeGameOverMessage
   val populatedGrid = spawnNumber(grid)
   display(populatedGrid)
   return run2048(manipulateGrid(populatedGrid, waitForValidInput()))

}

fun isGridSolved(grid: Array<Array<Int>>): Boolean = grid.any { row -> row.contains(2048) } fun isGridFull(grid: Array<Array<Int>>): Boolean = grid.all { row -> !row.contains(0) }

fun spawnNumber(grid: Array<Array<Int>>): Array<Array<Int>> {

   val coordinates = locateSpawnCoordinates(grid)
   val number = generateNumber()
   return updateGrid(grid, coordinates, number)

}

fun locateSpawnCoordinates(grid: Array<Array<Int>>): Pair<Int, Int> {

   val emptyCells = arrayListOf<Pair<Int, Int>>()
   grid.forEachIndexed { x, row ->
       row.forEachIndexed { y, cell ->
           if (cell == 0) emptyCells.add(Pair(x, y))
       }
   }
   return emptyCells[(Math.random() * (emptyCells.size - 1)).toInt()]

}

fun generateNumber(): Int = if (Math.random() > 0.10) 2 else 4

fun updateGrid(grid: Array<Array<Int>>, at: Pair<Int, Int>, value: Int): Array<Array<Int>> {

   val updatedGrid = grid.copyOf()
   updatedGrid[at.first][at.second] = value
   return updatedGrid

}

fun waitForValidInput(): String {

   val input = waitForInput()
   return if (isValidInput(input)) input else waitForValidInput()

}

fun isValidInput(input: String): Boolean = arrayOf("a", "s", "d", "w").contains(input)

fun waitForInput(): String {

   val reader = BufferedReader(InputStreamReader(System.`in`))
   println("Direction?  ")
   return reader.readLine()

}

fun manipulateGrid(grid: Array<Array<Int>>, input: String): Array<Array<Int>> = when (input) {

   "a" -> shiftCellsLeft(grid)
   "s" -> shiftCellsDown(grid)
   "d" -> shiftCellsRight(grid)
   "w" -> shiftCellsUp(grid)
   else -> throw IllegalArgumentException("Expected one of [a, s, d, w]")

}

fun shiftCellsLeft(grid: Array<Array<Int>>): Array<Array<Int>> =

       grid.map(::mergeAndOrganizeCells).toTypedArray()

fun shiftCellsRight(grid: Array<Array<Int>>): Array<Array<Int>> =

       grid.map { row -> mergeAndOrganizeCells(row.reversed().toTypedArray()).reversed().toTypedArray() }.toTypedArray()

fun shiftCellsUp(grid: Array<Array<Int>>): Array<Array<Int>> {

   val rows: Array<Array<Int>> = arrayOf(
           arrayOf(grid[0][0], grid[1][0], grid[2][0], grid[3][0]),
           arrayOf(grid[0][1], grid[1][1], grid[2][1], grid[3][1]),
           arrayOf(grid[0][2], grid[1][2], grid[2][2], grid[3][2]),
           arrayOf(grid[0][3], grid[1][3], grid[2][3], grid[3][3])
   )
   val updatedGrid = grid.copyOf()
   rows.map(::mergeAndOrganizeCells).forEachIndexed { rowIdx, row ->
       updatedGrid[0][rowIdx] = row[0]
       updatedGrid[1][rowIdx] = row[1]
       updatedGrid[2][rowIdx] = row[2]
       updatedGrid[3][rowIdx] = row[3]
   }
   return updatedGrid

}

fun shiftCellsDown(grid: Array<Array<Int>>): Array<Array<Int>> {

   val rows: Array<Array<Int>> = arrayOf(
           arrayOf(grid[3][0], grid[2][0], grid[1][0], grid[0][0]),
           arrayOf(grid[3][1], grid[2][1], grid[1][1], grid[0][1]),
           arrayOf(grid[3][2], grid[2][2], grid[1][2], grid[0][2]),
           arrayOf(grid[3][3], grid[2][3], grid[1][3], grid[0][3])
   )
   val updatedGrid = grid.copyOf()
   rows.map(::mergeAndOrganizeCells).forEachIndexed { rowIdx, row ->
       updatedGrid[3][rowIdx] = row[0]
       updatedGrid[2][rowIdx] = row[1]
       updatedGrid[1][rowIdx] = row[2]
       updatedGrid[0][rowIdx] = row[3]
   }
   return updatedGrid

}

fun mergeAndOrganizeCells(row: Array<Int>): Array<Int> = organize(merge(row.copyOf()))

fun merge(row: Array<Int>, idxToMatch: Int = 0, idxToCompare: Int = 1): Array<Int> {

   if (idxToMatch >= row.size) return row
   if (idxToCompare >= row.size) return merge(row, idxToMatch + 1, idxToMatch + 2)
   if (row[idxToMatch] == 0) return merge(row, idxToMatch + 1, idxToMatch + 2)
   return if (row[idxToMatch] == row[idxToCompare]) {
       row[idxToMatch] *= 2
       row[idxToCompare] = 0
       merge(row, idxToMatch + 1, idxToMatch + 2)
   } else {
       if (row[idxToCompare] != 0) merge(row, idxToMatch + 1, idxToMatch + 2)
       else merge(row, idxToMatch, idxToCompare + 1)
   }

}

fun organize(row: Array<Int>, idxToMatch: Int = 0, idxToCompare: Int = 1): Array<Int> {

   if (idxToMatch >= row.size) return row
   if (idxToCompare >= row.size) return organize(row, idxToMatch + 1, idxToMatch + 2)
   if (row[idxToMatch] != 0) return organize(row, idxToMatch + 1, idxToMatch + 2)
   return if (row[idxToCompare] != 0) {
       row[idxToMatch] = row[idxToCompare]
       row[idxToCompare] = 0
       organize(row, idxToMatch + 1, idxToMatch + 2)
   } else {
       organize(row, idxToMatch, idxToCompare + 1)
   }

}

fun display(grid: Array<Array<Int>>) {

   val prettyPrintableGrid = grid.map { row ->
       row.map { cell ->
           if (cell == 0) "    " else java.lang.String.format("%4d", cell)
       }
   }
   println("New Grid:")
   prettyPrintableGrid.forEach { row ->
       println("+----+----+----+----+")
       row.forEach { print("|$it") }
       println("|")
   }
   println("+----+----+----+----+")

}</lang>

Sample output:

New Grid:
+----+----+----+----+
|   2|    |    |    |
+----+----+----+----+
|    |    |    |   2|
+----+----+----+----+
|   4|  16|    |    |
+----+----+----+----+
|  16|   4|   2|    |
+----+----+----+----+
Direction?  

Latitude

Takes input on stdin using the words "left", "right", "up", "down".

<lang Latitude> use 'format import '[format]. use 'random.

Pos := Object clone tap {

 self x := 0.
 self y := 0.
 self move := {
   localize.
   case ($1) do {
     when 'left  do { pos (this x - 1, this y). }.
     when 'right do { pos (this x + 1, this y). }.
     when 'down  do { pos (this x, this y + 1). }.
     when 'up    do { pos (this x, this y - 1). }.
   }.
 }.
 self == := {
   (self x == $1 x) and (self y == $1 y).
 }.
 self inBounds? := {
   (self x >= 0) and (self x < 4) and (self y >= 0) and (self y < 4).
 }.
 self toString := {
   format "pos (~S, ~S)" call (self x, self y).
 }.

}.

pos := {

 takes '[x, y].
 Pos clone tap {
   self x := x.
   self y := y.
 }.

}.

allSquares := [] tap {

 localize.
 0 upto 4 visit {
   takes '[y].
   0 upto 4 visit {
     takes '[x].
     this pushBack (pos (x, y)).
   }.
 }.

}.

sample := {

 $1 nth (random nextInt mod ($1 length)).

}.

Grid ::= Object clone tap {

 self grid := 16 times to (Array) map { 0. }.
 self clone := {
   Parents above (parent self, 'clone) call tap {
     self grid := self grid clone.
   }.
 }.
 toIndex := {
   $1 x + 4 * $1 y.
 }.
 self at := {
   self grid nth (toIndex).
 }.
 self at= := {
   self grid nth= (toIndex).
 }.
 self isBlank? := {
   self at == 0.
 }.
 self spawnNew := {
   localize.
   candidates := allSquares filter { this isBlank?. } to (Array).
   if (candidates empty?) then {
     'gameover.
   } else {
     p := sample (candidates).
     this at (p) = if (random nextInt mod 10 == 0) then 4 else 2.
     'okay.
   }.
 }.
 canMoveCell := {
   takes '[grid, src, dir].
   dest := src move (dir).
   if (dest inBounds?) then {
     vs := grid at (src).
     vd := grid at (dest).
     (vs /= 0) and ((vd == 0) or (vs == vd)).
   } else {
     False.
   }.
 }.
 self canMove := {
   localize.
   takes '[dir].
   allSquares any { canMoveCell (this, $1, dir). }.
 }.
 self validMoves := {
   '[left, right, up, down] filter { parent self canMove. } to (Array).
 }.
 ;; Calculates the order of iteration for performing the moves
 axisCalc := {
   takes '[dir, major, minor].
   case (dir) do {
     when 'left do { pos (major, minor). }.
     when 'right do { pos (3 - major, minor). }.
     when 'up do { pos (minor, major). }.
     when 'down do { pos (minor, 3 - major). }.
   }.
 }.
 moveFrom := {
   takes '[grid, src, dir, locked].
   dest := src move (dir).
   local 'result = Nil.
   dest inBounds? ifTrue {
     locked contains (dest) ifFalse {
       vs := grid at (src).
       vd := grid at (dest).
       cond {
         when (vd == 0) do {
           grid at (dest) = vs.
           grid at (src) = 0.
           result = moveFrom (grid, dest, dir, locked).
         }.
         when (vd == vs) do {
           grid at (dest) = vs + vd.
           grid at (src) = 0.
           result = moveFrom (grid, dest, dir, locked).
           ;; We merged, so lock the final result cell.
           locked pushBack (result).
         }.
       }.
     }.
   }.
   (result) or (src).
 }.
 self doMove := {
   localize.
   takes '[dir].
   locked := [].
   0 upto 4 do {
     takes '[major].
     0 upto 4 do {
       takes '[minor].
       src := axisCalc (dir, major, minor).
       moveFrom: this, src, dir, locked.
     }.
   }.
 }.
 self pretty := {
   localize.
   lines := [].
   row := "+----+----+----+----+".
   lines pushBack (row).
   0 upto 4 visit {
     takes '[y].
     local 'line = "|".
     0 upto 4 visit {
       takes '[x].
       n := this at (pos (x, y)).
       if (n == 0) then {
         line = line ++ "    |".
       } else {
         line = line ++ n toString padRight (" ", 4) ++ "|".
       }.
     }.
     lines pushBack (line).
     lines pushBack (row).
   }.
   lines joinText "\n".
 }.
 self toArray := {
   allSquares map { parent self at. }.
 }.

}.

grid := Grid clone. endgame := loop* {

 ;; Check for victory
 (grid toArray any { $1 >= 2048. }) ifTrue {
   last 'victory.
 }.
 ;; Check for game over
 result := grid spawnNew.
 (result == 'gameover) ifTrue {
   last 'gameover.
 }.
 valid := grid validMoves.
 valid empty? ifTrue {
   last 'gameover.
 }.
 $stdout putln: grid pretty.
 move := loop* {
   $stdout puts: "Your move (left, right, up, down)> ".
   move := $stdin readln intern.
   valid contains (move) ifTrue { last (move). }.
 }.
 grid doMove (move).

}. $stdout putln: grid pretty.

if (endgame == 'victory) then {

 $stdout putln: "You win!".

} else {

 $stdout putln: "Better luck next time!".

}. </lang>

Sample output:

+----+----+----+----+
|16  |4   |    |    |
+----+----+----+----+
|4   |    |    |    |
+----+----+----+----+
|    |    |    |    |
+----+----+----+----+
|    |2   |    |    |
+----+----+----+----+
Your move (left, right, up, down)>

Lua

Sadly, ANSI C doesn't have low-level keyboard input, so neither does vanilla Lua, so the input is a bit cumbersome (wasd PLUS enter). <lang lua>-- 2048 for Lua 5.1-5.4, 12/3/2020 db local unpack = unpack or table.unpack -- for 5.3 +/- compatibility game = {

 cell = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
 best = 0,
 draw = function(self)
   local t = self.cell
   print("+----+----+----+----+")
   for r=0,12,4 do
     print(string.format("|%4d|%4d|%4d|%4d|\n+----+----+----+----+",t[r+1],t[r+2],t[r+3],t[r+4]))
   end
 end,
 incr = function(self)
   local t,open = self.cell,{}
   for i=1,16 do if t[i]==0 then open[#open+1]=i end end
   t[open[math.random(#open)]] = math.random()<0.1 and 4 or 2
 end,
 pack = function(self,ofr,oto,ost,ifr,ito,ist)
   local t = self.cell
   for outer=ofr,oto,ost do
     local skip = 0
     for inner=ifr,ito,ist do
       local i = outer+inner
       if t[i]==0 then skip=skip+1 else if skip>0 then t[i-skip*ist],t[i],self.diff = t[i],0,true end end
     end
   end
 end,
 comb = function(self,ofr,oto,ost,ifr,ito,ist)
   local t = self.cell
   for outer=ofr,oto,ost do
     for inner=ifr,ito-ist,ist do
       local i,j = outer+inner,outer+inner+ist
       if t[i]>0 and t[i]==t[j] then t[i],t[j],self.diff,self.best = t[i]*2,0,true,math.max(self.best,t[i]*2) end
     end
   end
 end,
 move = function(self,dir)
   local loopdata = {{0,12,4,1,4,1},{0,12,4,4,1,-1},{1,4,1,0,12,4},{1,4,1,12,0,-4}}
   local ofr,oto,ost,ifr,ito,ist = unpack(loopdata[dir])
   self:pack(ofr,oto,ost,ifr,ito,ist)
   self:comb(ofr,oto,ost,ifr,ito,ist)
   self:pack(ofr,oto,ost,ifr,ito,ist)
 end,
 full = function(self)
   local t = self.cell
   for r=0,12,4 do
     for c=1,4 do
       local i,v = r+c,t[r+c]
       if (v==0) or (c>1 and t[i-1]==v) or (c<4 and t[i+1]==v) or (r>0 and t[i-4]==v) or (r<12 and t[i+4]==v) then
         return false
       end
     end
   end
   return true
 end,
 play = function(self)
   math.randomseed(os.time())
   self:incr()
   self:incr()
   while true do
     self:draw()
     if self.best==2048 then print("WIN!") break end
     if self:full() then print("LOSE!") break end
     io.write("Your move (wasd + enter, or q + enter to quit):  ")
     local char = io.read()
     self.diff = false
     if (char=="a") then self:move(1)
     elseif (char=="d") then self:move(2)
     elseif (char=="w") then self:move(3)
     elseif (char=="s") then self:move(4)
     elseif (char=="q") then break end
     if self.diff then self:incr() end
   end
 end

} game:play()</lang>

Output:
+----+----+----+----+
|   2|   0|   0|   0|
+----+----+----+----+
|   4|   0|   0|   0|
+----+----+----+----+
|   2|   2|  16|   4|
+----+----+----+----+
|   4|  32|  64| 128|
+----+----+----+----+
Your move (wasd + enter, or q + enter to quit):

M2000 Interpreter

<lang M2000 Interpreter> Module Game2048 {

     \\ 10% 4 and 90% 2
     Def GetTlleNumber()=If(Random(10)<2->4, 2)
     \\ tile
     Def Tile$(x)=If$(x=0->"[    ]", format$("[{0::-4}]", x))
     \\ empty board
     BoardTileRight=lambda (x, y)->x+y*4
     BoardTileLeft=lambda (x, y)->3-x+y*4
     BoardTileUp=lambda (x, y)->x*4+y
     BoardTileDown=lambda (x, y)->(3-x)*4+y
     Dim Board(0 to 15)
     Inventory EmptyTiles
     \\ Score is a statement but we can use it as a variable too.
     Score=0
     \\ Win is also a statement but we can use it as a variable too.
     Win=False
     ExitNow=False
     BoardDirection=BoardtileRight
     Process(BoardDirection)
     \\ Split Rem lines to insert start condition to check valid moves
     Rem : board(0)=2
     Rem : board(1)=2, 2, 2   ' place to (1), (2), (3)
           While len(EmptyTiles) {
           NewTile()
           DrawBoard()
           Action=False
           do {
                 a$=key$
                 if len(a$)=2 then {
                       Action=true            
                       Select case Asc(mid$(a$,2))
                       Case 72
                       BoardDirection=BoardTileUp
                       Case 75
                       BoardDirection=BoardTileRight
                       Case 77
                       BoardDirection=BoardTileLeft
                       Case 80
                       BoardDirection=BoardTileDown
                       Case 79 ' End key
                             ExitNow=True
                       Else
                       Action=false
                       end select     
                 }
           } until Action
           If ExitNow then exit
           Process(BoardDirection)
     }
     If Win then {
           Print "You Win"
     } Else {
           Print "You Loose"
     }
     End
     Sub Process(Boardtile)
     Inventory EmptyTiles   ' clear inventory
     local where, i, j, k
     For i=0 to 3
           Gravity()
           k=boardtile(0,i)
           For j=1 to 3
                 where=boardtile(j,i)
                 if Board(where)<>0 then {
                       if board(k)=board(where) then {
                              board(k)*=2 : score+=board(where): board(where)=0
                              if board(k)=2048 Then Win=True : ExitNow=true
                       }
                 }
                 k=where 
           Next j
           Gravity()
           For j=0 to 3
                 where=boardtile(j,i)
                 if board(where)=0 then Append EmptyTiles, where
           Next j
     Next i
     End Sub
     Sub NewTile()
           local m=EmptyTiles(Random(0, len(EmptyTiles)-1)!)
           Board(m)=GetTlleNumber()
           Delete EmptyTiles, m
     End Sub
     Sub DrawBoard()
           Refresh 2000
           Cls
           Cursor 0, 10
           Local Doc$, line$
           Document Doc$
           Doc$=Format$("Game 2048 Score {0}", score)
           \\ Using Report 2 we use rendering as text, with center justify
           Report 2, Doc$
           Doc$={
           }
           Local i, j
           For i=0 to 3
                 line$=""
                 For j=0 to 3
                       line$+=Tile$(Board(BoardTileRight(j, i)))
                 Next j
                 Print Over $(2), Line$
                 Print
                 Doc$=Line$+{
                 }
           Next i
           Report 2, "Next:Use Arrows | Exit: Press End"
           Refresh
           ClipBoard Doc$
     End Sub     
     Sub Gravity()
           k=-1
           for j=0 to 3 {
                 where=boardtile(j,i)
                 if k=-1 then if board(where)=0 then k=j : continue
                 if board(where)=0  then continue
                 if k=-1 then continue
                 board(boardtile(k,i))=board(where)
                 board(where)=0
                 k++
           }  
     End Sub

} Game2048 </lang>

Each move copied to clipboard

Output:
Game 2048 Score 14
[   2][   4][   8][   8]
[    ][   2][   2][   2]
[    ][    ][    ][    ]
[    ][    ][    ][    ]
Game 2048 Score 24
[    ][   2][   4][  16]
[    ][    ][   2][   4]
[    ][    ][    ][    ]
[    ][    ][   2][    ]
Game 2048 Score 26
[    ][    ][    ][    ]
[    ][    ][    ][    ]
[   2][    ][   4][  16]
[    ][   2][   4][   4]
Game 2048 Score 30
[    ][    ][    ][    ]
[    ][    ][    ][    ]
[    ][   2][    ][  16]
[   2][   2][   8][   4]

Maple

This application requires a bunch of different components to properly run when being as close to the mobile game as possible. These components are: A math container for the grid, an arrow key for each direction, a restart button, a text box to display the game over/highscore/arrow key to start messages, labels for score and highscore, and textboxes for the highscore and score values. Once these are created, change the names to the ones in the main body of code, and include the proper procedures for the 4 arrows and the restart button.

Next is the main body of code: <lang Maple> macro(SP=DocumentTools:-SetProperty, GP=DocumentTools:-GetProperty); G := module()

export reset,f,getname; local a:=Matrix(4): local buttonpress:="False"; local score:=0; local highscoreM,highscore,hscore,hname,M,j,k,z,e,move,r,c,q,w,checklose,loss,matrixtotextarea;

getname:=proc(); hname:=GP("Name",value); buttonpress:="True"; if score>hscore then M:=Matrix(1, 2, score, hname): ExportMatrix("this:///Score.csv",M); reset(); else reset(); end if; end proc;

matrixtotextarea:=proc(m) local m2,colors; colors:=["White","Beige","LightGrey",ColorTools:-Color("RGB", [255/255, 127/255, 80/255]),ColorTools:-Color("RGB", [255/255, 99/255, 71/255]),ColorTools:-Color("RGB", [255/255, 69/255, 0/255]),ColorTools:-Color("RGB", [255/255, 0/255, 0/255]),ColorTools:-Color("RGB", [255/255, 215/255, 0/255]), ColorTools:-Color("RGB", [255/255, 255/255, 0/255]),ColorTools:-Color("RGB", [204/255, 204/255, 0/255]),ColorTools:-Color("RGB", [153/255, 153/255, 0/255]),ColorTools:-Color("RGB", [102/255, 102/255, 0/255]), ColorTools:-Color("RGB", [0/255, 0/255, 0/255])]; m2 := ArrayTools:-Reshape(m^%T, [16,1]): SP(seq([cat("TextArea",i),value,m2[i+1,1]],i=0..15)); SP(seq(["Table1",fillcolor[(`if`(i+1<5,1,`if`(i+1<9 and i+1>4,2,`if`(i+1<13 and i+1>8,3, `if`(i+1<17 and i+1>12,4,1))))),(i mod 4)+1],`if`(m2[i+1,1]=0,colors[1],`if`(m2[i+1,1]=2,colors[2],`if`(m2[i+1,1]=4,colors[3],`if`(m2[i+1,1]=8,colors[4],`if`(m2[i+1,1]=16,colors[5],`if`(m2[i+1,1]=32,colors[6],`if`(m2[i+1,1]=64,colors[7],`if`(m2[i+1,1]=128,colors[8],`if`(m2[i+1,1]=256,colors[9],`if`(m2[i+1,1]=512,colors[10],`if`(m2[i+1,1]=1024,colors[11],`if`(m2[i+1,1]=2048,colors[12],`if`(m2[i+1,1]>2048,colors[13],"White")))))))))))))],i=0..15)); SP(seq([cat("TextArea",i),fillcolor,`if`(m2[i+1,1]=0,colors[1],`if`(m2[i+1,1]=2,colors[2],`if`(m2[i+1,1]=4,colors[3],`if`(m2[i+1,1]=8,colors[4],`if`(m2[i+1,1]=16,colors[5],`if`(m2[i+1,1]=32,colors[6],`if`(m2[i+1,1]=64,colors[7],`if`(m2[i+1,1]=128,colors[8],`if`(m2[i+1,1]=256,colors[9],`if`(m2[i+1,1]=512,colors[10],`if`(m2[i+1,1]=1024,colors[11],`if`(m2[i+1,1]=2048,colors[12],`if`(m2[i+1,1]>2048,colors[13],"White")))))))))))))],i=0..15),refresh); SP(seq([cat("TextArea",i),fontcolor,`if`(m2[i+1,1]=0,colors[1],`if`(m2[i+1,1]=2,colors[13],`if`(m2[i+1,1]=4,colors[13],"White")))],i=0..15),refresh); end proc:

reset:=proc(); highscoreM := Import("this:///Score.csv", output = Matrix); hscore := highscoreM[1,1]; hname := highscoreM[1,2]; highscore:=sprintf("%s",cat(hscore,"\n",hname)); buttonpress:="False"; a:=Matrix(4, 4, [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]): score:=0; matrixtotextarea(a); SP("Score/Lose",visible,true); SP("Score/Lose",enabled,true); SP("Score/Lose",caption,"Click an Arrow to begin."); SP("Score",value,score); SP("Highscore",value,highscore); SP(seq([j, enabled, false], j in ["Name","Enter"])); SP(seq([j, visible, false], j in ["Name","Enter"])); SP(seq([j, enabled, true], j in ["Score","Highscore", seq(cat("Button",k),k=0..4)])); SP(seq([j, visible, true], j in ["Score","Highscore", seq(cat("Button",k),k=0..4)])); end proc;

checklose:=proc(); for q from 2 to 4 do for w from 4 to 1 by -1 do if a[q,w]=a[q-1,w] then loss:="False"; return loss; end if; end do; end do; return loss; end proc;

f:=proc(keypress); SP("Score/Lose",visible,false); SP("Score/Lose",enabled,false); j := rand(1 .. 4); k := rand(1 .. 4); z := rand(1 .. 10); e := 0; move:=proc(); for q from 4 to 2 by -1 do for w from 4 to 1 by -1 do if a[q,w]=a[q-1,w] then a[q-1,w]:=a[q-1,w]+a[q,w]; score:=score+a[q-1,w]; a[q,w]:=0; if q-1>1 and a[q-2,w]=0 then a[q-2,w]:=a[q-1,w]; a[q-1,w]:=0; if q-2>1 and a[q-3,w]=0 then a[q-3,w]:=a[q-2,w]; a[q-2,w]:=0; end if; end if; elif q-1>1 and a[q,w]=a[q-2,w] and a[q-1,w]=0 then a[q-2,w]:=a[q-2,w]+a[q,w]; score:=score+a[q-2,w]; a[q,w]:=0; if q-2>1 and a[q-3,w]=0 then a[q-3,w]:=a[q-2,w]; a[q-2,w]:=0; end if; elif q-2>1 and a[q,w]=a[q-3,w] and a[q-1,w]=0 and a[q-2,w]=0 then a[q-3,w]:=a[q-3,w]+a[q,w]; score:=score+a[q-3,w]; a[q,w]:=0; elif a[q-1,w]=0 then a[q-1,w]:=a[q-1,w]+a[q,w]; a[q,w]:=0; if q-1>1 and a[q-2,w]=0 then a[q-2,w]:=a[q-1,w]; a[q-1,w]:=0; if q-2>1 and a[q-3,w]=0 then a[q-3,w]:=a[q-2,w]; a[q-2,w]:=0; end if; end if; elif q-1>1 and a[q-2,w]=0 and a[q-1,w]=0 then a[q-2,w]:=a[q-2,w]+a[q,w]; a[q,w]:=0; if q-2>1 and a[q-3,w]=0 then a[q-3,w]:=a[q-2,w]; a[q-2,w]:=0; end if; elif q-2>1 and a[q-3,w]=0 and a[q-1,w]=0 and a[q-2,w]=0 then a[q-3,w]:=a[q-3,w]+a[q,w]; a[q,w]:=0; end if; end do; end do; end proc;

		r := j(); 
         c := k();  

if keypress="Up" then move();

elif keypress="Left" then a:=LinearAlgebra:-Transpose(a); move(); a:=LinearAlgebra:-Transpose(a);

elif keypress="Right" then a := ArrayTools:-FlipDimension(LinearAlgebra:-Transpose(a),1); move(); a := LinearAlgebra:-Transpose(ArrayTools:-FlipDimension(a,1));

elif keypress="Down" then a := ArrayTools:-FlipDimension(a, 1); move(); a := ArrayTools:-FlipDimension(a, 1); end if;

if a[r, c] = 0 then if z() > 3 then a[r, c] := 2; else; a[r, c] := 4; end if; else for q to 4 do for w to 4 do if a[q, w] <> 0 then; e:=e+1; end if; end do; end do; if e = 16 then loss:="True"; checklose(); a:=LinearAlgebra:-Transpose(a); checklose(); a:=LinearAlgebra:-Transpose(a); a := ArrayTools:-FlipDimension(LinearAlgebra:-Transpose(a),1); checklose(); a := LinearAlgebra:-Transpose(ArrayTools:-FlipDimension(a,1)); a := ArrayTools:-FlipDimension(a, 1); checklose(); a := ArrayTools:-FlipDimension(a, 1); if loss="True" then SP("Score/Lose",visible,"True"); SP("Score/Lose",enabled,"True"); SP("Score/Lose",caption,"You Lose!"); if score>hscore then SP("Score/Lose",caption,"Highscore! Enter your name below!"); SP("Enter",caption,"Confirm"); SP(seq([j, enabled, true], j in ["Name","Enter","Score/Lose"])); SP(seq([j, visible, true], j in ["Name","Enter","Score/Lose"])); SP(seq([j, enabled, false], j in [seq(cat("Button",k),k=0..4)])); SP(seq([j, visible, false], j in [seq(cat("Button",k),k=0..4)])); if buttonpress="True" then M:=Matrix(1, 2, score, hname): ExportMatrix("this:///Score.csv",M); buttonpress:="False"; reset(); end if; else SP("Score/Lose",caption,"Sorry, please try again."); SP("Enter",caption,"Restart"); SP("Enter",visible,"True"); SP("Enter",enabled,"True"); SP(seq([j, enabled, false], j in [seq(cat("Button",k),k=0..4)])); SP(seq([j, visible, false], j in [seq(cat("Button",k),k=0..4)])); if buttonpress="True" then buttonpress:="False"; reset(); end if; end if; end if; else

                 e:=0;
                 while a[r, c] <> 0 do 
                     r := j(); 
                     c := k(); 
                 end do; 
                 if z() > 1 then 
                     a[r, c] := 2; 
                 else 
                     a[r, c] := 4; 
                 end if; 

end if; end if; matrixtotextarea(a);

         SP("Score",value,score,refresh);    
         return a;  

end proc; end module; G:-reset();SP("Score/Lose",caption,"Click an Arrow to begin."); </lang>

MATLAB

<lang MATLAB>function field = puzzle2048(field)

if nargin < 1 || isempty(field)

   field = zeros(4);
   field = addTile(field);

end

clc rng('shuffle')

while true

   oldField = field;
   clc
   score = displayField(field);
   
   % check losing condition
   if isGameLost(field)
       sprintf('You lose with a score of %g.',score)
       return
   end
   
   direction = input('Which direction? (w,a,s,d) (x for exit)\n','s');
   switch direction
       case 'w'
           field = moveUp(field);
       case 'a'
           field = rot90( moveUp( rot90(field,-1) ) );
       case 's'
           field = flipud( moveUp( flipud(field) ) );
       case 'd'
           field = rot90( moveUp( rot90(field) ), -1);
       case 'x'
           return
   end
   
   if any(field>=2048,'all')
       disp('You win!')
       return
   end
   
   if ~all(field==oldField,'all')
       field = addTile(field);
   end

end

end

function gameIsLost = isGameLost(field)

if all(field,'all') && ...

       all(conv2(field,[1, -1],'same'),'all') && ...
       all(conv2(field,[1; -1],'same'),'all')
   gameIsLost = true;

else

   gameIsLost = false;

end

end

function field = addTile(field)

freeIndices = find(~field); newIndex = freeIndices( randi(length(freeIndices)) ); newNumber = 2 + 2 * (rand < 0.1); field(newIndex) = newNumber;

end

function score = displayField(field)

% Unicode characters for box drawings % 9484: U+250C Box Drawings Light Down and Right % 9472: U+2500 Box Drawings Light Horizontal % 9474: U+2502 Box Drawings Light Vertical % 9488: U+2510 Box Drawings Light Down and Left % 9492: U+2515 Box Drawings Light Up and Right % 9496: U+2518 Box Drawings Light Up and Left % 9500: U+251C Box Drawings Light Vertical and Right % 9508: U+2524 Box Drawings Light Vertical and Left % 9516: U+252C Box Drawings Light Down and Horizontal % 9524: U+2534 Box Drawings Light Up and Horizontal % 9532: U+253C Box Drawings Light Vertical and Horizontal score = sum(field(:)); cellField = arrayfun(@num2str, field, 'UniformOutput', false); cellField = cellfun(@(x) [ char(9474) blanks(5-length(x)) x ' ' ], ...

   cellField, 'UniformOutput', false);

topRow = repmat('-',1,7*size(field,2)+1); topRow(1:7:end) = char(9516); topRow([1 end]) = [ char(9484) char(9488) ]; midRow = topRow; midRow(1:7:end) = char(9532); midRow([1 end]) = [ char(9500) char(9508) ]; botRow = topRow; botRow(1:7:end) = char(9524); botRow([1 end]) = [ char(9492) char(9496) ]; charField = topRow; for iRow = cellField'

   charField = [ charField; iRow{:} char(9474); midRow ];

end charField(end,:) = botRow; charField(charField == '0') = ' ';

disp(charField) fprintf('\nYour score: %g\n', score)

end

function field = moveUp(field)

for iCol = 1:size(field,2)

   col = field(:,iCol);
   col = removeZeros(col);
   for iRow = 1:length(col)-1
       if col(iRow)==col(iRow+1)
           col(iRow:iRow+1) = [ 2*col(iRow); 0 ];
       end
   end
   col = removeZeros(col);
   if length(col) < length(field)
       col(end+1:length(field)) = 0;
   end
   field(:,iCol) = col;

end

end

function vector = removeZeros(vector)

vector(vector==0) = [];

end</lang>

You can start with an empty 4 x 4 board and save the last state of the playing field with: <lang MATLAB>field = puzzle2048();</lang>

Or you start from a saved playing field: <lang MATLAB>field = puzzle2048(savedField);</lang>

Nim

Works with: Nim Compiler version 0.19.4

<lang nim>import random, strutils, terminal

const

 BoardLength = 4
 BoardSize = BoardLength * BoardLength
 Target = 2048

type

 Operation = enum
   opInvalid
   opUp
   opDown
   opLeft
   opRight
   opQuit
   opRestart
 Board = object
   len: Natural
   largestNumber: Natural
   score: Natural
   rows: array[BoardLength, array[BoardLength, Natural]]

func handleKey(c: char): Operation =

 case c
 of 'w', 'W': opUp
 of 'a', 'A': opLeft
 of 's', 'S': opDown
 of 'd', 'D': opRight
 of 'q', 'Q': opQuit
 of 'r', 'R': opRestart
 else: opInvalid

proc getKey(): Operation =

 var c = getch()
 if c == '\e':
   c = getch()
   if c == '[':
     case getch()
     of 'A': opUp
     of 'D': opLeft
     of 'B': opDown
     of 'C': opRight
     else: opInvalid
   else: handleKey c
 else: handleKey c

proc spawnRandom(b: var Board) =

 if b.len < BoardSize:
   var
     x = rand 0..<BoardLength
     y = rand 0..<BoardLength
   while b.rows[y][x] != 0:
     x = rand 0..<BoardLength
     y = rand 0..<BoardLength
   b.rows[y][x] = if rand(1.0) < 0.9: 2 else: 4
   inc b.len
   b.largestNumber = max(b.rows[y][x], b.largestNumber)

proc initBoard(): Board =

 spawnRandom result

func `$`(b: Board): string =

 result = "┌────┬────┬────┬────┐\n"
 for idx, val in b.rows:
   for v in val:
     result.add "│"
     result.add center(if v != 0: $v else: "", 4)
   result.add "│\n"
   if idx < high(b.rows):
     result.add "├────┼────┼────┼────┤\n"
   else:
     result.add "└────┴────┴────┴────┘"

func shift(b: var Board; o: Operation; merge = true): bool =

 const BoardRange = 0..<BoardLength
 var
   x = 0
   y = 0
   vecX: range[-1..1] = 0
   vecY: range[-1..1] = 0
 case o
 of opUp:
   vecY = 1
 of opDown:
   vecY = -1
   y = BoardLength - 1
 of opLeft: vecX = 1
 of opRight:
   vecX = -1
   x = BoardLength - 1
 else: return
 let
   startX = x
   startY = y
 while x in BoardRange and y in BoardRange:
   while b.len < BoardSize and x in BoardRange and y in BoardRange:
     let
       nextX = x + vecX
       nextY = y + vecY
       prevX = x - vecX
       prevY = y - vecY
     if b.rows[y][x] == 0:
       if nextX in BoardRange and nextY in BoardRange and
          b.rows[nextY][nextX] != 0:
         result = true
         swap b.rows[y][x], b.rows[nextY][nextX]
         if prevX in BoardRange and prevY in BoardRange:
           x = prevX
           y = prevY
           continue
     x = nextX
     y = nextY
   if merge:
     x = if vecX != 0: startX else: x
     y = if vecY != 0: startY else: y
     while x in BoardRange and y in BoardRange:
       let
         nextX = x + vecX
         nextY = y + vecY
       if b.rows[y][x] != 0:
         if nextX in BoardRange and nextY in BoardRange and
            b.rows[nextY][nextX] == b.rows[y][x]:
           result = true
           b.rows[y][x] *= 2
           b.largestNumber = max(b.rows[y][x], b.largestNumber)
           b.score += b.rows[y][x]
           b.rows[nextY][nextX] = 0
           dec b.len
       x = nextX
       y = nextY
   if vecX == 0:
     inc x
     y = startY
   elif vecY == 0:
     inc y
     x = startX
 if merge and result: discard b.shift(o, false)

func shiftable(b: Board): bool =

 for row in 0..<BoardLength:
   for col in 0..<BoardLength:
     result = result or b.rows[row][col] == 0
     if result: break
     if row < BoardLength - 1:
       result = result or b.rows[row][col] == b.rows[row + 1][col]
     if col < BoardLength - 1:
       result = result or b.rows[row][col] == b.rows[row][col + 1]

when isMainModule:

 randomize()
 var
   board = initBoard()
   highscore = 0
 block gameLoop:
   while true:
     let gameover = not board.shiftable or board.largestNumber >= Target
     echo board
     highscore = max(highscore, board.score)
     echo "Score = ", board.score, "  Highscore = ", highscore
     if not gameover:
       echo "Press arrow keys or WASD to move, R to Restart, Q to Quit"
     elif board.largestNumber >= Target:
       echo "You win! Press R to Restart, Q to Quit"
     else:
       echo "Game over! Press R to Restart, Q to Quit"
     while true:
       let op = getKey()
       case op
       of opRestart:
         board = initBoard()
         break
       of opQuit: break gameLoop
       of opInvalid: continue
       elif gameover: continue
       else:
         if board.shift op:
           board.spawnRandom
           break
     for i in 1..BoardLength + 7:
       eraseLine()
       cursorUp()</lang>

OCaml

<lang ocaml> let list_make x v =

 let rec aux acc i =
   if i <= 0 then acc else aux (v::acc) (i-1)
 in
 aux [] x


let pad_right n line =

 let len = List.length line in
 let x = n - len in
 line @ (list_make x 0)


let _move_left line =

 let n = List.length line in
 let line = List.filter ((<>) 0) line in
 let rec aux acc = function
 | x::y::tl ->
     if x = y
     then aux (x+y::acc) tl
     else aux (x::acc) (y::tl)
 | x::[] ->
     aux (x::acc) []
 | [] ->
     List.rev acc
 in
 pad_right n (aux [] line)


let move_left grid =

 List.map _move_left grid


let move_right grid =

 grid
   |> List.map List.rev
   |> List.map _move_left
   |> List.map List.rev


let rec replace g n v =

 match g with
 | x::xs -> if n = 0 then v::xs else x::(replace xs (n-1) v)
 | [] -> raise (Invalid_argument "replace")


(* add a new value in a random cell containing zero *) let rec new_value grid =

 let zeros = ref [] in
 List.iteri (fun y line ->
   List.iteri (fun x v ->
     if v = 0 then zeros := (x, y) :: !zeros
   ) line;
 ) grid;
 let n = List.length !zeros in
 if n = 0 then raise Exit;
 let x, y = List.nth !zeros (Random.int n) in
 let v = if Random.int 10 = 0 then 4 else 2 in
 let line = List.nth grid y in
 let new_line = replace line x v in
 replace grid y new_line


(* turn counterclockwise *) let turn_ccw grid =

 let y = List.length grid in
 let x = List.length (List.nth grid 0) in
 List.init x (fun i ->
   List.init y (fun j ->
     List.nth (List.nth grid j) (x-i-1)
   )
 )


(* turn clockwise *) let turn_cw grid =

 let y = List.length grid in
 let x = List.length (List.nth grid 0) in
 List.init x (fun i ->
   List.init y (fun j ->
     List.nth (List.nth grid (y-j-1)) (i)
   )
 )


let move_up grid =

 grid
   |> turn_ccw
   |> move_left
   |> turn_cw


let move_down grid =

 grid
   |> turn_cw
   |> move_left
   |> turn_ccw


let display grid =

 List.iter (fun line ->
   print_string " [";
   line
     |> List.map (Printf.sprintf "%4d")
     |> String.concat "; "
     |> print_string;
   print_endline "]"
 ) grid


let () =

 Random.self_init ();
 let width =
   try int_of_string Sys.argv.(1)
   with _ -> 4
 in
 let line = list_make width 0 in
 let grid = list_make width line in
 let grid = new_value grid in
 let grid = new_value grid in
 print_endline {|
   s -> left
   f -> right
   e -> up
   d -> down
   q -> quit
 |};
 let rec loop grid =
   display grid;
   let grid =
     match read_line () with
     | "s" -> move_left grid
     | "f" -> move_right grid
     | "e" -> move_up grid
     | "d" -> move_down grid
     | "q" -> exit 0
     | _ -> grid
   in
   let grid =
     try new_value grid
     with Exit ->
       print_endline "Game Over";
       exit 0
   in
   loop grid
 in
 loop grid</lang>
Output:
$ ocaml game2048.ml 4

    s -> left
    f -> right
    e -> up
    d -> down
    q -> quit
  
 [   0;    0;    0;    4]
 [   0;    0;    0;    0]
 [   0;    0;    0;    0]
 [   0;    2;    0;    0]
d
 [   0;    0;    0;    0]
 [   2;    0;    0;    0]
 [   0;    0;    0;    0]
 [   0;    2;    0;    4]
d
 [   0;    0;    0;    0]
 [   4;    0;    0;    0]
 [   0;    0;    0;    0]
 [   2;    2;    0;    4]
f
 [   0;    2;    0;    0]
 [   0;    0;    0;    4]
 [   0;    0;    0;    0]
 [   0;    0;    4;    4]
f
 [   0;    0;    0;    2]
 [   0;    2;    0;    4]
 [   0;    0;    0;    0]
 [   0;    0;    0;    8]

Pascal

<lang Pascal> program game2048; uses Crt; const

   SIZE_MAP = 4;
   SIZETOPBOT = SIZE_MAP*6+6; (* Calculate the length of the top and bottom to create a box around the game  *)
   NOTHING = 0;
   UP = 93;
   RIGHT = 92;
   LEFT = 91;
   DOWN = 90;

type

   type_vector = array [1..SIZE_MAP] of integer;
   type_game = record 

lin,col:integer; map: array [1..SIZE_MAP,1..SIZE_MAP] of integer; score:integer;

   end;
   type_coord = record

lin,col:integer;

   end;

var

   game:type_game;
   end_game,movement:boolean;

procedure Create_Number(var game:type_game); (* Create the number(2 or 4) in a random position on the map *) (* The number 4 had a 10% of chance to be created *) var

   number:integer;
   RanP:type_coord;

begin

   randomize;
   if random(9) = 1 then

number:=4

   else

number:=2;

   RanP.lin:=random(game.lin)+1;
   RanP.col:=random(game.col)+1;
   while game.map[RanP.lin,RanP.col] <> NOTHING do 

begin RanP.lin:=random(game.lin)+1; RanP.col:=random(game.col)+1; end;

   game.map[RanP.lin,Ranp.Col]:=number;

end;

procedure initializing_game(var game:type_game); var i,j:integer; begin

   game.lin:=SIZE_MAP;
   game.col:=SIZE_MAP;
   game.score:=0;
   for i:=1 to game.lin do

for j:=1 to game.col do game.map[i,j]:=NOTHING;

   Create_Number(game);
   Create_Number(game);

end;

function CountDigit(number:integer):integer; begin

   if number <> 0 then

begin CountDigit:=0; while number <> 0 do begin CountDigit:=CountDigit+1; number:=number div 10; end; end

   else

CountDigit:=1; end;

procedure print_number(number:integer); (* Print the number aligned with other numbers in the matrix *) var k,hwdigit:integer; begin

   hwdigit:=CountDigit(number);
   write(' ');
   write(number);
   (* The 4 is used to aling the numbers because the greatest number *)
   (* possible is 2048, which have 4 digits *)
   for k:=1 to 4-hwdigit do

write(' ');

   write(' ');

end;

procedure print_line(lengthL:integer; ch:string); var i:integer; begin

   write('+');
   for i:=1 to (lengthL-2) do

begin if i mod 7 = 0 then write('+') else write(ch);

   end;
   writeln;

end;

procedure print_map(var game:type_game); var i,j:integer; begin

   print_line(SIZETOPBOT,'-');
   for i:=1 to game.lin do

begin write('|'); for j:=1 to game.col do if game.map[i,j] >= 0 then begin print_number(game.map[i,j]); write('|'); end; writeln; print_line(SIZETOPBOT,'-'); end; end;

function CanMove(var v_lin:type_vector; i:integer):integer; (* Returns 1 if the next position is free *) (* Returns 2 if the next position has an equal number *) (* Returns 0 if the next position is not free *) begin

   if v_lin[i-1] = NOTHING then
       CanMove:=1
   else if v_lin[i] = v_lin[i-1] then
       CanMove:=2
   else
       CanMove:=0;

end;

function MoveAndSum(var game:type_game; var v_lin:type_vector; size:integer):boolean; (* Move and Sum the elements of the vector *) (* The direction of the move is to the left *) (* Returns TRUE if a number was moved *) (* Returns FALSE if a move was not moved *) var

   i,e,ResultM:integer;
   v:type_vector;

begin

   MoveAndSum:=FALSE;
   (* Initializing the vector *)
   (* This Vector is to know what number sum and what not sum *)
   for i:=1 to size do
       v[i]:=0;
   for i:=2 to size do
       begin

if v_lin[i] <> 0 then begin (* Move the number in v_lin[i] to the left as much as possible *) e:=i; ResultM:=CanMove(v_lin,e); while (ResultM <> 0)and(e>1) do begin case ResultM of 1:begin v_lin[e-1]:=v_lin[e]; v_lin[e]:=NOTHING; end; 2:begin if v[e] = 0 then begin v_lin[e-1]:=v_lin[e-1]*2; game.score:=game.score+v_lin[e-1]; v_lin[e]:=NOTHING; (* This number will not be sum again *) v[e-1]:=1; end; end; end; e:=e-1; ResultM:=CanMove(v_lin,e); end; if e <> i then MoveAndSum:=TRUE; v[e-1]:=1; end;

       end;

end;


function move_left(var game:type_game):boolean; var

   i,j:integer;
   v:type_vector;

begin

   move_left:=FALSE;
   for i:=1 to game.lin do

begin for j:=1 to game.col do v[j]:=game.map[i,j];

if MoveAndSum(game,v,game.lin) then move_left:=TRUE;

for j:=1 to game.col do game.map[i,j]:=v[j]; end; end;


function move_right(var game:type_game):boolean; var

   i,j,k:integer;
   v:type_vector;

begin

   move_right:=FALSE;
   for i:=1 to game.lin do

begin (* The side which will be move had to be at the beginning of the vector *) (* For example, I want to move this line to the right: 0 2 0 3 6 *) (* The procedure "MoveAndSum" has to receive this vector: 6 3 0 2 0 *) k:=1; for j:=game.col downto 1 do begin v[k]:=game.map[i,j]; k:=k+1; end; if MoveAndSum(game,v,game.lin) then move_right:=TRUE; (* Copy to the right place in the matrix *) k:=1; for j:=game.col downto 1 do begin game.map[i,k]:=v[j]; k:=k+1; end; end; end;


function move_down(var game:type_game):boolean; var

   i,j,k:integer;
   v:type_vector;

begin

   move_down:=FALSE;
   for j:=1 to game.col do

begin k:=1; for i:=game.lin downto 1 do begin v[k]:=game.map[i,j]; k:=k+1; end; if MoveAndSum(game,v,game.lin) then move_down:=TRUE; k:=1; for i:=game.lin downto 1 do begin game.map[k,j]:=v[i]; k:=k+1; end; end; end;

function move_up(var game:type_game):boolean; var

   i,j:integer;
   v:type_vector;

begin

   move_up:=FALSE;
   for j:=1 to game.col do

begin for i:=1 to game.lin do v[i]:=game.map[i,j]; if MoveAndSum(game,v,game.lin) then move_up:=TRUE; for i:=1 to game.lin do game.map[i,j]:=v[i]; end; end;

function CheckWinLose(var game:type_game):integer; (* Returns 2 if the player win the game *) (* Returns 1 if the player lose the game *) (* Returns 0 if has a valid move*) var i,j:integer; begin

   with game do

begin CheckWinLose:=1; i:=1; while (i<=game.lin)and(CheckWinLose<>2) do begin j:=1; while (j<=game.col)and(CheckWinLose<>2) do begin if map[i,j] = 2048 then CheckWinLose:=2 else if map[i,j] = NOTHING then CheckWinLose:=0 else if (map[i,j] = map[i,j+1])and(j<>col) then CheckWinLose:=0 else if (map[i,j] = map[i,j-1])and(j<>1) then CheckWinLose:=0 else if (map[i,j] = map[i+1,j])and(i<>lin) then CheckWinLose:=0 else if (map[i,j] = map[i-1,j])and(i<>1) then CheckWinLose:=0; j:=j+1; end; i:=i+1; end;

   end;

end;

begin

   movement:=false;
   end_game:=false;
   initializing_game(game);
   repeat 

ClrScr; if movement then Create_Number(game); movement:=false;

writeln('SCORE: ',game.score); print_map(game); writeln(' Use the arrow keys to move '); writeln(' Press ESC to quit the game ');

case CheckWinLose(game) of 1:begin print_line(SIZETOPBOT,'-'); writeln('| Game Over! |'); print_line(SIZETOPBOT,'-'); end_game:=TRUE; end; 2:begin print_line(SIZETOPBOT,'-'); writeln('| You Win! |'); print_line(SIZETOPBOT,'-'); end_game:=TRUE; end; end;

repeat until KeyPressed; case ReadKey of #0:begin case ReadKey of #72:movement:=move_up(game); #77:movement:=move_right(game); #75:movement:=move_left(game); #80:movement:=move_down(game); end; end; #27:end_game:=true; end;

   until end_game;

end. </lang>


Perl

<lang perl>#!/usr/bin/perl

use strict; # https://rosettacode.org/wiki/2048 use warnings; use Tk;

my $N = shift // 4; $N < 2 and $N = 2; my @squares = 1 .. $N*$N; my %n2ch = (' ' => ' '); @n2ch{ map 2**$_, 1..26} = 'a'..'z'; my %ch2n = reverse %n2ch; my $winner = ; my @arow = 0 .. $N - 1; my @acol = map $_ * $N, @arow;

my $mw = MainWindow->new; $mw->geometry( '+300+0' ); $mw->title( 2048 ); $mw->focus; $mw->bind('<KeyPress-Left>' => sub { arrow($N, @arow) } ); $mw->bind('<KeyPress-Right>' => sub { arrow($N, reverse @arow) } ); $mw->bind('<KeyPress-Up>' => sub { arrow(1, @acol) } ); $mw->bind('<KeyPress-Down>' => sub { arrow(1, reverse @acol) } ); my $grid = $mw->Frame()->pack; for my $i ( 0 .. $#squares )

 {
 $grid->Label(-textvariable => \$squares[$i],
   -width => 5, -height => 2, -font => 'courierbold 30',
   -relief => 'ridge', -borderwidth => 5,
   )->grid(-row => int $i / $N, -column => $i % $N );
 }

my $buttons = $mw->Frame()->pack(-fill => 'x', -expand => 1); $buttons->Button(-text => 'Exit', -command => sub {$mw->destroy},

 -font => 'courierbold 14',
 )->pack(-side => 'right');

$buttons->Button(-text => 'New Game', -command => \&newgame,

 -font => 'courierbold 14',
 )->pack(-side => 'left');

$buttons->Label(-textvariable => \$winner,

 -font => 'courierbold 18', -fg => 'red2',
 )->pack;

newgame(); MainLoop; -M $0 < 0 and exec $0;

sub losecheck

 {
 local $_ = join , @n2ch{ @squares };
 / / || ($_ ^ substr $_, $N) =~ tr/\0// and return;
 /(.)\1/ and return for /.{$N}/g;
 $winner = 'You Lost';
 }

sub arrow

 {
 $winner and return;                                   # you won, no more play
 my ($inc, @ix) = @_;
 my $oldboard = "@squares";
 for ( 1 .. $N )
   {
   local $_ = join , @n2ch{ @squares[@ix] };         # extract 4 squares
   tr/ //d;                                            # force left
   s/(\w)\1/ chr 1 + ord $1 /ge;                       # group by twos
   @squares[@ix] = @ch2n{ split //, $_ . ' ' x $N };   # replace
   $_ += $inc for @ix;                                 # next row or col
   }
 $oldboard eq "@squares" and return;
 add2();
 losecheck();
 grep $_ eq 2048, @squares and $winner = 'WINNER !!';
 }

sub add2

 {
 my @blanks = grep $squares[$_] eq ' ', 0 .. $#squares;
 @blanks and $squares[ $blanks[rand @blanks] ] =
   $_[0] // (rand() < 0.1 ? 4 : 2);
 }

sub newgame

 {
 $_ = ' ' for @squares;
 add2(2) for 1, 2;
 $winner = ;
 }</lang>

Phix

Library: Phix/pGUI

Faithful desktop gui reproduction of the above link (https://gabrielecirulli.github.io/2048/) Now I just got figure out how to win... <lang Phix>-- -- demo\rosetta\2048.exw -- include pGUI.e

Ihandle canvas, dialog cdCanvas cddbuffer, cdcanvas

constant tile_colours = {#CCC0B4, -- blank

                        #EEE4DA,   -- 2
                        #EDE0C8,   -- 4
                        #F2B179,   -- 8
                        #F59563,   -- 16
                        #F67C5F,   -- 32
                        #F65E3B,   -- 64
                        #EDCF72,   -- 128
                        #EDCC61,   -- 256
                        #EDC850,   -- 512
                        #EDC53F,   -- 1024
                        #EDC22E}   -- 2048

-- the 4x4 board. -- note that values are [1..12] for [blank,2,4,8,..2048]. -- (merging two eights is not 8+8->16 but 4+1->5, internally) sequence board

integer newgame = 1

procedure add_rand(integer count) -- (nb infinite loop if board is full) integer x, y

   while count do
       x = rand(4)
       y = rand(4)
       if board[y][x]=1 then   -- blank
           board[y][x] = 2+(rand(10)=10)
           count -= 1
       end if
   end while   

end procedure

integer valid = 0 integer prev, nxt, bxy

procedure move_x(integer x, integer y, integer d)

   bxy = board[x][y]
   if bxy!=1 then
       if bxy=prev then
           board[x][y] = 1
           bxy += 1
           board[x][nxt] = bxy
           nxt += d
           prev = 13
           valid = 1 
       else
           if prev=1
           or y!=nxt then
               if prev!=1
               and prev!=13 then
                   nxt += d
               end if
               if y!=nxt then
                   board[x][y] = 1
                   board[x][nxt] = bxy
                   valid = 1
               end if
           end if
           prev = bxy
       end if
   end if

end procedure

procedure move_y(integer x, integer y, integer d)

   bxy = board[x][y]
   if bxy!=1 then
       if bxy=prev then
           board[x][y] = 1
           bxy += 1
           board[nxt][y] = bxy
           nxt += d
           prev = 13
           valid = 1 
       else
           if prev=1
           or x!=nxt then
               if prev!=1
               and prev!=13 then
                   nxt += d
               end if
               if x!=nxt then
                   board[x][y] = 1
                   board[nxt][y] = bxy
                   valid = 1
               end if
           end if
           prev = bxy
       end if
   end if

end procedure

function move(integer key) -- a non-zero result means it changed something.

   valid = 0
   if key=K_LEFT then
       for x=1 to 4 do
           prev = 13
           nxt = 1
           for y=1 to 4 do
               move_x(x,y,+1)
           end for
       end for
   elsif key=K_UP then
       for y=1 to 4 do
           prev = 13
           nxt = 4
           for x=4 to 1 by -1 do
               move_y(x,y,-1)
           end for
       end for
   elsif key=K_RIGHT then
       for x=1 to 4 do
           prev = 13
           nxt = 4
           for y=4 to 1 by -1 do
               move_x(x,y,-1)
           end for
       end for
   elsif key=K_DOWN then
       for y=1 to 4 do
           prev = 13
           nxt = 1
           for x=1 to 4 do
               move_y(x,y,+1)
           end for
       end for
   end if
   return valid

end function

function game_won()

   for i=1 to length(board) do
       if find(12,board[i]) then return 1 end if
   end for
   return 0

end function

constant valid_keys = {K_LEFT,K_DOWN,K_RIGHT,K_UP}

function no_valid_moves() sequence saved_board = board

   for i=1 to length(valid_keys) do
       if move(valid_keys[i]) then
           board = saved_board
           return 0    -- OK
       end if
   end for
   return 1 -- game over...

end function

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, integer /*posy*/) integer tx, ty, bxy,

       ox,oy,              -- top right coords
       os,ts,              -- overall and tile size
       ts2                 -- half tile, for number positioning

integer {dw,dh} = IupGetIntInt(canvas, "DRAWSIZE")

   if dw>=dh then
       ox = floor((dw-dh)/2)
       oy = 0
       os = dh
   else
       ox = 0
       oy = floor((dh-dw)/2)
       os = dw
   end if
   ts = floor((os-10)/4-7)
   ts2 = floor(ts/2+5)-10
   if newgame then
       board = repeat(repeat(1,4),4)
       add_rand(2)
       newgame = 0
   end if
   cdCanvasActivate(cddbuffer)
   cdCanvasSetBackground(cddbuffer, #FAF8EF)
   cdCanvasClear(cddbuffer)
   cdCanvasSetForeground(cddbuffer, #BBADA0)
   cdCanvasRoundedBox(cddbuffer, ox+5, ox+os-5, oy+5, oy+os-5, 10, 10)
   tx = ox+15
   for y=1 to 4 do
       ty = oy+15
       for x=1 to 4 do
           bxy = board[x][y]
           cdCanvasSetForeground(cddbuffer, tile_colours[bxy])
           cdCanvasRoundedBox(cddbuffer, tx, tx+ts-10, ty, ty+ts-10, 5, 5)
           if bxy>1 then
               cdCanvasSetForeground(cddbuffer, iff(bxy<=3?#776E65:#F9F6F2))
               cdCanvasFont(cddbuffer, "Calibri", CD_BOLD, iff(bxy>10?32:40))
               cdCanvasText(cddbuffer, tx+ts2, ty+ts2-25-iff(bxy<11?7:0), sprint(power(2,bxy-1))) 
           end if
           ty += ts+5
       end for
       tx += ts+5
   end for
   cdCanvasFlush(cddbuffer)
   return IUP_DEFAULT

end function

function map_cb(Ihandle ih)

   cdcanvas = cdCreateCanvas(CD_IUP, ih)
   cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
   {} = cdCanvasTextAlignment(cddbuffer, CD_SOUTH) 
   return IUP_DEFAULT

end function

function key_cb(Ihandle /*ih*/, atom c)

   if c=K_ESC then return IUP_CLOSE end if
   if find(c,valid_keys) then
       if move(c) then
           IupUpdate(canvas)
           string mbmsg = ""
           if game_won() then
               mbmsg = "!!!YOU WON!!!\n\nAnother Go?"
           else
               add_rand(1)

-- repaintWindow(main)

               IupUpdate(canvas)
               if no_valid_moves() then
                   mbmsg = "You Lost.\n\nAnother Go?"
               end if
           end if
           if length(mbmsg) then
               if IupAlarm("Game Over",mbmsg,"Yes","No")=1 then
                   newgame=1
               else
                   return IUP_CLOSE
               end if
           end if
       end if
       IupUpdate(canvas)
   end if
   return IUP_CONTINUE

end function

procedure main()

   IupOpen()
   canvas = IupCanvas("RASTERSIZE=520x540")
   IupSetCallback(canvas, "MAP_CB", Icallback("map_cb"))
   IupSetCallback(canvas, "ACTION", Icallback("redraw_cb"))
   dialog = IupDialog(canvas,"MINSIZE=440x450")
   IupSetAttribute(dialog,"TITLE","2048");
   IupSetCallback(dialog, "K_ANY", Icallback("key_cb"));
   IupShow(dialog)
   IupSetAttribute(canvas, "RASTERSIZE", NULL)
   IupMainLoop()
   IupClose()

end procedure main()</lang>

PHP

Works from PHP5 and upwards in CLI mode. <lang PHP> <?php

$game = new Game();

while(true) {

   $game->cycle();

}

class Game { private $field; private $fieldSize; private $command; private $error; private $lastIndexX, $lastIndexY; private $score; private $finishScore;

function __construct() { $this->field = array(); $this->fieldSize = 4; $this->finishScore = 2048; $this->score = 0; $this->addNumber(); $this->render(); }

public function cycle() { $this->command = strtolower($this->readchar('Use WASD, q exits')); $this->cls();

if($this->processCommand()) { $this->addNumber(); } else { if(count($this->getFreeList()) == 0 ) { $this->error = 'No options left!, You Lose!!'; } else { $this->error = 'Invalid move, try again!'; } } $this->render(); }

private function readchar($prompt) { readline_callback_handler_install($prompt, function() {}); $char = stream_get_contents(STDIN, 1); readline_callback_handler_remove(); return $char; }

/** * Insert a number in an empty spot on the field */ private function addNumber() { $freeList = $this->getFreeList(); if(count($freeList) == 0) { return; } $index = mt_rand(0, count($freeList)-1); $nr = (mt_rand(0,9) == 0)? 4 : 2; $this->field[$freeList[$index]['x']][$freeList[$index]['y']] = $nr; return; }

/** * @return array(array('x' => <x>, 'y' => <y>)) with empty positions in the field */ private function getFreeList() { $freeList = array(); for($y =0; $y< $this->fieldSize;$y++) { for($x=0; $x < $this->fieldSize; $x++) { if(!isset($this->field[$x][$y])) { $freeList[] = array('x' => $x, 'y' => $y); } elseif($this->field[$x][$y] == $this->finishScore) { $this->error = 'You Win!!'; } } } return $freeList; }

/** * Process a command: * @return is the command valid (Did it cause a change in the field) */ private function processCommand() { if(!in_array($this->command, array('w','a','s','d','q'))) { $this->error = 'Invalid Command'; return false; } if($this->command == 'q') { echo PHP_EOL. 'Bye!'. PHP_EOL; exit; }

// Determine over which axis and in which direction we move: $axis = 'x'; $sDir = 1;

switch($this->command) { case 'w': $axis = 'y'; $sDir = -1; break; case 'a': $sDir = -1; break; case 's': $axis = 'y'; break; case 'd': break; }

$done = 0; // shift all numbers in that direction $done += $this->shift($axis, $sDir); // merge equal numbers in opposite direction $done += $this->merge($axis, $sDir * -1); // shift merged numbers in that direction $done += $this->shift($axis, $sDir); return $done >0; }

private function shift($axis, $dir) { $totalDone = 0; for($i = 0; $i <$this->fieldSize; $i++) { $done = 0; foreach($this->iterate($axis,$dir) as $xy) { if($xy['vDest'] === NULL && $xy['vSrc'] !== NULL) { $this->field[$xy['dX']][$xy['dY']] = $xy['vSrc']; $this->field[$xy['sX']][$xy['sY']] = NULL; $done++; } } $totalDone += $done; if($done == 0) { // nothing to shift anymore break; } } return $totalDone; }

private function merge($axis, $dir) { $done = 0; foreach($this->iterate($axis,$dir) as $xy) { if($xy['vDest'] !== NULL && $xy['vDest'] === $xy['vSrc']) { $this->field[$xy['sX']][$xy['sY']] += $xy['vDest']; $this->field[$xy['dX']][$xy['dY']] = NULL; $this->score += $this->field[$xy['sX']][$xy['sY']]; $done ++; } } return $done; }

/** * @return array List of src, dest pairs and their values to iterate over. */ private function iterate($axis, $dir) { $res = array(); for($y = 0; $y < $this->fieldSize; $y++) { for($x=0; $x < $this->fieldSize; $x++) { $item = array('sX'=> $x,'sY' => $y, 'dX' => $x, 'dY' => $y, 'vDest' => NULL,'vSrc' => NULL);

if($axis == 'x') { $item['dX'] += $dir; } else { $item['dY'] += $dir; }

if($item['dX'] >= $this->fieldSize || $item['dY'] >=$this->fieldSize || $item['dX'] < 0 || $item['dY'] < 0) { continue; }

$item['vDest'] = (isset($this->field[$item['dX']][$item['dY']]))? $this->field[$item['dX']][$item['dY']] : NULL; $item['vSrc'] = (isset($this->field[$item['sX']][$item['sY']]))? $this->field[$item['sX']][$item['sY']] : NULL; $res[] = $item; } } if($dir < 0) { $res = array_reverse($res); } return $res; }

/// RENDER ///

/** * Clear terminal screen */ private function cls() { echo chr(27).chr(91).'H'.chr(27).chr(91).'J'; }

private function render() { echo $this->finishScore . '! Current score: '. $this->score .PHP_EOL;

if(!empty($this->error)) { echo $this->error . PHP_EOL; $this->error = NULL; } $this->renderField(); }

private function renderField() { $width = 5; $this->renderVSeperator($width); for($y =0; $y < $this->fieldSize; $y ++) { for($x = 0;$x < $this->fieldSize; $x++) { echo '|'; if(!isset($this->field[$x][$y])) { echo str_repeat(' ', $width); continue; } printf('%'.$width.'s', $this->field[$x][$y]); } echo '|'. PHP_EOL; $this->renderVSeperator($width); } }

private function renderVSeperator($width) { echo str_repeat('+'. str_repeat('-', $width), $this->fieldSize) .'+' .PHP_EOL; }

} </lang>

PicoLisp

<lang PicoLisp>(load "@lib/simul.l")

(seed (in "/dev/urandom" (rd 8)))

(setq *G (grid 4 4) *D NIL)

(de cell ()

  (use This
     (while
        (get
           (setq This
              (intern
                 (pack
                    (char (+ 96 (rand 1 4)))
                    (rand 1 4) ) ) )
           'N ) )
     (=: N (if (> 90 (rand 1 100)) 2 4) ) )
  (setq *D (fish '((This) (: N)) *G)) )

(de redraw (G S D)

  # zeroize *G
  (mapc
     '((I)
        (mapc '((This) (=: N NIL)) I) )
     *G )
  # draw again
  (mapc
     '((X This)
        (while (and This X)
           (=: N (pop 'X))
           (setq This (D This)) ) )
     G
     S ) )

(de summ (Lst)

  (mapcar
     '((L)
        (make
           (while L
              (ifn (= (car L) (cadr L))
                 (link (car L))
                 (link (+ (car L) (cadr L)))
                 (pop 'L) )
              (pop 'L) ) ) )
     Lst ) )

(de vertical ()

  (mapcar
     '((X) (extract '((This) (: N)) X))
     *G ) )

(de horizontal ()

  (mapcar
     '((This)
        (make
           (while This
              (when (: N) (link @))
              (setq This (east This)) ) ) )
     (car *G) ) )

(de finish? ()

  (nor
     (fish
        '((This)
           (when (atom This) (= NIL (: N))) )
        *G )
     (find
        '((L)
           (find
              '((This)
                 (when (: N)
                    (find
                       '((D)
                          (= (: N) (get (D This) 'N)) )
                       (quote north south west east) ) ) )
              L ) )
        *G ) ) )

(de board (D)

  (space 3)
  (prin '+)
  (for I G
     (prin (if (D (car I)) "   +" "---+")) )
  (prinl) )

(de display ()

  (let G (mapcar reverse *G)
     (board north)
     (while (caar G)
        (space 3)
        (prin '|)
        (for I G
           (with (car I)
              (prin
                 (if (: N) (align 3 (: N)) "   ")
                 (if (east This) " " '|) ) ) )
        (prinl)
        (board south)
        (map pop G) )
     (do 2
        (prinl) ) ) )

(do 2

  (cell) )

(display) (loop

  (case
     (pack
        (make
           (link (key))
           (while (key 100)
              (link @) ) ) )
     ("^[[D" #left
        (redraw (summ (horizontal)) '(a1 a2 a3 a4) east) )
     ("^[[C" #rigth
        (redraw
           (summ (mapcar reverse (horizontal)))
           '(d1 d2 d3 d4)
           west) )
     ("^[[B" #down
        (redraw (summ (vertical)) '(a1 b1 c1 d1) north) )
     ("^[[A" #up
        (redraw
           (summ (mapcar reverse (vertical)))
           '(a4 b4 c4 d4)
           south) ) )
  (when (diff *D (fish '((This) (: N)) *G))
     (cell) )
  (display)
  (T (finish?) (println 'Finish))
  (T (fish '((This) (= 512 (: N))) *G)
     (println 'Maximum) ) )

(bye)</lang>

Pony

Works with: ponyc version 0.10.0

<lang pony> use "term" use "random" use "time"

interface EdgeRow

 fun val row() : Iterator[U32] ref
 fun val inc() : I32

primitive TopRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [0,1,2,3]
   r.values()
 fun inc() : I32 => 4

primitive LeftRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [0,4,8,12]
   r.values()
 fun inc() : I32 => 1

primitive RightRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [3,7,11,15]
   r.values()
 fun inc() : I32 => -1

primitive BottomRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [12,13,14,15]
   r.values()
 fun inc() : I32 =>  -4

primitive LEFT primitive RIGHT primitive UP primitive DOWN type Move is (LEFT|RIGHT|UP|DOWN)

class KeyboardHandler is ANSINotify

  let _game : Game tag
  new iso create(game : Game tag) => _game = game
  fun ref apply(term: ANSITerm ref, input: U8 val) =>
    if input == 113 then
      _game.quit()
      term.dispose()
    end
  fun ref left(ctrl: Bool, alt: Bool, shift: Bool)  => _game.move(LEFT)
  fun ref down(ctrl: Bool, alt: Bool, shift: Bool)  => _game.move(DOWN)
  fun ref up(ctrl: Bool, alt: Bool, shift: Bool)    => _game.move(UP)
  fun ref right(ctrl: Bool, alt: Bool, shift: Bool) => _game.move(RIGHT)

type ROW is (U32,U32,U32,U32)

primitive Merger

 fun tag apply(r : ROW) : ROW =>
   match r
   | (0,0,0,_)            => (r._4,0,0,0)
   | (0,0,_,r._3)         => (r._3<<1,0,0,0)
   | (0,0,_,_)            => (r._3,r._4,0,0)
   | (0,_,r._2,_)         => (r._2<<1,r._4,0,0)
   | (0,_,0,r._2)         => (r._2<<1,0,0,0)
   | (0,_,0,_)            => (r._2,r._4,0,0)
   | (0,_,_,r._3)         => (r._2,r._3<<1,0,0)
   | (0,_,_,_)            => (r._2,r._3,r._4,0)
   | (_, r._1, _, r._3)   => (r._1<<1, r._3<<1, 0, 0)
   | (_, r._1, 0, _)      => (r._1<<1, r._4, 0, 0)
   | (_, r._1, _, _)      => (r._1<<1, r._3, r._4, 0)
   | (_, 0,r._1, _)       => (r._1<<1,r._4,0,0)
   | (_, 0,0, r._1)       => (r._1<<1,0,0,0)
   | (_, 0,0, _)          => (r._1,r._4,0,0)
   | (_, 0,_, r._3)       => (r._1, r._3<<1,0,0)
   | (_, 0,_, _)          => (r._1, r._3,r._4,0)
   | (_,_,r._2,_)         => (r._1, r._2<<1,r._4,0)
   | (_,_,0,r._2)         => (r._1, r._2<<1,0,0)
   | (_,_,0,_)            => (r._1, r._2,r._4,0)
   | (_,_,_,r._3)         => (r._1, r._2,r._3<<1,0)
   else
      r
   end

/**

  • Game actor
  • /

actor Game

 embed _grid : Array[U32] = Array[U32].init(0, 16)
 let _rand : Random = MT(Time.millis())
 let _env : Env
 let _board : String ref = recover String(1024) end
 new create(env: Env)=>
   _env = env
   _add_block()
   _add_block()
   _draw()
 fun _merge(start : U32, inc : I32) : (ROW | None) =>
   var st = start.i32()
   let rval : ROW = (_get(st),             _get(st + inc),
                     _get(st + (inc * 2)), _get(st + (inc * 3)))
   let rout = Merger(rval)
   if rout is rval then None else rout end
 fun ref _update(start : U32, inc : I32) : Bool =>
   match _merge(start, inc)
   | let rout : ROW =>
       var st = start.i32()
       _set(st,             rout._1)
       _set(st +  inc,      rout._2)
       _set(st + (inc * 2), rout._3)
       _set(st + (inc * 3), rout._4)
       true
   else
     false
   end
 fun ref _shift_to(edge : EdgeRow val) : Bool =>
   var updated = false
   for r in edge.row() do
     if _update(r, edge.inc()) then
       updated = true
     end
   end
   updated
 fun _fmt(i : U32) : String =>
   match i
   | 0 => " __ "
   | 2 => "\x1B[31m  2 \x1B[0m"
   | 4 => "\x1B[32m  4 \x1B[0m"
   | 8 => "\x1B[33m  8 \x1B[0m"
   | 16 => "\x1B[34m 16 \x1B[0m"
   | 32 => "\x1B[35m 32 \x1B[0m"
   | 64 => "\x1B[36m 64 \x1B[0m"
   | 128 => "\x1B[37m128 \x1B[0m"
   | 256 => "\x1B[41m\x1B[37m256 \x1B[0m"
   | 512 => "\x1B[42m\x1B[37m512 \x1B[0m"
   | 1024 => "\x1B[43m\x1B[37m1024\x1B[0m"
   | 2048 => "\x1B[47m\x1B[35m\x1B[1m\x1B[5m2048\x1B[0m"
   else
     i.string()
   end
 fun ref _draw() =>
   let s : String ref = _board
   s.truncate(0)
   var i : U32 = 0
   repeat
     if (i % 4) == 0 then
         s.append("---------------------\n")
     end
     s.append(_fmt(_get(i)))
     s.append(" ")
     i = i + 1
     if (i % 4) == 0 then
         s.append("\n")
     end
   until i==16 end
   _env.out.print(s.string())
   _env.out.print("Arrow keys to move. Press (q)uit key to quit.")
  fun ref _set(i:(I32|U32), v : U32) =>
    try
      _grid.update(i.usize(),v)
    else
      _env.out.print("cant update!")
    end
 fun _count() : U64 =>
    var c : U64 = 0
    for v in _grid.values() do
      c = c + if v == 0 then 0 else 1 end
    end
    c
 fun ref _add_block() =>
   let c = _count()
   if c == 16 then return end
   var hit =  _rand.int(16 - c)
   var i : U32 = 0
   while i < 16 do
     if (_get(i) == 0) then
       if hit == 0 then
         _set(i, if _rand.int(10) > 0 then 2 else 4 end)
         break
       end
       hit = hit - 1
     end
     i = i + 1
   end
 fun _get(i : (I32|U32)) : U32 => try  _grid(i.usize()) else 0  end
 fun _win() : Bool =>
   for v in _grid.values() do
     if v == 2048 then return true end
   end
   false
 fun _no_moves(edge : EdgeRow val) : Bool =>
   for r in edge.row() do
     match _merge(r, edge.inc())
     | let rout : ROW =>
       if (rout._1 == 0) or (rout._2 == 0) or
           (rout._3 == 0) or (rout._4 == 0) then
             return false
       end
     end
   end
   true
 fun _lose() : Bool =>
   (_grid.size() >= 16) and
   _no_moves(LeftRow) and
   _no_moves(RightRow) and
   _no_moves(TopRow) and
   _no_moves(BottomRow)
 be quit()=>
   _env.out.print("Exiting.. some terminals may require <ctrl-c>")
   _env.exitcode(0)
   _env.input.dispose()
 be move(m: Move) =>
   let updated =
     match m
     | LEFT =>  _shift_to(LeftRow)
     | RIGHT => _shift_to(RightRow)
     | UP =>    _shift_to(TopRow)
     | DOWN =>  _shift_to(BottomRow)
     else
       false
     end
   if _win() then
     _draw()
     _env.out.print("You win :)")
     quit()
   else
     if updated then
       _add_block()
       _draw()
     end
     if _lose() then
       _env.out.print("You lose :(")
       quit()
     end
   end

actor Main

 new create(env: Env) =>
   // unit test
   ifdef "test" then
     TestMain(env)
     return
   end
   // else game
   let input : Stdin tag = env.input
   env.out.print("Welcome to ponylang-2048...")
   let game = Game(env)
   let term = ANSITerm(KeyboardHandler(game), input)
   let notify : StdinNotify iso = object iso
       let term: ANSITerm = term
       let _in: Stdin tag = input
       fun ref apply(data: Array[U8] iso) => term(consume data)
       fun ref dispose() => _in.dispose()
   end
   input(consume notify)

</lang>

Prolog

Works with swi-prolog, any version. <lang Prolog>/* -------------------------------------------------------------

  Entry point, just create a blank grid and enter a 'game loop'
  -------------------------------------------------------------*/

play_2048 :- welcome_msg, length(Grid, 16), maplist(=(' '), Grid), % create a blank grid play(Grid, yes), !. % don't have to cut here but it makes the exit cleaner


/* -----------------------------------------------

  Messages that will be printed at various points 
  -----------------------------------------------*/

welcome_msg :- format('~nWelcome to the Prolog version of 2048~n~n'), format('To play using w,s,a,d keys for movement, q to quit~n~n'). contrats_msg :- format('Congratulations, you reached 2048!~n~n'). loser_msg :- format('Uh Oh, you could not quite make it to 2048...~n~n'). quit_msg :- format('Bye then!~n~n').


/* -------------------

  End game conditions 
  -------------------*/

player_not_won_yet(Grid) :- maplist(dif(2048), Grid). player_wins(Grid) :- member(2048, Grid). player_loses(G) :- move(up, G, G), move(down, G, G), move(left, G, G), move(right, G, G).


/* ---------

  Game loop 
  ---------*/	

% First check if the player has reached the win condition, if not find how many spaces are left play(Grid, _) :- player_wins(Grid), draw_grid(Grid), contrats_msg. play(Grid, CreateNewNum) :- player_not_won_yet(Grid), include(=(' '), Grid, Spaces), length(Spaces, NSpaces), % the number of spaces in the grid play(Grid, CreateNewNum, NSpaces).

% knowing the number of spaces, determine if there is a space, and if we need a new number, and generate. play(Grid, no, _) :- play(Grid). play(Grid, yes, 0) :- play(Grid). play(Grid, yes, NSpaces) :- dif(NSpaces, 0), random_space(NSpaces, Grid, GridWithRandom), play(GridWithRandom).

% with the new number on the grid we can tell if the player has lost the game yet % if not, draw the grid and get the next action by the player play(Grid) :- player_loses(Grid), draw_grid(Grid), loser_msg. play(Grid) :- \+ player_loses(Grid), draw_grid(Grid), next_move_by_player(Move), player_made_move(Grid, Move).

% determine the result of player move player_made_move(_, quit). player_made_move(Grid, Move) :- dif(Move, quit), move(Move, Grid, Grid), % The move creating the same grid indicates that no merge was done play(Grid, no). % don't create a new number player_made_move(Grid, Move) :- dif(Move, quit), move(Move, Grid, MovedGrid), dif(Grid, MovedGrid), play(MovedGrid, yes).


/* ---------------------------------------

  Get the next move from the player input 
  ---------------------------------------*/	

next_move_by_player(Move) :- repeat, get_single_char(Char), key_move(Char, Move).

% valid keys are: up = 'w', down = 's', left = 'a' right = 'd', quit = 'q' key_move(119, up). key_move(115, down). key_move(97, left). key_move(100, right). key_move(113, quit).


/* ------------------

  Draw the Game grid 
  ------------------*/

draw_grid([A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4]) :- format( '+-------------------+~n'), row([A1,A2,A3,A4]), row([B1,B2,B3,B4]), row([C1,C2,C3,C4]), maplist(draw, [D1,D2,D3,D4]), format('¦~n+-------------------+~n~n~n').

row([A,B,C,D]) :- maplist(draw, [A,B,C,D]), format('¦~n¦----+----+----+----¦~n').

draw(' ') :- format('¦ '). draw(X) :- member(X,[2,4,8]), format('¦ ~d ', X). draw(X) :- member(X,[16,32,64]), format('¦ ~d ', X). draw(X) :- member(X,[128,256,512]), format('¦ ~d', X). draw(X) :- member(X,[1024,2048]), format('¦~d', X).


/* ----------------------------------------

  Populate a random space with a new value 
  ----------------------------------------*/

random_space(0, G, G). random_space(1, Grid, GridWithRandom) :- four_or_two(V), select(' ', Grid, V, GridWithRandom). random_space(N, Grid, GridWithRandom) :- N > 1, four_or_two(V), random(1, N, P), replace_space(P, V, Grid, GridWithRandom).

replace_space(0, V, [' '|T], [V|T]). replace_space(P, V, [' '|T], [' '|R]) :- succ(NextP, P), replace_space(NextP, V, T, R). replace_space(P, V, [H|T], [H|R]) :- dif(' ', H), replace_space(P, V, T, R).

four_or_two(V) :- random(1, 10, IsFour), IsFour = 1 -> V = 4 ; V = 2.


/* ------------------------------------------

  Process a game move based on the direction 
  ------------------------------------------*/

move(Direction, UnMoved, Moved) :- map_move(Direction, UnMoved, UnMovedMapped), maplist(combine_row, UnMovedMapped, MovedMapped), map_move(Direction, Moved, MovedMapped).

% convert the array to a set of lists that can be moved in the same % direction. This can be reversed after the move is completed. map_move(up, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[D1,C1,B1,A1],[D2,C2,B2,A2],[D3,C3,B3,A3],[D4,C4,B4,A4]]). map_move(down, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[A1,B1,C1,D1],[A2,B2,C2,D2],[A3,B3,C3,D3],[A4,B4,C4,D4]]). map_move(left, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[A4,A3,A2,A1],[B4,B3,B2,B1],[C4,C3,C2,C1],[D4,D3,D2,D1]]). map_move(right, [A1,A2,A3,A4,B1,B2,B3,B4,C1,C2,C3,C4,D1,D2,D3,D4], [[A1,A2,A3,A4],[B1,B2,B3,B4],[C1,C2,C3,C4],[D1,D2,D3,D4]]).

% remove all the spaces, then put them at the front of the list combine_row(UnMoved, Moved) :- partition(=(' '), UnMoved, Blank, Set), append(Blank, Set, ReadyToMove), combine(ReadyToMove, Moved).

% combine based on the rules of the game. combine([A,B,C,D], [A,B,C,D]) :- dif(A,B), dif(B,C), dif(C,D). combine([A,A,B,B], [' ',' ',Ad,Bd]) :- dbl(A,Ad), dbl(B,Bd). combine([A,B,C,C], [' ',A,B,Cd]) :- dif(A,B), dbl(C,Cd). combine([A,B,B,C], [' ',A,Bd,C]) :- dif(B,C), dbl(B,Bd). combine([A,A,B,C], [' ',Ad,B,C]) :- dif(A,B), dif(B,C), dbl(A, Ad). combine([A,B,C,C], [' ',A,B,Cd]) :- dif(A,B), dif(B,C), dbl(C,Cd).

% this could be done using maths, but it is more prology this way. dbl(' ', ' '). dbl(2,4). dbl(4,8). dbl(8,16). dbl(16,32). dbl(32,64). dbl(64,128). dbl(128,256). dbl(256,512). dbl(512,1028). dbl(1028,2048). </lang>

Output:
?- play_2048.

Welcome to the Prolog version of 2048

To play using w,s,a,d keys for movement, q to quit

+-------------------+
¦    ¦    ¦    ¦    ¦
¦----+----+----+----¦
¦    ¦    ¦    ¦  2 ¦
¦----+----+----+----¦
¦    ¦    ¦    ¦    ¦
¦----+----+----+----¦
¦    ¦    ¦    ¦    ¦
+-------------------+

Python

Python: Original, with output

<lang python>#!/usr/bin/env python3

import curses from random import randrange, choice # generate and place new tile from collections import defaultdict

letter_codes = [ord(ch) for ch in 'WASDRQwasdrq'] actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit'] actions_dict = dict(zip(letter_codes, actions * 2))

def get_user_action(keyboard): char = "N" while char not in actions_dict: char = keyboard.getch() return actions_dict[char]

def transpose(field): return [list(row) for row in zip(*field)]

def invert(field): return [row[::-1] for row in field]

class GameField(object): def __init__(self, height=4, width=4, win=2048): self.height = height self.width = width self.win_value = win self.score = 0 self.highscore = 0 self.reset()

def reset(self): if self.score > self.highscore: self.highscore = self.score self.score = 0 self.field = [[0 for i in range(self.width)] for j in range(self.height)] self.spawn() self.spawn()

def move(self, direction): def move_row_left(row): def tighten(row): # squeese non-zero elements together new_row = [i for i in row if i != 0] new_row += [0 for i in range(len(row) - len(new_row))] return new_row

def merge(row): pair = False new_row = [] for i in range(len(row)): if pair: new_row.append(2 * row[i]) self.score += 2 * row[i] pair = False else: if i + 1 < len(row) and row[i] == row[i + 1]: pair = True new_row.append(0) else: new_row.append(row[i]) assert len(new_row) == len(row) return new_row return tighten(merge(tighten(row)))

moves = {} moves['Left'] = lambda field: \ [move_row_left(row) for row in field] moves['Right'] = lambda field: \ invert(moves['Left'](invert(field))) moves['Up'] = lambda field: \ transpose(moves['Left'](transpose(field))) moves['Down'] = lambda field: \ transpose(moves['Right'](transpose(field)))

if direction in moves: if self.move_is_possible(direction): self.field = moves[direction](self.field) self.spawn() return True else: return False

def is_win(self): return any(any(i >= self.win_value for i in row) for row in self.field)

def is_gameover(self): return not any(self.move_is_possible(move) for move in actions)

def draw(self, screen): help_string1 = '(W)Up (S)Down (A)Left (D)Right' help_string2 = ' (R)Restart (Q)Exit' gameover_string = ' GAME OVER' win_string = ' YOU WIN!' def cast(string): screen.addstr(string + '\n')

def draw_hor_separator(): top = '┌' + ('┬──────' * self.width + '┐')[1:] mid = '├' + ('┼──────' * self.width + '┤')[1:] bot = '└' + ('┴──────' * self.width + '┘')[1:] separator = defaultdict(lambda: mid) separator[0], separator[self.height] = top, bot if not hasattr(draw_hor_separator, "counter"): draw_hor_separator.counter = 0 cast(separator[draw_hor_separator.counter]) draw_hor_separator.counter += 1

def draw_row(row): cast(.join('│{: ^5} '.format(num) if num > 0 else '| ' for num in row) + '│')

screen.clear() cast('SCORE: ' + str(self.score)) if 0 != self.highscore: cast('HIGHSCORE: ' + str(self.highscore)) for row in self.field: draw_hor_separator() draw_row(row) draw_hor_separator() if self.is_win(): cast(win_string) else: if self.is_gameover(): cast(gameover_string) else: cast(help_string1) cast(help_string2)

def spawn(self): new_element = 4 if randrange(100) > 89 else 2 (i,j) = choice([(i,j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0]) self.field[i][j] = new_element

def move_is_possible(self, direction): def row_is_left_movable(row): def change(i): # true if there'll be change in i-th tile if row[i] == 0 and row[i + 1] != 0: # Move return True if row[i] != 0 and row[i + 1] == row[i]: # Merge return True return False return any(change(i) for i in range(len(row) - 1))

check = {} check['Left'] = lambda field: \ any(row_is_left_movable(row) for row in field)

check['Right'] = lambda field: \ check['Left'](invert(field))

check['Up'] = lambda field: \ check['Left'](transpose(field))

check['Down'] = lambda field: \ check['Right'](transpose(field))

if direction in check: return check[direction](self.field) else: return False

def main(stdscr): curses.use_default_colors() game_field = GameField(win=32) state_actions = {} # Init, Game, Win, Gameover, Exit def init(): game_field.reset() return 'Game'

state_actions['Init'] = init

def not_game(state): game_field.draw(stdscr) action = get_user_action(stdscr) responses = defaultdict(lambda: state) responses['Restart'], responses['Exit'] = 'Init', 'Exit' return responses[action]

state_actions['Win'] = lambda: not_game('Win') state_actions['Gameover'] = lambda: not_game('Gameover')

def game(): game_field.draw(stdscr) action = get_user_action(stdscr) if action == 'Restart': return 'Init' if action == 'Exit': return 'Exit' if game_field.move(action): # move successful if game_field.is_win(): return 'Win' if game_field.is_gameover(): return 'Gameover' return 'Game'

state_actions['Game'] = game

state = 'Init' while state != 'Exit': state = state_actions[state]()

curses.wrapper(main) </lang>

Python: using tkinter

<lang python>#!/usr/bin/env python3

Python 3.6.5 code using Tkinter graphical user interface.

   Option to set goal to powers of 2 from 128 to 2048. 

from tkinter import * from tkinter import messagebox from tkinter import ttk import random

  1. ************************************************

class Board:

   def __init__(self):
       self.bd = []* 16
       self.goal = 2048
       self.choices = '2222222224'
       
   # place 2 random squares on empty board:
   def place_two(self):
       idx = range(15)
       a, b = random.sample(idx, 2)
       self.bd[a] = random.choice(self.choices)        
       self.bd[b] = random.choice(self.choices)       
   # return text on square at index=idx of board:    
   def get_text(self, idx):
       return self.bd[idx]
   # move squares on board based on arrow key entered:
   def move_squares(self, key):
       if key in ('LR'):
           # generate 4x4 2D array for row processing:
           rows = [[self.bd[0],  self.bd[1],  self.bd[2],  self.bd[3]],
                   [self.bd[4],  self.bd[5],  self.bd[6],  self.bd[7]],
                   [self.bd[8],  self.bd[9],  self.bd[10], self.bd[11]],
                   [self.bd[12], self.bd[13], self.bd[14], self.bd[15]]]
       else:
           # generate transposed 4x4 2D array instead:
           rows = [[self.bd[0],  self.bd[4],  self.bd[8],  self.bd[12]],
                   [self.bd[1],  self.bd[5],  self.bd[9],  self.bd[13]],
                   [self.bd[2],  self.bd[6],  self.bd[10], self.bd[14]],
                   [self.bd[3],  self.bd[7],  self.bd[11], self.bd[15]]]
       # build a new 4x4 array of "moved" rows:
       nrows = []       
       for row in rows:
           if key in 'RD':
               # reverse these rows and slide to left:
               row = row[::-1]
           nrow = self.slide_squares(row)
           if key in 'RD':
               # restore reversed rows:
               nrow = nrow[::-1]
           nrows.append(nrow)
       if key in ('UD'):
           # transpose arrays that were transposed:
           nrows = list(map(list, zip(*nrows)))
       # flatten 4x4 2D array:
       newbd = []
       for row in nrows:
           for r in row:
               newbd.append(r)
       # place a '2' or '4' in random open square of newbd:
       if newbd != self.bd and  in newbd:
           loi = []
           for i in range(16):
               if newbd[i] == :
                   loi.append(i)
           i = random.choice(loi)
           newbd[i] = random.choice(self.choices)
       
       self.bd = newbd
       return
   # slide squares in row to the left:                  
   def slide_squares(self, row):
       new = []  * 4
       icmb = -1
       inew = 0
       for x in row:
           if x:
               if (inew > 0         and
                   x == new[inew-1] and
                   icmb != inew-1):
                   new[inew-1] = str(2*int(x))
                   icmb = inew-1
               else:
                   new[inew] = x
                   inew += 1
       return new
   # check if game won, lost, or continuing:
   def is_end(self):
       if self.goal in self.bd:
           return 'W'
       if  in self.bd:
           return 'C'
       for i in [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14]:
           if self.bd[i] == self.bd[i+1]:
               return 'C'
       for i in range(12):
           if self.bd[i] == self.bd[i+4]:
               return 'C'
       return 'L'
               
  1. ************************************************

class Game:

   def __init__(self, gw):
       self.window = gw
       self.rosybrown1 = '#ffc1c1'
       self.lemonchiffon = '#fffacd'
       self.skyblue1 = '#87ceff'
       self.springgreen = '#00ff7f'
       self.tomato1 = '#ff6347'
       self.hotpink = '#ff69b4'
       self.brilliantlavender = '#edcaf6'
       self.cobaltgreen = '#3d9140'
       self.dodgerblue = '#1e90ff'
       self.darkgoldenrod1 = '#ffb90f'
       self.yellow = '#ffff00'
       self.imperialred = '#ed2939'
       self.navyblue = '#000080'
       self.lightgreen = '#90ee90'
       self.lightsteelblue = '#b0c4de'
       self.white = '#ffffff'
       self.palegreen4 = '#548b54'
       self.darkgreen = '#013220'
       self.black = '#000000'
       self.doc = {:self.rosybrown1,
                   '2':self.lemonchiffon,
                   '4':self.skyblue1,
                   '8':self.springgreen,
                   '16':self.tomato1,
                   '32':self.hotpink,
                   '64':self.brilliantlavender,
                   '128':self.cobaltgreen,
                   '256':self.dodgerblue,
                   '512':self.darkgoldenrod1,
                   '1024':self.yellow,
                   '2048':self.imperialred}
       # game data:
       self.bd = None
       self.playable = False
       # top frame:
       self.top_fr = Frame(gw,
                           width=600,
                           height=100,
                           bg=self.lightgreen)
       self.top_fr.pack(fill=X)
       self.hdg = Label(self.top_fr,
                        text='  2048  ',
                        font='arial 22 bold',
                        fg=self.navyblue,
                        bg=self.white)
       self.hdg.place(relx=0.5, rely=0.4,
                      anchor=CENTER)
       self.dir = Label(self.top_fr,
                text="(Select a 'Goal' & Click 'New Game')",
                font='arial 12 ',
                fg=self.navyblue,
                bg=self.lightgreen)
       self.dir.place(relx=0.5, rely=0.8,
                      anchor=CENTER)
       self.play_btn = Button(self.top_fr,
                              text='New \nGame',
                              bd=5,
                              bg=self.palegreen4,
                              fg=self.white,
                              font='times 12 bold',
                              command=self.new_game)
       self.play_btn.place(relx=0.92, rely=0.5,
                      anchor=E)
       self.lbl_cb = Label(self.top_fr,
                           text='     Goal',
                           font='arial 12 bold ',
                           fg=self.darkgreen,
                           bg=self.lightgreen)
       self.lbl_cb.place(relx=0.08, rely=0.35,
                      anchor=W)
       goals = ['2048', '1024', '512', '256', '128']
       self.cur_goal = StringVar()
       self.goal_cb = ttk.Combobox(self.top_fr,
                                   foreground=self.darkgreen,
                                   values=goals,
                                   font='times 12 bold',
                                   justify='left',
                                   state='readonly',
                                   textvariable=self.cur_goal, 
                                   width=7,
                                   height=30)
       self.goal_cb.place(relx=0.08, rely=0.6,
                      anchor=W)
       self.goal_cb.current(0)
       # bottom frame:
       self.btm_fr = Frame(gw,
                           width=600,
                           height=500,
                           bg=self.lightsteelblue)
       self.btm_fr.pack(fill=X)
       # board frame:
       self.bd_fr = Frame(self.btm_fr,
                          width=400+2,
                          height=400+2,
                          relief='solid',
                          bd=1,
                          bg=self.lemonchiffon)
       self.bd_fr.place(relx=0.5, rely=0.5,
                        anchor=CENTER)
       self.bd = Board()
       self.play_game()
  1. ************************************************
   # action to take if 'new game' button is clicked
   # or if 'play again' is chosen after win or loss:
   def new_game(self):   
       self.playable = True
       self.bd = Board()
       self.bd.place_two()
       self.bd.goal = self.goal_cb.get()
       self.goal_cb.config(state='disabled')
       self.dir.config(text='(Use arrow keys to play game)')
       self.play_game()
   # show current contents of board:
   def play_game(self):
       objh = 100  # widget height
       objw = 100  # widget width
       objx = 0    # x-position of widget in frame
       objy = 0    # y-position of widget in frame
       i = 0
       for r in range(4):
           for c in range(4):
               txt = self.bd.get_text(i)
               bg_color = self.doc[txt]         
               game_sq = Label(self.bd_fr,
                               text=txt,
                               relief='solid',
                               bd=1,
                               fg=self.black,
                               bg=bg_color,
                               font='times 16 bold')
               game_sq.place(x=objx, y=objy,
                             height=objh, width=objw)
               i += 1
               objx = objx + objw
           objx = 0
           objy = objy + objh
   # control play when an arrow key is pressed:
   def key(self, event):
       if event.keysym in ('Left', 'Right', 'Up', 'Down'):
           if self.playable:
               self.bd.move_squares(event.keysym[0])
               self.play_game()
               x = self.bd.is_end()
               if x == 'C':
                   return
               elif x == 'W':
                   msg = 'You won!!!   Play again?'
               elif x == 'L':
                   msg = 'You lost!!!   Play again?'
               ans = messagebox.askquestion(msg)
               if ans == 'no':
                   self.window.destroy()
               else:
                   self.new_game()
  1. ************************************************

root = Tk() root.title('2048') root.geometry('600x600+100+50') root.resizable(False, False) g = Game(root) root.bind_all('<Key>', g.key) root.mainloop() </lang>

QB64

<lang QB64> _DEFINE A-Z AS _INTEGER64 DIM SHARED Grid(0 TO 5, 0 TO 5) AS INTEGER CONST Left = 19200 CONST Right = 19712 CONST Down = 20480 CONST Up = 18432 CONST ESC = 27 CONST LCtrl = 100306 CONST RCtrl = 100305

Init MakeNewGame DO

   _LIMIT 30
   ShowGrid
   CheckInput flag
   IF flag THEN GetNextNumber
   _DISPLAY

LOOP

SUB CheckInput (flag) flag = 0 k = _KEYHIT SELECT CASE k

   CASE ESC: SYSTEM
   CASE 83, 115 'S
       IF _KEYDOWN(LCtrl) OR _KEYDOWN(RCtrl) THEN MakeNewGame
   CASE Left
       MoveLeft
       flag = -1 'we hit a valid move key.  Even if we don't move, get a new number
   CASE Up
       MoveUp
       flag = -1
   CASE Down
       MoveDown
       flag = -1
   CASE Right
       MoveRight
       flag = -1

END SELECT END SUB

SUB MoveDown 'first move everything left to cover the blank spaces DO

   moved = 0
   FOR y = 4 TO 1 STEP -1
       FOR x = 1 TO 4
           IF Grid(x, y) = 0 THEN 'every point above this moves down
               FOR j = y TO 1 STEP -1
                   Grid(x, j) = Grid(x, j - 1)
                   IF Grid(x, j) <> 0 THEN moved = -1
               NEXT
           END IF
       NEXT
   NEXT
   IF moved THEN y = y + 1 'recheck the same column

LOOP UNTIL NOT moved FOR y = 4 TO 1 STEP -1

   FOR x = 1 TO 4
       IF Grid(x, y) <> 0 AND Grid(x, y) = Grid(x, y - 1) THEN 'add them together and every point above this moves
           Grid(x, y) = Grid(x, y) * 2
           FOR j = y - 1 TO 1
               Grid(x, j) = Grid(x, j - 1)
           NEXT
       END IF
   NEXT

NEXT END SUB

SUB MoveLeft 'first move everything to cover the blank spaces DO

   moved = 0
   FOR x = 1 TO 4
       FOR y = 1 TO 4
           IF Grid(x, y) = 0 THEN 'every point right of this moves left
               FOR j = x TO 4
                   Grid(j, y) = Grid(j + 1, y)
                   IF Grid(j, y) <> 0 THEN moved = -1
               NEXT
           END IF
       NEXT
   NEXT
   IF moved THEN x = x - 1 'recheck the same row

LOOP UNTIL NOT moved FOR x = 1 TO 4

   FOR y = 1 TO 4
       IF Grid(x, y) <> 0 AND Grid(x, y) = Grid(x + 1, y) THEN 'add them together and every point right of this moves left
           Grid(x, y) = Grid(x, y) * 2
           FOR j = x + 1 TO 4
               Grid(j, y) = Grid(j + 1, y)
           NEXT
       END IF
   NEXT

NEXT END SUB

SUB MoveUp 'first move everything to cover the blank spaces DO

   moved = 0
   FOR y = 1 TO 4
       FOR x = 1 TO 4
           IF Grid(x, y) = 0 THEN 'every point below of this moves up
               FOR j = y TO 4
                   Grid(x, j) = Grid(x, j + 1)
                   IF Grid(x, j) <> 0 THEN moved = -1
               NEXT
           END IF
       NEXT
   NEXT
   IF moved THEN y = y - 1 'recheck the same column

LOOP UNTIL NOT moved FOR y = 1 TO 4

   FOR x = 1 TO 4
       IF Grid(x, y) <> 0 AND Grid(x, y) = Grid(x, y + 1) THEN 'add them together and every point below this moves
           Grid(x, y) = Grid(x, y) * 2
           FOR j = y + 1 TO 4
               Grid(x, j) = Grid(x, j + 1)
           NEXT
           Grid(x, 4) = 0
       END IF
   NEXT

NEXT END SUB

SUB MoveRight 'first move everything to cover the blank spaces DO

   moved = 0
   FOR x = 4 TO 1 STEP -1
       FOR y = 1 TO 4
           IF Grid(x, y) = 0 THEN 'every point right of this moves left
               FOR j = x TO 1 STEP -1
                   Grid(j, y) = Grid(j - 1, y)
                   IF Grid(j, y) <> 0 THEN moved = -1
               NEXT
           END IF
       NEXT
   NEXT
   IF moved THEN x = x - 1 'recheck the same row

LOOP UNTIL NOT moved

FOR x = 4 TO 1 STEP -1

   FOR y = 1 TO 4
       IF Grid(x, y) <> 0 AND Grid(x, y) = Grid(x - 1, y) THEN 'add them together and every point right of this moves left
           Grid(x, y) = Grid(x, y) * 2
           FOR j = x - 1 TO 1 STEP -1
               Grid(j, y) = Grid(j - 1, y)
           NEXT
       END IF
   NEXT

NEXT END SUB

SUB ShowGrid 'SUB MakeBox (Mode AS INTEGER, x1 AS INTEGER, y1 AS INTEGER, x2 AS INTEGER, y2 AS INTEGER, 'Caption AS STRING, FontColor AS _UNSIGNED LONG, FontBackground AS _UNSIGNED LONG, 'BoxColor AS _UNSIGNED LONG, BoxHighLight AS _UNSIGNED LONG, XOffset AS INTEGER, YOffset AS INTEGER) w = 120 h = 120 FOR x = 1 TO 4

   FOR y = 1 TO 4
       t$ = LTRIM$(STR$(Grid(x, y)))
       IF t$ = "0" THEN t$ = ""
       MakeBox 4, (x - 1) * w, (y - 1) * h, w, h, t$, -1, 0, 0, -1, 0, 0
   NEXT

NEXT END SUB

SUB Init ws = _NEWIMAGE(480, 480, 32) SCREEN ws _DELAY 1 _TITLE "Double Up" _SCREENMOVE _MIDDLE RANDOMIZE TIMER f& = _LOADFONT("C:\Windows\Fonts\courbd.ttf", 32, "MONOSPACE") _FONT f&

END SUB

SUB MakeNewGame FOR x = 1 TO 4

   FOR y = 1 TO 4
       Grid(x, y) = 0
   NEXT

NEXT GetNextNumber GetNextNumber END SUB

SUB GetNextNumber FOR x = 1 TO 4

   FOR y = 1 TO 4
       IF Grid(x, y) = 0 THEN valid = -1
   NEXT

NEXT IF valid THEN 'If all the grids are full, we can't add any more numbers

   'This doesn't mean the game is over, as the player may be able to
   DO
       x = _CEIL(RND * 4)
       y = _CEIL(RND * 4)
   LOOP UNTIL Grid(x, y) = 0
   Grid(x, y) = 2

END IF END SUB

SUB MakeBox (Mode AS INTEGER, x1 AS INTEGER, y1 AS INTEGER, x2 AS INTEGER, y2 AS INTEGER, Caption AS STRING, FontColor AS _UNSIGNED LONG, FontBackground AS _UNSIGNED LONG, BoxColor AS _UNSIGNED LONG, BoxHighLight AS _UNSIGNED LONG, XOffset AS INTEGER, YOffset AS INTEGER)

'This is an upgrade version of my original Button routine. 'It's more versitile (but complex) than the original. 'Mode 0 (or any unsupported number) will tell the box to size itself from X1,Y1 to X2,Y2 'Mode 1 will tell the box to autosize itself according to whatever text is placed within it. 'Mode 2 will tell the box to use X2 and Y2 as relative coordinates and not absolute coordinates. 'Mode 3 will tell the box to autocenter text with X2, Y2 being absolute coordinates. 'Mode 4 will tell the box to autocenter text with X2, Y2 being relative coordinates. 'Mode otherwise is unused, but available for expanded functionality. 'X1 carries the X location of where we want to place our box on the screen. 'Y2 carries the Y location of where we want to place our box on the screen. 'X2 is the X boundry of our box on the screen, depending on our mode. 'Y2 is the Y boundry of our box on the screen, depending on our mode.

'Caption is the text that we want our box to contain.

'FontColor is our font color for our caption 'FontBackground is the font background color for our caption 'NOTE: IF FONTCOLOR OR FONTBACKGROUND IS SET TO ZERO, THEY WILL **NOT** AFFECT THE COLOR BEHIND THEM. 'This can be used to mimic the function of _KEEPBACKGROUND, _FILLBACKGROUND, or _ONLYBACKGROUND

'BoxColor is our box color 'BoxHighlight is our box highligh colors 'NOTE: SAME WITH BOXCOLOR AND BOXHIGHLIGHT. IF SET TO ZERO, THEY WILL HAVE **NO** COLOR AT ALL TO THEM, AND WILL NOT AFFECT THE BACKGROUND OF ANYTHING BEHIND THEM.

'XOffset is used to offset our text # pixels from the X1 top. 'YOffset is used to offset our text # pixels from the Y1 top. 'These can be used to place our text wherever we want on our box. 'But remember, if Mode = 3 or 4, the box will autocenter the text and ignore these parameters completely.

DIM BoxBlack AS _UNSIGNED LONG

dc& = _DEFAULTCOLOR: bg& = _BACKGROUNDCOLOR IF Black <> 0 THEN

   'We have black either as a CONST or a SHARED color
   BoxBlack = Black

ELSE

   'We need to define what Black is for our box.
   BoxBlack = _RGB32(0, 0, 0)

END IF

IF _FONTWIDTH <> 0 THEN cw = _FONTWIDTH * LEN(Caption) ELSE cw = _PRINTWIDTH(Caption) ch = _FONTHEIGHT

tx1 = x1: tx2 = x2: ty1 = y1: ty2 = y2 SELECT CASE Mode

   CASE 0
       'We use the X2, Y2 coordinates provided as absolute coordinates
   CASE 1
       tx2 = tx1 + cw + 8
       ty2 = ty1 + ch + 8
       XOffset = 5: YOffset = 5
   CASE 2
       tx2 = tx1 + x2
       ty2 = ty1 + y2
   CASE 3
       XOffset = (tx2 - tx1 - cw) \ 2
       YOffset = (ty2 - ty1 - ch) \ 2
   CASE 4
       tx2 = tx1 + x2
       ty2 = ty1 + y2
       XOffset = (tx2 - tx1) \ 2 - cw \ 2
       YOffset = (ty2 - ty1 - ch) \ 2

END SELECT LINE (tx1, ty1)-(tx2, ty2), BoxBlack, BF LINE (tx1 + 1, ty1 + 1)-(tx2 - 1, ty2 - 1), BoxHighLight, B LINE (tx1 + 2, ty1 + 2)-(tx2 - 2, ty2 - 2), BoxHighLight, B LINE (tx1 + 3, ty1 + 3)-(tx2 - 3, ty2 - 3), BoxBlack, B LINE (tx1, ty1)-(tx1 + 3, ty1 + 3), BoxBlack LINE (tx2, ty1)-(tx2 - 3, ty1 + 3), BoxBlack LINE (tx1, ty2)-(tx1 + 3, ty2 - 3), BoxBlack LINE (tx2, ty2)-(tx2 - 3, ty2 - 3), BoxBlack LINE (tx1 + 3, y1 + 3)-(tx2 - 3, ty2 - 3), BoxColor, BF COLOR FontColor, FontBackground _PRINTSTRING (tx1 + XOffset, ty1 + YOffset), Caption$ COLOR dc&, bg& END SUB </lang>

R

orginal R package : https://github.com/ThinkRstat/r2048 <lang R> GD <- function(vec) {

   c(vec[vec != 0], vec[vec == 0])

} DG <- function(vec) {

   c(vec[vec == 0], vec[vec != 0])

}

DG_ <- function(vec, v = TRUE) {

   if (v) 
       print(vec)
   rev(GD_(rev(vec), v = FALSE))

}

GD_ <- function(vec, v = TRUE) {

   if (v) {
       print(vec)
   }
   vec2 <- GD(vec)
   # on cherche les 2 cote a cote
   pos <- which(vec2 == c(vec2[-1], 9999))
   # put pas y avoir consécutif dans pos
   pos[-1][which(abs(pos - c(pos[-1], 999)) == 1)]
   av <- which(c(0, c(pos[-1], 9) - pos) == 1)
   if (length(av) > 0) {
       pos <- pos[-av]
   }
   vec2[pos] <- vec2[pos] + vec2[pos + 1]
   vec2[pos + 1] <- 0
   GD(vec2)
   

}

H_ <- function(base) {

   apply(base, MARGIN = 2, FUN = GD_, v = FALSE)

} B_ <- function(base) {

   apply(base, MARGIN = 2, FUN = DG_, v = FALSE)

} G_ <- function(base) {

   t(apply(base, MARGIN = 1, FUN = GD_, v = FALSE))

} D_ <- function(base) {

   t(apply(base, MARGIN = 1, FUN = DG_, v = FALSE))

}

H <- function(base) {

   apply(base, MARGIN = 2, FUN = GD, v = FALSE)

} B <- function(base) {

   apply(base, MARGIN = 2, FUN = DG, v = FALSE)

} G <- function(base) {

   t(apply(base, MARGIN = 1, FUN = GD, v = FALSE))

} D <- function(base) {

   t(apply(base, MARGIN = 1, FUN = DG, v = FALSE))

}

add2or4 <- function(base, p = 0.9) {

   lw <- which(base == 0)
   if (length(lw) > 1) {
       tirage <- sample(lw, 1)
   } else {
       tirage <- lw
   }
   base[tirage] <- sample(c(2, 4), 1, prob = c(p, 1 - p))
   base

} print.dqh <- function(base) {

   cat("\n\n")
   for (i in 1:nrow(base)) {
       cat(paste("     ", base[i, ], " "))
       cat("\n")
   }
   cat("\n")

}


  1. -*- coding: utf-8 -*-
  2. ' @encoding UTF-8
  3. ' @title run_2048
  4. ' @description The 2048 game
  5. ' @param nrow nomber of row
  6. ' @param ncol numver of col
  7. ' @param p probability to obtain a 2 (1-p) is the probability to obtain a 4
  8. ' @examples
  9. ' \dontrun{
  10. ' run_2048()
  11. ' }
  12. ' @export


run_2048 <- function(nrow, ncol, p = 0.9) {


   help <- function() {
       cat("   *** KEY BINDING ***  \n\n")
       cat("press ECHAP to quit\n\n")
       cat("choose moove E (up) ; D (down) ; S (left); F (right) \n")
       cat("choose moove 8 (up) ; 2 (down) ; 4 (left); 6 (right) \n")
       cat("choose moove I (up) ; K (down) ; J (left); L (right) \n\n\n")
       
   }
   
   
   if (missing(nrow) & missing(ncol)) {
       nrow <- ncol <- 4
   }
   if (missing(nrow)) {
       nrow <- ncol
   }
   if (missing(ncol)) {
       ncol <- nrow
   }
   
   base <- matrix(0, nrow = nrow, ncol = ncol)
   
   while (length(which(base == 2048)) == 0) {
       base <- add2or4(base, p = p)
       # print(base)
       
       class(base) <- "dqh"
       print(base)
       flag <- sum((base == rbind(base[-1, ], 0)) + (base == rbind(0, 
           base[-nrow(base), ])) + (base == cbind(base[, -1], 0)) + (base == 
           cbind(0, base[, -nrow(base)])))
       if (flag == 0) {
 
           break
       }
       
       y <- character(0)
       while (length(y) == 0) {
           cat("\n", "choose moove E (up) ; D (down) ; s (left); f (right) OR H for help", 
               "\n")  # prompt
           y <- scan(n = 1, what = "character")
       }
       
       
       baseSAVE <- base
       base <- switch(EXPR = y, E = H_(base), D = B_(base), S = G_(base), 
           F = D_(base), e = H_(base), d = B_(base), s = G_(base), f = D_(base), 
           `8` = H_(base), `2` = B_(base), `4` = G_(base), `6` = D_(base), 
           H = help(), h = help(), i = H_(base), k = B_(base), j = G_(base), 
           l = D_(base), I = H_(base), K = B_(base), J = G_(base), L = D_(base))
       if (is.null(base)) {
           cat(" wrong KEY \n")
           base <- baseSAVE
       }
       
       
       
   }
   
   if (sum(base >= 2048) > 1) {
       cat("YOU WIN ! \n")
   } else {
       cat("YOU LOOSE \n")
   }

}

</lang>

Racket

Original repo: https://github.com/danprager/2048 Play the RacketScript fork online here: http://rapture.twistedplane.com:8080/#example/2048-game

<lang Racket>

LICENSE
See License file LICENSE (MIT license)
Repository
https://github.com/danprager/2048
Copyright 2014
Daniel Prager
daniel.a.prager@gmail.com
This is a largely clean-room, functional implementation in Racket
of the game 2048 by Gabriele Cirulli, based on 1024 by Veewo Studio,
and conceptually similar to Threes by Asher Vollmer.
HOW TO PLAY
* Use your arrow keys to slide the tiles.
* When two tiles with the same number touch, they merge into one!
* Press <space> to rotate the board.
  1. lang racket

(require rackunit

        2htdp/image
        (rename-in 2htdp/universe
                   [left left-arrow]
                   [right right-arrow]
                   [up up-arrow]
                   [down down-arrow]))


(define *side* 4)  ; Side-length of the grid (define *time-limit* #f)  ; Use #f for no time limit, or number of seconds

(define *amber-alert* 60)  ; Time indicator goes orange when less than this number of seconds remaining (define *red-alert* 10)  ; Time indicator goes red when less than this number of seconds remaining

(define *tile-that-wins* 2048) ; You win when you get a tile = this number (define *magnification* 2)  ; Scales the game board

(define (set-side! n)

 (set! *side* n))
Numbers can be displayed with substiture text. Just edit this table...

(define *text*

 '((0 "")
   (2 "2")))
Color scheme
From https://github.com/gabrielecirulli/2048/blob/master/style/main.css

(define *grid-color* (color #xbb #xad #xa0))

(define *default-tile-bg-color* (color #x3c #x3a #x32)) (define *default-tile-fg-color* 'white)

(define *tile-bg-colors*

 (map (lambda (x)
        (match-define (list n r g b) x)
        (list n (color r g b)))
      '((0 #xcc #xc0 #xb3)
        (2 #xee #xe4 #xda)
        (4 #xed #xe0 #xc8)
        (8 #xf2 #xb1 #x79)
        (16 #xf5 #x95 #x63)
        (32 #xf6 #x7c #x5f)
        (64 #xf6 #x5e #x3b)
        (128 #xed #xcf #x72)
        (256 #xed #xcc #x61)
        (512 #xed #xc8 #x50)
        (1024 #xed #xc5 #x3f)
        (2048 #xed #xc2 #x2e))))

(define *tile-fg-colors*

 '((0 dimgray)
   (2 dimgray)
   (4 dimgray)
   (8 white)
   (16 white)
   (32 white)
   (64 white)
   (128 white)
   (256 white)
   (512 white)
   (1024 white)
   (2048 white)))
--------------------------------------------------------------------
Rows may be represented as lists, with 0s representing empty spots.

(define (nonzero? x) (not (zero? x)))

Append padding to lst to make it n items long

(define (pad-right lst padding n)

 (append lst (make-list (- n (length lst)) padding)))
Slide items towards the head of the list, doubling adjacent pairs
when no item is a 0.
E.g. (combine '(2 2 2 4 4)) -> '(4 2 8)

(define (combine lst)

 (cond [(<= (length lst) 1) lst]
       [(= (first lst) (second lst))
        (cons (* 2 (first lst)) (combine (drop lst 2)))]
       [else (cons (first lst) (combine (rest lst)))]))
Total of new elements introduced by combining.
E.g. (combine-total '(2 2 2 4 4)) -> 4 + 8 = 12

(define (combine-total lst)

 (cond [(<= (length lst) 1) 0]
       [(= (first lst) (second lst))
        (+ (* 2 (first lst)) (combine-total (drop lst 2)))]
       [else (combine-total (rest lst))]))
Slide towards the head of the list, doubling pairs, 0 are
allowed (and slid through), and length is preserved by
padding with 0s.
E.g. (slide-left '(2 2 2 0 4 4)) -> '(4 2 8 0 0 0)

(define (slide-left row)

 (pad-right (combine (filter nonzero? row)) 0 (length row)))
Slide towards the tail of the list
E.g. (slide-right '(2 2 0 0 4 4)) -> '(0 0 0 0 0 4 8)

(define (slide-right row) (reverse (slide-left (reverse row))))


--------------------------------------------------------------------
We use a sparse representation for transitions in a row.
Moves take the form '(value initial-position final-position)

(define (moves-row-left row [last #f] [i 0] [j -1])

 (if (null? row) 
     null
     (let ([head (first row)])
       (cond [(zero? head) (moves-row-left (rest row) last (add1 i) j)]
             [(equal? last head) 
              (cons (list head i j)
                    (moves-row-left (rest row) #f (add1 i) j))]
             [else (cons (list head i (add1 j))
                         (moves-row-left (rest row) head (add1 i) (add1 j)))]))))
Convert a row into the sparse representaiton without any sliding.
E.g. (moves-row-none '(0 2 0 4)) -> '((2 1 1) (4 3 3))

(define (moves-row-none row)

 (for/list ([value row]
            [i (in-naturals)]
            #:when (nonzero? value))
   (list value i i)))
Reverse all moves so that
'(value initial final) -> '(value (- n initial 1) (- n final 1)

(define (reverse-moves moves n)

 (define (flip i) (- n i 1))
 (map (λ (m)
        (match-define (list a b c) m)
        (list a (flip b) (flip c)))
      moves))

(define (transpose-moves moves)

 (for/list ([m moves])
   (match-define (list v (list a b) (list c d)) m)
   (list v (list b a) (list d c))))

(define (moves-row-right row [n *side*])

 (reverse-moves (moves-row-left (reverse row)) n))
--------------------------------------------------------------------
Lift the sparse representation for transitions
up to two dimensions...
'(value initial final) -> '(value (x initial) (x final))

(define (add-row-coord i rows)

 (for/list ([r rows])
   (match-define (list a b c) r)
   (list a (list i b) (list i c))))

(define (transpose lsts)

 (apply map list lsts))
Slide the entire grid in the specified direction

(define (left grid)

 (map slide-left grid))

(define (right grid)

 (map slide-right grid))

(define (up grid)

 ((compose transpose left transpose) grid))

(define (down grid)

 ((compose transpose right transpose) grid))
Calculate the change to score from sliding the grid left or right.

(define (score-increment grid)

 (apply + (map (λ (row) 
                 (combine-total (filter nonzero? row)))
               grid)))
Slide the grid in the specified direction and
determine the transitions of the tiles.
We'll use these operations to animate the sliding of the tiles.

(define (moves-grid-action grid action)

 (let ([n (length (first grid))])
   (apply append
          (for/list ([row grid]
                     [i (in-range n)])
            (add-row-coord i (action row))))))

(define (moves-grid-left grid)

 (moves-grid-action grid moves-row-left))

(define (moves-grid-right grid)

 (moves-grid-action grid moves-row-right))

(define (moves-grid-up grid)

 ((compose transpose-moves moves-grid-left transpose) grid))

(define (moves-grid-down grid)

 ((compose transpose-moves moves-grid-right transpose) grid))
Rotating the entire grid doesn't involve sliding.
It's a convenience to allow the player to view the grid from a different
orientation.

(define (moves-grid-rotate grid)

 (let ([n (length (first grid))])
   (for/list ([item (moves-grid-action grid moves-row-none)])
     (match-define (list v (list i j) _) item)
     (list v (list i j) (list j (- n i 1))))))
Chop a list into a list of sub-lists of length n. Used to move from
a flat representation of the grid into a list of rows.

(define (chop lst [n *side*])

 (if (<= (length lst) n) 
     (list lst)
     (cons (take lst n) (chop (drop lst n) n)))) 
The next few functions are used to determine where to place a new
number in the grid...
How many zeros in the current state?

(define (count-zeros state)

 (length (filter zero? state)))
What is the absolute index of the nth zero in lst?
E.g. (index-of-nth-zero '(0 2 0 4) 1 2)) 1) -> 2

(define (index-of-nth-zero lst n)

 (cond [(null? lst) #f]
       [(zero? (first lst)) 
        (if (zero? n)
            0
            (add1 (index-of-nth-zero (rest lst) (sub1 n))))]
       [else (add1 (index-of-nth-zero (rest lst) n))]))
Place the nth zero in the lst with val.
E.g. (replace-nth-zero '(0 2 0 4) 1 2)) -> '(0 2 2 4)

(define (replace-nth-zero lst n val)

 (let ([i (index-of-nth-zero lst n)])
   (append (take lst i) (cons val (drop lst (add1 i))))))
There's a 90% chance that a new tile will be a two; 10% a four.

(define (new-tile)

 (if (> (random) 0.9) 4 2))
Create a random initial game-board with two non-zeros (2 or 4)
and the rest 0s.
E.g. '(0 0 0 0
0 2 0 0
2 0 0 0
0 0 0 0)

(define (initial-state [side *side*])

 (shuffle (append (list (new-tile) (new-tile))
                  (make-list (- (sqr side) 2) 0))))
The game finishes when no matter which way you slide, the board doesn't
change.

(define (finished? state [n *side*])

 (let ([grid (chop state n)])
   (for/and ([op (list left right up down)])
     (equal? grid (op grid)))))
--------------------------------------------------------------------
Graphics

(define *text-size* 30) (define *max-text-width* 40) (define *tile-side* 50) (define *grid-spacing* 5) (define *grid-side* (+ (* *side* *tile-side*)

                      (* (add1 *side*) *grid-spacing*)))
Memoization - caching images takes the strain off the gc

(define-syntax define-memoized

 (syntax-rules ()
   [(_ (f args ...) bodies ...)
    (define f
      (let ([results (make-hash)])
        (lambda (args ...)
          ((λ vals
             (when (not (hash-has-key? results vals))
               (hash-set! results vals (begin bodies ...)))
             (hash-ref results vals))
           args ...))))]))
Look-up the (i,j)th element in the flat representation.

(define (square/ij state i j)

 (list-ref state (+ (* *side* i) j)))
Linear interpolation between a and b
(interpolate 0.0 a b) -> a
(interpolate 1.0 a b) -> b

(define (interpolate k a b)

 (+ (* (- 1 k) a)
    (* k b)))
Key value lookup with default return - is there an out-of-the-box function
for this?

(define (lookup key lst default)

 (let ([value (assoc key lst)])
   (if value (second value) default)))


Make a tile without a number on it in the appropriate color.

(define (plain-tile n)

 (square *tile-side* 
         'solid 
         (lookup n *tile-bg-colors* *default-tile-bg-color*)))
Make text for a tile

(define (tile-text n)

 (let* ([t (text (lookup n *text* (number->string n))
                 *text-size*
                 (lookup n *tile-fg-colors* *default-tile-fg-color*))]
        [side (max (image-width t) (image-height t))])
   (scale (if (> side *max-text-width*) (/ *max-text-width* side) 1) t)))

(define-memoized (make-tile n)

 (overlay
  (tile-text n)
  (plain-tile n)))
Place a tile on an image of the grid at (i,j)

(define (place-tile/ij tile i j grid-image)

 (define (pos k)
   (+ (* (add1 k) *grid-spacing*)
      (* k *tile-side*)))
 (underlay/xy grid-image (pos j) (pos i) tile))
Make an image of the grid from the flat representation

(define *last-state* null) ; Cache the previous grid to avoid (define *last-grid* null)  ; senseless regeneration

(define (state->image state)

 (unless (equal? state *last-state*)
   (set! *last-grid*
         (for*/fold ([im (square *grid-side* 'solid *grid-color*)])
           ([i (in-range *side*)]
            [j (in-range *side*)])
           (place-tile/ij (make-tile (square/ij state i j))
                          i j
                          im)))
   (set! *last-state* state))
 *last-grid*)

(define *empty-grid-image*

 (state->image (make-list (sqr *side*) 0)))
Convert the sparse representation of moves into a single frame in an
animation at time k, where k is between 0.0 (start state) and 1.0
(final state).

(define (moves->frame moves k)

 (for*/fold ([grid *empty-grid-image*])
   ([m moves])
   (match-define (list value (list i1 j1) (list i2 j2)) m)
   (place-tile/ij (make-tile value)
                  (interpolate k i1 i2) (interpolate k j1 j2)
                  grid)))
Animation of simultaneously moving tiles.

(define (animate-moving-tiles state op)

 (let ([grid (chop state)])
   (build-list 9 (λ (i) 
                   (λ () 
                     (moves->frame (op grid) 
                                   (* 0.1 (add1 i))))))))
Animation of a tile appearing in a previously blank square.

(define (animate-appearing-tile state value index)

 (let ([start (state->image state)]
       [tile (make-tile value)]
       [i (quotient index *side*)]
       [j (remainder index *side*)])
   (build-list 4 (λ (m) 
                   (λ () 
                     (place-tile/ij (overlay 
                                     (scale (* 0.2 (add1 m)) tile)
                                     (plain-tile 0))
                                    i j
                                    start))))))
--------------------------------------------------------------
The Game
an image-procedure is a procedure of no arguments that produces an image
a world contains
state is a ?
score is a number
winning-total is #f or a number, representing the final score <-- is this
necessary?
frames is a (list-of image-procedure)
start-time is a number, in seconds

(define-struct world (state score winning-total frames start-time) #:transparent)

The game is over when any animations have been finished and
no more moves are possible.
note that winning the game does *not* end the game.

(define (game-over? w)

 (match-define (world state score wt frames start-time) w)
 (and (null? frames) ; Finish animations to reach final state and show the banner
      (or (finished? state)
          (out-of-time? (world-start-time w)))))
Is the player out of time?

(define (out-of-time? start-time)

 (and *time-limit* (< (+ start-time *time-limit*) (current-seconds))))
Given an arrow key return the operations to change the state and
produce the sliding animation.

(define (key->ops a-key)

 (cond
   [(key=? a-key "left")  (list left moves-grid-left)]
   [(key=? a-key "right") (list right moves-grid-right)]
   [(key=? a-key "up")    (list up moves-grid-up)]
   [(key=? a-key "down")  (list down moves-grid-down)]
   [else (list #f #f)]))
Respond to a key-press

(define (change w a-key)

 (match-let ([(list op moves-op) (key->ops a-key)]
             [(world st score wt frames start-time) w])
   (cond [(out-of-time? start-time) w] ; Stop accepting key-presses 
         [op
          (let* ([grid (chop st)]
                 [slide-state (flatten (op grid))])
            (if (equal? slide-state st)
                w                       ; sliding had no effect
                (let* ([replace (random (count-zeros slide-state))]
                       [index (index-of-nth-zero slide-state replace)]
                       [value (new-tile)]
                       [new-state (replace-nth-zero slide-state replace value)]
                       [horizontal? (member a-key (list "left" "right"))])
                  (make-world new-state
                              (+ score (score-increment 
                                        (if horizontal? grid (transpose grid))))
                              (cond [wt wt]
                                    [(won-game? new-state) 
                                     (apply + (flatten new-state))]
                                    [else #f])
                              (append frames
                                      (animate-moving-tiles st moves-op)
                                      (animate-appearing-tile slide-state value index))
                              start-time))))]
         [(key=? a-key " ")             ; rotate the board
          (make-world ((compose flatten transpose reverse) (chop st))
                      score wt
                      (append frames
                              (animate-moving-tiles st moves-grid-rotate))
                      start-time)] 
         [else w])))                    ; unrecognised key - no effect
Are we there yet?

(define (won-game? state)

 (= (apply max state) *tile-that-wins*))
Banner overlay text
e.g. You won! / Game Over, etc.

(define (banner txt state [color 'black])

 (let ([b-text (text txt 30 color)])
   (overlay
    b-text
    (rectangle (* 1.2 (image-width b-text))
               (* 1.4 (image-height b-text))
               'solid 'white)
    (state->image state))))
Convert number of seconds to "h
mm
ss" or "m:ss" format

(define (number->time-string s)

 (define hrs (quotient s 3600))
 (define mins (quotient (remainder s 3600) 60))
 (define secs (remainder s 60))
 (define (xx n)
   (cond [(<= n 0) "00"]
         [(<= n 9) (format "0~a" n)]
         [else (remainder n 60)]))
 (if (>= s 3600)
     (format "~a:~a:~a" hrs (xx mins) (xx secs))
     (format "~a:~a" mins (xx secs))))
     

(define (time-remaining start)

 (+ *time-limit* start (- (current-seconds))))

(define (time-elapsed start)

 (- (current-seconds) start))
Display the grid with score below.
If there are frames, show the next one. Otherwise show the steady state.

(define (show-world w)

 (match-define (world state score wt frames start-time) w)
 (let* ([board (if (null? frames)
                   (cond [(finished? state) (banner "Game over" state)]
                         [(out-of-time? start-time) (banner "Out of Time" state 'red)]
                         
                         ;; Q: Why wt (i.e. winning-total) rather than won-game? 
                         ;; A: wt allows the keen player to continue playing...
                         [(equal? (apply + (flatten state)) wt) (banner "You won!" state)]
                         [else (state->image state)])
                   ((first frames)))]
        [score-text (text (format "Score: ~a" score) 16 'dimgray)]
        [seconds ((if *time-limit* time-remaining time-elapsed) start-time)]
        [time-text (text (format "Time: ~a" 
                                 (number->time-string seconds)) 
                         16
                         (cond [(or (> seconds *amber-alert*) (not *time-limit*)) 'gray]
                               [(> seconds *red-alert*) 'orange] 
                               [else 'red]))])        
   (scale *magnification*
          (above
           board
           (rectangle 0 5 'solid 'white)
           (beside
            score-text
            (rectangle (- (image-width board)
                          (image-width score-text)
                          (image-width time-text)) 0 'solid 'white)
            time-text)))))
Move to the next frame in the animation.

(define (advance-frame w)

 (match-define (world state score wt frames start-time) w)
 (if (null? frames)
     w
     (make-world state score wt (rest frames) start-time)))
Use this state to preview the appearance of all the tiles

(define (all-tiles-state)

 (let ([all-tiles '(0 2 4 8 16 32 64 128 256 512 1024 2048 4096)])
   (append all-tiles (make-list (- (sqr *side*) (length all-tiles)) 0))))
The event loop

(define (start)

 (big-bang (make-world (initial-state) 
                       ;(all-tiles-state) 
                       0 #f null (current-seconds))
           (to-draw show-world)
           (on-key change)
           (on-tick advance-frame 0.01)
           (stop-when game-over? show-world)
           (name "2048 - Racket edition")))
TESTS

(module+ test

 (set-side! 4)
 
 (check-equal? (slide-left '(0 0 0 0)) '(0 0 0 0))
 (check-equal? (slide-left '(1 2 3 4)) '(1 2 3 4))
 (check-equal? (slide-left '(2 0 4 0)) '(2 4 0 0))
 (check-equal? (slide-left '(0 0 2 4)) '(2 4 0 0))
 (check-equal? (slide-left '(2 0 2 0)) '(4 0 0 0))
 (check-equal? (slide-left '(0 8 8 0)) '(16 0 0 0))
 (check-equal? (slide-left '(4 4 8 8)) '(8 16 0 0))
 (check-equal? (slide-right '(4 4 8 8)) '(0 0 8 16))
 (check-equal? (slide-right '(4 4 4 0)) '(0 0 4 8))
 
 (check-equal? (moves-row-left '(0 0 0 0)) '())
 (check-equal? (moves-row-left '(1 2 3 4)) 
               '((1 0 0)
                 (2 1 1)
                 (3 2 2)
                 (4 3 3)))
 
 (check-equal? (moves-row-left '(2 0 4 0)) '((2 0 0)
                                             (4 2 1)))
 
 (check-equal? (moves-row-right '(2 0 4 0)) '((4 2 3)
                                              (2 0 2)))
 
 (check-equal? (moves-row-left '(0 0 2 4)) '((2 2 0)
                                             (4 3 1)))
 
 (check-equal? (moves-row-left '(2 0 2 0)) '((2 0 0)
                                             (2 2 0)))
 
 (check-equal? (moves-row-left '(2 2 2 0)) '((2 0 0)
                                             (2 1 0)
                                             (2 2 1)))
 
 (check-equal? (moves-row-right '(2 2 2 0)) '((2 2 3)
                                              (2 1 3)
                                              (2 0 2)))
 
 (check-equal? (moves-row-left '(2 2 4 4)) '((2 0 0)
                                             (2 1 0)
                                             (4 2 1)
                                             (4 3 1)))
 
 (check-equal? (moves-row-right '(2 2 4 4)) '((4 3 3)
                                              (4 2 3)
                                              (2 1 2)
                                              (2 0 2)))
 
 (check-equal? (add-row-coord 7 '((2 0 0)
                                  (2 1 0)
                                  (4 2 1)))
               '((2 (7 0) (7 0))
                 (2 (7 1) (7 0))
                 (4 (7 2) (7 1))))
 
 (check-equal? (left '(( 0 8 8 0)
                       (16 0 0 0)
                       ( 2 2 4 4)
                       ( 0 2 2 2)))
               '((16 0 0 0)
                 (16 0 0 0)
                 ( 4 8 0 0)
                 ( 4 2 0 0)))
 (check-equal? (right '(( 0 8 8 0)
                        (16 0 0 0)
                        ( 2 2 4 4)
                        ( 0 2 2 2)))
               '((0 0 0 16)
                 (0 0 0 16)
                 (0 0 4  8)
                 (0 0 2  4)))
 (check-equal? (up '((0 16 2 0) 
                     (8  0 2 2) 
                     (8  0 4 2) 
                     (0  0 4 2)))
               '((16 16 4 4) 
                 (0  0  8 2) 
                 (0  0  0 0) 
                 (0  0  0 0)))
 (check-equal? (down '((0 16 2 0) 
                       (8  0 2 2) 
                       (8  0 4 2) 
                       (0  0 4 2)))
               '((0  0  0 0) 
                 (0  0  0 0) 
                 (0  0  4 2) 
                 (16 16 8 4)))
 
 (check-equal? (left '(( 0 8 8 0)
                       (16 0 0 0)
                       ( 2 2 4 4)
                       ( 0 2 2 2)))
               '((16 0 0 0)
                 (16 0 0 0)
                 ( 4 8 0 0)
                 ( 4 2 0 0)))
 
 (check-equal? (moves-grid-left '(( 0 8 8 0)
                                  (16 0 0 0)
                                  ( 2 2 4 4)
                                  ( 0 2 2 2)))
               '((8  (0 1) (0 0))
                 (8  (0 2) (0 0))
                 (16 (1 0) (1 0))
                 (2  (2 0) (2 0))
                 (2  (2 1) (2 0))
                 (4  (2 2) (2 1))
                 (4  (2 3) (2 1))
                 (2  (3 1) (3 0))
                 (2  (3 2) (3 0))
                 (2  (3 3) (3 1))))
 
 (check-equal? (moves-grid-right '(( 0 8 8 0)
                                   (16 0 0 0)
                                   ( 2 2 4 4)
                                   ( 0 2 2 2)))
               '((8  (0 2) (0 3))
                 (8  (0 1) (0 3))
                 (16 (1 0) (1 3))
                 (4  (2 3) (2 3))
                 (4  (2 2) (2 3))
                 (2  (2 1) (2 2))
                 (2  (2 0) (2 2))
                 (2  (3 3) (3 3))
                 (2  (3 2) (3 3))
                 (2  (3 1) (3 2))))
 
 
 (check-equal? (moves-grid-up '(( 0 8 8 0)
                                (16 0 0 0)
                                ( 2 2 4 4)
                                ( 0 2 2 2)))
               '((16 (1 0) (0 0))
                 (2  (2 0) (1 0))
                 (8  (0 1) (0 1))
                 (2  (2 1) (1 1))
                 (2  (3 1) (1 1))
                 (8  (0 2) (0 2))
                 (4  (2 2) (1 2))
                 (2  (3 2) (2 2))
                 (4  (2 3) (0 3))
                 (2  (3 3) (1 3))))
 
 (check-equal? (moves-grid-down '(( 0 8 8 0)
                                  (16 0 0 0)
                                  ( 2 2 4 4)
                                  ( 0 2 2 2)))
               '((2  (2 0) (3 0))
                 (16 (1 0) (2 0))
                 (2  (3 1) (3 1))
                 (2  (2 1) (3 1))
                 (8  (0 1) (2 1))
                 (2  (3 2) (3 2))
                 (4  (2 2) (2 2))
                 (8  (0 2) (1 2))
                 (2  (3 3) (3 3))
                 (4  (2 3) (2 3)))) 
 
 (check-equal? (chop '(1 2 3 4 5 6 7 8) 4)
               '((1 2 3 4) (5 6 7 8)))
 
 (check-equal? (length (initial-state 5)) 25)
 
 (let* ([initial (initial-state)]
        [initial-sum (apply + initial)]
        [largest-3 (take (sort initial >) 3)])
   (check-equal? (length initial) 16)
   (check-true (or (= initial-sum 4)
                   (= initial-sum 6)
                   (= initial-sum 8)))
   (check-true (or (equal? largest-3  '(2 2 0))
                   (equal? largest-3  '(4 2 0))
                   (equal? largest-3  '(4 4 0)))))
 
 (check-equal? (count-zeros '(1 0 1 0 0 0 1)) 4)
 (check-equal? (count-zeros '(1 1)) 0)
 (check-equal? (replace-nth-zero '(0 0 0 1 2 0) 2 5)
               '(0 0 5 1 2 0))
 
 (check-true (finished? '(1 2 3 4) 2))
 (check-false (finished? '(2 2 3 4) 2)))

(start) </lang>

Raku

(formerly Perl 6) Uses termios to set the terminal options, so only compatible with POSIX terminals. This version does not include a specific "win" or "lose" condition. (though it would be trivial to add them.) You can continue to play this even after getting a 2048 tile; and if there is no valid move you can make, you can't do anything but quit.

Works with: Rakudo version 2018.05

<lang perl6>use Term::termios;

constant $saved = Term::termios.new(fd => 1).getattr; constant $termios = Term::termios.new(fd => 1).getattr;

  1. raw mode interferes with carriage returns, so
  2. set flags needed to emulate it manually

$termios.unset_iflags(<BRKINT ICRNL ISTRIP IXON>); $termios.unset_lflags(< ECHO ICANON IEXTEN ISIG>); $termios.setattr(:DRAIN);

  1. reset terminal to original setting on exit

END { $saved.setattr(:NOW) }

constant n = 4; # board size constant cell = 6; # cell width constant ansi = True; # color!

my @board = ( [ xx n] xx n ); my $save = ; my $score = 0;

constant $top = join '─' x cell, '┌', '┬' xx n-1, '┐'; constant $mid = join '─' x cell, '├', '┼' xx n-1, '┤'; constant $bot = join '─' x cell, '└', '┴' xx n-1, '┘';

my %dir = (

  "\e[A" => 'up',
  "\e[B" => 'down',
  "\e[C" => 'right',
  "\e[D" => 'left',

);

my @ANSI = <0 1;97 1;93 1;92 1;96 1;91 1;95 1;94 1;30;47 1;43

   1;42 1;46 1;41 1;45 1;44 1;33;43 1;33;42 1;33;41 1;33;44>;

sub row (@row) { '│' ~ (join '│', @row».&center) ~ '│' }

sub center ($s){

   my $c   = cell - $s.chars;
   my $pad = ' ' x ceiling($c/2);
   my $tile = sprintf "%{cell}s", "$s$pad";
   my $idx = $s ?? $s.log(2) !! 0;
   ansi ?? "\e[{@ANSI[$idx]}m$tile\e[0m" !! $tile;

}

sub draw-board {

   run('clear');
   print qq:to/END/;


Press direction arrows to move.

Press q to quit.

$top { join "\n\t$mid\n\t", map { .&row }, @board } $bot

Score: $score

END }

sub squash (@c) {

   my @t = grep { .chars }, @c;
   map { combine(@t[$_], @t[$_+1]) if @t[$_] && @t[$_+1] == @t[$_] }, ^@t-1;
   @t = grep { .chars }, @t;
   @t.push:  while @t < n;
   @t;

}

sub combine ($v is rw, $w is rw) { $v += $w; $w = ; $score += $v; }

proto sub move (|) {*};

multi move('up') {

   map { @board[*;$_] = squash @board[*;$_] }, ^n;

}

multi move('down') {

   map { @board[*;$_] = reverse squash reverse @board[*;$_] }, ^n;

}

multi move('left') {

   map { @board[$_] = squash @board[$_] }, ^n;

}

multi move('right') {

   map { @board[$_;*] = reverse squash reverse @board[$_] }, ^n;

}

sub another {

   my @empties;
   for @board.kv -> $r, @row {
       @empties.push(($r, $_)) for @row.grep(:k, );
   }
   my ( $x, $y ) = @empties.roll;
   @board[$x; $y] = (flat 2 xx 9, 4).roll;

}

sub save () { join '|', flat @board».list }

loop {

   another if $save ne save();
   draw-board;
   $save = save();
   # Read up to 4 bytes from keyboard buffer.
   # Page navigation keys are 3-4 bytes each.
   # Specifically, arrow keys are 3.
   my $key = $*IN.read(4).decode;
   move %dir{$key} if so %dir{$key};
   last if $key eq 'q'; # (q)uit

}</lang> Sample output:


	Press direction arrows to move.

	Press q to quit. 

	┌──────┬──────┬──────┬──────┐
	│  4   │  2   │      │      │
	├──────┼──────┼──────┼──────┤
	│  16  │  8   │      │      │
	├──────┼──────┼──────┼──────┤
	│  64  │  32  │  16  │      │
	├──────┼──────┼──────┼──────┤
	│ 128  │ 512  │ 128  │  64  │
	└──────┴──────┴──────┴──────┘

	Score: 6392

Red

Works with: Red version 0.6.4

<lang Red>Red [Needs: 'View]

random/seed now board: random [2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

----------- move engine -----------

tasse: function [b] [

   forall b [while [b/1 = 0] [remove b]]
   head append/dup b 0 4 - length? b

] somme: function [b] [

   tasse b
   repeat n 3 [
       m: n + 1
       if all [b/:n <> 0 b/:n = b/:m] [
           poke b n b/:n * 2
           poke b m 0
       ]
   ]
   tasse b

] reshape: function [b d][

   res: copy []
   switch d [
       up    [repeat n 4 [extract/index/into b 4 n res] res]
       down  [repeat n 4 [extract/index/into b 4 n res] reverse res]
       left [res: copy b]
       right  [res: reverse copy b]
   ]

] mov: function [b d][

   b1: reshape b d
   moved: copy []
   foreach [x y z t] b1 [append moved somme reduce [x y z t]]
   reshape moved d

]

--------- GUI ---------

colors: [0 gray 2 snow 4 linen 8 brick 16 brown 32 sienna

   64 water 128 teal 256 olive 512 khaki 1024 tanned 2028 wheat]

tsize: 110x110 padsize: 4x4 padlen: 114 mainsize: tsize * 4 + (padsize * 5) tfont: make font! [size: 30 color: black style: 'bold]

display: does [

   foreach face lay/pane [
       n: face/data
       face/text: either board/:n = 0 [""] [form board/:n]
       face/color: reduce select colors board/:n
   ]

] lay: layout [

   size mainsize
   title "2048 game"
   backdrop white
   on-key [ 
       if find [up down left right] d: event/key [            
           if board <> newboard: mov board d [
               board: newboard
               if find board 2048 [alert "You win!"]
               until [
                   pos: random 16
                   0 = board/:pos
               ]
               poke board pos either 1 = random 10 [4] [2]
               display
               conds: reduce [not find board 0]
               foreach d [up down left right] [
                   append conds board = mov board d
               ]
               if all conds [alert "You lose!"]
           ]
       ]
   ]
   space padsize

] repeat n length? board [append lay/pane make face! [

   type: 'base
   offset: padsize + padlen * as-pair (n - 1 % 4) (n - 1 / 4)
   size: tsize
   color: reduce select colors board/:n
   data: n
   font: tfont

]] display view lay </lang>

Output:

graphical game interface

REXX

This REXX version has the features:

  •   allows specification of N,   the size of the grid   (default is 4).
  •   allows specification of the winning number   (default is 2048)
  •   allows specification for the random BIF's seed   (no default).
  •   allows abbreviations for the directions   (Up, Down, Left, Right).
  •   allows the player to quit the game at any time.
  •   clears the screen if a legal move is in upper case.
  •   does error checking/validation for entered directions   (in response to the prompt).
  •   keeps track of the number of legal moves made and the score.
  •   displays the number of moves and the score   (when a blank is entered).
  •   displays an error message if a move doesn't do anything.
  •   displays a message if a winning move was entered.
  •   displays the game board as a grid   (with boxes).

<lang rexx>/*REXX program lets a user play the 2048 game on an NxN grid (default is 4x4 grid).*/ parse arg N win seed . /*obtain optional arguments from the CL*/ if N== | N=="," then N= 4 /*Not specified? Then use the default.*/ if win== | win=="," then win= 2**11 /* " " " " " " */ if datatype(seed, 'W') then call random ,,seed /*Specified? Then use seed for RANDOM.*/ L= length(win) + 2 /*L: used for displaying the grid #'s.*/ eye=copies("─", 8); pad=left(, length(eye)+2) /*eye catchers; and perusable perusing.*/ b= ' ' /*comfortable readable name for a blank*/ @cls= 'CLS' /*hardcoded command to clear the screen*/ prompt= eye "Please enter a direction (Up, Down, Right, Left) ───or─── Quit:" move= 1; moves= 0; score= 0; ok= 1 /*simulation that a move was performed.*/ @.= b /*define all grid elements to a blank. */

    do  until any(win);  if ok  then call put;   ok= 1;   say;    call showGrid
    say;  say prompt;   parse pull a x . 1 d 2 1 way xx   /*show prompt; obtain answer.*/
    if datatype(a, 'U')     then @cls           /*if uppercase, then clear the screen. */
    if a==  then do;  ok= 0                   /*the user entered blank(s) or nothing.*/
                        say copies(eye, 5)   'moves:'   moves    eye     "score:"   score
                        iterate                 /* [↑]  display # of moves & the score.*/
                   end
    upper d a x                                 /*uppercase contents of three variables*/
    if x\==               then call err  "too many arguments entered: "   xx
    if abbrev('QUIT',a,1)   then do;  say;  say eye  "quitting the game".;  exit 1;   end
    good=abbrev('UP',a,1) | abbrev("DOWN",a,1) | abbrev('RIGHT',a,1) | abbrev("LEFT",a,1)
    if \good                then call err  "invalid direction: "       way
    if \ok  then  iterate;       moves= moves + 1;                call mov
    end   /*until*/

say say translate(eye "Congrats!! You've won the" win 'game!' eye,"═",'─') "score:" score exit 0 /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ @: procedure expose @.; parse arg row,col; return @.row.col any: arg ?; do r=1 for N; do c=1 for N; if @.r.c==? then return 1; end; end; return 0 err: say; say eye '***error*** ' arg(1); say; ok=0; return o_c: $=; do k=1 for N; $=$ word(@.k.c .,1); end;  !=space(translate($,,.))==; return $ o_r: $=; do k=1 for N; $=$ word(@.r.k .,1); end;  !=space(translate($,,.))==; return $ put: if \any(b) then call err ,"game over, no more moves."; if move then call two; return row: if r==0 | r>N then return copies('═', L); return center(@.r.c, L) ten: if random(9)==4 then return 4; return 2 /*10% of the time, use 4 instead of 2.*/ two: do until @.p.q==b; p= random(1,N); q= random(1,N); end; @.p.q= ten(); return /*──────────────────────────────────────────────────────────────────────────────────────*/ showGrid: do r=0 for N+2; _= '║'; __= "╠"

                do c=1  for N;      _= _  ||  row()'║';     __= __  ||  copies("═", L)'╬'
                end   /*c*/
             if r==0  then _= '╔'translate( substr(_, 2, length(_) - 2),  "╦",  '║')"╗"
             if r >N  then _= '╚'translate( substr(_, 2, length(_) - 2),  "╩",  '║')"╝"
                                say pad _
             if r<N & r>0  then say pad substr(__, 1, length(__) - 1)"╣"
             end      /*r*/;        return

/*──────────────────────────────────────────────────────────────────────────────────────*/ mov: move= 0; if d=='R' then call moveLR N, 1, -1 /*move (slide) numbers ► */

                if d=='L'  then call moveLR 1, N, +1    /*  "     "       "       ◄    */
                if d=='U'  then call moveUD 1, N, +1    /*  "     "       "       ↑    */
                if d=='D'  then call moveUD N, 1, -1    /*  "     "       "       ↓    */
    if \move  then call err 'moving '    way    " doesn't change anything.";       return

/*──────────────────────────────────────────────────────────────────────────────────────*/ moveLR: parse arg start, sTo, # /*slide ◄ or ► */

         do   r=1  for N;    old= o_r();      if !  then iterate /*is this row blank?  */
           do N-1;           call packLR                         /*pack    ◄  or   ►   */
           end        /*N-1*/                                    /* [↓]  get new tiles.*/
         new= o_r();              move= move | (old\==new)       /*indicate tiles moved*/
             do c=start  for N-1  by #  while @.r.c\==b          /*slide   ◄  or   ►   */
             if @.r.c\==@(r,c+#)  then iterate                   /*not a duplicate ?   */
             @.r.c= @.r.c * 2;    score= score + @.r.c           /*double;  bump score */
             c= c + #        ;    @.r.c= b;         move= 1      /*bump C; blank dup 2.*/
             end      /*c*/                                      /* [↑]  indicate move.*/
         call packLR                                             /*pack    ◄  or    ►  */
         end          /*r*/;                        return

/*──────────────────────────────────────────────────────────────────────────────────────*/ moveUD: parse arg start, Sto, # /*slide ↑ or ↓ */

         do   c=1  for N;    old= o_c();      if !  then iterate /*is this col blank?  */
           do N-1;           call packUD                         /*pack  up or down.   */
           end        /*N-1*/                                    /* [↓]  get new tiles.*/
         new= o_c();              move= move | (old\==new)       /*indicate tiles moved*/
             do r=start  for N-1  by #  while @.r.c\==b          /*slide   ↑   or   ↓  */
             if @.r.c\==@(r+#,c)  then iterate                   /*not a duplicate ?   */
             @.r.c= @.r.c * 2;    score= score + @.r.c           /*double;  bump score */
             r= r + #        ;    @.r.c= b;         move= 1      /*bump R; blank dup 2.*/
             end      /*r*/                                      /* [↑]  indicate move.*/
         call packUD                                             /*pack    ↑   or   ↓  */
         end          /*c*/;                        return

/*──────────────────────────────────────────────────────────────────────────────────────*/ packLR: do c=start for N-1 by #; if @.r.c\==b then iterate /*Not a blank? Skip. */

            do s=c  to sTo  by #;     @.r.s= @(r, s + #)         /*slide   ◄   or   ►  */
            end   /*s*/;              @.r.sTo= b                 /*handle the last one.*/
         end      /*c*/;                            return

/*──────────────────────────────────────────────────────────────────────────────────────*/ packUD: do r=start for N-1 by #; if @.r.c\==b then iterate /*Not a blank? Skip. */

            do s=r  to sTo  by #;     @.s.c= @(s + #, c)         /*slide   ↑   or   ↓  */
            end   /*s*/;              @.sTo.c= b                 /*handle the last one.*/
         end      /*r*/;                            return</lang>

Programming note:   with a little more program complexity,   the   moveLR   and   moveUD   subroutines could've
been combined,   as well as the   packLR   and   packUD   subroutines.


output   when using the default inputs:
           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
right                                         ◄■■■■■■■■■■■■■ user input

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
up                                            ◄■■■■■■■■■■■■■ user input

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║  4   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
                                              ◄■■■■■■■■■■■■■ user input (a blank)
──────────────────────────────────────── moves: 2 ──────── score: 4

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║  4   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
left                                          ◄■■■■■■■■■■■■■ user input 

           ╔══════╦══════╦══════╦══════╗
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
l                                             ◄■■■■■■■■■■■■■ user input

           ╔══════╦══════╦══════╦══════╗
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║  4   ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
dow                                           ◄■■■■■■■■■■■■■ user input

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║  4   ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
left                                          ◄■■■■■■■■■■■■■ user input

           ╔══════╦══════╦══════╦══════╗
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
lef                                           ◄■■■■■■■■■■■■■ user input

           ╔══════╦══════╦══════╦══════╗
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
d                                             ◄■■■■■■■■■■■■■ user input

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║  2   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
d                                             ◄■■■■■■■■■■■■■ user input 

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║  2   ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║  4   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
                                              ◄■■■■■■■■■■■■■ user input (a blank)
──────────────────────────────────────── moves: 9 ──────── score: 32

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║  2   ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║  4   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
q                                             ◄■■■■■■■■■■■■■ user input

──────── quitting the game

Ring

<lang ring>

  1. Project : 2048 Game

load "stdlib.ring" load "guilib.ring"

C_GAMETITLE = '2048 Game' C_WINDOWBACKGROUND = "background-color: gray;" if isMobile() C_LABELFONTSIZE = "font-size:120px;" C_BUTTONFONTSIZE = "font-size:160px;" else C_LABELFONTSIZE = "font-size:50px;" C_BUTTONFONTSIZE = "font-size:80px;" ok C_PLAYERSCORESTYLE = "color:white;background-color:rgb(50,50,50);border-radius:17px;" + C_LABELFONTSIZE C_NEWGAMESTYLE = 'color:white;background-color:rgb(50,50,50);border-radius:17px;' + C_LABELFONTSIZE C_EMPTYBUTTONSTYLE = 'border-radius:17px;background-color:silver;' + C_BUTTONFONTSIZE C_BUTTON2STYLE = 'border-radius:17px;color:black; background-color: yellow ;' + C_BUTTONFONTSIZE C_BUTTON4STYLE = 'border-radius:17px;color:black; background-color: violet ;' + C_BUTTONFONTSIZE C_BUTTON8STYLE = 'border-radius:17px;color:white; background-color: purple ;' + C_BUTTONFONTSIZE C_BUTTON16STYLE = 'border-radius:17px;color:white; background-color: blue ;' + C_BUTTONFONTSIZE C_BUTTON32STYLE = 'border-radius:17px;color:white; background-color: red ;' + C_BUTTONFONTSIZE C_BUTTON64STYLE = 'border-radius:17px;color:black; background-color: lightgray ;' + C_BUTTONFONTSIZE C_BUTTON128STYLE = 'border-radius:17px;color:black; background-color: white ;' + C_BUTTONFONTSIZE C_BUTTON256STYLE = 'border-radius:17px;color:white; background-color: black ;' + C_BUTTONFONTSIZE C_BUTTON512STYLE = 'border-radius:17px;color:white; background-color: Purple ;' + C_BUTTONFONTSIZE C_BUTTON1024STYLE = 'border-radius:17px;color:black; background-color: Yellow ;' + C_BUTTONFONTSIZE C_BUTTON2048STYLE = 'border-radius:17px;color:white; background-color: Green ;' + C_BUTTONFONTSIZE C_LAYOUTSPACING = 10 C_PLAYERSCORE = 'Player Score : '

size = 4 limit = 2 num = 0 flag = 0 x1 = 0 x2 = 0 y1 = 0 y2 = 0 nScore = 0 button = newlist(size,size) buttonsave = newlist(size,size) LayoutButtonRow = list(size+2) moveleft = [] moveright = [] moveup = [] movedown = [] myfilter2 = null myfilter3 = null winheight = 0 winwidth = 0

app = new qApp {

         StyleFusion()
         processevents()
         win = new qWidget() {
                 setWindowTitle(C_GAMETITLE)
                 setgeometry(100,100,600,700)
                 setminimumwidth(300)
                 setminimumheight(300)

if not isMobile() grabkeyboard() ok

                 setstylesheet(C_WINDOWBACKGROUND)
                 move(490,100) 
                 for n = 1 to size
                      for m = 1 to size
                           button[n][m] = new MyButton(win)
                      next
                 next
                 newgame = new qLabel(win)
                 playerscore = new qLabel(win)
                 myfilter3 = new qAllEvents(win) {
                 setMouseButtonPressEvent("pPress()")
                 setMouseButtonReleaseEvent("pRelease()")}
                 installeventfilter(myfilter3)
                 myfilter2 = new qAllEvents(win) {
                 setkeypressevent("keypress()") }
                 installeventfilter(myfilter2)
                 winwidth = win.width()
                 winheight = win.height()
                 for n = 1 to size + 2

LayoutButtonRow[n] = new QHBoxLayout() { setSpacing(C_LAYOUTSPACING) }

                 next
                 for n = 1 to size
                      for m = 1 to size
                           button[n][m] { temp = text() }
                           buttonsave[n][m] = temp
                           button[n][m] = new MyButton(win) {
                                                  setalignment(Qt_AlignHCenter | Qt_AlignVCenter)
                                                  setstylesheet(C_EMPTYBUTTONSTYLE)
                                                  show()
                                          }
                      next
                 next
                 for n = 1 to size
                      for m = 1 to size
                           LayoutButtonRow[n].AddWidget(button[m][n])
                           win.show()
                           temp = buttonsave[n][m]
                           button[n][m].settext(temp)
                      next
 		       LayoutButtonRow[n].setSpacing(C_LAYOUTSPACING)
                 next
                 playerscore {
                                   setGeometry(0,4*floor(winheight/6),winwidth,floor(winheight/6))
                                   setalignment(Qt_AlignHCenter | Qt_AlignVCenter)
                                   settext(C_PLAYERSCORE + nScore)

setStylesheet(C_PLAYERSCORESTYLE)

                                   show()

}

                 newgame  {
                                 setGeometry(0,5*floor(winheight/6),winwidth,floor(winheight/6))
                                 setalignment(Qt_AlignHCenter | Qt_AlignVCenter)
                                 setstylesheet(C_NEWGAMESTYLE)
                                 settext('New Game')
                                 myfilter4 = new qallevents(newgame)
                                 myfilter4.setMouseButtonPressEvent("pbegin()")
                                 installeventfilter(myfilter4)
                                 show()
                                 }
                 LayoutButtonRow[size+1].AddWidget(playerscore)
                 LayoutButtonRow[size+2].AddWidget(newgame)
                 LayoutButtonMain = new QVBoxLayout() {

setSpacing(C_LAYOUTSPACING) for n = 1 to size+2 AddLayout(LayoutButtonRow[n]) win.show() next } win.setLayout(LayoutButtonMain)

                 win.show()
                 pbegin()  
                 show()
        }
   exec()

}

func pPress()

       x1 = myfilter3.getglobalx()
       y1 = myfilter3.getglobaly()

func pRelease()

       x2 = myfilter3.getglobalx()
       y2 = myfilter3.getglobaly()
       difx = x2 - x1

dify = y2 - y1 if fabs(difx) > fabs(dify) if difx < 0 pleft() else pRight() ok else if dify < 0 pUp() else pDown() ok ok

func keypress()

       nKey = myfilter2.getkeycode() 
       switch nKey
                  on 16777234 pleft()
                  on 16777236 pright()
                  on 16777235 pup()
                  on 16777237 pdown()
       off

func pbegin()

      numbers = [['2','2'],['2','4']]
      randnew = newlist(2,2)
      for n = 1 to size
           for m = 1 to size
                button[n][m].setStylesheet(C_EMPTYBUTTONSTYLE)
                button[n][m].settext()
           next
       next
       while true
               rn1 = random(size - 1) + 1
               rm1 = random(size - 1) + 1
               rn2 = random(size - 1) + 1
               rm2 = random(size - 1) + 1
               bool = (rn1 = rn2) and (rm1 = rm2)
               if not bool
                  exit
               ok
       end
       rand = random(limit - 1) + 1
       button[rn1][rm1].settext(numbers[rand][1])
       button[rn2][rm2].settext(numbers[rand][2])
       nScore = 0
       playerscore.settext(C_PLAYERSCORE)

func pMoveInDirection cFunc

       num = gameover()
       if num = size*size
          flag = 1
          msgBox('You lost!')
          pbegin()
       ok
       if flag = 0
          call cFunc()
          sleep(0.5)
          newnum()
       ok

func pdown() pMoveInDirection(:pMoveDown)

func pup() pMoveInDirection(:pMoveUp)

func pleft() pMoveInDirection(:pMoveLeft)

func pright() pMoveInDirection(:pMoveRight)

func pmoveleft()

      for n = 1 to size
           moveleft = []
           for m = 1 to size
                button[m][n] {temp = text()}
                if temp != 
                   add(moveleft,temp)
                ok
           next
           movetilesleft(n,moveleft)
      next

func pmoveright()

      for n = 1 to size
           moveright = []
           for m = size to 1 step -1
                button[m][n] {temp = text()}
                if temp != 
                   add(moveright,temp)
                ok
           next
           movetilesright(n,moveright)
       next
       return

func pmoveup()

      for n = 1 to size
           moveup = []
           for m = 1 to size
                button[n][m] {temp = text()}
                if temp != 
                   add(moveup,temp)
                ok
           next
           movetilesup(n,moveup)
       next
       return

func pmovedown()

      for n = 1 to size
           movedown = []
           for m = size to 1 step -1
                button[n][m] {temp = text()}
                if temp != 
                   add(movedown,temp)
                ok
           next
           movetilesdown(n,movedown)
       next
       return

func movetilesleft(nr,moveleft)

      for p = 1 to len(moveleft) - 1
           temp1 = moveleft[p]
           temp2 = moveleft[p+1]
           temp = string(number(temp1) + number(temp2))
           if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != ) and (temp2 != )
              if temp != '0' and temp != 
                 nScore = nScore + temp
                 playerscore.settext(C_PLAYERSCORE + nScore)
                 flag = 1
                 moveleft[p] = temp
                 del(moveleft,p+1)
              ok
           ok
      next
      for n = 1 to len(moveleft)
           button[n][nr].settext(moveleft[n])
      next
      for n = len(moveleft) + 1 to size 
           if n <= size
              button[n][nr].setStylesheet(C_EMPTYBUTTONSTYLE)
              button[n][nr].settext()
           ok
      next
      return

func movetilesright(nr,moveright)

      flag = 0
      for p = 2 to len(moveright)
           temp1 = moveright[p]
           temp2 = moveright[p-1]
            if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != ) and (temp2 != )
              temp = string(number(temp1) + number(temp2))
              if temp != '0' and temp != 
                 nScore = nScore + temp
                 playerscore.settext(C_PLAYERSCORE + nScore)
                 flag = 1
                 moveright[p] = temp
                 del(moveright,p-1)
              ok
           ok
      next
      for n = 1 to len(moveright)
           button[size-n+1][nr].settext(moveright[n])
      next
      for n = 1 to size - len(moveright)
           if n <= size
              button[n][nr].setStylesheet(C_EMPTYBUTTONSTYLE)
              button[n][nr].settext()
           ok
      next


func movetilesup(nr,moveup)

       flag = 0
       for p = 1 to len(moveup) - 1
            temp1 = moveup[p] 
            temp2 = moveup[p+1]
            if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != ) and (temp2 != )
              temp = string(number(temp1) + number(temp2))
              if temp != '0' and temp != 
                 nScore = nScore + temp
                 playerscore.settext(C_PLAYERSCORE + nScore)
                 flag = 1
                 moveup[p] = temp
                 del(moveup,p+1)
              ok
           ok
      next
      for n = 1 to len(moveup)
           button[nr][n].settext(moveup[n])
      next
      for n = len(moveup) + 1 to size 
           if n <= size
              button[nr][n].setStylesheet(C_EMPTYBUTTONSTYLE)
              button[nr][n].settext()
           ok
      next

func movetilesdown(nr,movedown)

       flag = 0
       for p = 1 to len(movedown) - 1
            temp1 = movedown[p]
            temp2 = movedown[p+1]
            if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != ) and (temp2 != )
              temp = string(number(temp1) + number(temp2))
              if temp != '0' and temp != 
                 nScore = nScore + temp
                 playerscore.settext(C_PLAYERSCORE + nScore)
                 flag = 1
                 movedown[p] = temp
                 del(movedown,p+1)
              ok
           ok
      next
      for n = 1 to len(movedown)
           button[nr][size-n+1].settext(movedown[n])
      next
      for n = size - len(movedown) to 1 step -1 
           if n <= size
              button[nr][n].setStylesheet(C_EMPTYBUTTONSTYLE)
              app.processevents()
              button[nr][n].settext()
           ok
      next

func newnum()

       while true
               rn = random(size - 1) + 1
               rm = random(size - 1) + 1
               if button[rn][rm].text() = 
                  button[rn][rm].settext('2')
                  exit
               ok
       end
       return

func gameover()

       num = 0
       flag = 0
       for n = 1 to size
            for m = 1 to size 
                 if button[n][m].text() != 
                    num = num + 1
                 ok
             next
       next
       return num

func msgBox(text) { m = new qMessageBox(win) { setWindowTitle('2048 Game') setText(text) show() }

       }

func showarray(vect)

       see "["
       svect = ""
       for n = 1 to len(vect)
             svect = svect + vect[n] + " "
       next
       svect = left(svect, len(svect) - 1)
       see svect
       see "]" + nl

class MyButton from qLabel

      func setText(cValue)
             Super.setText(cValue)
             switch cValue 
                       on '2' 		setStyleSheet(C_BUTTON2STYLE)
                       on '4' 		setStylesheet(C_BUTTON4STYLE)
                       on '8' 		setStylesheet(C_BUTTON8STYLE)
                       on '16' 	setStylesheet(C_BUTTON16STYLE)
                       on '32' 	setStylesheet(C_BUTTON32STYLE)
                       on '64' 	setStylesheet(C_BUTTON64STYLE)
                       on '128' 	setStylesheet(C_BUTTON128STYLE)

on '256' setStylesheet(C_BUTTON256STYLE) on '512' setStylesheet(C_BUTTON512STYLE) on '1024' setStylesheet(C_BUTTON1024STYLE) on '2048' setStylesheet(C_BUTTON2048STYLE)

             off

</lang>

Ruby

inspired by the Raku version <lang ruby>

  1. !/usr/bin/ruby

require 'io/console'

class Board

 def initialize size=4, win_limit=2048, cell_width = 6
   @size = size; @cw = cell_width; @win_limit = win_limit
   @board = Array.new(size) {Array.new(size, 0)}
   @moved = true; @score = 0; @no_more_moves = false
   spawn
 end
 def draw
   print "\n\n" if @r_vert
   print '    ' if @r_hori
   print '┌' + (['─' * @cw] * @size).join('┬')  + '┐'
   @board.each do |row|
     print "\n"
     formated = row.map {|num| num == 0 ? ' ' * @cw : format(num)}
     print '    ' if @r_hori
     puts '│' + formated.join('│') + '│'
     print '    ' if @r_hori
     print '├' + ([' '  * @cw] * @size).join('┼') + '┤'
   end
   print "\r"
   print '    ' if @r_hori
   puts '└' + (['─' * @cw] * @size).join('┴')  + '┘'
 end
 def move direction
   case direction
   when :up
     @board = column_map {|c| logic(c)}
     @r_vert = false if $rumble
   when :down
     @board = column_map {|c| logic(c.reverse).reverse} 
     @r_vert = true if $rumble
   when :left 
     @board = row_map {|r| logic(r)}
     @r_hori = false if $rumble
   when :right
     @board = row_map {|r| logic(r.reverse).reverse} 
     @r_hori = true if $rumble
   end
   spawn
   @moved = false
 end
 def print_score
   puts "Your Score is #@score."
   puts "Congratulations, you have won!" if to_enum.any? {|e| e >= @win_limit}
 end
 def no_more_moves?; @no_more_moves; end
 def won?;  to_enum.any? {|e| e >= @win_limit}; end
 def reset!; initialize @size, @win_limit, @cw; end
 private
 def set x, y, val
   @board[y][x] = val
 end
 def spawn 
   free_pos = to_enum.select{|elem,x,y| elem == 0}.map{|_,x,y| [x,y]}
   unless free_pos.empty?
     set *free_pos.sample, rand > 0.1 ? 2 : 4 if @moved
   else
     snap = @board
     unless @stop
       @stop = true
       %i{up down left right}.each{|s| move(s)}
       @no_more_moves = true if snap.flatten == @board.flatten
       @board = snap
       @stop = false
     end
   end
 end
 def logic list
   jump = false
   result =
   list.reduce([]) do |res, val|
     if res.last == val && !jump

res[-1] += val @score += val

       jump = true
     elsif val != 0

res.push val

       jump = false
     end
     res
   end
   result += [0] * (@size - result.length)
   @moved ||= list != result
   result
 end
 def column_map
   xboard = @board.transpose
   xboard.map!{|c| yield c }
   xboard.transpose
 end
 def row_map
   @board.map {|r| yield r }
 end
 def to_enum
   @enum ||= Enumerator.new(@size * @size) do |yielder|
     (@size*@size).times do |i|

yielder.yield (@board[i / @size][i % @size]), (i % @size), (i / @size )

     end
   end
   @enum.rewind
 end
 def format(num)
   if $color
     cstart = "\e[" + $colors[Math.log(num, 2)] + "m"
     cend = "\e[0m"
   else
     cstart = cend = ""
   end
   cstart + num.to_s.center(@cw) + cend
 end

end

$color = true $colors = %W{0 1;97 1;93 1;92 1;96 1;91 1;95 1;94 1;30;47 1;43 1;42 1;46 1;41 1;45 1;44 1;33;43 1;33;42 1;33;41 1;33;44} $rumble = false

$check_score = true unless ARGV.empty?

 puts "Usage: #$0 [gridsize] [score-threshold] [padwidth] [--no-color] [--rumble]"; exit if %W[-h --help].include?(ARGV[0])
 args = ARGV.map(&:to_i).reject{|n| n == 0}
 b = Board.new(*args) unless args.empty?
 $rumble = true if ARGV.any?{|a| a =~ /rumble/i }
 $color = false if ARGV.any?{|a| a =~ /no.?color/i}

end

b ||= Board.new puts "\e[H\e[2J" b.draw puts "Press h for help, q to quit" loop do

 input = STDIN.getch
 if input == "\e" 
   2.times {input << STDIN.getch}
 end
 case input
 when "\e[A", "w" then b.move(:up)
 when "\e[B", "s" then b.move(:down)
 when "\e[C", "d" then b.move(:right)
 when "\e[D", "a" then b.move(:left)

 when "q","\u0003","\u0004"  then b.print_score; exit
 when "h" 
   puts <<-EOM.gsub(/^\s*/, )
     ┌─                                                                                  ─┐
     │Use the arrow-keys or WASD on your keyboard to push board in the given direction.   
     │Tiles with the same number merge into one.                                          
     │Get a tile with a value of #{ARGV[1] || 2048} to win.                               
     │In case you cannot move or merge any tiles anymore, you loose.                      
     │You can start this game with different settings by providing commandline argument:  
     │For instance:                                                                       
     │  %> #$0 6 8192 --rumble                                                            
     └─                                                                                  ─┘
     PRESS q TO QUIT (or Ctrl-C or Ctrl-D)
   EOM
   input = STDIN.getch
 end
 puts "\e[H\e[2J"
 b.draw
 if b.no_more_moves? or $check_score && b.won?
   b.print_score
   if b.no_more_moves?
     puts "No more moves possible"
     puts "Again? (y/n)"
     exit if STDIN.gets.chomp.downcase == "n"
     $check_score = true
     b.reset!
     puts "\e[H\e[2J"
     b.draw
   else
     puts "Continue? (y/n)"
     exit if STDIN.gets.chomp.downcase == "n"
     $check_score = false
     puts "\e[H\e[2J"
     b.draw
   end
 end

end </lang>

Rust

Text mode

A simple implementation in rust. The user has to input an endline since i did not find a way to read a key press

Library: rand

<lang rust> use std::io::{self,BufRead}; extern crate rand;

enum Usermove {

   Up,
   Down,
   Left,
   Right,

}

fn print_game(field :& [[u32;4];4] ){

   println!("{:?}",&field[0] );
   println!("{:?}",&field[1] );
   println!("{:?}",&field[2] );
   println!("{:?}",&field[3] );

}

fn get_usermove()-> Usermove {

   let umove: Usermove ;
   loop{
       let mut input = String::new();
       io::stdin().read_line(&mut input).unwrap();
       match input.chars().nth(0){
           Some('a') =>{umove = Usermove::Left ;break },
           Some('w') =>{umove = Usermove::Up   ;break },
           Some('s') =>{umove = Usermove::Down ;break },
           Some('d') =>{umove = Usermove::Right;break },
           _   => {println!("input was {}: invalid character should be a,s,w or d ",input.chars().nth(0).unwrap());} ,
       }
   }
   umove

}

//this function inplements the user moves. //for every element it looks if the element is zero // if the element is zero it looks against the direction of the movement if any //element is not zero then it will move it to the element its place then it will look for //a matching element // if the element is not zero then it will look for a match if no match is found // then it will look for the next element

fn do_game_step(step : &Usermove, field:&mut [[u32;4];4]){

   match *step {
       Usermove::Left =>{
           for array in field{
               for  col in 0..4 {
                   for testcol in (col+1)..4 {
                       if array[testcol] != 0 {
                           if array[col] == 0 {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                           }
                           else if array[col] == array[testcol] {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                               break;
                           } else {
                               break
                           }
                       }
                   }
               }
           }
       } ,
       Usermove::Right=>{
           for array in field{
               for  col in (0..4).rev() {
                   for testcol in (0..col).rev() {
                       if array[testcol] != 0 {
                           if array[col] == 0 {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                           }
                           else if array[col] == array[testcol] {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                               break;
                           }else {
                               break;
                           }
                       }
                   }
               }
           }
       } ,
       Usermove::Down   =>{
           for col in 0..4 {
               for row in (0..4).rev() {
                   for testrow in (0..row).rev() {
                       if field[testrow][col] != 0 {
                           if field[row][col] == 0 {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                           } else if field[row][col] == field[testrow][col] {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                               break;
                           }else {
                               break;
                           }
                       }
                   }
               }
           }
       } ,
       Usermove::Up =>{
           for col in 0..4 {
               for row in 0..4{
                   for testrow in (row+1)..4 {
                       if field[testrow][col] != 0 {
                           if field[row][col] == 0 {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                           } else if field[row][col] == field[testrow][col] {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                               break;
                           }else {
                               break;
                           }
                       }
                   }
               }
           }
       },
   }

}

fn spawn( field: &mut [[u32;4];4]){

   loop{
       let x = rand::random::<usize>();
       if field[x % 4][(x/4)%4] == 0 {
           if x % 10 == 0 {
               field[x % 4][(x/4)%4]= 4;
           }else{
               field[x % 4][(x/4)%4]= 2;
           }
           break;
       }
   }

}


fn main() {

   let mut field : [[u32; 4];4] =  [[0;4];4];
   let mut test : [[u32; 4];4] ;
   'gameloop:loop {
       //check if there is still an open space
       test=field.clone();
       spawn(&mut field);
       //if all possible moves do not yield a change then there is no valid move left
       //and it will be game over
       for i in [Usermove::Up,Usermove::Down,Usermove::Left,Usermove::Right].into_iter(){
           do_game_step(i, &mut test);
           if test != field{
               break;//found a valid move
           }
           match *i{
               Usermove::Right=> {
                   println!("No more valid move, you lose");
                   break 'gameloop;
               },
               _=>{},
           }
       }
       print_game(&field);
       println!("move the blocks");
       test=field.clone();
       while test==field {
           do_game_step(&get_usermove(), &mut field);
       }
       for row in field.iter(){
           if row.iter().any(|x| *x == 2048){
               print_game(&field );
               println!("You Won!!");
               break;
           }
       }
   }

} </lang>

Scala

 <lang scala>import java.awt.event.{KeyAdapter, KeyEvent, MouseAdapter, MouseEvent}

import java.awt.{BorderLayout, Color, Dimension, Font, Graphics2D, Graphics, RenderingHints} import java.util.Random

import javax.swing.{JFrame, JPanel, SwingUtilities}

object Game2048 {

 val target = 2048
 var highest = 0
 def main(args: Array[String]): Unit = {
   SwingUtilities.invokeLater(() => {
     val f = new JFrame
     f.setDefaultCloseOperation(3)
     f.setTitle("2048")
     f.add(new Game, BorderLayout.CENTER)
     f.pack()
     f.setLocationRelativeTo(null)
     f.setVisible(true)
   })
 }
 class Game extends JPanel {
   private val (rand , side)= (new Random, 4)
   private var (tiles, gamestate)= (Array.ofDim[Tile](side, side), Game2048.State.start)
   final private val colorTable =
     Seq(new Color(0x701710), new Color(0xFFE4C3), new Color(0xfff4d3), new Color(0xffdac3), new Color(0xe7b08e), new Color(0xe7bf8e),
       new Color(0xffc4c3), new Color(0xE7948e), new Color(0xbe7e56), new Color(0xbe5e56), new Color(0x9c3931), new Color(0x701710))
   setPreferredSize(new Dimension(900, 700))
   setBackground(new Color(0xFAF8EF))
   setFont(new Font("SansSerif", Font.BOLD, 48))
   setFocusable(true)
   addMouseListener(new MouseAdapter() {
     override def mousePressed(e: MouseEvent): Unit = {
       startGame()
       repaint()
     }
   })
   addKeyListener(new KeyAdapter() {
     override def keyPressed(e: KeyEvent): Unit = {
       e.getKeyCode match {
         case KeyEvent.VK_UP => moveUp()
         case KeyEvent.VK_DOWN => moveDown()
         case KeyEvent.VK_LEFT => moveLeft()
         case KeyEvent.VK_RIGHT => moveRight()
         case _ =>
       }
       repaint()
     }
   })
   override def paintComponent(gg: Graphics): Unit = {
     super.paintComponent(gg)
     val g = gg.asInstanceOf[Graphics2D]
     g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
     drawGrid(g)
   }
   private def drawGrid(g: Graphics2D): Unit = {
     val (gridColor, emptyColor, startColor) = (new Color(0xBBADA0), new Color(0xCDC1B4), new Color(0xFFEBCD))
     if (gamestate == State.running) {
       g.setColor(gridColor)
       g.fillRoundRect(200, 100, 499, 499, 15, 15)
       for (
         r <- 0 until side;
         c <- 0 until side
       ) if (Option(tiles(r)(c)).isEmpty) {
         g.setColor(emptyColor)
         g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7)
       }
       else drawTile(g, r, c)
     } else {
       g.setColor(startColor)
       g.fillRoundRect(215, 115, 469, 469, 7, 7)
       g.setColor(gridColor.darker)
       g.setFont(new Font("SansSerif", Font.BOLD, 128))
       g.drawString("2048", 310, 270)
       g.setFont(new Font("SansSerif", Font.BOLD, 20))
       if (gamestate == Game2048.State.won) g.drawString("you made it!", 390, 350)
       else if (gamestate == Game2048.State.over) g.drawString("game over", 400, 350)
       g.setColor(gridColor)
       g.drawString("click to start a new game", 330, 470)
       g.drawString("(use arrow keys to move tiles)", 310, 530)
     }
   }
   private def drawTile(g: Graphics2D, r: Int, c: Int): Unit = {
     val value = tiles(r)(c).value
     g.setColor(colorTable((math.log(value) / math.log(2)).toInt + 1))
     g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7)
     g.setColor(if (value < 128) colorTable.head else colorTable(1))
     val (s , fm)= (value.toString, g.getFontMetrics)
     val asc = fm.getAscent
     val (x, y) = (215 + c * 121 + (106 - fm.stringWidth(s)) / 2,115 + r * 121 + (asc + (106 - (asc + fm.getDescent)) / 2))
     g.drawString(s, x, y)
   }
   private def moveUp(checkingAvailableMoves: Boolean = false) = move(0, -1, 0, checkingAvailableMoves)
   private def moveDown(checkingAvailableMoves: Boolean = false) = move(side * side - 1, 1, 0, checkingAvailableMoves)
   private def moveLeft(checkingAvailableMoves: Boolean = false) = move(0, 0, -1, checkingAvailableMoves)
   private def moveRight(checkingAvailableMoves: Boolean = false) = move(side * side - 1, 0, 1, checkingAvailableMoves)
   private def clearMerged(): Unit = for (row <- tiles; tile <- row) if (Option(tile).isDefined) tile.setMerged()
   private def movesAvailable() = moveUp(true) || moveDown(true) || moveLeft(true) || moveRight(true)
   def move(countDownFrom: Int, yIncr: Int, xIncr: Int, checkingAvailableMoves: Boolean): Boolean = {
     var moved = false
     for (i <- 0 until side * side) {
       val j = math.abs(countDownFrom - i)
       var( r,c) = (j / side,  j % side)
       if (Option(tiles(r)(c)).isDefined) {
         var (nextR, nextC, breek) = (r + yIncr, c + xIncr, false)
         while ((nextR >= 0 && nextR < side && nextC >= 0 && nextC < side) && !breek) {
           val (next, curr) = (tiles(nextR)(nextC),tiles(r)(c))
           if (Option(next).isEmpty)
             if (checkingAvailableMoves) return true
             else {
               tiles(nextR)(nextC) = curr
               tiles(r)(c) = null
               r = nextR
               c = nextC
               nextR += yIncr
               nextC += xIncr
               moved = true
             }
           else if (next.canMergeWith(curr)) {
             if (checkingAvailableMoves) return true
             Game2048.highest = math.max(next.mergeWith(curr), Game2048.highest)
             tiles(r)(c) = null
             breek = true
             moved = true
           } else breek = true
         }
       }
     }
     if (moved) if (Game2048.highest < Game2048.target) {
       clearMerged()
       addRandomTile()
       if (!movesAvailable) gamestate = State.over
     }
     else if (Game2048.highest == Game2048.target) gamestate = State.won
     moved
   }
   private def startGame(): Unit = {
     if (gamestate ne Game2048.State.running) {
       Game2048.highest = 0
       gamestate = Game2048.State.running
       tiles = Array.ofDim[Tile](side, side)
       addRandomTile()
       addRandomTile()
     }
   }
   private def addRandomTile(): Unit = {
     var pos = rand.nextInt(side * side)
     var (row, col) = (0, 0)
     do {
       pos = (pos + 1) % (side * side)
       row = pos / side
       col = pos % side
     } while (Option(tiles(row)(col)).isDefined)
     tiles(row)(col) = new Tile(if (rand.nextInt(10) == 0) 4 else 2)
   }
   class Tile(var value: Int) {
     private var merged = false
     def setMerged(): Unit = merged = false
     def mergeWith(other: Tile): Int = if (canMergeWith(other)) {
       merged = true
       value *= 2
       value
     } else -1
     def canMergeWith(other: Tile): Boolean = !merged && Option(other).isDefined && !other.merged && value == other.value
   }
 }
 object State extends Enumeration {
   type State = Value
   val start, won, running, over = Value
 }

}</lang>

Seed7

The Seed7 program below works in a text console. Commands are read from the file KEYBOARD, which delivers cursor keys and function keys as single characters (e.g. KEY_LEFT or KEY_F1). Additionally KEYBOARD delivers single key-presses without echo. All this is done independent from the operating system or terminal/console. The output of the program is written to STD_CONSOLE, which allows cursor positioning, after it has been opened. STD_CONSOLE works also always the same, independent from the operating system or terminal/console.

<lang seed7>$ include "seed7_05.s7i";

 include "console.s7i";
 include "keybd.s7i";

const integer: boardLength is 4; const integer: boardSize is boardLength * boardLength; const integer: target is 2048;

const type: stateType is new struct

   var integer: fieldsOccupied is 0;
   var integer: largestNumber is 0;
   var integer: score is 0;
   var array array integer: board is boardLength times boardLength times 0;
 end struct;

const proc: addTile (inout stateType: state) is func

 local
   var integer: row is 0;
   var integer: col is 0;
   var integer: field is 2;
 begin
   if state.fieldsOccupied < boardSize then
     repeat
       col := rand(1, boardLength);
       row := rand(1, boardLength);
     until state.board[row][col] = 0;
     if rand(1, 10) = 10 then
       field := 4;
     end if;
     state.board[row][col] := field;
     incr(state.fieldsOccupied);
     state.largestNumber := max(field, state.largestNumber);
   end if;
 end func;

const proc: showBoard (in stateType: state) is func

 local
   var integer: row is 0;
   var integer: field is 0;
 begin
   writeln("┌────┬────┬────┬────┐");
   for key row range state.board do
     for field range state.board[row] do
       if field = 0 then
         write("│    ");
       else
         write("│" <& field lpad (5 + length(str(field))) div 2 rpad 4);
       end if;
     end for;
     writeln("│");
     if row < maxIdx(state.board) then
       writeln("├────┼────┼────┼────┤");
     end if;
   end for;
   writeln("└────┴────┴────┴────┘");
 end func;

const func boolean: doMove (inout stateType: state, in integer: startRow,

   in integer: startCol, in integer: deltaRow, in integer: deltaCol, in boolean: doMerge) is func
 result
   var boolean: boardChanged is FALSE;
 local
   const set of integer: boardRange is {1 .. boardLength};
   var integer: row is 1;
   var integer: col is 1;
   var integer: nextRow is 0;
   var integer: nextCol is 0;
 begin
   row := startRow;
   col := startCol;
   while row in boardRange and col in boardRange do
     while row in boardRange and col in boardRange do
       nextRow := row + deltaRow;
       nextCol := col + deltaCol;
       if state.board[row][col] = 0 and
           nextRow in boardRange and nextCol in boardRange and
           state.board[nextRow][nextCol] <> 0 then
         boardChanged := TRUE;
         state.board[row][col] := state.board[nextRow][nextCol];
         state.board[nextRow][nextCol] := 0;
         if row - deltaRow in boardRange and col - deltaCol in boardRange then
           nextRow := row - deltaRow;
           nextCol := col - deltaCol;
         end if;
       end if;
       row := nextRow;
       col := nextCol;
     end while;
     if doMerge then
       if deltaRow <> 0 then
         row := startRow;
       elsif deltaCol <> 0 then
         col := startCol;
       end if;
       while row in boardRange and col in boardRange do
         nextRow := row + deltaRow;
         nextCol := col + deltaCol;
         if state.board[row][col] <> 0 and
             nextRow in boardRange and nextCol in boardRange and
             state.board[nextRow][nextCol] = state.board[row][col] then
           boardChanged := TRUE;
           state.board[row][col] *:= 2;
           state.largestNumber := max(state.board[row][col], state.largestNumber);
           state.score +:= state.board[row][col];
           state.board[nextRow][nextCol] := 0;
           decr(state.fieldsOccupied);
         end if;
         row := nextRow;
         col := nextCol;
       end while;
     end if;
     if deltaRow = 0 then
       incr(row);
       col := startCol;
     elsif deltaCol = 0 then
       incr(col);
       row := startRow;
     end if;
   end while;
   if doMerge and boardChanged then
     ignore(doMove(state, startRow, startCol, deltaRow, deltaCol, FALSE));
   end if;
 end func;

const func boolean: canMove (in stateType: state) is func

 result
   var boolean: canMove is FALSE;
 local
   var integer: row is 0;
   var integer: col is 0;
 begin
   for row range 1 to boardLength until canMove do
     for col range 1 to boardLength until canMove do
       canMove := state.board[row][col] = 0 or
           (row < boardLength and state.board[row][col] = state.board[succ(row)][col]) or
           (col < boardLength and state.board[row][col] = state.board[row][succ(col)]);
     end for;
   end for;
 end func;

const proc: main is func

 local
   var stateType: state is stateType.value;
   var integer: highscore is 0;
   var char: command is ' ';
   var boolean: quit is FALSE;
   var boolean: moveOkay is FALSE;
 begin
   OUT := open(CONSOLE);
   addTile(state);
   repeat
     setPos(STD_CONSOLE, 1, 1);
     showBoard(state);
     highscore := max(highscore, state.score);
     writeln("Score = " <& state.score <& "  Highscore = " <& highscore);
     if canMove(state) and state.largestNumber < target then
       writeln("Press arrow keys to move, R to Restart, Q to Quit");
     elsif state.largestNumber >= target then
       writeln("You win! Press R to Restart, Q to Quit           ");
     else
       writeln("Game over! Press R to Restart, Q to Quit         ");
     end if;
     repeat
       moveOkay := FALSE;
       command := getc(KEYBOARD);
       case command of
         when {'r', 'R'}:
           state := stateType.value;
           clear(STD_CONSOLE);
           moveOkay := TRUE;
         when {'q', 'Q'}:
           moveOkay := TRUE;
           quit := TRUE;
         when {KEY_LEFT}:
           moveOkay := doMove(state,           1,           1,  0,  1, TRUE);
         when {KEY_RIGHT}:
           moveOkay := doMove(state,           1, boardLength,  0, -1, TRUE);
         when {KEY_UP}:
           moveOkay := doMove(state,           1,           1,  1,  0, TRUE);
         when {KEY_DOWN}:
           moveOkay := doMove(state, boardLength,           1, -1,  0, TRUE);
       end case;

if moveOkay and not quit then addTile(state);

       end if;
     until moveOkay;
   until quit;
 end func;</lang>

Tcl

Text mode

<lang tcl>

  1. A minimal implementation of the game 2048 in Tcl.
  2. For a maintained version with expanded functionality see
  3. https://tcl.wiki/40557.

package require Tcl 8.5 package require struct::matrix package require struct::list

  1. Board size.

set size 4

  1. Iterate over all cells of the game board and run script for each.
  2. The game board is a 2D matrix of a fixed size that consists of elements
  3. called "cells" that each can contain a game tile (corresponds to numerical
  4. values of 2, 4, 8, ..., 2048) or nothing (zero).
  5. - cellList is a list of cell indexes (coordinates), which are
  6. themselves lists of two numbers each. They each represent the location
  7. of a given cell on the board.
  8. - varName1 are varName2 are names of the variables the will be assigned
  9. the index values.
  10. - cellVarName is the name of the variable that at each step of iteration
  11. will contain the numerical value of the present cell. Assigning to it will
  12. change the cell's value.
  13. - script is the script to run.

proc forcells {cellList varName1 varName2 cellVarName script} {

   upvar $varName1 i
   upvar $varName2 j
   upvar $cellVarName c
   foreach cell $cellList {
       set i [lindex $cell 0]
       set j [lindex $cell 1]
       set c [cell-get $cell]
       uplevel $script
       cell-set "$i $j" $c
   }

}

  1. Generate a list of cell indexes for all cells on the board, i.e.,
  2. {{0 0} {0 1} ... {0 size-1} {1 0} {1 1} ... {size-1 size-1}}.

proc cell-indexes {} {

   global size
   set list {}
   foreach i [::struct::list iota $size] {
       foreach j [::struct::list iota $size] {
           lappend list [list $i $j]
       }
   }
   return $list

}

  1. Check if a number is a valid cell index (is 0 to size-1).

proc valid-index {i} {

   global size
   expr {0 <= $i && $i < $size}

}

  1. Return 1 if the predicate pred is true when applied to all items on the list
  2. or 0 otherwise.

proc map-and {list pred} {

   set res 1
   foreach item $list {
       set res [expr {$res && [$pred $item]}]
       if {! $res} break
   }
   return $res

}

  1. Check if list represents valid cell coordinates.

proc valid-cell? cell {

   map-and $cell valid-index

}

  1. Get the value of a game board cell.

proc cell-get cell {

   board get cell {*}$cell

}

  1. Set the value of a game board cell.

proc cell-set {cell value} {

   board set cell {*}$cell $value

}

  1. Filter a list of board cell indexes cellList to only have those indexes
  2. that correspond to empty board cells.

proc empty {cellList} {

   ::struct::list filterfor x $cellList {[cell-get $x] == 0}

}

  1. Pick a random item from the given list.

proc pick list {

   lindex $list [expr {int(rand() * [llength $list])}]

}

  1. Put a "2" into an empty cell on the board.

proc spawn-new {} {

   set emptyCell [pick [empty [cell-indexes]]]
   if {[llength $emptyCell] > 0} {
       forcells [list $emptyCell] i j cell {
           set cell 2
       }
   }
   return $emptyCell

}

  1. Return vector sum of lists v1 and v2.

proc vector-add {v1 v2} {

   set result {}
   foreach a $v1 b $v2 {
       lappend result [expr {$a + $b}]
   }
   return $result

}

  1. If checkOnly is false try to shift all cells one step in the direction of
  2. directionVect. If checkOnly is true just say if that move is possible.

proc move-all {directionVect {checkOnly 0}} {

   set changedCells 0
   forcells [cell-indexes] i j cell {
       set newIndex [vector-add "$i $j" $directionVect]
       set removedStar 0
       # For every nonempty source cell and valid destination cell...
       if {$cell != 0 && [valid-cell? $newIndex]} {
           if {[cell-get $newIndex] == 0} {
               # Destination is empty.
               if {$checkOnly} {
                   # -level 2 is to return from both forcells and move-all.
                   return -level 2 true
               } else {
                   # Move tile to empty cell.
                   cell-set $newIndex $cell
                   set cell 0
                   incr changedCells
               }
           } elseif {([cell-get $newIndex] eq $cell) &&
                     [string first + $cell] == -1} {
               # Destination is the same number as source.
               if {$checkOnly} {
                   return -level 2 true
               } else {
                   # When merging two tiles into one mark the new tile with
                   # the marker of "+" to ensure it doesn't get combined
                   # again this turn.
                   cell-set $newIndex [expr {2 * $cell}]+
                   set cell 0
                   incr changedCells
               }
           }
       }
   }
   if {$checkOnly} {
       return false
   }
   # Remove "changed this turn" markers at the end of the turn.
   if {$changedCells == 0} {
       forcells [cell-indexes] i j cell {
           set cell [string trim $cell +]
       }
   }
   return $changedCells

}

  1. Is it possible to move any tiles in the direction of directionVect?

proc can-move? {directionVect} {

   move-all $directionVect 1

}

  1. Check win condition. The player wins when there's a 2048 tile.

proc check-win {} {

   forcells [cell-indexes] i j cell {
       if {$cell == 2048} {
           puts "You win!"
           exit 0
       }
   }

}

  1. Check lose condition. The player loses when the win condition isn't met and
  2. there are no possible moves.

proc check-lose {possibleMoves} {

   set values [dict values $possibleMoves]
   if {!(true in $values || 1 in $values)} {
       puts "You lose."
       exit 0
   }

}

  1. Pretty-print the board. Specify an index in highlight to highlight a cell.

proc print-board {{highlight {-1 -1}}} {

   forcells [cell-indexes] i j cell {
       if {$j == 0} {
           puts ""
       }
       puts -nonewline [
           if {$cell != 0} {
               if {[::struct::list equal "$i $j" $highlight]} {
                   format "\[%4s\]" $cell*
               } else {
                   format "\[%4s\]" $cell
               }
           } else {
               lindex "......"
           }
       ]
   }
   puts "\n"

}

proc main {} {

   global size
   struct::matrix board
   # Generate an empty board of a given size.
   board add columns $size
   board add rows $size
   forcells [cell-indexes] i j cell {
       set cell 0
   }
   set controls {
       h {0 -1}
       j {1 0}
       k {-1 0}
       l {0 1}
   }
   # Game loop.
   while true {
       set playerMove 0
       set possibleMoves {}
       # Add new tile to the board and print the board highlighting this tile.
       print-board [spawn-new]
       check-win
       # Find possible moves.
       foreach {button vector} $controls {
           dict set possibleMoves $button [can-move? $vector]
       }
       check-lose $possibleMoves
       # Get valid input from the player.
       while {$playerMove == 0} {
           # Print prompt.
           puts -nonewline "Move ("
           foreach {button vector} $controls {
               if {[dict get $possibleMoves $button]} {
                   puts -nonewline $button
               }
           }
           puts ")?"
           set playerInput [gets stdin]
           # Validate input.
           if {[dict exists $possibleMoves $playerInput] &&
               [dict get $possibleMoves $playerInput]} {
               set playerMove [dict get $controls $playerInput]
           }
       }
       # Apply current move until no changes occur on the board.
       while true {
           if {[move-all $playerMove] == 0} break
       }
   }

}

main </lang>

Tk

See https://tcl.wiki/39566.

XPL0

<lang XPL0>include c:\cxpl\codes; \intrinsic 'code' declarations int Box(16), Moved;

proc ShiftTiles(I0, DI); \Shift tiles, add adjacents, shift again int I0, DI; int Done, M, N, I; [Done:= false; loop [for M:= 1 to 3 do \shift all tiles in a single row or column

           [I:= I0;
           for N:= 1 to 3 do
               [if Box(I)=0 & Box(I+DI)#0 then
                       [Box(I):= Box(I+DI);  Box(I+DI):= 0;  Moved:= true];
               I:= I+DI;
               ];
           ];
       if Done then return;
       Done:= true;
       I:= I0;                 \add identical adjacent tiles into a new tile
       for N:= 1 to 3 do
               [if Box(I)=Box(I+DI) & Box(I)#0 then
                       [Box(I):= Box(I)+1;  Box(I+DI):= 0;  Moved:= true];
               I:= I+DI;
               ];
       ];                      \loop back to close any gaps that were opened

]; \ShiftTiles

int I, J, X, Y, C; [Clear; for I:= 0 to 15 do Box(I):= 0; \empty the box of tiles loop [repeat I:= Ran(16) until Box(I)=0; \in a random empty location

       Box(I):= if Ran(10) then 1 else 2;      \insert a 2^1=2 or 2^2=4
       for I:= 0 to 15 do                      \show board with its tiles
               [X:= ((I&3)+5)*6;               \get coordinates of tile
                Y:= I>>2*3+6;
                Attrib(((Box(I)+1)&7)<<4 + $F);\set color based on tile value
                for J:= 0 to 2 do              \draw a square (6*8x3*16)
                       [Cursor(X, Y+J);
                       Text(6, "      ");
                       ];
                if Box(I)#0 then               \box contains a tile
                       [J:= 1;                 \center numbers somewhat
                       if Box(I) <= 9 then J:= 2;
                       if Box(I) <= 3 then J:= 3;
                       Cursor(X+J, Y+1);
                       IntOut(6, 1<<Box(I));
                       ];
               ];
       Moved:= false;                          \a tile must move to continue
       repeat  repeat C:= ChIn(1) until C#0;   \get key scan code, or ASCII
               for I:= 3 downto 0 do           \for all rows or columns
                       [case C of
                         $4B:  ShiftTiles(I*4, 1);     \left arrow
                         $4D:  ShiftTiles(I*4+3, -1);  \right arrow
                         $50:  ShiftTiles(I+12, -4);   \down arrow
                         $48:  ShiftTiles(I, 4);       \up arrow
                         $1B:  [Clear;  exit]          \Esc
                       other   [];                     \ignore all other keys
                       ];
       until   Moved;
       ];

]</lang>

Visual Basic .NET

Translation of: C#

<lang vbnet>Friend Class Tile

   Public Sub New()
       Me.Value = 0
       Me.IsBlocked = False
   End Sub
   Public Property Value As Integer
   Public Property IsBlocked As Boolean

End Class

Friend Enum MoveDirection

    Up
    Down
    Left
    Right

End Enum

   Friend Class G2048
       Public Sub New()
           _isDone = False
           _isWon = False
           _isMoved = True
           _score = 0
           InitializeBoard()
       End Sub
       Private Sub InitializeBoard()
           For y As Integer = 0 To 3
               For x As Integer = 0 To 3
                   _board(x, y) = New Tile()
               Next
           Next
       End Sub
       Private _isDone As Boolean
       Private _isWon As Boolean
       Private _isMoved As Boolean
       Private _score As Integer
       Private ReadOnly _board As Tile(,) = New Tile(3, 3) {}
       Private ReadOnly _rand As Random = New Random()
       Const empty As String = " "
       Public Sub [Loop]()
           AddTile()
           While True
               If _isMoved Then AddTile()
               DrawBoard()
               If _isDone Then Exit While
               WaitKey()
           End While
           Dim endMessage As String = If(_isWon, "You've made it!", "Game Over!")
           Console.WriteLine(endMessage)
       End Sub
       Public Sub DrawBoard()
           Console.Clear()
           Console.WriteLine("Score: " & _score & vbNewLine)
           For y As Integer = 0 To 3
               Console.WriteLine("+------+------+------+------+")
               Console.Write("| ")
               For x As Integer = 0 To 3
                   If _board(x, y).Value = 0 Then
                       Console.Write(empty.PadRight(4))
                   Else
                       Console.Write(_board(x, y).Value.ToString().PadRight(4))
                   End If
                   Console.Write(" | ")
               Next
               Console.WriteLine()
           Next
           Console.WriteLine("+------+------+------+------+" & vbNewLine & vbNewLine)
       End Sub
       Private Sub WaitKey()
           _isMoved = False
           Console.WriteLine("(W) Up (S) Down (A) Left (D) Right")
           Dim input As Char
           Char.TryParse(Console.ReadKey().Key.ToString(), input)
           Select Case input
               Case "W"c
                   Move(MoveDirection.Up)
               Case "A"c
                   Move(MoveDirection.Left)
               Case "S"c
                   Move(MoveDirection.Down)
               Case "D"c
                   Move(MoveDirection.Right)
           End Select
           For y As Integer = 0 To 3
               For x As Integer = 0 To 3
                   _board(x, y).IsBlocked = False
               Next
           Next
       End Sub
       Private Sub AddTile()
           For y As Integer = 0 To 3
               For x As Integer = 0 To 3
                   If _board(x, y).Value <> 0 Then Continue For
                   Dim a As Integer, b As Integer
                   Do
                       a = _rand.Next(0, 4)
                       b = _rand.Next(0, 4)
                   Loop While _board(a, b).Value <> 0
                   Dim r As Double = _rand.NextDouble()
                   _board(a, b).Value = If(r > 0.89F, 4, 2)
                   If CanMove() Then Return
               Next
           Next
           _isDone = True
       End Sub
       Private Function CanMove() As Boolean
           For y As Integer = 0 To 3
               For x As Integer = 0 To 3
                   If _board(x, y).Value = 0 Then Return True
               Next
           Next
           For y As Integer = 0 To 3
               For x As Integer = 0 To 3
                   If TestAdd(x + 1, y, _board(x, y).Value) OrElse TestAdd(x - 1, y, _board(x, y).Value) OrElse TestAdd(x, y + 1, _board(x, y).Value) OrElse TestAdd(x, y - 1, _board(x, y).Value) Then Return True
               Next
           Next
           Return False
       End Function
       Private Function TestAdd(ByVal x As Integer, ByVal y As Integer, ByVal value As Integer) As Boolean
           If x < 0 OrElse x > 3 OrElse y < 0 OrElse y > 3 Then Return False
           Return _board(x, y).Value = value
       End Function
       Private Sub MoveVertically(ByVal x As Integer, ByVal y As Integer, ByVal d As Integer)
           If _board(x, y + d).Value <> 0 AndAlso _board(x, y + d).Value = _board(x, y).Value AndAlso Not _board(x, y).IsBlocked AndAlso Not _board(x, y + d).IsBlocked Then
               _board(x, y).Value = 0
               _board(x, y + d).Value *= 2
               _score += _board(x, y + d).Value
               _board(x, y + d).IsBlocked = True
               _isMoved = True
           ElseIf _board(x, y + d).Value = 0 AndAlso _board(x, y).Value <> 0 Then
               _board(x, y + d).Value = _board(x, y).Value
               _board(x, y).Value = 0
               _isMoved = True
           End If
           If d > 0 Then
               If y + d < 3 Then MoveVertically(x, y + d, 1)
           Else
               If y + d > 0 Then MoveVertically(x, y + d, -1)
           End If
       End Sub
       Private Sub MoveHorizontally(ByVal x As Integer, ByVal y As Integer, ByVal d As Integer)
           If _board(x + d, y).Value <> 0 AndAlso _board(x + d, y).Value = _board(x, y).Value AndAlso Not _board(x + d, y).IsBlocked AndAlso Not _board(x, y).IsBlocked Then
               _board(x, y).Value = 0
               _board(x + d, y).Value *= 2
               _score += _board(x + d, y).Value
               _board(x + d, y).IsBlocked = True
               _isMoved = True
           ElseIf _board(x + d, y).Value = 0 AndAlso _board(x, y).Value <> 0 Then
               _board(x + d, y).Value = _board(x, y).Value
               _board(x, y).Value = 0
               _isMoved = True
           End If
           If d > 0 Then
               If x + d < 3 Then MoveHorizontally(x + d, y, 1)
           Else
               If x + d > 0 Then MoveHorizontally(x + d, y, -1)
           End If
       End Sub
       Private Sub Move(ByVal direction As MoveDirection)
           Select Case direction
               Case MoveDirection.Up
                   For x As Integer = 0 To 3
                       Dim y As Integer = 1
                       While y < 4
                           If _board(x, y).Value <> 0 Then MoveVertically(x, y, -1)
                           y += 1
                       End While
                   Next
               Case MoveDirection.Down
                   For x As Integer = 0 To 3
                       Dim y As Integer = 2
                       While y >= 0
                           If _board(x, y).Value <> 0 Then MoveVertically(x, y, 1)
                           y -= 1
                       End While
                   Next
               Case MoveDirection.Left
                   For y As Integer = 0 To 3
                       Dim x As Integer = 1
                       While x < 4
                           If _board(x, y).Value <> 0 Then MoveHorizontally(x, y, -1)
                           x += 1
                       End While
                   Next
               Case MoveDirection.Right
                   For y As Integer = 0 To 3
                       Dim x As Integer = 2
                       While x >= 0
                           If _board(x, y).Value <> 0 Then MoveHorizontally(x, y, 1)
                           x -= 1
                       End While
                   Next
           End Select
       End Sub
   End Class
   Module Module1
       Sub Main()
           RunGame()
       End Sub
       Private Sub RunGame()
           Dim game As G2048 = New G2048()
           game.Loop()
           CheckRestart()
       End Sub
       Private Sub CheckRestart()
           Console.WriteLine("(N) New game (P) Exit")
           While True
               Dim input As Char
               Char.TryParse(Console.ReadKey().Key.ToString(), input)
               Select Case input
                   Case "N"c
                       RunGame()
                   Case "P"c
                       Return
                   Case Else
                       ClearLastLine()
               End Select
           End While
       End Sub
       Private Sub ClearLastLine()
           Console.SetCursorPosition(0, Console.CursorTop)
           Console.Write(New String(" ", Console.BufferWidth))
           Console.SetCursorPosition(0, Console.CursorTop - 1)
       End Sub
   End Module

</lang>

Output:
Score: 572

+------+------+------+------+
|      | 2    | 16   | 4    |
+------+------+------+------+
|      | 2    | 4    | 64   |
+------+------+------+------+
| 4    | 16   | 32   | 4    |
+------+------+------+------+
| 2    | 4    | 2    | 16   |
+------+------+------+------+


(W) Up (S) Down (A) Left (D) Right