# 2048

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

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

Rules of the game
•   The rules are that on each turn the player must choose a direction   (up, down, left or right).
•   All tiles move as far as possible in that direction, some move 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, including by combination.
•   A new tile is spawned at the end of each turn at a randomly chosen empty square   (if there is one).
•   Most of the time, a new 2 is to be added, but occasionally (10% of the time), a 4.
•   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]
```

•   Check for valid moves. The player shouldn't be able to gain new tile by trying a move that doesn't change the board.
•   Check for a win condition.
•   Check for a lose condition.

## AArch64 Assembly

Works with: as version Raspberry Pi 3B version Buster 64 bits
```/* 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 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_flags:
.struct  sa_flags + 8
sa_sigaction:
.struct  sa_sigaction + 8
sa_fin:

/* structure poll see doc linux */
.struct  0
.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"
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
/*********************************/
/* 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
bl affichageMess
bl razTable
2:
cmp x0,#-1
beq 5f                            // end game
bl displayGame
3:
mov x0,x22
bl waitKey
cmp x0,0
beq 3b
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
bl affichageMess
b 10f
5:                                    // display message no solution
bl affichageMess

10:                                   // display new game ?
bl affichageMess
bl affichageMess
11:
mov x0,x22
bl waitKey
cmp x0,0
beq 11b
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

/******************************************************************/
/*     raz table cases                                                   */
/******************************************************************/
razTable:
stp x0,lr,[sp,-16]!     // save  registres
stp x1,x2,[sp,-16]!     // save  registres
mov x2,#0
1:
str xzr,[x1,x2,lsl #3]
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:
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
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
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
cmp x3,#SIZE
blt 2b
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
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
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
str x4,[x1,x5,lsl #3]       // and store to right empty position
mov x0,#1
3:
cmp x7,#0                   // first digit
beq 4f
ldr x9,[x1,x8,lsl #3]
cmp x4,x9                   // equal ?
bne 4f
cmp x10,x4
beq 4f
mov x10,x4
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
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
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
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
str x4,[x1,x5,lsl #3]       // and store to right empty position
mov x0,#1
3:
cmp x7,#0                   // first digit
beq 4f
ldr x9,[x1,x8,lsl #3]
cmp x4,x9                   // equal ?
bne 4f
cmp x10,x4
beq 4f
mov x10,x4
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
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
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
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
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
cmp x2,#SIZE
blt 2b
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     */
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
mov x3,#0
mov x4,#0
1:
ldr x2,[x1,x3,lsl 3]
cmp x2,#0
bne 2f
str x3,[fp,x4,lsl 3]
2:
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
/******************************************************************/
/*            display game                                      */
/******************************************************************/
displayGame:
stp x1,lr,[sp,-16]!          // save  registres
stp x2,x3,[sp,-16]!          // save  registres
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
mov x2,#0
1:
ldr x0,[x1,x2,lsl #3]
bl digitString
bl affichageMess
bl affichageMess
cmp x2,#SIZE
blt 1b
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
2:
ldr x0,[x1,x2,lsl #3]
bl digitString
bl affichageMess
bl affichageMess
cmp x2,#SIZE*2
blt 2b
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
3:
ldr x0,[x1,x2,lsl #3]
bl digitString
bl affichageMess
bl affichageMess
cmp x2,#SIZE*3
blt 3b
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
4:
ldr x0,[x1,x2,lsl #3]
bl digitString
bl affichageMess
bl affichageMess
cmp x2,#SIZE*4
blt 4b
bl affichageMess
bl affichageMess
bl affichageMess

100:
ldp x2,x3,[sp],16           // restaur des  2 registres
ldp x1,lr,[sp],16           // restaur des  2 registres
ret
/******************************************************************/
/*            digits string                                       */
/******************************************************************/
/* x0 contains number */
/* x0 return address string */
digitString:
stp x1,lr,[sp,-16]!          // save  registres
cmp x0,#0
bne 1f
b 100f
1:
cmp x0,#2
bne 2f
b 100f
2:
cmp x0,#4
bne 3f
b 100f
3:
cmp x0,#8
bne 4f
b 100f
4:
cmp x0,#16
bne 5f
b 100f
5:
cmp x0,#32
bne 6f
b 100f
6:
cmp x0,#64
bne 7f
b 100f
7:
cmp x0,#128
bne 8f
b 100f
8:
cmp x0,#256
bne 9f
b 100f
9:
cmp x0,#512
bne 10f
b 100f
10:
cmp x0,#1024
bne 11f
b 100f
11:
cmp x0,#2048
bne 12f
b 100f
12:
bl   displayError
100:
ldp x1,lr,[sp],16           // restaur des  2 registres
ret

/***************************************************/
/*   Generation random number                  */
/***************************************************/
/* x0 contains limit  */
genereraleas:
stp x1,lr,[sp,-16]!            // save  registers
stp x2,x3,[sp,-16]!            // save  registers
ldr x2,[x1]
ldr x3,qNbDep1
mul x2,x3,x2
ldr x3,qNbDep2
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
/*****************************************************/

/******************************************************************/
/*     traitement du signal                                       */
/******************************************************************/
sighandler:
stp x0,lr,[sp,-16]!            // save  registers
str x1,[sp,-16]!
mov x1,#1                      // maj zone end
str x1,[x0]
ldr x1,[sp],16
ldp x0,lr,[sp],16              // restaur  2 registers
/***************************************************/
/*   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
bl conversion16                // conversion hexa
ldr x0,qAdrszMessErr           // display error message
bl strInsertAtCharInc               // insert result at @ character
mov x3,x0
mov x0,x2                      // error code
bl conversion10S                // conversion decimale
mov x0,x3
bl strInsertAtCharInc               // insert result at @ character
bl affichageMess
100:
ldp x2,lr,[sp],16              // restaur  2 registers
/*********************************/
/* init terminal state            */
/*********************************/
initTerm:
stp x1,lr,[sp,-16]!            // save  registers
mov x0,STDIN                   // input console
mov x1,TCGETS
mov x8,IOCTL                   // call system Linux
svc 0
cbnz x0,98f                    // error ?

str x0,[x1,sa_handler]         // maj handler
mov x0,SIGINT                  // signal type
mov x2,0
mov x3,8
mov x8,SIGACTION               // call system
svc 0

cmp x0,0                       // error ?
bne 98f
mov x0,SIGQUIT
mov x2,0                       // NULL
mov x8,SIGACTION               // call system
svc 0
cmp x0,0                       // error ?
bne 98f
mov x0,SIGTERM
mov x2,0                       // NULL
mov x8,SIGACTION               // appel systeme
svc 0
cmp x0,0
bne 98f
//
str x0,[x1,sa_handler]
mov x0,SIGTTOU                 //invalidate other process signal
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
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 x3,[x1,#term_c_lflag]
str x3,[x1,#term_c_lflag]      // and store
mov x0,STDIN                   // maj terminal current state
mov x1,TCSETS
mov x8,IOCTL                   // call system
svc 0
cbz x0,100f
98:                                // error display
bl   displayError
mov x0,-1
100:
ldp x1,lr,[sp],16              // restaur  2 registers
/*********************************/
/* init instance epool            */
/*********************************/
initPoll:
stp x1,lr,[sp,-16]!            // save  registers
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 x2,STDIN                   // Fd to we want add
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
bl   displayError
mov x0,-1
100:
ldp x1,lr,[sp],16              // restaur  2 registers
/*********************************/
/* wait key                      */
/*********************************/
/* x0 contains FD poll    */
waitKey:
stp x1,lr,[sp,-16]!            // save  registers
str xzr,[x11]                  // raz key
1:
ldr x1,qAdrqEnd                // if signal ctrl-c  -> end
ldr x1,[x1]
cbnz x1,100f

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
bl   displayError
mov x0,-1
100:
ldp x1,lr,[sp],16              // restaur  2 registers
/*********************************/
/*********************************/
/* x0 returns key value */
stp x1,lr,[sp,-16]!            // save  registers
mov x0,STDIN                   // File Descriptor
mov x2,KEYSIZE                 // key size
svc #0
cmp x0,0                       // error ?
ble 98f
ldr x0,[x2]
b 100f
98:                                // error display
bl   displayError
mov x0,-1
100:
ldp x1,lr,[sp],16              // restaur  2 registers
/*********************************/
/* restaur terminal state        */
/*********************************/
restauTerm:
stp x1,lr,[sp,-16]!            // save  registers
mov x0,STDIN                   // end then restaur begin state terminal
mov x1,TCSETS
mov x8,IOCTL                   // call system
svc 0
cbz x0,100f
bl   displayError
100:
ldp x1,lr,[sp],16              // restaur  2 registers
/********************************************************/
/*        File Include fonctions                        */
/********************************************************/
/* for this file see task include a file in language AArch64 assembly */
.include "../includeARM64.inc"```

Works with: GNAT
```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
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
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
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;

procedure Reset_Game is
begin
Board  := (others => (others => 0));
Blanks := 16;
Score  := 0;
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
for Row in t_Board'Range loop
for Column in t_Row'Range loop
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);

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);

Main_Loop: loop
Reset_Game;
Game_Loop: loop
Display_Board;
case Get_Keystroke is
when Restart => exit Game_Loop;
when Quit    => exit Main_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_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_Loop;
end if;
end if;
end loop Game_Loop;
end loop Main_Loop;
end Play_2048;
```
Output:
```+----+----+----+----+
|  2 | 16 |  2 |  2 |
+----+----+----+----+
| 64 |    |    |    |
+----+----+----+----+
|  4 |    |    |    |
+----+----+----+----+
|    |    |    |    |
+----+----+----+----+
Score = 184```

## ALGOL 68

```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);

TO 2 DO add new number(board) OD;

# 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);
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
FI
OD;

win: print board(board); print(("you win!", new line)) EXIT
lose: print(("you lose!", new line))
)```

## Amazing Hopper

```VERSION 1: "Hopper" flavour.
```
```#context-free select Position of aleatory tail
#context-free show Table
#context-free chek Winner or Game Over
#context-free print Table structure
#proto MovingRightDown(_X_)
#proto MovingLeftUp(_X_)
#proto checkPointLeftUp(_X_)
#proto checkPointRightDown(_X_)
#proto checkMoveRightDown(_X_)
#proto checkMoveLeftUp(_X_)
#define KUP        5
#define KDOWN     24
#define KLEFT     19
#define KRIGHT     4
#define KESCAPE   27
#define MOVEHORZ   1
#define MOVEVERT   0
#define equaltables(_X_,_Y_)   _I_=1,\
__LOOP_ROW__:,\
_J_=1,\
__LOOP_COL__:,\
[_I_,_J_]get(_X_),get(_Y_),neq? do{{0},jmp(__OUT__LOOP__)},\
++_J_,{4,_J_}jle(__LOOP_COL__),\
++_I_,{4,_I_}jle(__LOOP_ROW__),\
{1},__OUT__LOOP__:,clear mark
#include <hopper.h>

main:
.ctrl c
score=0
table=0,{4,4}zeros array(table)  // create table
oldTable=0, show Structure=1
/* define initial positions */
{""}tok sep
select Position of aleatory tail
select Position of aleatory tail
home
hide cursor
show Table
c=0, go=1,swFin=1

/* game! */
__PLAY_GAME__:

if key pressed?
lastkey(c)
oldTable = table
switch(c)
case(KRIGHT) :: do{ _check Move Right Down(MOVEHORZ), exit }
case(KDOWN)  :: do{ _check Move Right Down(MOVEVERT), exit }
case(KLEFT)  :: do{ _check Move Left Up(MOVEHORZ),    exit }
case(KUP)    :: do{ _check Move Left Up(MOVEVERT),    exit }
case(KESCAPE):: do{ go=0,swFin=0 }
end switch
kbfree
chek Winner or Game Over
{go}do{
if ( not( equal tables(oldTable, table) ) )
select Position  of aleatory tail
end if
show Table
}
end if
{go},jt(__PLAY_GAME__)

if ( {swFin} )
{"          \LG","YOU WIN!!!\OFF"}
else
{"          \LR","GAME OVER\OFF"}
end if
println
show cursor
exit(0)

.locals
Moving Right Down(tmpTab)
{tmpTab} compact,ht=0,cpy(ht), length,{4}sub,sizet=0,mov(sizet)
clear(tmpTab),{sizet}zerosarray(tmpTab)
{ht,tmpTab}array(CONCAT)
{tmpTab}
back
Moving Left Up(tmpTab)
{tmpTab} compact,clear(tmpTab),cpy(tmpTab), length,{4}sub,sizet=0,mov(sizet)
{sizet}zero?,not,do{ ht=0,{sizet}zerosarray(ht)
{ht,tmpTab}array(CONCAT) }
{tmpTab}
back
check Point Right Down(tmpTab)
v1=0,v2=0,tScore=0,totScore=0
for(k=4,{k}gthan(1),--k)
[k]get(tmpTab),mov(v1)
[{k}minus(1)]get(tmpTab),mov(v2)
if( {v1} eqto (v2) )
{tScore}plus(totScore),mov(totScore)
end if
next
{tmpTab,totScore}
back
check Move Right Down (_DIRECTION_)
tmpTab=0
for(i=1,{i}lethan(4),++i)
if ( {_DIRECTION_} )    // rows or cols??
[i,1:4]   // rows!
else
[1:4,i]   // cols!
end if
get(table), mov(tmpTab)

if( {tmpTab}stats(SUMMATORY) )     // exist numbers in the row??
clear mark
_Moving Right Down(tmpTab),mov(tmpTab)    // move its!
clear mark
_check Point Right Down(tmpTab),plus(score),mov(score)  // check score...
mov(tmpTab)
_Moving Right Down(tmpTab),mov(tmpTab)   // move remanents!
if( {_DIRECTION_} )
[i,1:4]
else
[1:4,i]
end if
{tmpTab}, put(table)
end if
next
clear mark.
back

check Point Left Up(tmpTab)
v1=0,v2=0,tScore=0,totScore=0
for(k=1,{k}lthan(4),++k)
[k]get(tmpTab),mov(v1)
[{k}plus(1)]get(tmpTab),mov(v2)
if( {v1} eqto (v2) )
{tScore}plus(totScore),mov(totScore)
end if
next
{tmpTab,totScore}
back

check Move Left Up(_DIRECTION_)
tmpTab=0
for(i=1,{i}lethan(4),++i)
if( {_DIRECTION_} )
[i,1:4]
else
[1:4,i]
end if
get(table),mov(tmpTab)
if( {tmpTab}stats(SUMMATORY) )  // exist numbers in the row??
clear mark
_Moving Left Up(tmpTab),mov(tmpTab)  // move its!
clear mark
_check Point Left Up(tmpTab),plus(score),mov(score)  // check score...
mov(tmpTab)
_Moving Left Up(tmpTab),mov(tmpTab)  // move remanents!
if( {_DIRECTION_} )
[i,1:4]
else
[1:4,i]
end if
{tmpTab},put(table)
end if
next
clear mark.
back

chek Winner or Game Over:
{table}gthan(0),xtonum,stats(SUMMATORY),{16} eq? do{{0}mov(go),{0}mov(swFin),back}  // You loose!
{0}reshape(table),{2048,table}array(SCAN){0} neq? do{{0}mov(go),back}   // you Win!
{4,4}reshape(table)
back

select Position of aleatory tail:
prec(-1)
__NO_VALID_POS__:
{10}rand, mulby(10),ceil,module(5),x=0,cpy(x),zero?,do{x=1}
{10}rand, mulby(10),ceil,module(5),y=0,cpy(y),zero?,do{y=1}
[x,y]get(table),jnz(__NO_VALID_POS__)
newTail=2
{1}rand,gthan(0.9), do{ newTail=4 }
{newTail},put(table), clear mark.
prec(0)
back

show Table:
tmpTable=0
// prepare colours of tiles
{"\BGLGR\BK      \OFF","  0   ",tmpTable}   transform, mov(tmpTable)
{"\BGLGR\BK  2   \OFF","  2   ",tmpTable}   transform, mov(tmpTable)
{"\BGLGR\B  4   \OFF","  4   ",tmpTable}    transform, mov(tmpTable)
{"\BGLGR\B  8   \OFF","  8   ",tmpTable}    transform, mov(tmpTable)
{"\BGR\W  16  \OFF","  16  ",tmpTable}      transform, mov(tmpTable)
{"\BGY\BK  32  \OFF","  32  ",tmpTable}     transform, mov(tmpTable)
{"\BGB\W  64  \OFF","  64  ",tmpTable}      transform, mov(tmpTable)
{"\BGLM\BK 128  \OFF"," 128  ",tmpTable}    transform, mov(tmpTable)
{"\BGG\W 256  \OFF"," 256  ",tmpTable}      transform, mov(tmpTable)
{"\BGB\W 512  \OFF"," 512  ",tmpTable}      transform, mov(tmpTable)
{"\BGR\W 1024 \OFF"," 1024 ",tmpTable}      transform, mov(tmpTable)
{"\BGBK\W\ENF 2048 \OFF"," 2048 ",tmpTable} transform, mov(tmpTable)

// and PRINT!!
{show Structure} do{ print Table structure,{0} mov(show Structure) }

clear mark
scrx=2
for (i=1, {i}lethan(4),++i)
{2,scrx}goxy,[1,i]get(tmpTable),print
{4,scrx}goxy,[2,i]get(tmpTable),print
{6,scrx}goxy,[3,i]get(tmpTable),print
{8,scrx}goxy,[4,i]get(tmpTable),print
clear mark
scrx += 7
next

{"\BGB\W\ENF","\n\n\t   2 0 4 8   \OFF\n\n"}
{"Movimiento # ",contador de movimientos,", \INVSCORE=",score,"\OFF\n"}//,tmpTable}
println

back

print Table structure:
{"┌──────┬──────┬──────┬──────┐\n"},strtoutf8,
{"│ 2048 │ 2048 │ 2048 │ 2048 │\n"},strtoutf8,
{"├──────┼──────┼──────┼──────┤\n"},strtoutf8,
{"│ 2048 │ 2048 │ 2048 │ 2048 │\n"},strtoutf8,
{"├──────┼──────┼──────┼──────┤\n"},strtoutf8,
{"│ 2048 │ 2048 │ 2048 │ 2048 │\n"},strtoutf8,
{"├──────┼──────┼──────┼──────┤\n"},strtoutf8,
{"│ 2048 │ 2048 │ 2048 │ 2048 │\n"},strtoutf8,
{"└──────┴──────┴──────┴──────┘"},strtoutf8,
println
back```
Output:
```Begin play...
┌──────┬──────┬──────┬──────┐
│      │      │  2   │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│      │      │      │      │
├──────┼──────┼──────┼──────┤
│  2   │      │      │      │
└──────┴──────┴──────┴──────┘
2 0 4 8

Movimiento # 0, SCORE=0

```
```Playing...
┌──────┬──────┬──────┬──────┐
│      │  2   │      │      │
├──────┼──────┼──────┼──────┤
│      │      │  2   │  4   │
├──────┼──────┼──────┼──────┤
│      │  2   │  32  │ 128  │
├──────┼──────┼──────┼──────┤
│      │  8   │  4   │  16  │
└──────┴──────┴──────┴──────┘
2 0 4 8

Movimiento # 90, SCORE=940
```
```Game Over...
┌──────┬──────┬──────┬──────┐
│  2   │  16  │  2   │  4   │
├──────┼──────┼──────┼──────┤
│  4   │ 128  │  4   │  8   │
├──────┼──────┼──────┼──────┤
│  16  │  8   │ 512  │  4   │
├──────┼──────┼──────┼──────┤
│  2   │  4   │  32  │  2   │
└──────┴──────┴──────┴──────┘
2 0 4 8

Movimiento # 347, SCORE=5040

GAME OVER
\$
```
```VERSION 2: "Hopper-BASIC" flavour.
```
```// Definicion de "contextos"
#context-free select Position of aleatory tail
set Decimal (-1)
tSIZE=0,
Let( tSIZE := var(SIZE) Plus (1) )
__NO_VALID_POS__:
x=0, y=0
When( Is Zero? ( Ceil( Rand(10) Mul by(10) ) Module (tSIZE) » (x) )){ x = 1 }
When( Is Zero? ( Ceil( Rand(10) Mul by(10) ) Module (tSIZE) » (y) )){ y = 1 }
At Interval [x,y], Get (table), Goto If Not Zero(__NO_VALID_POS__)
newTail=2
When ( Rand(1) Is Gt (0.9) ) { newTail=4 }
Take( newTail ), and SPut(table).
set Decimal(0)
Return\\

#context-free check Winner or Game Over
When ( Summatory ( Val( var(table) Is Gt (0) ) ), Is Eq? ( var(SIZE) Mulby(SIZE) ) ) {
MStore( 0, go, swFin ), and Back // You loose!
}
ReDim( table, 0 )  // convierte en vector
When( Scan(1, 2048, table ) Is Not Eq? (0) ){
ReDim( table, SIZE, SIZE ), Let ( go:=0 ) and Back   // You Win!
}
ReDim( table, SIZE, SIZE )
Return\\

#context-free show Table
tmpTable=0
Let ( tmpTable := CPad\$(" ", 6, Str\$(table)) )
Let ( tmpTable := Tran\$("\BGLGR\BK      \OFF","  0   ", tmpTable) )
Let ( tmpTable := Tran\$("\BGLGR\BK  2   \OFF","  2   ", tmpTable) )
Let ( tmpTable := Tran\$("\BGLGR\B  4   \OFF","  4   ", tmpTable) )
Let ( tmpTable := Tran\$("\BGLGR\B  8   \OFF","  8   ", tmpTable) )
Let ( tmpTable := Tran\$("\BGR\W  16  \OFF","  16  ", tmpTable) )
Let ( tmpTable := Tran\$("\BGY\BK  32  \OFF","  32  ", tmpTable) )
Let ( tmpTable := Tran\$("\BGB\W  64  \OFF","  64  ", tmpTable) )
Let ( tmpTable := Tran\$("\BGLM\BK 128  \OFF"," 128  ", tmpTable) )
Let ( tmpTable := Tran\$("\BGG\W 256  \OFF"," 256  ", tmpTable) )
Let ( tmpTable := Tran\$("\BGB\W 512  \OFF"," 512  ", tmpTable) )
Let ( tmpTable := Tran\$("\BGR\W 1024 \OFF"," 1024 ", tmpTable) )
Let ( tmpTable := Tran\$("\BGBK\W\ENF 2048 \OFF"," 2048 ", tmpTable) )
When( show Structure ) { print Table structure, and Let ( show Structure := 0 ) }
Clear Mark
scrx=2
For (i=1, var(i) Is Le (SIZE), ++i)
Locate in Column (scrx)
Locate in Row (2), At Interval [1,i], Print( Get (tmpTable) )
Locate in Row (4), At Interval [2,i], Print( Get (tmpTable) )
Locate in Row (6), At Interval [3,i], Print( Get (tmpTable) )
Locate in Row (8), At Interval [4,i], Print( Get (tmpTable) )
When( var(SIZE) Is Ge? (5) ) {Locate in Row (10), At Interval [5,i], Print( Get (tmpTable) )}
When( var(SIZE) Is Ge? (6) ) {Locate in Row (12), At Interval [6,i], Print( Get (tmpTable) )}
scrx += 7
Next
Clear Mark
Take( "\BGB\W\ENF","\n\n\t   2 0 4 8   \OFF\n\n",\
"Movimiento # ",contador de movimientos,", \INVSCORE=",score,"\OFF\n" )
and Print It
Return\\

#context-free print Table structure
Locate (1,1)
If ( var(SIZE) Is Eq? (4) )
Str2Utf8\$("┌──────┬──────┬──────┬──────┐\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("└──────┴──────┴──────┴──────┘")
Else If( var(SIZE) Is Eq? (5) )
Str2Utf8\$("┌──────┬──────┬──────┬──────┬──────┐\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("└──────┴──────┴──────┴──────┴──────┘")
Else If( var(SIZE) Is Eq? (6) )
Str2Utf8\$("┌──────┬──────┬──────┬──────┬──────┬──────┐\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("├──────┼──────┼──────┼──────┼──────┼──────┤\n")
Str2Utf8\$("│ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │ 2048 │\n")
Str2Utf8\$("└──────┴──────┴──────┴──────┴──────┴──────┘")
End If
now Print It
Return\\

// definicion de prototipos:
#proto MovingRightDown(_X_)
#proto MovingLeftUp(_X_)
#proto checkPointLeftUp(_X_)
#proto checkPointRightDown(_X_)
#proto checkMoveRightDown(_X_)
#proto checkMoveLeftUp(_X_)

// definiciones varias:
#define KUP        5
#define KDOWN     24
#define KLEFT     19
#define KRIGHT     4
#define KESCAPE   27
#define MOVEHORZ   1
#define MOVEVERT   0

// archivo de inclusión de macros H-BASIC:
#include <hbasic.h>

Begin
Option Ctrl+C
Option Stack 16

score=0, SIZE=4
When( ArgCount, Is Eq? (2) ){ get ArgNumber(2,SIZE) }

If ( var(SIZE) Is Not Between?(4,includ,6,includ) )
Print("Usage: hopper 2048.bas [4(default)-6]\n")
Stop
End If

Dim (SIZE,SIZE) for Zeros Array (table)

oldTable=0, show Structure=1
/* define initial positions */
Token Sep("")
select Position of aleatory tail
select Position of aleatory tail
Cls
Hide Cursor
show Table
c=0, go=1,swFin=1

/* game! */
While ( go )
Let ( c:=GetCh )
oldTable = table
Switch(c)
Case(KRIGHT) { _check Move Right Down(MOVEHORZ), Exit }
Case(KDOWN)  { _check Move Right Down(MOVEVERT), Exit }
Case(KLEFT)  { _check Move Left Up(MOVEHORZ),    Exit }
Case(KUP)    { _check Move Left Up(MOVEVERT),    Exit }
Case(KESCAPE){ go=0, swFin=0 }
End Switch

check Winner or Game Over
When( go ){
If ( are Not EqArray? (oldTable, table) )  //( not( equal tables(oldTable, table) ) )
select Position  of aleatory tail
End If
show Table
}
Wend

If ( swFin )
show Table
Print("          \LG","YOU WIN!!!\OFF")
Else
Print("          \LR","GAME OVER\OFF")
End If
Put a Newl
Show Cursor
End

Subrutines

Moving Right Down(tmpTab)
ht=0, sizet=0
Let( sizet := var(SIZE) Minus ( Length( Compact(tmpTab), Copy to (ht) ) ) )
If ( Is Not Zero?(sizet) )
Clear(tmpTab)
Dim (sizet) for Zeros Array (tmpTab)
Concat( ht,tmpTab )
End If
Return (tmpTab)

Moving Left Up(tmpTab)
sizet=0
Compact( tmpTab ),Clear(tmpTab) and Copy to (tmpTab); get Length It, Subtracted from (SIZE); then Move to (sizet)
//When( Is Not Zero?(sizet) ){
When( var(sizet) Is Not Zero? ){
ht=0
Dim (sizet) for Zeros Array (ht)
Concat( ht, tmpTab )
}
Return (tmpTab)

check Point Right Down(tmpTab)
v1=0,v2=0,tScore=0,totScore=0
For(k=SIZE, var(k) Is Gt (1), --k)
Set Interval [k] and Get (tmpTab); then Move to (v1)
Set Interval [ var(k) Minus(1)], Get(tmpTab), and Move to (v2)
If( var(v1) Is Eq? (v2) )
At Interval [k] Put(tmpTab)
At Interval [ var(k) Minus(1) ], now Take(0); then Put (tmpTab)
Let( totScore := var(tScore) Plus (totScore) )
End If
Next
Take(tmpTab,totScore) and Return

check Move Right Down (_DIRECTION_)
tmpTab=0
For(i=1, while var(i) Is Le (SIZE), ++i)
If ( _DIRECTION_ )    // rows or cols??
Set Interval [i,1:SIZE]   // rows!
Else
Set Interval [1:SIZE,i]   // cols!
End If
now Let( tmpTab := Get(table) )

If( Summatory ( tmpTab ) )     // exist numbers in the row??
Clear Mark

Let( tmpTab := _Moving Right Down(tmpTab) )    // move its!
Clear Mark
Store ( _check Point Right Down(tmpTab) Plus (score), tmpTab, score )  // check score...
Clear Mark
Let ( tmpTab := _Moving Right Down(tmpTab) )   // move remanents!
If( _DIRECTION_ )
Set Interval [i,1:SIZE]
Else
Set Interval [1:SIZE,i]
End If
Take( tmpTab ), and Put(table)
End If
Next
Clear(tmpTab) and Clear Mark.
Return

check Point Left Up(tmpTab)
v1=0,v2=0,tScore=0,totScore=0
For(k=1, while var(k) Is Lt (SIZE),++k)
At Interval [k] Get (tmpTab), and Move to (v1)
At Interval [ var(k) Plus(1) ] Get(tmpTab), and Move to (v2)
If( var(v1) Is Eq? (v2) )
At Interval [k] Put(tmpTab), At Interval [ var(k) Plus(1)]; then Take(0) and Put(tmpTab)
Let( totScore := var(tScore) Plus (totScore) )
End If
Next
Take (tmpTab,totScore)
Return

check Move Left Up(_DIRECTION_)
tmpTab=0
For(i=1, while var(i) Is Le (SIZE),++i)
If( _DIRECTION_ )
Set Interval [i,1:SIZE]
Else
Set Interval [1:SIZE,i]
End If
now Get(table), and Move to (tmpTab)
If( Summatory (tmpTab) )  // exist numbers in the row??
Clear Mark
Let ( tmpTab := _Moving Left Up(tmpTab) )  // move its!
Clear Mark
Store ( _check Point Left Up(tmpTab) Plus (score), tmpTab, score )  // check score...

Clear Mark
Let ( tmpTab := _Moving Left Up(tmpTab) )  // move remanents!
If( _DIRECTION_ )
Set Interval [i,1:SIZE]
Else
Set Interval [1:SIZE,i]
End If
now Take (tmpTab), and Put(table)
End If
Next
Clear Mark.
Return```
Output:
```Se invoca como:

rxvt -g 79x38 -fn "xft:FantasqueSansMono-Regular:pixelsize=25" -e hopper bas/2048.bas

```

## Applesoft BASIC

```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  ***************
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   ***************
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```

## ARM Assembly

Works with: as version Raspberry Pi
```/* 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_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
.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
bl affichageMess
bl razTable
2:
cmp r0,#-1
beq 5f                            @ end game
bl displayGame
3:
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
bl affichageMess
b 10f
5:                                    @ display message no solution
bl affichageMess

10:                                   @ display new game ?
bl affichageMess
bl affichageMess
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

/******************************************************************/
/*     raz table cases                                                   */
/******************************************************************/
razTable:
push {r0-r2,lr}                  @ save  registers
mov r0,#0
mov r2,#0
1:
str r0,[r1,r2,lsl #2]
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:
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
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
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
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
cmp r3,#SIZE
blt 2b
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
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
ldr r4,[r1,r5,lsl #2]
cmp r4,#0
beq 5f

cmp r6,#0
beq 3f                      @ no empty right case
mov r0,#0
str r0,[r1,r5,lsl #2]       @ raz digit
str r4,[r1,r5,lsl #2]       @ and store to right empty position
mov r0,#1
3:
cmp r7,#0                   @ first digit
beq 4f
ldr r9,[r1,r8,lsl #2]
cmp r4,r9                   @ equal ?
bne 4f
cmp r10,r4
beq 4f
mov r10,r4
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
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
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
ldr r4,[r1,r5,lsl #2]
cmp r4,#0
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
str r4,[r1,r5,lsl #2]       @ and store to right empty position
mov r0,#1
3:
cmp r7,#0                   @ first digit
beq 4f
ldr r9,[r1,r8,lsl #2]
cmp r4,r9                   @ equal ?
bne 4f
cmp r10,r4
beq 4f
mov r10,r4
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
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
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
ldr r4,[r1,r5,lsl #2]
cmp r4,#0
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
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
cmp r2,#SIZE
blt 2b
cmp r3,#SIZE
blt 1b

100:
pop {r1-r5,lr}
bx lr                          @ return
/******************************************************************/
/*           add new digit on game                                   */
/******************************************************************/
/* r0 return -1 if ok     */
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
mov r3,#0
mov r4,#0
1:
ldr r2,[r1,r3,lsl #2]
cmp r2,#0
bne 2f
str r3,[fp,r4,lsl #2]
2:
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
/******************************************************************/
/*            display game                                      */
/******************************************************************/
displayGame:
push {r1-r3,lr}            @ save registers
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
mov r2,#0
1:
ldr r0,[r1,r2,lsl #2]
bl digitString
bl affichageMess
bl affichageMess
cmp r2,#SIZE
blt 1b
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
2:
ldr r0,[r1,r2,lsl #2]
bl digitString
bl affichageMess
bl affichageMess
cmp r2,#SIZE*2
blt 2b
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
3:
ldr r0,[r1,r2,lsl #2]
bl digitString
bl affichageMess
bl affichageMess
cmp r2,#SIZE*3
blt 3b
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
bl affichageMess
4:
ldr r0,[r1,r2,lsl #2]
bl digitString
bl affichageMess
bl affichageMess
cmp r2,#SIZE*4
blt 4b
bl affichageMess
bl affichageMess
bl affichageMess

100:
pop {r1-r3,lr}
bx lr                       @ return
/******************************************************************/
/*            digits string                                       */
/******************************************************************/
/* r0 contains number */
/* r0 return address string */
digitString:
push {r1,lr}        @ save registers
cmp r0,#0
bne 1f
b 100f
1:
cmp r0,#2
bne 2f
b 100f
2:
cmp r0,#4
bne 3f
b 100f
3:
cmp r0,#8
bne 4f
b 100f
4:
cmp r0,#16
bne 5f
b 100f
5:
cmp r0,#32
bne 6f
b 100f
6:
cmp r0,#64
bne 7f
b 100f
7:
cmp r0,#128
bne 8f
b 100f
8:
cmp r0,#256
bne 9f
b 100f
9:
cmp r0,#512
bne 10f
b 100f
10:
cmp r0,#1024
bne 11f
b 100f
11:
cmp r0,#2048
bne 12f
b 100f
12:
bl   displayError
100:
pop {r1,lr}
bx lr                   @ return

/***************************************************/
/*   Generation random number                  */
/***************************************************/
/* r0 contains limit  */
genereraleas:
push {r1-r4,lr}         @ save registers
ldr r2,[r4]
ldr r3,iNbDep1
mul r2,r3,r2
ldr r3,iNbDep2
str r2,[r4]             @ maj de la graine pour l appel suivant
cmp r0,#0
beq 100f
mov r0,r2               @ dividende
bl division
mov r0,r3               @ résult = remainder

100:                        @ end function
pop {r1-r4,lr}          @ restaur registers
bx lr                   @ return
/*****************************************************/
iNbDep1:     .int 0x343FD
iNbDep2:     .int 0x269EC3
/***************************************************/
/***************************************************/
push {r1-r7,lr}
mov r5,#0
str r5,[r1]                                  @ raz 4 bytes iTouche
mov r0,#STDIN                                @ input console
mov r1,#TCGETS
mov r7, #IOCTL                               @ call system Linux
svc #0
cmp r0,#0                                    @ error ?
beq 1f
bl   displayError
mov r0,#-1
b 100f
1:
str r0,[r1,#sa_handler]                      @ maj handler
mov r0,#SIGINT                               @ signal type
mov r2,#0                                    @ NULL
mov r7, #SIGACTION                           @ call system
svc #0
cmp r0,#0                                    @ error ?
bne 97f
mov r0,#SIGQUIT
mov r2,#0                                    @ NULL
mov r7, #SIGACTION                           @ call system
svc #0
cmp r0,#0                                    @ error ?
bne 97f
mov r0,#SIGTERM
mov r2,#0                                    @ NULL
mov r7, #SIGACTION                           @ appel systeme
svc #0
cmp r0,#0
bne 97f
@
str r0,[r1,#sa_handler]
mov r0,#SIGTTOU                              @invalidate other process signal
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
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 r3,[r1,#term_c_lflag]
str r3,[r1,#term_c_lflag]                    @ and store
mov r0,#STDIN                                @ maj terminal current state
mov r1,#TCSETS
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
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
mov r0,#STDIN                                 @ File Descriptor
mov r2,#BUFFERSIZE                            @ buffer size
svc #0
cmp r0,#0                                     @ error ?
bgt 98f

97:                                               @ error detected
bl   displayError
mov r5,#-1
98:                                               @ end then restaur begin state terminal
mov r0,#STDIN
mov r1,#TCSETS
mov r7,#IOCTL                                 @ call system
svc #0
cmp r0,#0
beq 99f                                       @ restaur ok
bl   displayError
mov r0,#-1
b 100f
99:
cmp r5,#0                                     @ no error or control-c ?
ldreqb r0,[r2,#2]                             @ return key byte
movne r0,r5                                   @ or error
100:
pop {r1-r7, lr}
bx lr
iSIG_IGN:                 .int 1
/******************************************************************/
/*     traitement du signal                                       */
/******************************************************************/
sighandler:
push {r0,r1}
mov r1,#1                 @ maj zone end
str r1,[r0]
pop {r0,r1}
bx lr
/***************************************************/
/*      ROUTINES INCLUDE                           */
/***************************************************/
.include "../affichage.inc"```
Output:
```-----------------------------
|      |      |      |      |
|   8  |   4  |   4  |   2  |
|      |      |      |      |
-----------------------------
|      |      |      |      |
|  64  |  16  |      |      |
|      |      |      |      |
-----------------------------
|      |      |      |      |
|  16  |      |      |   2  |
|      |      |      |      |
-----------------------------
|      |      |      |      |
|   2  |      |      |      |
|      |      |      |      |
-----------------------------
```

## AutoHotkey

```Grid := [], s := 16, w := h := S * 4.5
Gui, font, s%s%
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
;------------------------------
#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
}
}
}
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
}
}
}
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
}

}
}
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
}
}
}
return

;------------------------------
#IfWinActive
;------------------------------
if EndOfGame()
{
MsgBox Done `nPress OK to retry
goto start
}
return

;------------------------------
EndOfGame(){
global
if Move
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%
}
}

;------------------------------
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
}
;------------------------------
```

## Batch File

```:: 2048 Game Task from RosettaCode
:: Batch File Implementation v2.0.1

@echo off
setlocal enabledelayedexpansion

rem initialization
:begin_game
set "size=4"   %== board size ==%
set "score=0"   %== current score ==%
set "won=0"   %== boolean for winning ==%
set "target=2048"   %== as the game title says ==%
for /l %%R in (1,1,%size%) do for /l %%C in (1,1,%size%) do set "X_%%R_%%C=0"

rem add two numbers in the board

rem main game loop
:main_loop
call :display
echo(
echo(Keys: WASD (Slide Movement), N (New game), P (Exit)

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

set "changed=0"   %== boolean for changed board ==%
set "valid_key=0"   %== boolean for pressing WASD ==%
rem process keypress
if /i "!key!" equ "W" (set "valid_key=1" & call :slide "C" "1,1,%size%" "X")
if /i "!key!" equ "A" (set "valid_key=1" & call :slide "R" "1,1,%size%" "X")
if /i "!key!" equ "S" (set "valid_key=1" & call :slide "C" "%size%,-1,1" "X")
if /i "!key!" equ "D" (set "valid_key=1" & call :slide "R" "%size%,-1,1" "X")
if /i "!key!" equ "N" goto begin_game
if /i "!key!" equ "P" exit /b 0
if "%valid_key%" equ "0" goto main_loop

rem check if the board changed
if %changed% neq 0 call :addtile

rem check for win condition
if %won% equ 1 (
set "msg=Nice one... You WON^!^!"
goto gameover
)

rem check for lose condition
if %blank_count% equ 0 (
for /l %%R in (1,1,%size%) do for /l %%C in (1,1,%size%) do set "LX_%%R_%%C=!X_%%R_%%C!"
set "save_changed=%changed%" & set "changed=0"   %== save actual changed for test ==%
call :slide "C" "1,1,%size%" "LX"
call :slide "R" "1,1,%size%" "LX"
if !changed! equ 0 (
set "msg=No moves are possible... Game Over :("
goto gameover
) else set "changed=!save_changed!"
)
goto main_loop

rem add number to a random blank tile
set "blank_count=0"   %== blank tile counter ==%
set "new_tile="   %== clearing ==%
rem create pseudo-array blank_tiles
for /l %%R in (1,1,%size%) do (
for /l %%C in (1,1,%size%) do (
if !X_%%R_%%C! equ 0 (
set "blank_tiles[!blank_count!]=X_%%R_%%C"
set /a "blank_count+=1"
)
)
)
if %blank_count% equ 0 goto :EOF
set /a "pick_tile=%random%%%%blank_count%"
set "new_tile=!blank_tiles[%pick_tile%]!"
set /a "rnd_newnum=%random%%%10"
rem 10% chance new number is 4, 90% chance it's 2
if %rnd_newnum% equ 5 (set "%new_tile%=4") else (set "%new_tile%=2")
set /a "blank_count-=1"   %== to be used for checking lose condition ==%
goto :EOF

rem display the board
:display
cls
echo(2048 Game in Batch
echo(
set "wall=+"
for /l %%C in (1,1,%size%) do set "wall=!wall!----+"
for /l %%R in (1,1,%size%) do (
set "disp_row=|"
for /l %%C in (1,1,%size%) do (
if "!new_tile!" equ "X_%%R_%%C" (set "DX_%%R_%%C=  +!X_%%R_%%C!") else (
set "DX_%%R_%%C=!X_%%R_%%C!"
if !X_%%R_%%C! lss 1000 set "DX_%%R_%%C= !DX_%%R_%%C!"
if !X_%%R_%%C! lss 100 set "DX_%%R_%%C= !DX_%%R_%%C!"
if !X_%%R_%%C! lss 10 set "DX_%%R_%%C= !DX_%%R_%%C!"
if !X_%%R_%%C! equ 0 set "DX_%%R_%%C=    "
)
set "disp_row=!disp_row!!DX_%%R_%%C!|"
)
echo(%wall%
echo(!disp_row!
)
echo(%wall%
echo(
echo(Score: %score%
goto :EOF

rem the main slider of numbers in tiles
:slide
rem %%A and %%B are used here because sliding direction is variable
for /l %%A in (1,1,%size%) do (
rem first slide: removing blank tiles in the middle
set "slide_1="
set "last_blank=0"   %== boolean if last tile is blank ==%
for /l %%B in (%~2) do (
if "%~1" equ "R" (set "curr_tilenum=!%~3_%%A_%%B!"
) else if "%~1" equ "C" (set "curr_tilenum=!%~3_%%B_%%A!")
if !curr_tilenum! equ 0 (set "last_blank=1") else (
set "slide_1=!slide_1! !curr_tilenum!"
if !last_blank! equ 1 set "changed=1"
set "last_blank=0"
)
)
rem second slide: addition of numbered tiles
rem slide_2 would be pseudo-array
set "slide_2_count=0"
set "skip=1"   %== boolean for skipping after previous summing ==%
if "!slide_1!" neq "" for %%S in (!slide_1! 0) do (
if !skip! equ 1 (
set "prev_tilenum=%%S" & set "skip=0"
) else if !skip! equ 0 (
if %%S equ !prev_tilenum! (
set /a "sum=%%S+!prev_tilenum!"
if "%~3" equ "X" set /a "score+=sum"
set "changed=1" & set "skip=1"
rem check for winning condition!
if !sum! equ !target! set "won=1"
) else (
set "sum=!prev_tilenum!"
set "prev_tilenum=%%S"
)
set "slide_2[!slide_2_count!]=!sum!"
set /a "slide_2_count+=1"
)
)
rem new values of tiles
set "slide_2_run=0"   %== running counter for slide_2 ==%
for /l %%B in (%~2) do (
if "%~1" equ "R" (set "curr_tile=%~3_%%A_%%B"
) else if "%~1" equ "C" (set "curr_tile=%~3_%%B_%%A")
for %%? in ("!slide_2_run!") do (
if %%~? lss !slide_2_count! (set "!curr_tile!=!slide_2[%%~?]!"
) else (set "!curr_tile!=0")
)
set /a "slide_2_run+=1"
)
)
goto :EOF

rem game over xD
:gameover
call :display
echo(
echo(!msg!
echo(
echo(Press any key to exit . . .
pause>nul
exit /b 0
```
Output:
```2048 Game in Batch

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

Score: 60

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

## BASIC

### QBasic

El código es de MichD (https://github.com/michd/2048-qbasic)

Yo solo lo transcribo.

```SCREEN 13
PALETTE 1, pColor(35, 33, 31)
PALETTE 2, pColor(46, 46, 51)
PALETTE 3, pColor(59, 56, 50)
PALETTE 4, pColor(61, 44, 30)
PALETTE 5, pColor(61, 37, 25)
PALETTE 6, pColor(62, 31, 24)
PALETTE 7, pColor(62, 24, 15)
PALETTE 8, pColor(59, 52, 29)
PALETTE 9, pColor(59, 51, 24)
PALETTE 10, pColor(59, 50, 20)
PALETTE 11, pColor(59, 49, 16)
PALETTE 12, pColor(59, 49, 12)
PALETTE 13, pColor(15, 15, 13)
PALETTE 14, pColor(23, 22, 20)

DIM SHARED gDebug
DIM SHARED gOriginX
DIM SHARED gOriginY
DIM SHARED gTextOriginX
DIM SHARED gTextOriginY
DIM SHARED gSquareSide
DIM SHARED gGridSize

gGridSize = 4  ' grid size (4 -> 4x4)

DIM SHARED gGrid(gGridSize, gGridSize)
DIM SHARED gScore

' Don't touch these numbers, seriously

gOriginX = 75 'pixel X of top left of grid
gOriginY = 12 'pixel Y of top right of grid
gTextOriginX = 11
gTextOriginY = 3
gSquareSide = 38 'width/height of block in pixels

'set up all the things!
gDebug = 0

RANDOMIZE TIMER
CLS

start:
initGrid
initGraphicGrid
renderGrid

gScore = 0

LOCATE 23, 1
PRINT "Move with arrow keys. (R)estart, (Q)uit"

' keyboard input loop
DO
DO
k\$ = INKEY\$
LOOP UNTIL k\$ <> ""

SELECT CASE k\$
CASE CHR\$(0) + CHR\$(72) 'up
processMove ("u")
CASE CHR\$(0) + CHR\$(80) 'down
processMove ("d")
CASE CHR\$(0) + CHR\$(77) 'right
processMove ("r")
CASE CHR\$(0) + CHR\$(75) 'left
processMove ("l")
CASE CHR\$(27)           'escape
GOTO programEnd
CASE "q"
GOTO programEnd
CASE "Q"
GOTO programEnd
CASE "r"
GOTO start
CASE "R"
GOTO start
END SELECT
LOOP

programEnd:

DIM emptyCells(gGridSize * gGridSize, 2)
emptyCellCount = 0

FOR x = 0 TO gGridSize - 1
FOR y = 0 TO gGridSize - 1
IF gGrid(x, y) = 0 THEN
emptyCells(emptyCellCount, 0) = x
emptyCells(emptyCellCount, 1) = y
emptyCellCount = emptyCellCount + 1
END IF
NEXT y
NEXT x

IF emptyCellCount > 0 THEN
index = INT(RND * emptyCellCount)
num = CINT(RND + 1) * 2
gGrid(emptyCells(index, 0), emptyCells(index, 1)) = num
END IF

END SUB

SUB drawNumber (num, xPos, yPos)
SELECT CASE num
CASE 0:    c = 16
CASE 2:    c = 2
CASE 4:    c = 3
CASE 8:    c = 4
CASE 16:   c = 5
CASE 32:   c = 6
CASE 64:   c = 7
CASE 128:  c = 8
CASE 256:  c = 9
CASE 512:  c = 10
CASE 1024: c = 11
CASE 2048: c = 12
CASE 4096: c = 13
CASE 8192: c = 13
CASE ELSE: c = 13
END SELECT

x = xPos * (gSquareSide + 2) + gOriginX + 1
y = yPos * (gSquareSide + 2) + gOriginY + 1
LINE (x + 1, y + 1)-(x + gSquareSide - 1, y + gSquareSide - 1), c, BF

IF num > 0 THEN
LOCATE gTextOriginY + 1 + (yPos * 5), gTextOriginX + (xPos * 5)
PRINT "    "
LOCATE gTextOriginY + 2 + (yPos * 5), gTextOriginX + (xPos * 5)
LOCATE gTextOriginY + 3 + (yPos * 5), gTextOriginX + (xPos * 5)
'PRINT "    "
END IF

END SUB

FUNCTION getAdjacentCell (x, y, d AS STRING)

IF (d = "l" AND x = 0) OR (d = "r" AND x = gGridSize - 1) OR (d = "u" AND y = 0) OR (d = "d" AND y = gGridSize - 1) THEN
ELSE
SELECT CASE d
CASE "l": getAdjacentCell = gGrid(x - 1, y)
CASE "r": getAdjacentCell = gGrid(x + 1, y)

CASE "u": getAdjacentCell = gGrid(x, y - 1)
CASE "d": getAdjacentCell = gGrid(x, y + 1)
END SELECT
END IF

END FUNCTION

'Draws the outside grid (doesn't render tiles)
SUB initGraphicGrid

gridSide = (gSquareSide + 2) * gGridSize

LINE (gOriginX, gOriginY)-(gOriginX + gridSide, gOriginY + gridSide), 14, BF 'outer square, 3 thick
LINE (gOriginX, gOriginY)-(gOriginX + gridSide, gOriginY + gridSide), 1, B 'outer square, 3 thick
LINE (gOriginX - 1, gOriginY - 1)-(gOriginX + gridSide + 1, gOriginY + gridSide + 1), 1, B
LINE (gOriginX - 2, gOriginY - 2)-(gOriginX + gridSide + 2, gOriginY + gridSide + 2), 1, B

FOR x = gOriginX + gSquareSide + 2 TO gOriginX + (gSquareSide + 2) * gGridSize STEP gSquareSide + 2  ' horizontal lines
LINE (x, gOriginY)-(x, gOriginY + gridSide), 1
NEXT x

FOR y = gOriginY + gSquareSide + 2 TO gOriginY + (gSquareSide + 2) * gGridSize STEP gSquareSide + 2 ' vertical lines
LINE (gOriginX, y)-(gOriginX + gridSide, y), 1
NEXT y

END SUB

'Init the (data) grid with 0s
SUB initGrid
FOR x = 0 TO 3
FOR y = 0 TO 3
gGrid(x, y) = 0
NEXT y
NEXT x

END SUB

SUB moveBlock (sourceX, sourceY, targetX, targetY, merge)

IF sourceX < 0 OR sourceX >= gGridSize OR sourceY < 0 OR sourceY >= gGridSize AND gDebug = 1 THEN
LOCATE 0, 0
PRINT "moveBlock: source coords out of bounds"
END IF

IF targetX < 0 OR targetX >= gGridSize OR targetY < 0 OR targetY >= gGridSize AND gDebug = 1 THEN
LOCATE 0, 0
PRINT "moveBlock: source coords out of bounds"
END IF

sourceSquareValue = gGrid(sourceX, sourceY)
targetSquareValue = gGrid(targetX, targetY)

IF merge = 1 THEN

IF sourceSquareValue = targetSquareValue THEN
gGrid(sourceX, sourceY) = 0
gGrid(targetX, targetY) = targetSquareValue * 2
gScore = gScore + targetSquareValue * 2 ' Points!
ELSEIF gDebug = 1 THEN
LOCATE 0, 0
PRINT "moveBlock: Attempted to merge unequal sqs"
END IF

ELSE

IF targetSquareValue = 0 THEN
gGrid(sourceX, sourceY) = 0
gGrid(targetX, targetY) = sourceSquareValue
ELSEIF gDebug = 1 THEN
LOCATE 0, 0
PRINT "moveBlock: Attempted to move to non-empty block"
END IF

END IF

END SUB

strNum\$ = LTRIM\$(STR\$(num))

SELECT CASE LEN(strNum\$)
CASE 1: pad = "  " + strNum\$ + " "
CASE 2: pad = " " + strNum\$ + " "
CASE 3: pad = " " + strNum\$
END SELECT

END FUNCTION

FUNCTION pColor (r, g, b)
pColor = (r + g * 256 + b * 65536)
END FUNCTION

SUB processMove (dir AS STRING)
' dir can be 'l', 'r', 'u', or 'd'

hasMoved = 0

IF dir = "l" THEN

FOR y = 0 TO gGridSize - 1
wasMerge = 0
FOR x = 0 TO gGridSize - 1
GOSUB processBlock
NEXT x
NEXT y

ELSEIF dir = "r" THEN

FOR y = 0 TO gGridSize - 1
wasMerge = 0
FOR x = gGridSize - 1 TO 0 STEP -1
GOSUB processBlock
NEXT x
NEXT y

ELSEIF dir = "u" THEN
FOR x = 0 TO gGridSize - 1
wasMerge = 0
FOR y = 0 TO gGridSize - 1
GOSUB processBlock
NEXT y
NEXT x

ELSEIF dir = "d" THEN
FOR x = 0 TO gGridSize - 1
wasMerge = 0
FOR y = gGridSize - 1 TO 0 STEP -1
GOSUB processBlock
NEXT y
NEXT x

END IF

GOTO processMoveEnd

moveToObstacle:
curX = x
curY = y

DO WHILE getAdjacentCell(curX, curY, dir) = 0
SELECT CASE dir
CASE "l": curX = curX - 1
CASE "r": curX = curX + 1
CASE "u": curY = curY - 1
CASE "d": curY = curY + 1
END SELECT
LOOP
RETURN

processBlock:

merge = 0
IF gGrid(x, y) <> 0 THEN ' have block

GOSUB moveToObstacle ' figure out where it can be moved to
IF getAdjacentCell(curX, curY, dir) = gGrid(x, y) AND wasMerge = 0 THEN  ' obstacle can be merged with
merge = 1
wasMerge = 1
ELSE
wasMerge = 0
END IF

IF curX <> x OR curY <> y OR merge = 1 THEN
mergeDirX = 0
mergeDirY = 0
IF merge = 1 THEN
SELECT CASE dir
CASE "l": mergeDirX = -1
CASE "r": mergeDirX = 1
CASE "u": mergeDirY = -1
CASE "d": mergeDirY = 1
END SELECT
END IF

CALL moveBlock(x, y, curX + mergeDirX, curY + mergeDirY, merge) ' move to before obstacle or merge
hasMoved = 1
END IF
END IF
RETURN

processMoveEnd:

IF hasMoved = 1 THEN addblock
renderGrid

END SUB

SUB renderGrid
FOR x = 0 TO gGridSize - 1
FOR y = 0 TO gGridSize - 1
CALL drawNumber(gGrid(x, y), x, y)
NEXT y
NEXT x
END SUB

LOCATE 1, 10
PRINT "Score:" + STR\$(gScore)
END SUB
```

## BBC BASIC

```      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
```
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 :-(```

## BQN

```#!/usr/bin/env BQN
# 2048 game
# The game is controlled with the vim movement keys:
# h: left, j: down, k: up, l: right, q: quit
# needs a VT-100 compatible terminal

Merge←{𝕩 0⊸≠⊸/↩ ⋄ m←<`=⟜«𝕩 ⋄ 4↑(¬»m)/𝕩×1+m}⌾⌽ # Merge a single row to the right
Step←Merge˘    # Merges each row of the board
Up←Step⌾(⌽⍉∘⌽) # Each direction merges the board
Down←Step⌾⍉    # by rotating it to the correct orientation, merging the rows
Right←Step     # and reversing the rotation
Left←Step⌾(⌽˘)
# Spawns a 2 or a 4 (10% chance) at a random empty position
Spawn←{i←•rand.Range∘≠⊸⊑(0⊸= /○⥊ ↕∘≢)𝕩 ⋄ (•rand.Range∘≠⊸⊑9‿1/2‿4)⌾(i⊸⊑) 𝕩}
Lose←Left∘Right∘Down∘Up⊸≡ # Losing condition, no moves change the board
Win←∨´·∨˝2048⊸= #  Winning condition, 2048!

Quit←{•Out e∾"[?12l"∾e∾"[?25h" ⋄ •Exit 𝕩} # Restores the terminal and exits
Display←{ # Displays the board, score and controls
•Out e∾"[H"∾e∾"[2J" # Cursor to origin and clear screen
•Out "Controls: h: left, j: down, k: up, l: right, q: quit"
•Show 𝕩
•Out "score: "∾•Repr ⌈´⌈˝ 𝕩
}

board←Spawn 4‿4⥊0
e←@+27 # Escape character for the ANSI escape codes
•term.RawMode 1
•Out e∾"[?25l"∾e∾"[2J"∾e∾"[H" # Cursor to origin, hide it and clear screen
{𝕤⋄
Display board
{𝕤⋄•Out "You win!" ⋄ Quit 0}⍟Win board
{𝕤⋄•Out "You lose!"⋄ Quit 1}⍟Lose board
⊑key∊"hjklq"? # Valid key?
{𝕤⋄ Quit 0}⍟(key='q')@ # Quit key?
move←⊑(key="hjkl")/Left‿Down‿Up‿Right # Get movement function from the key
{𝕤⋄board↩Spawn∘Move 𝕩}⍟(Move⊸≢) board # Generate the next board if the move is valid
; @
}•_While_{𝕤⋄1}@
```

## 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.

```#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#define D_INVALID -1
#define D_UP       1
#define D_DOWN     2
#define D_RIGHT    3
#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 */
#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;
}

#undef MERGE_DIRECTION
}

void do_gravity(int d)
{
#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;
}

#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;
}
```
Output:
```Score: 1100 (+4)
-------------------------
|  64 |  32 |  64 |  32 |
|  32 |  16 |   2 |   8 |
|  16 |   4 |   8 |   4 |
|   4 |   2 |   4 |   2 |
-------------------------
You lose!
```

### Version 2

```#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define EMPTY_TILE 0
#define ROWS 4
#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
*/

{
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;
}
}
}
}
}

{
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);
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")) {
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);
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")) {
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 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;
}
```

## C#

Translation of: C++
```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()
{
while (true)
{
if (_isMoved)
{
}

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 = " ";
}
else
{
}

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;

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;
}
}
}

{
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;
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);
}
}
}
```
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++

```#include <time.h>
#include <iostream>
#include <string>
#include <iomanip>
#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()
{
while( true )
{
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;
}
{
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" );
}
```
Output:
```SCORE: 2024

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

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

## 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 []
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))

(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]))))

(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))))

;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)

(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)
```
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.

```(ql:quickload '(cffi alexandria))

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

(in-package :2048-lisp)

;; Load msvcrt.dll to access _getch.
(define-foreign-library msvcrt
(:windows (:default "msvcrt")))

(use-foreign-library msvcrt)

(defcfun "_getch" :int)

"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))

"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)
(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..."))
(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))
```
Output:
```* (2048-lisp::prompt)

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

Some time later...

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

## D

Translation of: C++
```import std.stdio, std.string, std.random;
import core.stdc.stdlib: exit;

struct G2048 {
void gameLoop() {
while (true) {
if (moved)
drawBoard;
if (done)
break;
waitKey;
}
writeln(win ? "You win!" : "Game Over!");
}

private:
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 {
writeln("SCORE: ", score, "\n");
foreach (y; 0 .. side) {
write("+------+------+------+------+\n| ");
foreach (x; 0 .. side) {
if (board[x][y].val)
writef("%4d", board[x][y].val);
else
writef("%4s", " ");
write(" | ");
}
writeln;
}
writeln("+------+------+------+------+\n");
}

void waitKey() /*@safe*/ {
moved = false;
write("(W)Up (S)Down (A)Left (D)Right (Q)Quit: ");

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 (y; 0 .. side)
foreach (x; 0 .. side)
board[x][y].blocked = false;
}

void endGame() const {
writeln("Game ended with score: ", score);
exit(0);
}

foreach (y; 0 .. side) {
foreach (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 {
foreach (y; 0 .. side)
foreach (x; 0 .. side)
if (!board[x][y].val)
return true;

foreach (y; 0 .. side) {
foreach (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) ||
return true;
}
}
return false;
}

bool testAdd(uint x, uint y, uint v) const {
if (x > 3 || y > 3)
return false;
return board[x][y].val == v;
}

void moveVertically(uint x, uint y, uint 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)
moveVertically(x, y + d,  1);
} else {
if (y + d > 0)
moveVertically(x, y + d, -1);
}
}

void moveHorizontally(uint x, uint y, uint 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)
moveHorizontally(x + d, y,  1);
} else {
if (x + d > 0)
moveHorizontally(x + d, y, -1);
}
}

void move(moveDir d) {
final switch (d) with(moveDir) {
case up:
foreach (x; 0 .. side)
foreach (y; 1 .. side)
if (board[x][y].val)
moveVertically(x, y, -1);
break;
case down:
foreach (x; 0 .. side)
foreach_reverse (y; 0 .. 3)
if (board[x][y].val)
moveVertically(x, y, 1);
break;
case left:
foreach (y; 0 .. side)
foreach (x; 1 .. side)
if (board[x][y].val)
moveHorizontally(x, y, -1);
break;
case right:
foreach (y; 0 .. side)
foreach_reverse (x; 0 .. 3)
if (board[x][y].val)
moveHorizontally(x, y, 1);
}
}
}

void main() /*safe*/ {
G2048 g;
g.gameLoop;
}
```

The output is the same as the C++ version.

## Delphi

Thanks for Rudy Velthuis [1].

Translation of: C++
```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();
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 }

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
while (true) do
begin
if (FisMoved) then

DrawBoard();
if (FisDone) then
break;

WaitKey();
end;

if FisWon then
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');
'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');
end.
```
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
```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})
end

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
```
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]

```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
| 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 =

-- 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 ->

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 )

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
let
newTiles =
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
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 =
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" )
, ( "float", "left" )
]

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" )
, ( "text-align", "center" )
, ( "font-size", "30px" )
, ( "font-weight", "bold" )
]

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
]
```

## 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).

```// 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()
```

## 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

```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

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 )
;

: 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
```

## 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.

```\ 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 ;
@ SWAP @ ( other-val dest-val )
DUP 0<> -ROT = AND
;

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

: TRY-MERGE ( dest-y dest-x other-y other-x -- )
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 )
2DUP CAN-GRAVITY IF
TRUE HAVE-MOVED !
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
```
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.

```        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 |.
```

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...

```      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 ```