2048

From Rosetta Code
Revision as of 22:50, 19 November 2017 by rosettacode>Gerard Schildberger (→‎{{header|REXX}}: added wording to the REXX section header.)
Task
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.

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

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

Requirements:

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

to the right should result in

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

and not

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

to the right should result in

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

and not

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



AutoHotkey

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

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

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

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

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

GuiClose: ExitApp return

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

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

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

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

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

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

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

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

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

} } gosub, AddNew return

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

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

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

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

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

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

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

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

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

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

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

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

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

Batch File

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

Batch File Implementation

@echo off setlocal enabledelayedexpansion cls

begin_game

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

call :addtile call :addtile

%== Main game loop ==%

main_loop

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

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

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

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

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

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


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

%== Game Over xD ==%

gameover

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

key_loop

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

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

slide

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

%== Add number to tile ==%

addtile

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

%== Display the table ==%

display

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

Output:
2048 Game in Batch

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

Score: 60

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

BBC BASIC

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

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

C

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

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

const long values[] = {

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

};

const char *colors[] = {

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

};

struct gamestate_struct__ {

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

} game;

struct termios oldt, newt;

void do_draw(void) {

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

}

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

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

}

void do_gravity(int d) {

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

}

int do_check_end_condition(void) {

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

}

int do_tick(int d) {

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

}

void do_newblock(void) {

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

}

int main(void) {

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

game_lose:

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

game_win:

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

game_quit:

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

} </lang>

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

C++

<lang cpp>

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

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

class tile { public:

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

};

class g2048 { public:

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

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

   }

private:

   void drawBoard()
   {

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

   }
   void waitKey()
   {

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

   }
   void addTile()
   {

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

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

   }
   bool canMove()
   {

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

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

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

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

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

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

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

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

   }
   void move( movDir d )
   {

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

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

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

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

} </lang>

Output:
SCORE: 2024

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

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

Clojure

<lang clojure> (ns 2048

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

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

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

(def directions {:w :up

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

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

Output

(defn cells->str [line]

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

(defn walls->str [width]

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

(defn field->str [field]

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

(defn handle-input []

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

(defn get-columns [field]

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

(defn reverse-lines [field]

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

(defn padding [coll n sym]

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

(defn find-empties [field]

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

(defn random-add [field]

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

(defn win-check [field]

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

(defn lose-check [field]

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

(defn create-start-field [y x]

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

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

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

(defn shift-line [line]

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

(defn shift-field [direction field]

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

(defn handle-turn [field]

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

(defn play-2048 []

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

(play-2048)</lang>

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

Common Lisp

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

(defpackage :2048-lisp

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

(in-package :2048-lisp)

(defvar *lib-loaded* nil)

(unless *lib-loaded*

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

(defun read-arrow ()

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

return a char of whatever was pressed."

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

(defmacro swap (place1 place2)

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

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

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

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

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

Non-destructive version."

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

(defun transpose (board)

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

(defun add-random-piece (board)

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

Return T if successful, NIL otherwise."

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

(defun squash-line (line)

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

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

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

(defun squash-board (board)

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

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

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

(defun make-move (board direction)

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

(defun winp (board winning-tile)

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

(defun game-overp (board)

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

(defun print-divider (cells cell-size)

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

(defun print-board (board cell-size)

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

(defun init-board ()

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

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

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

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

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

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

Some time later...

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

D

Translation of: C++

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

struct G2048 {

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

private:

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

}

void main() /*safe*/ {

   G2048 g;
   g.gameLoop;

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

Elixir

Works with: Elixir version 1.3

<lang elixir>defmodule Game2048 do

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

end

Game2048.play 512</lang>

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

Elm

Works with: Elm 0.18.0

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

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


main =

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


-- MODEL


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

   Maybe Int


type alias Model =

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


initialModel : Model initialModel =

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


-- UPDATE


type alias RandomTileInfo =

   ( Int, Int )


type Msg

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


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

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


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

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


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

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


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

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


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



-- VIEW


containerStyle : List ( String, String ) containerStyle =

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


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

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


viewTile : Tile -> Html Msg viewTile tile =

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


viewGrid : List Tile -> Html Msg viewGrid tiles =

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


viewLost : Html Msg viewLost =

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

viewWin : Html Msg viewWin =

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


view : Model -> Html Msg view model =

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

</lang>

Fortran

The Plan

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

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

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

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

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

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

Source

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

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

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

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

Concoct a board layout.

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

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

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

Cast forth an invitation, and obtain a choice.

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

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

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

Choose a random blank square.

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

Check for success.

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

Curses!

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

</lang>

Output

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

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

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

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

Go

<lang Go>package main

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

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

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

type button int

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

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

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

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

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

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

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

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

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

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

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

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

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

return len(free) == 1 }

type point struct{ x, y int }

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

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

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

var starts []point var next point

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

moved := false winning := false

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

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

if !moved { return false }

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

draw()

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

return false }

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

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

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

stdin := bufio.NewReader(os.Stdin)

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

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

Haskell

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

-- Logic

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

type Position = Int

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

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

reflect :: a ->a reflect = map reverse

type Move = Position -> Position

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

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

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

win = any $ any (>=2048)

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


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

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

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

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

-}

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

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

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

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

-- Easy, is'n it'?

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

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

main :: IO () main = do

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

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

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

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

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

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

</lang>

J

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

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

coclass 'g2048' Target=: 2048

new2048=: verb define

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

)

newnum=: verb define

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

)

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

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

move=: conjunction define

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

)

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

eval=: verb define

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

)

showGrid=: echo

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

g2048Con_z_=: conew&'g2048con'

coclass 'g2048con' coinsert 'g2048'

create=: verb define

 echo Instructions
 startnew y

)

destroy=: codestroy quit=: destroy

startnew=: update@new2048

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

update=: verb define

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

)

Instructions=: noun define

2048

Object:

  Create the number 2048 by merging numbers.

How to play:

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

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

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

  right__grd 

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

  down__grd 

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

Java

Works with: Java version 8

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

public class Game2048 extends JPanel {

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


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

}

class Tile {

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

}</lang>

Julia

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

@enum Direction2048 Right Left Up Down

"""

   shifttiles!

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

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

end


function compactleft!(b, siz, row)

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

end

"""

   leftshift!

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

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

end

"""

   app2048

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

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

end


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

Kotlin

Stateless with focus on clarity rather than conciseness.

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

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

fun main(args: Array<String>) {

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

}

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

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

}

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

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

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

}

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

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

}

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

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

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

}

fun waitForValidInput(): String {

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

}

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

fun waitForInput(): String {

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

}

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

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

}

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

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

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

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

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

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

}

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

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

}

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

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

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

}

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

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

}

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

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

}</lang>

Sample output:

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

Maple

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

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

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

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

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

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

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

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

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

if keypress="Up" then move();

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

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

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

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

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

end if; end if; matrixtotextarea(a);

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

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

Perl 6

Uses termios to set the terminal options, so only compatible with POSIX terminals. This version does not include a specific "win" or "lose" condition. (though it would be trivial to add them.) You can continue to play this even after getting a 2048 tile; and if there is no valid move you can make, you can't do anything but quit.

Works with: Rakudo version 2017.03

<lang perl6>use Term::termios;

constant $saved = Term::termios.new(fd => 1).getattr; constant $termios = Term::termios.new(fd => 1).getattr;

  1. raw mode interferes with carriage returns, so
  2. set flags needed to emulate it manually

$termios.unset_iflags(<BRKINT ICRNL ISTRIP IXON>); $termios.unset_lflags(< ECHO ICANON IEXTEN ISIG>); $termios.setattr(:DRAIN);

  1. reset terminal to original setting on exit

END { $saved.setattr(:NOW) }

constant n = 4; # board size constant cell = 6; # cell width constant ansi = True; # color!

my @board = ( [ xx n] xx n ); my $save = ; my $score = 0;

constant $top = join '─' x cell, '┌', '┬' xx n-1, '┐'; constant $mid = join '─' x cell, '├', '┼' xx n-1, '┤'; constant $bot = join '─' x cell, '└', '┴' xx n-1, '┘';

my %dir = (

  "\e[A" => 'up',
  "\e[B" => 'down',
  "\e[C" => 'right',
  "\e[D" => 'left',

);

my @ANSI = <0 1;97 1;93 1;92 1;96 1;91 1;95 1;94 1;30;47 1;43

   1;42 1;46 1;41 1;45 1;44 1;33;43 1;33;42 1;33;41 1;33;44>;

sub row (@row) { '│' ~ (join '│', @row».&center) ~ '│' }

sub center ($s){

   my $c   = cell - $s.chars;
   my $pad = ' ' x ceiling($c/2);
   my $tile = sprintf "%{cell}s", "$s$pad";
   my $idx = $s ?? $s.log(2) !! 0;
   ansi ?? "\e[{@ANSI[$idx]}m$tile\e[0m" !! $tile;

}

sub draw-board {

   run('clear');
   print qq:to/END/;


Press direction arrows to move.

Press q to quit.

$top { join "\n\t$mid\n\t", map { .&row }, @board } $bot

Score: $score

END }

sub squash (@c) {

   my @t = grep { .chars }, @c;
   map { combine(@t[$_], @t[$_+1]) if @t[$_] && @t[$_+1] == @t[$_] }, ^@t-1;
   @t = grep { .chars }, @t;
   @t.push:  while @t < n;
   @t;

}

sub combine ($v is rw, $w is rw) { $v += $w; $w = ; $score += $v; }

multi sub move('up') {

   map { @board[*;$_] = squash @board[*;$_] }, ^n;

}

multi sub move('down') {

   map { @board[*;$_] = reverse squash reverse @board[*;$_] }, ^n;

}

multi sub move('left') {

   map { @board[$_] = squash @board[$_] }, ^n;

}

multi sub move('right') {

   map { @board[$_;*] = reverse squash reverse @board[$_;*] }, ^n;

}

sub another {

   my @empties;
   for @board.kv -> $r, @row {
       @empties.push(($r, $_)) for @row.grep(:k, );
   }
   my ( $x, $y ) = @empties.roll;
   @board[$x; $y] = (flat 2 xx 9, 4).roll;

}

sub save () { join '|', flat @board».list }

loop {

   another if $save ne save();
   draw-board;
   $save = save();
   # Read up to 4 bytes from keyboard buffer.
   # Page navigation keys are 3-4 bytes each.
   # Specifically, arrow keys are 3.
   my $key = $*IN.read(4).decode;
   move %dir{$key} if so %dir{$key};
   last if $key eq 'q'; # (q)uit

}</lang> Sample output:


	Press direction arrows to move.

	Press q to quit. 

	┌──────┬──────┬──────┬──────┐
	│  4   │  2   │      │      │
	├──────┼──────┼──────┼──────┤
	│  16  │  8   │      │      │
	├──────┼──────┼──────┼──────┤
	│  64  │  32  │  16  │      │
	├──────┼──────┼──────┼──────┤
	│ 128  │ 512  │ 128  │  64  │
	└──────┴──────┴──────┴──────┘

	Score: 6392

Phix

Library: pGUI

Faithful desktop gui reproduction of the above link (https://gabrielecirulli.github.io/2048/) Now I just got figure out how to win... <lang Phix>-- -- demo\rosetta\2048.exw -- include pGUI.e

Ihandle canvas, dialog cdCanvas cddbuffer, cdcanvas

constant tile_colours = {#CCC0B4, -- blank

                        #EEE4DA,   -- 2
                        #EDE0C8,   -- 4
                        #F2B179,   -- 8
                        #F59563,   -- 16
                        #F67C5F,   -- 32
                        #F65E3B,   -- 64
                        #EDCF72,   -- 128
                        #EDCC61,   -- 256
                        #EDC850,   -- 512
                        #EDC53F,   -- 1024
                        #EDC22E}   -- 2048

-- the 4x4 board. -- note that values are [1..12] for [blank,2,4,8,..2048]. -- (merging two eights is not 8+8->16 but 4+1->5, internally) sequence board

integer newgame = 1

procedure add_rand(integer count) -- (nb infinite loop if board is full) integer x, y

   while count do
       x = rand(4)
       y = rand(4)
       if board[y][x]=1 then   -- blank
           board[y][x] = 2+(rand(10)=10)
           count -= 1
       end if
   end while   

end procedure

integer valid = 0 integer prev, nxt, bxy

procedure move_x(integer x, integer y, integer d)

   bxy = board[x][y]
   if bxy!=1 then
       if bxy=prev then
           board[x][y] = 1
           bxy += 1
           board[x][nxt] = bxy
           nxt += d
           prev = 13
           valid = 1 
       else
           if prev=1
           or y!=nxt then
               if prev!=1
               and prev!=13 then
                   nxt += d
               end if
               if y!=nxt then
                   board[x][y] = 1
                   board[x][nxt] = bxy
                   valid = 1
               end if
           end if
           prev = bxy
       end if
   end if

end procedure

procedure move_y(integer x, integer y, integer d)

   bxy = board[x][y]
   if bxy!=1 then
       if bxy=prev then
           board[x][y] = 1
           bxy += 1
           board[nxt][y] = bxy
           nxt += d
           prev = 13
           valid = 1 
       else
           if prev=1
           or x!=nxt then
               if prev!=1
               and prev!=13 then
                   nxt += d
               end if
               if x!=nxt then
                   board[x][y] = 1
                   board[nxt][y] = bxy
                   valid = 1
               end if
           end if
           prev = bxy
       end if
   end if

end procedure

function move(integer key) -- a non-zero result means it changed something.

   valid = 0
   if key=K_LEFT then
       for x=1 to 4 do
           prev = 13
           nxt = 1
           for y=1 to 4 do
               move_x(x,y,+1)
           end for
       end for
   elsif key=K_UP then
       for y=1 to 4 do
           prev = 13
           nxt = 4
           for x=4 to 1 by -1 do
               move_y(x,y,-1)
           end for
       end for
   elsif key=K_RIGHT then
       for x=1 to 4 do
           prev = 13
           nxt = 4
           for y=4 to 1 by -1 do
               move_x(x,y,-1)
           end for
       end for
   elsif key=K_DOWN then
       for y=1 to 4 do
           prev = 13
           nxt = 1
           for x=1 to 4 do
               move_y(x,y,+1)
           end for
       end for
   end if
   return valid

end function

function game_won()

   for i=1 to length(board) do
       if find(12,board[i]) then return 1 end if
   end for
   return 0

end function

constant valid_keys = {K_LEFT,K_DOWN,K_RIGHT,K_UP}

function no_valid_moves() sequence saved_board = board

   for i=1 to length(valid_keys) do
       if move(valid_keys[i]) then
           board = saved_board
           return 0    -- OK
       end if
   end for
   return 1 -- game over...

end function

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, integer /*posy*/) integer tx, ty, bxy,

       ox,oy,              -- top right coords
       os,ts,              -- overall and tile size
       ts2                 -- half tile, for number positioning

integer {dw,dh} = IupGetIntInt(canvas, "DRAWSIZE")

   if dw>=dh then
       ox = floor((dw-dh)/2)
       oy = 0
       os = dh
   else
       ox = 0
       oy = floor((dh-dw)/2)
       os = dw
   end if
   ts = floor((os-10)/4-7)
   ts2 = floor(ts/2+5)-10
   if newgame then
       board = repeat(repeat(1,4),4)
       add_rand(2)
       newgame = 0
   end if
   cdCanvasActivate(cddbuffer)
   cdCanvasSetBackground(cddbuffer, #FAF8EF)
   cdCanvasClear(cddbuffer)
   cdCanvasSetForeground(cddbuffer, #BBADA0)
   cdCanvasRoundedBox(cddbuffer, ox+5, ox+os-5, oy+5, oy+os-5, 10, 10)
   tx = ox+15
   for y=1 to 4 do
       ty = oy+15
       for x=1 to 4 do
           bxy = board[x][y]
           cdCanvasSetForeground(cddbuffer, tile_colours[bxy])
           cdCanvasRoundedBox(cddbuffer, tx, tx+ts-10, ty, ty+ts-10, 5, 5)
           if bxy>1 then
               cdCanvasSetForeground(cddbuffer, iff(bxy<=3?#776E65:#F9F6F2))
               cdCanvasFont(cddbuffer, "Calibri", CD_BOLD, iff(bxy>10?32:40))
               cdCanvasText(cddbuffer, tx+ts2, ty+ts2-25-iff(bxy<11?7:0), sprint(power(2,bxy-1))) 
           end if
           ty += ts+5
       end for
       tx += ts+5
   end for
   cdCanvasFlush(cddbuffer)
   return IUP_DEFAULT

end function

function map_cb(Ihandle ih)

   cdcanvas = cdCreateCanvas(CD_IUP, ih)
   cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
   {} = cdCanvasTextAlignment(cddbuffer, CD_SOUTH) 
   return IUP_DEFAULT

end function

function key_cb(Ihandle /*ih*/, atom c)

   if c=K_ESC then return IUP_CLOSE end if
   if find(c,valid_keys) then
       if move(c) then
           IupUpdate(canvas)
           string mbmsg = ""
           if game_won() then
               mbmsg = "!!!YOU WON!!!\n\nAnother Go?"
           else
               add_rand(1)

-- repaintWindow(main)

               IupUpdate(canvas)
               if no_valid_moves() then
                   mbmsg = "You Lost.\n\nAnother Go?"
               end if
           end if
           if length(mbmsg) then
               if IupAlarm("Game Over",mbmsg,"Yes","No")=1 then
                   newgame=1
               else
                   return IUP_CLOSE
               end if
           end if
       end if
       IupUpdate(canvas)
   end if
   return IUP_CONTINUE

end function

procedure main()

   IupOpen()
   canvas = IupCanvas("RASTERSIZE=520x540")
   IupSetCallback(canvas, "MAP_CB", Icallback("map_cb"))
   IupSetCallback(canvas, "ACTION", Icallback("redraw_cb"))
   dialog = IupDialog(canvas,"MINSIZE=440x450")
   IupSetAttribute(dialog,"TITLE","2048");
   IupSetCallback(dialog, "K_ANY", Icallback("key_cb"));
   IupShow(dialog)
   IupSetAttribute(canvas, "RASTERSIZE", NULL)
   IupMainLoop()
   IupClose()

end procedure main()</lang>

PHP

Works from PHP5 and upwards in CLI mode. <lang PHP> <?php

$game = new Game();

while(true) {

   $game->cycle();

}

class Game { private $field; private $fieldSize; private $command; private $error; private $lastIndexX, $lastIndexY; private $score; private $finishScore;

function __construct() { $this->field = array(); $this->fieldSize = 4; $this->finishScore = 2048; $this->score = 0; $this->addNumber(); $this->render(); }

public function cycle() { $this->command = strtolower($this->readchar('Use WASD, q exits')); $this->cls();

if($this->processCommand()) { $this->addNumber(); } else { if(count($this->getFreeList()) == 0 ) { $this->error = 'No options left!, You Lose!!'; } else { $this->error = 'Invalid move, try again!'; } } $this->render(); }

private function readchar($prompt) { readline_callback_handler_install($prompt, function() {}); $char = stream_get_contents(STDIN, 1); readline_callback_handler_remove(); return $char; }

/** * Insert a number in an empty spot on the field */ private function addNumber() { $freeList = $this->getFreeList(); if(count($freeList) == 0) { return; } $index = mt_rand(0, count($freeList)-1); $nr = (mt_rand(0,9) == 0)? 4 : 2; $this->field[$freeList[$index]['x']][$freeList[$index]['y']] = $nr; return; }

/** * @return array(array('x' => <x>, 'y' => <y>)) with empty positions in the field */ private function getFreeList() { $freeList = array(); for($y =0; $y< $this->fieldSize;$y++) { for($x=0; $x < $this->fieldSize; $x++) { if(!isset($this->field[$x][$y])) { $freeList[] = array('x' => $x, 'y' => $y); } elseif($this->field[$x][$y] == $this->finishScore) { $this->error = 'You Win!!'; } } } return $freeList; }

/** * Process a command: * @return is the command valid (Did it cause a change in the field) */ private function processCommand() { if(!in_array($this->command, array('w','a','s','d','q'))) { $this->error = 'Invalid Command'; return false; } if($this->command == 'q') { echo PHP_EOL. 'Bye!'. PHP_EOL; exit; }

// Determine over which axis and in which direction we move: $axis = 'x'; $sDir = 1;

switch($this->command) { case 'w': $axis = 'y'; $sDir = -1; break; case 'a': $sDir = -1; break; case 's': $axis = 'y'; break; case 'd': break; }

$done = 0; // shift all numbers in that direction $done += $this->shift($axis, $sDir); // merge equal numbers in opposite direction $done += $this->merge($axis, $sDir * -1); // shift merged numbers in that direction $done += $this->shift($axis, $sDir); return $done >0; }

private function shift($axis, $dir) { $totalDone = 0; for($i = 0; $i <$this->fieldSize; $i++) { $done = 0; foreach($this->iterate($axis,$dir) as $xy) { if($xy['vDest'] === NULL && $xy['vSrc'] !== NULL) { $this->field[$xy['dX']][$xy['dY']] = $xy['vSrc']; $this->field[$xy['sX']][$xy['sY']] = NULL; $done++; } } $totalDone += $done; if($done == 0) { // nothing to shift anymore break; } } return $totalDone; }

private function merge($axis, $dir) { $done = 0; foreach($this->iterate($axis,$dir) as $xy) { if($xy['vDest'] !== NULL && $xy['vDest'] === $xy['vSrc']) { $this->field[$xy['sX']][$xy['sY']] += $xy['vDest']; $this->field[$xy['dX']][$xy['dY']] = NULL; $this->score += $this->field[$xy['sX']][$xy['sY']]; $done ++; } } return $done; }

/** * @return array List of src, dest pairs and their values to iterate over. */ private function iterate($axis, $dir) { $res = array(); for($y = 0; $y < $this->fieldSize; $y++) { for($x=0; $x < $this->fieldSize; $x++) { $item = array('sX'=> $x,'sY' => $y, 'dX' => $x, 'dY' => $y, 'vDest' => NULL,'vSrc' => NULL);

if($axis == 'x') { $item['dX'] += $dir; } else { $item['dY'] += $dir; }

if($item['dX'] >= $this->fieldSize || $item['dY'] >=$this->fieldSize || $item['dX'] < 0 || $item['dY'] < 0) { continue; }

$item['vDest'] = (isset($this->field[$item['dX']][$item['dY']]))? $this->field[$item['dX']][$item['dY']] : NULL; $item['vSrc'] = (isset($this->field[$item['sX']][$item['sY']]))? $this->field[$item['sX']][$item['sY']] : NULL; $res[] = $item; } } if($dir < 0) { $res = array_reverse($res); } return $res; }

/// RENDER ///

/** * Clear terminal screen */ private function cls() { echo chr(27).chr(91).'H'.chr(27).chr(91).'J'; }

private function render() { echo $this->finishScore . '! Current score: '. $this->score .PHP_EOL;

if(!empty($this->error)) { echo $this->error . PHP_EOL; $this->error = NULL; } $this->renderField(); }

private function renderField() { $width = 5; $this->renderVSeperator($width); for($y =0; $y < $this->fieldSize; $y ++) { for($x = 0;$x < $this->fieldSize; $x++) { echo '|'; if(!isset($this->field[$x][$y])) { echo str_repeat(' ', $width); continue; } printf('%'.$width.'s', $this->field[$x][$y]); } echo '|'. PHP_EOL; $this->renderVSeperator($width); } }

private function renderVSeperator($width) { echo str_repeat('+'. str_repeat('-', $width), $this->fieldSize) .'+' .PHP_EOL; }

} </lang>


PicoLisp

<lang PicoLisp>(load "@lib/simul.l")

(seed (in "/dev/urandom" (rd 8)))

(setq *G (grid 4 4) *D NIL)

(de cell ()

  (use This
     (while
        (get
           (setq This
              (intern
                 (pack
                    (char (+ 96 (rand 1 4)))
                    (rand 1 4) ) ) )
           'N ) )
     (=: N (if (> 90 (rand 1 100)) 2 4) ) )
  (setq *D (fish '((This) (: N)) *G)) )

(de redraw (G S D)

  # zeroize *G
  (mapc
     '((I)
        (mapc '((This) (=: N NIL)) I) )
     *G )
  # draw again
  (mapc
     '((X This)
        (while (and This X)
           (=: N (pop 'X))
           (setq This (D This)) ) )
     G
     S ) )

(de summ (Lst)

  (mapcar
     '((L)
        (make
           (while L
              (ifn (= (car L) (cadr L))
                 (link (car L))
                 (link (+ (car L) (cadr L)))
                 (pop 'L) )
              (pop 'L) ) ) )
     Lst ) )

(de vertical ()

  (mapcar
     '((X) (extract '((This) (: N)) X))
     *G ) )

(de horizontal ()

  (mapcar
     '((This)
        (make
           (while This
              (when (: N) (link @))
              (setq This (east This)) ) ) )
     (car *G) ) )

(de finish? ()

  (nor
     (fish
        '((This)
           (when (atom This) (= NIL (: N))) )
        *G )
     (find
        '((L)
           (find
              '((This)
                 (when (: N)
                    (find
                       '((D)
                          (= (: N) (get (D This) 'N)) )
                       (quote north south west east) ) ) )
              L ) )
        *G ) ) )

(de board (D)

  (space 3)
  (prin '+)
  (for I G
     (prin (if (D (car I)) "   +" "---+")) )
  (prinl) )

(de display ()

  (let G (mapcar reverse *G)
     (board north)
     (while (caar G)
        (space 3)
        (prin '|)
        (for I G
           (with (car I)
              (prin
                 (if (: N) (align 3 (: N)) "   ")
                 (if (east This) " " '|) ) ) )
        (prinl)
        (board south)
        (map pop G) )
     (do 2
        (prinl) ) ) )

(do 2

  (cell) )

(display) (loop

  (case
     (pack
        (make
           (link (key))
           (while (key 100)
              (link @) ) ) )
     ("^[[D" #left
        (redraw (summ (horizontal)) '(a1 a2 a3 a4) east) )
     ("^[[C" #rigth
        (redraw
           (summ (mapcar reverse (horizontal)))
           '(d1 d2 d3 d4)
           west) )
     ("^[[B" #down
        (redraw (summ (vertical)) '(a1 b1 c1 d1) north) )
     ("^[[A" #up
        (redraw
           (summ (mapcar reverse (vertical)))
           '(a4 b4 c4 d4)
           south) ) )
  (when (diff *D (fish '((This) (: N)) *G))
     (cell) )
  (display)
  (T (finish?) (println 'Finish))
  (T (fish '((This) (= 512 (: N))) *G)
     (println 'Maximum) ) )

(bye)</lang>

Pony

Works with: ponyc version 0.10.0

<lang pony> use "term" use "random" use "time"

interface EdgeRow

 fun val row() : Iterator[U32] ref
 fun val inc() : I32

primitive TopRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [0,1,2,3]
   r.values()
 fun inc() : I32 => 4

primitive LeftRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [0,4,8,12]
   r.values()
 fun inc() : I32 => 1

primitive RightRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [3,7,11,15]
   r.values()
 fun inc() : I32 => -1

primitive BottomRow is EdgeRow

 fun row() : Iterator[U32] ref => let r : Array[U32] box = [12,13,14,15]
   r.values()
 fun inc() : I32 =>  -4

primitive LEFT primitive RIGHT primitive UP primitive DOWN type Move is (LEFT|RIGHT|UP|DOWN)

class KeyboardHandler is ANSINotify

  let _game : Game tag
  new iso create(game : Game tag) => _game = game
  fun ref apply(term: ANSITerm ref, input: U8 val) =>
    if input == 113 then
      _game.quit()
      term.dispose()
    end
  fun ref left(ctrl: Bool, alt: Bool, shift: Bool)  => _game.move(LEFT)
  fun ref down(ctrl: Bool, alt: Bool, shift: Bool)  => _game.move(DOWN)
  fun ref up(ctrl: Bool, alt: Bool, shift: Bool)    => _game.move(UP)
  fun ref right(ctrl: Bool, alt: Bool, shift: Bool) => _game.move(RIGHT)

type ROW is (U32,U32,U32,U32)

primitive Merger

 fun tag apply(r : ROW) : ROW =>
   match r
   | (0,0,0,_)            => (r._4,0,0,0)
   | (0,0,_,r._3)         => (r._3<<1,0,0,0)
   | (0,0,_,_)            => (r._3,r._4,0,0)
   | (0,_,r._2,_)         => (r._2<<1,r._4,0,0)
   | (0,_,0,r._2)         => (r._2<<1,0,0,0)
   | (0,_,0,_)            => (r._2,r._4,0,0)
   | (0,_,_,r._3)         => (r._2,r._3<<1,0,0)
   | (0,_,_,_)            => (r._2,r._3,r._4,0)
   | (_, r._1, _, r._3)   => (r._1<<1, r._3<<1, 0, 0)
   | (_, r._1, 0, _)      => (r._1<<1, r._4, 0, 0)
   | (_, r._1, _, _)      => (r._1<<1, r._3, r._4, 0)
   | (_, 0,r._1, _)       => (r._1<<1,r._4,0,0)
   | (_, 0,0, r._1)       => (r._1<<1,0,0,0)
   | (_, 0,0, _)          => (r._1,r._4,0,0)
   | (_, 0,_, r._3)       => (r._1, r._3<<1,0,0)
   | (_, 0,_, _)          => (r._1, r._3,r._4,0)
   | (_,_,r._2,_)         => (r._1, r._2<<1,r._4,0)
   | (_,_,0,r._2)         => (r._1, r._2<<1,0,0)
   | (_,_,0,_)            => (r._1, r._2,r._4,0)
   | (_,_,_,r._3)         => (r._1, r._2,r._3<<1,0)
   else
      r
   end

/**

  • Game actor
  • /

actor Game

 embed _grid : Array[U32] = Array[U32].init(0, 16)
 let _rand : Random = MT(Time.millis())
 let _env : Env
 let _board : String ref = recover String(1024) end
 new create(env: Env)=>
   _env = env
   _add_block()
   _add_block()
   _draw()
 fun _merge(start : U32, inc : I32) : (ROW | None) =>
   var st = start.i32()
   let rval : ROW = (_get(st),             _get(st + inc),
                     _get(st + (inc * 2)), _get(st + (inc * 3)))
   let rout = Merger(rval)
   if rout is rval then None else rout end
 fun ref _update(start : U32, inc : I32) : Bool =>
   match _merge(start, inc)
   | let rout : ROW =>
       var st = start.i32()
       _set(st,             rout._1)
       _set(st +  inc,      rout._2)
       _set(st + (inc * 2), rout._3)
       _set(st + (inc * 3), rout._4)
       true
   else
     false
   end
 fun ref _shift_to(edge : EdgeRow val) : Bool =>
   var updated = false
   for r in edge.row() do
     if _update(r, edge.inc()) then
       updated = true
     end
   end
   updated
 fun _fmt(i : U32) : String =>
   match i
   | 0 => " __ "
   | 2 => "\x1B[31m  2 \x1B[0m"
   | 4 => "\x1B[32m  4 \x1B[0m"
   | 8 => "\x1B[33m  8 \x1B[0m"
   | 16 => "\x1B[34m 16 \x1B[0m"
   | 32 => "\x1B[35m 32 \x1B[0m"
   | 64 => "\x1B[36m 64 \x1B[0m"
   | 128 => "\x1B[37m128 \x1B[0m"
   | 256 => "\x1B[41m\x1B[37m256 \x1B[0m"
   | 512 => "\x1B[42m\x1B[37m512 \x1B[0m"
   | 1024 => "\x1B[43m\x1B[37m1024\x1B[0m"
   | 2048 => "\x1B[47m\x1B[35m\x1B[1m\x1B[5m2048\x1B[0m"
   else
     i.string()
   end
 fun ref _draw() =>
   let s : String ref = _board
   s.truncate(0)
   var i : U32 = 0
   repeat
     if (i % 4) == 0 then
         s.append("---------------------\n")
     end
     s.append(_fmt(_get(i)))
     s.append(" ")
     i = i + 1
     if (i % 4) == 0 then
         s.append("\n")
     end
   until i==16 end
   _env.out.print(s.string())
   _env.out.print("Arrow keys to move. Press (q)uit key to quit.")
  fun ref _set(i:(I32|U32), v : U32) =>
    try
      _grid.update(i.usize(),v)
    else
      _env.out.print("cant update!")
    end
 fun _count() : U64 =>
    var c : U64 = 0
    for v in _grid.values() do
      c = c + if v == 0 then 0 else 1 end
    end
    c
 fun ref _add_block() =>
   let c = _count()
   if c == 16 then return end
   var hit =  _rand.int(16 - c)
   var i : U32 = 0
   while i < 16 do
     if (_get(i) == 0) then
       if hit == 0 then
         _set(i, if _rand.int(10) > 0 then 2 else 4 end)
         break
       end
       hit = hit - 1
     end
     i = i + 1
   end
 fun _get(i : (I32|U32)) : U32 => try  _grid(i.usize()) else 0  end
 fun _win() : Bool =>
   for v in _grid.values() do
     if v == 2048 then return true end
   end
   false
 fun _no_moves(edge : EdgeRow val) : Bool =>
   for r in edge.row() do
     match _merge(r, edge.inc())
     | let rout : ROW =>
       if (rout._1 == 0) or (rout._2 == 0) or
           (rout._3 == 0) or (rout._4 == 0) then
             return false
       end
     end
   end
   true
 fun _lose() : Bool =>
   (_grid.size() >= 16) and
   _no_moves(LeftRow) and
   _no_moves(RightRow) and
   _no_moves(TopRow) and
   _no_moves(BottomRow)
 be quit()=>
   _env.out.print("Exiting.. some terminals may require <ctrl-c>")
   _env.exitcode(0)
   _env.input.dispose()
 be move(m: Move) =>
   let updated =
     match m
     | LEFT =>  _shift_to(LeftRow)
     | RIGHT => _shift_to(RightRow)
     | UP =>    _shift_to(TopRow)
     | DOWN =>  _shift_to(BottomRow)
     else
       false
     end
   if _win() then
     _draw()
     _env.out.print("You win :)")
     quit()
   else
     if updated then
       _add_block()
       _draw()
     end
     if _lose() then
       _env.out.print("You lose :(")
       quit()
     end
   end

actor Main

 new create(env: Env) =>
   // unit test
   ifdef "test" then
     TestMain(env)
     return
   end
   // else game
   let input : Stdin tag = env.input
   env.out.print("Welcome to ponylang-2048...")
   let game = Game(env)
   let term = ANSITerm(KeyboardHandler(game), input)
   let notify : StdinNotify iso = object iso
       let term: ANSITerm = term
       let _in: Stdin tag = input
       fun ref apply(data: Array[U8] iso) => term(consume data)
       fun ref dispose() => _in.dispose()
   end
   input(consume notify)

</lang>

Python

<lang python>

  1. !/usr/bin/env python3

import curses from random import randrange, choice # generate and place new tile from collections import defaultdict

letter_codes = [ord(ch) for ch in 'WASDRQwasdrq'] actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit'] actions_dict = dict(zip(letter_codes, actions * 2))

def get_user_action(keyboard): char = "N" while char not in actions_dict: char = keyboard.getch() return actions_dict[char]

def transpose(field): return [list(row) for row in zip(*field)]

def invert(field): return [row[::-1] for row in field]

class GameField(object): def __init__(self, height=4, width=4, win=2048): self.height = height self.width = width self.win_value = 2048 self.score = 0 self.highscore = 0 self.reset()

def reset(self): if self.score > self.highscore: self.highscore = self.score self.score = 0 self.field = [[0 for i in range(self.width)] for j in range(self.height)] self.spawn() self.spawn()

def move(self, direction): def move_row_left(row): def tighten(row): # squeese non-zero elements together new_row = [i for i in row if i != 0] new_row += [0 for i in range(len(row) - len(new_row))] return new_row

def merge(row): pair = False new_row = [] for i in range(len(row)): if pair: new_row.append(2 * row[i]) self.score += 2 * row[i] pair = False else: if i + 1 < len(row) and row[i] == row[i + 1]: pair = True new_row.append(0) else: new_row.append(row[i]) assert len(new_row) == len(row) return new_row return tighten(merge(tighten(row)))

moves = {} moves['Left'] = lambda field: \ [move_row_left(row) for row in field] moves['Right'] = lambda field: \ invert(moves['Left'](invert(field))) moves['Up'] = lambda field: \ transpose(moves['Left'](transpose(field))) moves['Down'] = lambda field: \ transpose(moves['Right'](transpose(field)))

if direction in moves: if self.move_is_possible(direction): self.field = moves[direction](self.field) self.spawn() return True else: return False

def is_win(self): return any(any(i >= self.win_value for i in row) for row in self.field)

def is_gameover(self): return not any(self.move_is_possible(move) for move in actions)

def draw(self, screen): help_string1 = '(W)Up (S)Down (A)Left (D)Right' help_string2 = ' (R)Restart (Q)Exit' gameover_string = ' GAME OVER' win_string = ' YOU WIN!' def cast(string): screen.addstr(string + '\n')

def draw_hor_separator(): top = '┌' + ('┬──────' * self.width + '┐')[1:] mid = '├' + ('┼──────' * self.width + '┤')[1:] bot = '└' + ('┴──────' * self.width + '┘')[1:] separator = defaultdict(lambda: mid) separator[0], separator[self.height] = top, bot if not hasattr(draw_hor_separator, "counter"): draw_hor_separator.counter = 0 cast(separator[draw_hor_separator.counter]) draw_hor_separator.counter += 1

def draw_row(row): cast(.join('│{: ^5} '.format(num) if num > 0 else '| ' for num in row) + '│')

screen.clear() cast('SCORE: ' + str(self.score)) if 0 != self.highscore: cast('HGHSCORE: ' + str(self.highscore)) for row in self.field: draw_hor_separator() draw_row(row) draw_hor_separator() if self.is_win(): cast(win_string) else: if self.is_gameover(): cast(gameover_string) else: cast(help_string1) cast(help_string2)

def spawn(self): new_element = 4 if randrange(100) > 89 else 2 (i,j) = choice([(i,j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0]) self.field[i][j] = new_element

def move_is_possible(self, direction): def row_is_left_movable(row): def change(i): # true if there'll be change in i-th tile if row[i] == 0 and row[i + 1] != 0: # Move return True if row[i] != 0 and row[i + 1] == row[i]: # Merge return True return False return any(change(i) for i in range(len(row) - 1))

check = {} check['Left'] = lambda field: \ any(row_is_left_movable(row) for row in field)

check['Right'] = lambda field: \ check['Left'](invert(field))

check['Up'] = lambda field: \ check['Left'](transpose(field))

check['Down'] = lambda field: \ check['Right'](transpose(field))

if direction in check: return check[direction](self.field) else: return False

def main(stdscr): curses.use_default_colors() game_field = GameField(win=32) state_actions = {} # Init, Game, Win, Gameover, Exit def init(): game_field.reset() return 'Game'

state_actions['Init'] = init

def not_game(state): game_field.draw(stdscr) action = get_user_action(stdscr) responses = defaultdict(lambda: state) responses['Restart'], responses['Exit'] = 'Init', 'Exit' return responses[action]

state_actions['Win'] = lambda: not_game('Win') state_actions['Gameover'] = lambda: not_game('Gameover')

def game(): game_field.draw(stdscr) action = get_user_action(stdscr) if action == 'Restart': return 'Init' if action == 'Exit': return 'Exit' if game_field.move(action): # move successful if game_field.is_win(): return 'Win' if game_field.is_gameover(): return 'Gameover' return 'Game'

state_actions['Game'] = game

state = 'Init' while state != 'Exit': state = state_actions[state]()

curses.wrapper(main) </lang>

R

orginal R package : https://github.com/ThinkRstat/r2048 <lang R> GD <- function(vec) {

   c(vec[vec != 0], vec[vec == 0])

} DG <- function(vec) {

   c(vec[vec == 0], vec[vec != 0])

}

DG_ <- function(vec, v = TRUE) {

   if (v) 
       print(vec)
   rev(GD_(rev(vec), v = FALSE))

}

GD_ <- function(vec, v = TRUE) {

   if (v) {
       print(vec)
   }
   vec2 <- GD(vec)
   # on cherche les 2 cote a cote
   pos <- which(vec2 == c(vec2[-1], 9999))
   # put pas y avoir consécutif dans pos
   pos[-1][which(abs(pos - c(pos[-1], 999)) == 1)]
   av <- which(c(0, c(pos[-1], 9) - pos) == 1)
   if (length(av) > 0) {
       pos <- pos[-av]
   }
   vec2[pos] <- vec2[pos] + vec2[pos + 1]
   vec2[pos + 1] <- 0
   GD(vec2)
   

}

H_ <- function(base) {

   apply(base, MARGIN = 2, FUN = GD_, v = FALSE)

} B_ <- function(base) {

   apply(base, MARGIN = 2, FUN = DG_, v = FALSE)

} G_ <- function(base) {

   t(apply(base, MARGIN = 1, FUN = GD_, v = FALSE))

} D_ <- function(base) {

   t(apply(base, MARGIN = 1, FUN = DG_, v = FALSE))

}

H <- function(base) {

   apply(base, MARGIN = 2, FUN = GD, v = FALSE)

} B <- function(base) {

   apply(base, MARGIN = 2, FUN = DG, v = FALSE)

} G <- function(base) {

   t(apply(base, MARGIN = 1, FUN = GD, v = FALSE))

} D <- function(base) {

   t(apply(base, MARGIN = 1, FUN = DG, v = FALSE))

}

add2or4 <- function(base, p = 0.9) {

   lw <- which(base == 0)
   if (length(lw) > 1) {
       tirage <- sample(lw, 1)
   } else {
       tirage <- lw
   }
   base[tirage] <- sample(c(2, 4), 1, prob = c(p, 1 - p))
   base

} print.dqh <- function(base) {

   cat("\n\n")
   for (i in 1:nrow(base)) {
       cat(paste("     ", base[i, ], " "))
       cat("\n")
   }
   cat("\n")

}


  1. -*- coding: utf-8 -*-
  2. ' @encoding UTF-8
  3. ' @title run_2048
  4. ' @description The 2048 game
  5. ' @param nrow nomber of row
  6. ' @param ncol numver of col
  7. ' @param p probability to obtain a 2 (1-p) is the probability to obtain a 4
  8. ' @examples
  9. ' \dontrun{
  10. ' run_2048()
  11. ' }
  12. ' @export


run_2048 <- function(nrow, ncol, p = 0.9) {


   help <- function() {
       cat("   *** KEY BINDING ***  \n\n")
       cat("press ECHAP to quit\n\n")
       cat("choose moove E (up) ; D (down) ; S (left); F (right) \n")
       cat("choose moove 8 (up) ; 2 (down) ; 4 (left); 6 (right) \n")
       cat("choose moove I (up) ; K (down) ; J (left); L (right) \n\n\n")
       
   }
   
   
   if (missing(nrow) & missing(ncol)) {
       nrow <- ncol <- 4
   }
   if (missing(nrow)) {
       nrow <- ncol
   }
   if (missing(ncol)) {
       ncol <- nrow
   }
   
   base <- matrix(0, nrow = nrow, ncol = ncol)
   
   while (length(which(base == 2048)) == 0) {
       base <- add2or4(base, p = p)
       # print(base)
       
       class(base) <- "dqh"
       print(base)
       flag <- sum((base == rbind(base[-1, ], 0)) + (base == rbind(0, 
           base[-nrow(base), ])) + (base == cbind(base[, -1], 0)) + (base == 
           cbind(0, base[, -nrow(base)])))
       if (flag == 0) {
 
           break
       }
       
       y <- character(0)
       while (length(y) == 0) {
           cat("\n", "choose moove E (up) ; D (down) ; s (left); f (right) OR H for help", 
               "\n")  # prompt
           y <- scan(n = 1, what = "character")
       }
       
       
       baseSAVE <- base
       base <- switch(EXPR = y, E = H_(base), D = B_(base), S = G_(base), 
           F = D_(base), e = H_(base), d = B_(base), s = G_(base), f = D_(base), 
           `8` = H_(base), `2` = B_(base), `4` = G_(base), `6` = D_(base), 
           H = help(), h = help(), i = H_(base), k = B_(base), j = G_(base), 
           l = D_(base), I = H_(base), K = B_(base), J = G_(base), L = D_(base))
       if (is.null(base)) {
           cat(" wrong KEY \n")
           base <- baseSAVE
       }
       
       
       
   }
   
   if (sum(base >= 2048) > 1) {
       cat("YOU WIN ! \n")
   } else {
       cat("YOU LOOSE \n")
   }

}

</lang>


REXX

This REXX version has the features:

  •   allows specification of N,   the size of the grid   (default is 4).
  •   allows specification of the winning number   (default is 2048)
  •   allows specification for the random BIF's seed   (no default).
  •   allows abbreviations for the directions   (Up, Down, Left, Right).
  •   allows the player to quit the game at any time.
  •   does error checking/validation for entered directions   (in response to the prompt).
  •   keeps track of the number of legal moves made and the score.
  •   displays the number of moves and the score   (when a blank is entered).
  •   displays an error message if a move doesn't do anything.
  •   displays the game board as a grid   (with boxes).

<lang rexx>/*REXX program lets a user play the 2048 game on an NxN grid (default is 4x4 grid).*/ parse arg N win seed . /*obtain optional arguments from the CL*/ if N== | N=="," then N= 4 /*Not specified? Then use the default.*/ if win== | win=="," then win=2**11 /* " " " " " " */ if datatype(seed, 'W') then call random ,,seed /*Specified? Then use seed for RANDOM.*/ L=length(win) + 2 /*L: used for displaying the grid #'s.*/ eye=copies("─", 8); pad=left(, length(eye)+2) /*eye-catchers; and perusable perusing.*/ b= ' ' /*comfortable readable name for a blank*/ prompt= eye "Please enter a direction (Up, Down, Right, Left) ───or─── Quit:" move=1; moves=0; score=0; ok=1 /*simulation that a move was performed.*/ @.=b /*define all grid elements to a blank. */

    do  until any(win);  if ok  then call put;   ok=1;   say;   call showGrid
    say;  say prompt;  parse pull a x . 1 d 2 1 way xx;  upper d a x
    if a==  then do;      ok=0                /*the user entered blank(s) or nothing.*/
                   say copies(eye,5)    'moves:'      moves     eye     "score:"    score
                   iterate                      /* [↑]  display # of moves & the score.*/
                   end
    if x\==               then call err  "too many arguments entered: "   xx
    if abbrev('QUIT',a,1)   then do;  say;  say eye  "quitting the game".;  exit 1;   end
    good=abbrev('UP',a,1) | abbrev('DOWN',a,1) | abbrev('RIGHT',a,1) | abbrev('LEFT',a,1)
    if \good                then call err  "invalid direction: "       way
    if \ok  then  iterate;       moves=moves + 1;                 call mov
    end   /*until*/

say say translate(eye "Congrats!! You've won the" win 'game!' eye,"═",'─') "score:" score exit 0 /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ showGrid: do r=0 for N+2; _= '║'; __= '╠'

                do c=1  for N;      _=_ || row()'║';    __=__ || copies("═", L)'╬'
                end   /*c*/
             if r==0  then _= '╔'translate(substr(_, 2, length(_)-2),  "╦",  '║')"╗"
             if r >N  then _= '╚'translate(substr(_, 2, length(_)-2),  "╩",  '║')"╝"
                                say pad _
             if r<N & r>0  then say pad substr(__, 1, length(__) -1)"╣"
             end      /*r*/;        return

/*──────────────────────────────────────────────────────────────────────────────────────*/ @: procedure expose @.; parse arg row,col; return @.row.col any: arg ?; do r=1 for N; do c=1 for N; if @.r.c==? then return 1; end; end; return 0 err: say; say eye '***error*** ' arg(1); say; ok=0; return o_c: $=; do k=1 for N; $=$ word(@.k.c .,1); end;  !=space(translate($,,.))==; return $ o_r: $=; do k=1 for N; $=$ word(@.r.k .,1); end;  !=space(translate($,,.))==; return $ put: if \any(b) then call err ,"game over, no more moves."; if move then call two; return row: if r==0 | r>N then return copies('═', L); return center(@.r.c, L) ten: if random(9)==4 then return 4; return 2 /*10% of the time, put 4 instead of 2.*/ two: do until @.p.q==b; p=random(1,N); q=random(1,N); end; @.p.q=ten(); return /*──────────────────────────────────────────────────────────────────────────────────────*/ mov: move=0; if d=='R' then call moveLR N, 1, -1 /*move (slide) numbers right. */

                 if d=='L'  then call moveLR 1, N, +1   /*  "     "       "      left. */
                 if d=='U'  then call moveUD 1, N, +1   /*  "     "       "        up. */
                 if d=='D'  then call moveUD N, 1, -1   /*  "     "       "      down. */
    if \move  then call err 'moving '    way    " doesn't change anything.";       return

/*──────────────────────────────────────────────────────────────────────────────────────*/ moveLR: parse arg start, sTo, #

         do   r=1  for N;    old=o_r();    if !  then iterate    /*is this row blank?  */
           do N-1;           call packLR                         /*pack  left or right.*/
           end        /*N-1*/                                    /* [↓]  get new tiles.*/
         new=o_r();             move= move | (old\==new)         /*indicate tiles moved*/
             do c=start  for N-1  by #  while @.r.c\==b          /*slide left or right.*/
             if @.r.c\==@(r,c+#)  then iterate                   /*not a duplicate ?   */
             @.r.c=@.r.c * 2;  score=score + @.r.c               /*double;  bump score */
             c=c + #        ;  @.r.c=b;    move=1                /*bump C; blank dup 2.*/
             end      /*c*/                                      /* [↑]  indicate move.*/
         call packLR                                             /*pack  left or right.*/
         end          /*r*/;                             return

/*──────────────────────────────────────────────────────────────────────────────────────*/ moveUD: parse arg start, Sto, #

         do   c=1  for N;    old=o_c();    if !  then iterate    /*is this col blank?  */
           do N-1;           call packUD                         /*pack  up or down.   */
           end        /*N-1*/                                    /* [↓]  get new tiles.*/
         new=o_c();             move= move | (old\==new)         /*indicate tiles moved*/
             do r=start  for N-1  by #  while @.r.c\==b          /*slide up or down.   */
             if @.r.c\==@(r+#,c)  then iterate                   /*not a duplicate ?   */
             @.r.c=@.r.c * 2;  score=score + @.r.c               /*double;  bump score */
             r=r + #        ;  @.r.c=b;    move=1                /*bump R; blank dup 2.*/
             end      /*r*/                                      /* [↑]  indicate move.*/
         call packUD                                             /*pack  up or down.   */
         end          /*c*/;                             return

/*──────────────────────────────────────────────────────────────────────────────────────*/ packLR: do c=start for N-1 by # /*slide left or right.*/

             if @.r.c\==b  then iterate                          /*Not a blank?  Skip. */
                do s=c  to sTo  by #;      @.r.s=@(r, s+#)       /*slide left or right.*/
                end   /*s*/;               @.r.sTo=b             /*handle the last one.*/
             end      /*c*/;               return

/*──────────────────────────────────────────────────────────────────────────────────────*/ packUD: do r=start for N-1 by # /*slide up or down. */

             if @.r.c\==b  then iterate                          /*Not a blank?  Skip. */
                do s=r  to sTo  by #;      @.s.c=@(s+#, c)       /*slide up or down.   */
                end   /*s*/;               @.sTo.c=b             /*handle the last one.*/
             end      /*r*/;               return</lang>
output   when using the default inputs:
           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
right

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
up

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║  4   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:

──────────────────────────────────────── moves: 2 ──────── score: 4

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║  4   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
left

           ╔══════╦══════╦══════╦══════╗
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
l

           ╔══════╦══════╦══════╦══════╗
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║  4   ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
dow

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║  4   ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
left

           ╔══════╦══════╦══════╦══════╗
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
lef

           ╔══════╦══════╦══════╦══════╗
           ║  2   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  2   ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
d

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  4   ║      ║      ║  2   ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║  2   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
d

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║  2   ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║  4   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:

──────────────────────────────────────── moves: 9 ──────── score: 32

           ╔══════╦══════╦══════╦══════╗
           ║      ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║      ║  2   ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║      ║
           ╠══════╬══════╬══════╬══════╣
           ║  8   ║      ║      ║  4   ║
           ╚══════╩══════╩══════╩══════╝

──────── Please enter a direction  (Up, Down, Right, Left)       ───or───    Quit:
q

──────── quitting the game.

Ruby

inspired by the Perl6 version <lang ruby>

  1. !/usr/bin/ruby

require 'io/console'

class Board

 def initialize size=4, win_limit=2048, cell_width = 6
   @size = size; @cw = cell_width; @win_limit = win_limit
   @board = Array.new(size) {Array.new(size, 0)}
   @moved = true; @score = 0; @no_more_moves = false
   spawn
 end
 def draw
   print "\n\n" if @r_vert
   print '    ' if @r_hori
   print '┌' + (['─' * @cw] * @size).join('┬')  + '┐'
   @board.each do |row|
     print "\n"
     formated = row.map {|num| num == 0 ? ' ' * @cw : format(num)}
     print '    ' if @r_hori
     puts '│' + formated.join('│') + '│'
     print '    ' if @r_hori
     print '├' + ([' '  * @cw] * @size).join('┼') + '┤'
   end
   print "\r"
   print '    ' if @r_hori
   puts '└' + (['─' * @cw] * @size).join('┴')  + '┘'
 end
 def move direction
   case direction
   when :up
     @board = column_map {|c| logic(c)}
     @r_vert = false if $rumble
   when :down
     @board = column_map {|c| logic(c.reverse).reverse} 
     @r_vert = true if $rumble
   when :left 
     @board = row_map {|r| logic(r)}
     @r_hori = false if $rumble
   when :right
     @board = row_map {|r| logic(r.reverse).reverse} 
     @r_hori = true if $rumble
   end
   spawn
   @moved = false
 end
 def print_score
   puts "Your Score is #@score."
   puts "Congratulations, you have won!" if to_enum.any? {|e| e >= @win_limit}
 end
 def no_more_moves?; @no_more_moves; end
 def won?;  to_enum.any? {|e| e >= @win_limit}; end
 def reset!; initialize @size, @win_limit, @cw; end
 private
 def set x, y, val
   @board[y][x] = val
 end
 def spawn 
   free_pos = to_enum.select{|elem,x,y| elem == 0}.map{|_,x,y| [x,y]}
   unless free_pos.empty?
     set *free_pos.sample, rand > 0.1 ? 2 : 4 if @moved
   else
     snap = @board
     unless @stop
       @stop = true
       %i{up down left right}.each{|s| move(s)}
       @no_more_moves = true if snap.flatten == @board.flatten
       @board = snap
       @stop = false
     end
   end
 end
 def logic list
   jump = false
   result =
   list.reduce([]) do |res, val|
     if res.last == val && !jump

res[-1] += val @score += val

       jump = true
     elsif val != 0

res.push val

       jump = false
     end
     res
   end
   result += [0] * (@size - result.length)
   @moved ||= list != result
   result
 end
 def column_map
   xboard = @board.transpose
   xboard.map!{|c| yield c }
   xboard.transpose
 end
 def row_map
   @board.map {|r| yield r }
 end
 def to_enum
   @enum ||= Enumerator.new(@size * @size) do |yielder|
     (@size*@size).times do |i|

yielder.yield (@board[i / @size][i % @size]), (i % @size), (i / @size )

     end
   end
   @enum.rewind
 end
 def format(num)
   if $color
     cstart = "\e[" + $colors[Math.log(num, 2)] + "m"
     cend = "\e[0m"
   else
     cstart = cend = ""
   end
   cstart + num.to_s.center(@cw) + cend
 end

end

$color = true $colors = %W{0 1;97 1;93 1;92 1;96 1;91 1;95 1;94 1;30;47 1;43 1;42 1;46 1;41 1;45 1;44 1;33;43 1;33;42 1;33;41 1;33;44} $rumble = false

$check_score = true unless ARGV.empty?

 puts "Usage: #$0 [gridsize] [score-threshold] [padwidth] [--no-color] [--rumble]"; exit if %W[-h --help].include?(ARGV[0])
 args = ARGV.map(&:to_i).reject{|n| n == 0}
 b = Board.new(*args) unless args.empty?
 $rumble = true if ARGV.any?{|a| a =~ /rumble/i }
 $color = false if ARGV.any?{|a| a =~ /no.?color/i}

end

b ||= Board.new puts "\e[H\e[2J" b.draw puts "Press h for help, q to quit" loop do

 input = STDIN.getch
 if input == "\e" 
   2.times {input << STDIN.getch}
 end
 case input
 when "\e[A", "w" then b.move(:up)
 when "\e[B", "s" then b.move(:down)
 when "\e[C", "d" then b.move(:right)
 when "\e[D", "a" then b.move(:left)

 when "q","\u0003","\u0004"  then b.print_score; exit
 when "h" 
   puts <<-EOM.gsub(/^\s*/, )
     ┌─                                                                                  ─┐
     │Use the arrow-keys or WASD on your keyboard to push board in the given direction.   
     │Tiles with the same number merge into one.                                          
     │Get a tile with a value of #{ARGV[1] || 2048} to win.                               
     │In case you cannot move or merge any tiles anymore, you loose.                      
     │You can start this game with different settings by providing commandline argument:  
     │For instance:                                                                       
     │  %> #$0 6 8192 --rumble                                                            
     └─                                                                                  ─┘
     PRESS q TO QUIT (or Ctrl-C or Ctrl-D)
   EOM
   input = STDIN.getch
 end
 puts "\e[H\e[2J"
 b.draw
 if b.no_more_moves? or $check_score && b.won?
   b.print_score
   if b.no_more_moves?
     puts "No more moves possible"
     puts "Again? (y/n)"
     exit if STDIN.gets.chomp.downcase == "n"
     $check_score = true
     b.reset!
     puts "\e[H\e[2J"
     b.draw
   else
     puts "Continue? (y/n)"
     exit if STDIN.gets.chomp.downcase == "n"
     $check_score = false
     puts "\e[H\e[2J"
     b.draw
   end
 end

end </lang>

Rust

Text mode

A simple implementation in rust. The user has to input an endline since i did not find a way to read a key press

Library: rand

<lang rust> use std::io::{self,BufRead}; extern crate rand;

enum Usermove {

   Up,
   Down,
   Left,
   Right,

}

fn print_game(field :& [[u32;4];4] ){

   println!("{:?}",&field[0] );
   println!("{:?}",&field[1] );
   println!("{:?}",&field[2] );
   println!("{:?}",&field[3] );

}

fn get_usermove()-> Usermove {

   let umove: Usermove ;
   loop{
       let mut input = String::new();
       io::stdin().read_line(&mut input).unwrap();
       match input.chars().nth(0){
           Some('a') =>{umove = Usermove::Left ;break },
           Some('w') =>{umove = Usermove::Up   ;break },
           Some('s') =>{umove = Usermove::Down ;break },
           Some('d') =>{umove = Usermove::Right;break },
           _   => {println!("input was {}: invalid character should be a,s,w or d ",input.chars().nth(0).unwrap());} ,
       }
   }
   umove

}

//this function inplements the user moves. //for every element it looks if the element is zero // if the element is zero it looks against the direction of the movement if any //element is not zero then it will move it to the element its place then it will look for //a matching element // if the element is not zero then it will look for a match if no match is found // then it will look for the next element

fn do_game_step(step : &Usermove, field:&mut [[u32;4];4]){

   match *step {
       Usermove::Left =>{
           for array in field{
               for  col in 0..4 {
                   for testcol in (col+1)..4 {
                       if array[testcol] != 0 {
                           if array[col] == 0 {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                           }
                           else if array[col] == array[testcol] {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                               break;
                           } else {
                               break
                           }
                       }
                   }
               }
           }
       } ,
       Usermove::Right=>{
           for array in field{
               for  col in (0..4).rev() {
                   for testcol in (0..col).rev() {
                       if array[testcol] != 0 {
                           if array[col] == 0 {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                           }
                           else if array[col] == array[testcol] {
                               array[col] += array[testcol];
                               array[testcol] = 0;
                               break;
                           }else {
                               break;
                           }
                       }
                   }
               }
           }
       } ,
       Usermove::Down   =>{
           for col in 0..4 {
               for row in (0..4).rev() {
                   for testrow in (0..row).rev() {
                       if field[testrow][col] != 0 {
                           if field[row][col] == 0 {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                           } else if field[row][col] == field[testrow][col] {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                               break;
                           }else {
                               break;
                           }
                       }
                   }
               }
           }
       } ,
       Usermove::Up =>{
           for col in 0..4 {
               for row in 0..4{
                   for testrow in (row+1)..4 {
                       if field[testrow][col] != 0 {
                           if field[row][col] == 0 {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                           } else if field[row][col] == field[testrow][col] {
                               field[row][col] += field[testrow][col];
                               field[testrow][col] = 0;
                               break;
                           }else {
                               break;
                           }
                       }
                   }
               }
           }
       },
   }

}

fn spawn( field: &mut [[u32;4];4]){

   loop{
       let x = rand::random::<usize>();
       if field[x % 4][(x/4)%4] == 0 {
           if x % 10 == 0 {
               field[x % 4][(x/4)%4]= 4;
           }else{
               field[x % 4][(x/4)%4]= 2;
           }
           break;
       }
   }

}


fn main() {

   let mut field : [[u32; 4];4] =  [[0;4];4];
   let mut test : [[u32; 4];4] ;
   'gameloop:loop {
       //check if there is still an open space
       test=field.clone();
       spawn(&mut field);
       //if all possible moves do not yield a change then there is no valid move left
       //and it will be game over
       for i in [Usermove::Up,Usermove::Down,Usermove::Left,Usermove::Right].into_iter(){
           do_game_step(i, &mut test);
           if test != field{
               break;//found a valid move
           }
           match *i{
               Usermove::Right=> {
                   println!("No more valid move, you lose");
                   break 'gameloop;
               },
               _=>{},
           }
       }
       print_game(&field);
       println!("move the blocks");
       test=field.clone();
       while test==field {
           do_game_step(&get_usermove(), &mut field);
       }
       for row in field.iter(){
           if row.iter().any(|x| *x == 2048){
               print_game(&field );
               println!("You Won!!");
               break;
           }
       }
   }

} </lang>

Tcl

Text mode

<lang tcl>

  1. A minimal implementation of the game 2048 in Tcl.
  2. For a maintained version with expanded functionality see
  3. https://tcl.wiki/40557.

package require Tcl 8.5 package require struct::matrix package require struct::list

  1. Board size.

set size 4

  1. Iterate over all cells of the game board and run script for each.
  2. The game board is a 2D matrix of a fixed size that consists of elements
  3. called "cells" that each can contain a game tile (corresponds to numerical
  4. values of 2, 4, 8, ..., 2048) or nothing (zero).
  5. - cellList is a list of cell indexes (coordinates), which are
  6. themselves lists of two numbers each. They each represent the location
  7. of a given cell on the board.
  8. - varName1 are varName2 are names of the variables the will be assigned
  9. the index values.
  10. - cellVarName is the name of the variable that at each step of iteration
  11. will contain the numerical value of the present cell. Assigning to it will
  12. change the cell's value.
  13. - script is the script to run.

proc forcells {cellList varName1 varName2 cellVarName script} {

   upvar $varName1 i
   upvar $varName2 j
   upvar $cellVarName c
   foreach cell $cellList {
       set i [lindex $cell 0]
       set j [lindex $cell 1]
       set c [cell-get $cell]
       uplevel $script
       cell-set "$i $j" $c
   }

}

  1. Generate a list of cell indexes for all cells on the board, i.e.,
  2. {{0 0} {0 1} ... {0 size-1} {1 0} {1 1} ... {size-1 size-1}}.

proc cell-indexes {} {

   global size
   set list {}
   foreach i [::struct::list iota $size] {
       foreach j [::struct::list iota $size] {
           lappend list [list $i $j]
       }
   }
   return $list

}

  1. Check if a number is a valid cell index (is 0 to size-1).

proc valid-index {i} {

   global size
   expr {0 <= $i && $i < $size}

}

  1. Return 1 if the predicate pred is true when applied to all items on the list
  2. or 0 otherwise.

proc map-and {list pred} {

   set res 1
   foreach item $list {
       set res [expr {$res && [$pred $item]}]
       if {! $res} break
   }
   return $res

}

  1. Check if list represents valid cell coordinates.

proc valid-cell? cell {

   map-and $cell valid-index

}

  1. Get the value of a game board cell.

proc cell-get cell {

   board get cell {*}$cell

}

  1. Set the value of a game board cell.

proc cell-set {cell value} {

   board set cell {*}$cell $value

}

  1. Filter a list of board cell indexes cellList to only have those indexes
  2. that correspond to empty board cells.

proc empty {cellList} {

   ::struct::list filterfor x $cellList {[cell-get $x] == 0}

}

  1. Pick a random item from the given list.

proc pick list {

   lindex $list [expr {int(rand() * [llength $list])}]

}

  1. Put a "2" into an empty cell on the board.

proc spawn-new {} {

   set emptyCell [pick [empty [cell-indexes]]]
   if {[llength $emptyCell] > 0} {
       forcells [list $emptyCell] i j cell {
           set cell 2
       }
   }
   return $emptyCell

}

  1. Return vector sum of lists v1 and v2.

proc vector-add {v1 v2} {

   set result {}
   foreach a $v1 b $v2 {
       lappend result [expr {$a + $b}]
   }
   return $result

}

  1. If checkOnly is false try to shift all cells one step in the direction of
  2. directionVect. If checkOnly is true just say if that move is possible.

proc move-all {directionVect {checkOnly 0}} {

   set changedCells 0
   forcells [cell-indexes] i j cell {
       set newIndex [vector-add "$i $j" $directionVect]
       set removedStar 0
       # For every nonempty source cell and valid destination cell...
       if {$cell != 0 && [valid-cell? $newIndex]} {
           if {[cell-get $newIndex] == 0} {
               # Destination is empty.
               if {$checkOnly} {
                   # -level 2 is to return from both forcells and move-all.
                   return -level 2 true
               } else {
                   # Move tile to empty cell.
                   cell-set $newIndex $cell
                   set cell 0
                   incr changedCells
               }
           } elseif {([cell-get $newIndex] eq $cell) &&
                     [string first + $cell] == -1} {
               # Destination is the same number as source.
               if {$checkOnly} {
                   return -level 2 true
               } else {
                   # When merging two tiles into one mark the new tile with
                   # the marker of "+" to ensure it doesn't get combined
                   # again this turn.
                   cell-set $newIndex [expr {2 * $cell}]+
                   set cell 0
                   incr changedCells
               }
           }
       }
   }
   if {$checkOnly} {
       return false
   }
   # Remove "changed this turn" markers at the end of the turn.
   if {$changedCells == 0} {
       forcells [cell-indexes] i j cell {
           set cell [string trim $cell +]
       }
   }
   return $changedCells

}

  1. Is it possible to move any tiles in the direction of directionVect?

proc can-move? {directionVect} {

   move-all $directionVect 1

}

  1. Check win condition. The player wins when there's a 2048 tile.

proc check-win {} {

   forcells [cell-indexes] i j cell {
       if {$cell == 2048} {
           puts "You win!"
           exit 0
       }
   }

}

  1. Check lose condition. The player loses when the win condition isn't met and
  2. there are no possible moves.

proc check-lose {possibleMoves} {

   set values [dict values $possibleMoves]
   if {!(true in $values || 1 in $values)} {
       puts "You lose."
       exit 0
   }

}

  1. Pretty-print the board. Specify an index in highlight to highlight a cell.

proc print-board {{highlight {-1 -1}}} {

   forcells [cell-indexes] i j cell {
       if {$j == 0} {
           puts ""
       }
       puts -nonewline [
           if {$cell != 0} {
               if {[::struct::list equal "$i $j" $highlight]} {
                   format "\[%4s\]" $cell*
               } else {
                   format "\[%4s\]" $cell
               }
           } else {
               lindex "......"
           }
       ]
   }
   puts "\n"

}

proc main {} {

   global size
   struct::matrix board
   # Generate an empty board of a given size.
   board add columns $size
   board add rows $size
   forcells [cell-indexes] i j cell {
       set cell 0
   }
   set controls {
       h {0 -1}
       j {1 0}
       k {-1 0}
       l {0 1}
   }
   # Game loop.
   while true {
       set playerMove 0
       set possibleMoves {}
       # Add new tile to the board and print the board highlighting this tile.
       print-board [spawn-new]
       check-win
       # Find possible moves.
       foreach {button vector} $controls {
           dict set possibleMoves $button [can-move? $vector]
       }
       check-lose $possibleMoves
       # Get valid input from the player.
       while {$playerMove == 0} {
           # Print prompt.
           puts -nonewline "Move ("
           foreach {button vector} $controls {
               if {[dict get $possibleMoves $button]} {
                   puts -nonewline $button
               }
           }
           puts ")?"
           set playerInput [gets stdin]
           # Validate input.
           if {[dict exists $possibleMoves $playerInput] &&
               [dict get $possibleMoves $playerInput]} {
               set playerMove [dict get $controls $playerInput]
           }
       }
       # Apply current move until no changes occur on the board.
       while true {
           if {[move-all $playerMove] == 0} break
       }
   }

}

main </lang>

Tk

See https://tcl.wiki/39566.

XPL0

<lang XPL0>include c:\cxpl\codes; \intrinsic 'code' declarations int Box(16), Moved;

proc ShiftTiles(I0, DI); \Shift tiles, add adjacents, shift again int I0, DI; int Done, M, N, I; [Done:= false; loop [for M:= 1 to 3 do \shift all tiles in a single row or column

           [I:= I0;
           for N:= 1 to 3 do
               [if Box(I)=0 & Box(I+DI)#0 then
                       [Box(I):= Box(I+DI);  Box(I+DI):= 0;  Moved:= true];
               I:= I+DI;
               ];
           ];
       if Done then return;
       Done:= true;
       I:= I0;                 \add identical adjacent tiles into a new tile
       for N:= 1 to 3 do
               [if Box(I)=Box(I+DI) & Box(I)#0 then
                       [Box(I):= Box(I)+1;  Box(I+DI):= 0;  Moved:= true];
               I:= I+DI;
               ];
       ];                      \loop back to close any gaps that were opened

]; \ShiftTiles

int I, J, X, Y, C; [Clear; for I:= 0 to 15 do Box(I):= 0; \empty the box of tiles loop [repeat I:= Ran(16) until Box(I)=0; \in a random empty location

       Box(I):= if Ran(10) then 1 else 2;      \insert a 2^1=2 or 2^2=4
       for I:= 0 to 15 do                      \show board with its tiles
               [X:= ((I&3)+5)*6;               \get coordinates of tile
                Y:= I>>2*3+6;
                Attrib(((Box(I)+1)&7)<<4 + $F);\set color based on tile value
                for J:= 0 to 2 do              \draw a square (6*8x3*16)
                       [Cursor(X, Y+J);
                       Text(6, "      ");
                       ];
                if Box(I)#0 then               \box contains a tile
                       [J:= 1;                 \center numbers somewhat
                       if Box(I) <= 9 then J:= 2;
                       if Box(I) <= 3 then J:= 3;
                       Cursor(X+J, Y+1);
                       IntOut(6, 1<<Box(I));
                       ];
               ];
       Moved:= false;                          \a tile must move to continue
       repeat  repeat C:= ChIn(1) until C#0;   \get key scan code, or ASCII
               for I:= 3 downto 0 do           \for all rows or columns
                       [case C of
                         $4B:  ShiftTiles(I*4, 1);     \left arrow
                         $4D:  ShiftTiles(I*4+3, -1);  \right arrow
                         $50:  ShiftTiles(I+12, -4);   \down arrow
                         $48:  ShiftTiles(I, 4);       \up arrow
                         $1B:  [Clear;  exit]          \Esc
                       other   [];                     \ignore all other keys
                       ];
       until   Moved;
       ];

]</lang>