2048

Revision as of 02:29, 4 November 2015 by Thundergnat (talk | contribs) (→‎{{header|Perl 6}}: some more refactoring, DRY)

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

2048 is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The rules are that each turn the player must perform a valid move shifting all tiles in one direction (up, down, left or right). A move is valid when at least one tile can be moved in that direction. When moved against each other tiles with the same number on them combine into one. A new tile with the value of 2 is spawned at the end of each turn if there is an empty spot for it. 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.

Batch File

This code uses XCOPY.EXE as key input. Check HERE for details. <lang dos>::

2048 Task from Rosetta Code Wiki
Batch File Implementation
Directly OPEN the Batch File to play.
I think this code is slow in some systems and needs improvements...

@echo off title 2048 Game setlocal enabledelayedexpansion

GENERATING A GAME
begin

set score=0 set won= set lose= for %%a in (1,2,3,4) do (for %%b in (1,2,3,4) do set a%%a%%b=0) set list=a11a12a13a14a21a22a23a24a31a32a33a34a41a42a43a44 set blanktiles=16 call :addtile call :addtile

/GENERATING A GAME
MAIN GAME LOOP
gameplay

call :display echo.Your Move: set "k=" for /F "usebackq delims=" %%L in (`xcopy /L /w "%~f0" "%~f0" 2^>NUL`) do (

 if not defined k set "k=%%L"

) set k=%k:~-1% set moved=0&set list=&set blanktiles=0&set x1=&set test=0&set inp= if /i "!k!"=="n" (goto :begin) if /i "!k!"=="p" (exit) if /i "!k!"=="s" (set inp=1) if /i "!k!"=="w" (set inp=2) if /i "!k!"=="a" (set inp=3) if /i "!k!"=="d" (set inp=4) if not "!inp!"=="" (for %%a in (a1 a2 a3 a4) do (set x=%%a&call :proc%inp%)) if !won!==1 (set "msg=Congrats^! You SOLVED the 2048 puzzle^!"&goto :res) if !moved!==0 ( if !blanktiles!==0 ( if "!x1!"=="" ( set test=1&set lose=1 for %%a in (a1 a2 a3 a4) do (set x=%%a&call :adder3) if !lose!==1 (set "msg=Game Over due to Out of Moves... Sorry :("&goto :res) ) else ( set test=1&set lose=1 for %%a in (a1 a2 a3 a4) do (set x=%%a&call :adder1) if !lose!==1 (set "msg=Game Over due to Out of Moves... Sorry :("&goto :res) ) ) goto :gameplay ) call :addtile goto :gameplay

/MAIN GAME LOOP
ADDTILE FUNCTION
addtile

set /a rnd1=(%random%%%%blanktiles%)*3 set /a rnd2=%random%%%10 set pick=!list:~%rnd1%,3! set %pick%=2 if %rnd2%==1 set %pick%=4 set list=!list:%pick%=! set /a blanktiles-=1 goto :EOF

/ADDTILE FUNCTION
PROCESSOR DOWN
proc1

set cyc=4

cyc1

if !%x%%cyc%!==0 ( set /a tm=%cyc%-1 for /l %%b in (!tm!,-1,1) do ( if not !%x%%%b!==0 ( set "%x%%cyc%=!%x%%%b!" set "%x%%%b=0" set moved=1 goto :break1 ) ) )

break1

if %cyc%==2 (goto :adder1) else (set /a cyc-=1&goto :cyc1)

adder1

if !%x%3!==!%x%4! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%4*=2 set %x%3=!%x%2! set %x%2=!%x%1! set %x%1=0 set /a "score+=!%x%4!" if !%x%4!==2048 (set won=1) if not !%x%4!==0 (set moved=1) ) if !%x%2!==!%x%3! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%3*=2 set %x%2=!%x%1! set %x%1=0 set /a "score+=!%x%3!" if !%x%3!==2048 (set won=1) if not !%x%3!==0 (set moved=1) ) else ( if !%x%1!==!%x%2! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%2*=2 set %x%1=0 set /a "score+=!%x%2!" if !%x%2!==2048 (set won=1) if not !%x%2!==0 (set moved=1) ) ) if !test!==0 ( if !%x%1!==0 (set list=!list!%x%1&set /a blanktiles+=1) if !%x%2!==0 (set list=!list!%x%2&set /a blanktiles+=1) if !%x%3!==0 (set list=!list!%x%3&set /a blanktiles+=1) if !%x%4!==0 (set list=!list!%x%4&set /a blanktiles+=1) ) goto :EOF

/PROCESSOR DOWN
PROCESSOR UP
proc2

set cyc=1

cyc2

if !%x%%cyc%!==0 ( set /a tm=%cyc%+1 for /l %%b in (!tm!,1,4) do ( if not !%x%%%b!==0 ( set "%x%%cyc%=!%x%%%b!" set "%x%%%b=0" set moved=1 goto :break2 ) ) )

break2

if %cyc%==3 (goto :adder2) else (set /a cyc+=1&goto :cyc2)

adder2

if !%x%1!==!%x%2! ( set /a %x%1*=2 set %x%2=!%x%3! set %x%3=!%x%4! set %x%4=0 set /a "score+=!%x%1!" if !%x%1!==2048 (set won=1) if not !%x%1!==0 (set moved=1) ) if !%x%2!==!%x%3! ( set /a %x%2*=2 set %x%3=!%x%4! set %x%4=0 set /a "score+=!%x%2!" if !%x%2!==2048 (set won=1) if not !%x%2!==0 (set moved=1) ) else ( if !%x%3!==!%x%4! ( set /a %x%3*=2 set %x%4=0 set /a "score+=!%x%3!" if !%x%3!==2048 (set won=1) if not !%x%3!==0 (set moved=1) ) ) if !%x%1!==0 (set list=!list!%x%1&set /a blanktiles+=1) if !%x%2!==0 (set list=!list!%x%2&set /a blanktiles+=1) if !%x%3!==0 (set list=!list!%x%3&set /a blanktiles+=1) if !%x%4!==0 (set list=!list!%x%4&set /a blanktiles+=1) goto :EOF

/PROCESSOR UP
PROCESSOR LEFT
proc3

set x1=%x:~1,1% set x=%x:~0,1% set cyc=1

cyc3

if !%x%%cyc%%x1%!==0 ( set /a tm=%cyc%+1 for /l %%b in (!tm!,1,4) do ( if not !%x%%%b%x1%!==0 ( set "%x%%cyc%%x1%=!%x%%%b%x1%!" set "%x%%%b%x1%=0" set moved=1 goto :break3 ) ) )

break3

if %cyc%==3 (goto :adder3) else (set /a cyc+=1&goto :cyc3)

adder3

if !test!==1 ( set x1=%x:~1,1% set x=%x:~0,1% ) if !%x%1%x1%!==!%x%2%x1%! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%1%x1%*=2 set %x%2%x1%=!%x%3%x1%! set %x%3%x1%=!%x%4%x1%! set %x%4%x1%=0 set /a "score+=!%x%1%x1%!" if !%x%1%x1%!==2048 (set won=1) if not !%x%1%x1%!==0 (set moved=1) ) if !%x%2%x1%!==!%x%3%x1%! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%2%x1%*=2 set %x%3%x1%=!%x%4%x1%! set %x%4%x1%=0 set /a "score+=!%x%2%x1%!" if !%x%2%x1%!==2048 (set won=1) if not !%x%2%x1%!==0 (set moved=1) ) else ( if !%x%3%x1%!==!%x%4%x1%! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%3%x1%*=2 set %x%4%x1%=0 set /a "score+=!%x%3%x1%!" if !%x%3%x1%!==2048 (set won=1) if not !%x%3%x1%!==0 (set moved=1) ) ) if !test!==0 ( if !%x%1%x1%!==0 (set list=!list!%x%1%x1%&set /a blanktiles+=1) if !%x%2%x1%!==0 (set list=!list!%x%2%x1%&set /a blanktiles+=1) if !%x%3%x1%!==0 (set list=!list!%x%3%x1%&set /a blanktiles+=1) if !%x%4%x1%!==0 (set list=!list!%x%4%x1%&set /a blanktiles+=1) ) goto :EOF

/PROCESSOR LEFT
PROCESSOR RIGHT
proc4

set x1=%x:~1,1% set x=%x:~0,1% set cyc=4

cyc4

if !%x%%cyc%%x1%!==0 ( set /a tm=%cyc%-1 for /l %%b in (!tm!,-1,1) do ( if not !%x%%%b%x1%!==0 ( set "%x%%cyc%%x1%=!%x%%%b%x1%!" set "%x%%%b%x1%=0" set moved=1 goto :break4 ) ) )

break4

if %cyc%==2 (goto :adder4) else (set /a cyc-=1&goto :cyc4)

adder4

if !%x%3%x1%!==!%x%4%x1%! ( set /a %x%4%x1%*=2 set %x%3%x1%=!%x%2%x1%! set %x%2%x1%=!%x%1%x1%! set %x%1%x1%=0 set /a "score+=!%x%4%x1%!" if !%x%4%x1%!==2048 (set won=1) if not !%x%4%x1%!==0 (set moved=1) ) if !%x%2%x1%!==!%x%3%x1%! ( set /a %x%3%x1%*=2 set %x%2%x1%=!%x%1%x1%! set %x%1%x1%=0 set /a "score+=!%x%3%x1%!" if !%x%3%x1%!==2048 (set won=1) if not !%x%3%x1%!==0 (set moved=1) ) else ( if !%x%1%x1%!==!%x%2%x1%! ( set /a %x%2%x1%*=2 set %x%1%x1%=0 set /a "score+=!%x%2%x1%!" if !%x%2%x1%!==2048 (set won=1) if not !%x%2%x1%!==0 (set moved=1) ) ) if !%x%1%x1%!==0 (set list=!list!%x%1%x1%&set /a blanktiles+=1) if !%x%2%x1%!==0 (set list=!list!%x%2%x1%&set /a blanktiles+=1) if !%x%3%x1%!==0 (set list=!list!%x%3%x1%&set /a blanktiles+=1) if !%x%4%x1%!==0 (set list=!list!%x%4%x1%&set /a blanktiles+=1) goto :EOF

/PROCESSOR RIGHT
FINAL RESULT
res

call :display echo !msg! echo. echo Press N for NEW GAME, or P to EXIT... set "qwerty=" for /F "usebackq delims=" %%L in (`xcopy /L /w "%~f0" "%~f0" 2^>NUL`) do (

 if not defined qwerty set "qwerty=%%L"

) set qwerty=%qwerty:~-1% if /i "!qwerty!"=="n" (goto :begin) if /i "!qwerty!"=="p" (exit) goto :res

/FINAL RESULT
DISPLAY
display

for %%a in (1 2 3 4) do (for %%b in (1 2 3 4) do ( set da%%a%%b=!a%%a%%b! if !a%%a%%b! lss 1000 (set da%%a%%b= !da%%a%%b!) if !a%%a%%b! lss 100 (set da%%a%%b= !da%%a%%b!) if !a%%a%%b! lss 10 (set da%%a%%b= !da%%a%%b!) if !a%%a%%b!==0 (set da%%a%%b= !da%%a%%b:0=!) ) ) cls echo. echo 2048 Game echo Batch File Implementation echo. echo. echo. echo. +----+----+----+----+ echo. ^|%da11%^|%da21%^|%da31%^|%da41%^| W - Slide UP echo. +----+----+----+----+ S - Slide DOWN echo. ^|%da12%^|%da22%^|%da32%^|%da42%^| A - Slide LEFT echo. +----+----+----+----+ D - Slide RIGHT echo. ^|%da13%^|%da23%^|%da33%^|%da43%^| N - New GAME echo. +----+----+----+----+ P - EXIT echo. ^|%da14%^|%da24%^|%da34%^|%da44%^| echo. +----+----+----+----+ echo. echo.Score: !score! echo. goto :EOF

/DISPLAY</lang>

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;         \
                   }                                                       \
               }                                                           \
           }                                                               \
           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;
               default:
                   found_valid_key = 0;
                   break;
           }
       }  while (!found_valid_key);
       do_tick(direction);
       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");

   if (0)

game_win: printf("You win!\n");

   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

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.

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>

Kotlin

Stateless with focus on clarity rather than conciseness.

<lang kotlin> package com.sbg.g2048.stateless

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 br = BufferedReader(InputStreamReader(System.`in`));
   println("Direction?  ")
   return br.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 { row -> mergeAndOrganizeCells(row) }.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 { row ->
       mergeAndOrganizeCells(row)
   }.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 { row ->
       mergeAndOrganizeCells(row)
   }.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)
   if (row[idxToMatch] == row[idxToCompare]) {
       row[idxToMatch] *= 2
       row[idxToCompare] = 0
       return merge(row, idxToMatch + 1, idxToMatch + 2)
   } else {
       return 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)
   if (row[idxToCompare] != 0) {
       row[idxToMatch] = row[idxToCompare]
       row[idxToCompare] = 0
       return organize(row, idxToMatch + 1, idxToMatch + 2)
   } else {
       return 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?  

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

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

my @board = ( [, , , ] xx 4 ); my $save = ; my $score = 0; constant $cell = 6; # width constant $tab = "\t\t"; # spacing from left edge constant $top = join '─' x $cell, '┌', '┬' xx 3, '┐'; constant $mid = join '─' x $cell, '├', '┼' xx 3, '┤'; constant $bot = join '─' x $cell, '└', '┴' xx 3, '┘'; constant left = 'left'; constant right = 'right';

my %dir = (

  (27, 91, 65) => 'up',
  (27, 91, 66) => 'down',
  (27, 91, 67) => 'right',
  (27, 91, 68) => 'left',

);

sub row (@row) {

   sprintf("│%{$cell}s│%{$cell}s│%{$cell}s│%{$cell}s│\n", @row».&center )

}

sub center ($s){

   my $c   = $cell - $s.chars;
   my $pad = ' ' x ceiling($c/2);
   sprintf "%{$cell}s", "$s$pad";

}

sub draw-board {

   run('clear');
   print "\n\n{$tab}Press direction arrows to move.";
   print "\n\n{$tab}Press q to quit.\n\n$tab$top\n$tab";
   print join "$tab$mid\n$tab", map { $_.&row }, @board;
   print "$tab$bot\n\n{$tab}Score: ";

}

multi sub squash ('left', @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 < 4;
   @t;

}

multi sub squash ('right', @c) {

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

}

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

multi sub move('up') {

   map { @board[*]»[$_] = squash left, @board[*]»[$_] }, 0..3

}

multi sub move('down') {

   map { @board[*]»[$_] = squash right, @board[*]»[$_] }, 0..3

}

multi sub move('left') {

   map { @board[$_] = squash left, flat @board[$_]»[*] }, 0..3

}

multi sub move('right') {

   map { @board[$_] = squash right, flat @board[$_]»[*] }, 0..3

}

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;

}

loop {

  another if (join '|', flat @board».list) ne $save;
  draw-board;
  say $score;
  # Read up to 4 bytes from keyboard buffer.
  # Page navigation keys are 3-4 bytes each.
  # Specifically, arrow keys are 3.
  my $char = $*IN.read(4).decode.ords;
  $save = join '|', flat @board».list;
  move(%dir{$char}) if so %dir{$char};
  last if $char eq 113; # (q)uit

}</lang> Sample output:

		Press direction arrows to move.

                Press q to quit.

		┌──────┬──────┬──────┬──────┐
		│      │      │      │      │
		├──────┼──────┼──────┼──────┤
		│      │      │  2   │  2   │
		├──────┼──────┼──────┼──────┤
		│      │  2   │  2   │  16  │
		├──────┼──────┼──────┼──────┤
		│  16  │ 128  │ 256  │  64  │
		└──────┴──────┴──────┴──────┘

		Score: 2832

Phix

Faithful desktop gui (windows only) reproduction of the above link (https://gabrielecirulli.github.io/2048/) Now I just got figure out how to win... <lang Phix>include ..\arwen\arwen.ew include ..\arwen\axtra.ew

constant main = create(Window, "2048", 0, 0, 20, 20, 520, 540, 0),

        mainDC = getPrivateDC(main),
        viewDC = c_func(xCreateCompatibleDC, {NULL})

constant TA_CENTER = 6 {} = c_func(xSetTextAlign,{viewDC,TA_CENTER}) constant hFont40 = createFontForDC(viewDC, "Calibri", 40, Bold) constant hFont32 = createFontForDC(viewDC, "Calibri", 32, Bold)

constant tile_colours = {#B4C0CC, -- blank

                        #DAE4EE,   -- 2
                        #C8E0ED,   -- 4
                        #79B1F2,   -- 8
                        #6395F5,   -- 16
                        #5F7CF6,   -- 32
                        #3B5EF6,   -- 64
                        #72CFED,   -- 128
                        #61CCED,   -- 256
                        #50C8ED,   -- 512
                        #3FC5ED,   -- 1024
                        #2EC2ED}   -- 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=VK_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=VK_DOWN 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=VK_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=VK_UP 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 = {VK_LEFT,VK_DOWN,VK_RIGHT,VK_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

integer dw = 0, dh = 0 -- client area width and height atom bmView integer vwX = 0, vwY = 0 -- actual size of the view bitmap

integer ox,oy, -- top tight coords

       os,ts,              -- overall and tile size
       ts2                 -- half tile, for number positioning

function mainHandler(integer id, integer msg, atom wParam, object lParam) integer tx, ty, bxy string mbmsg

   if msg=WM_SIZE then
       {{},{},dw,dh} = getClientRect(main)
       if dw>vwX or dh>vwY then
           -- we need a bigger bitmap
           bmView = c_func(xCreateCompatibleBitmap, {mainDC, dw, dh})
           {} = deleteObject(selectObject(viewDC,bmView))
           {vwX,vwY} = {dw,dh}
       end if
       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
   elsif msg=WM_PAINT then
       if newgame then
           board = repeat(repeat(1,4),4)
           add_rand(2)
           newgame = 0
       end if
       setPenColor(#EFF8FA)
       drawRectangleh(viewDC, True, 0, 0, dw, dh)
       setPenColor(#A0ADBB)
       drawRoundRecth(viewDC, True, ox+5, oy+5, ox+os-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]
               setPenColor(tile_colours[bxy])
               drawRoundRecth(viewDC, True, tx, ty, tx+ts-10, ty+ts-10, 5, 5)
               if bxy>1 then
                   setPenColor(iff(bxy<=3?#656E77:#F2F6F9))
                   {} = selectObject(viewDC,iff(bxy>10?hFont32:hFont40))
                   wPuts2(viewDC, tx+ts2, ty+ts2-25-iff(bxy<11?7:0), power(2,bxy-1))
               end if
               ty += ts+5
           end for
           tx += ts+5
       end for
       void = c_func(xBitBlt,{mainDC,0,0,dw,dh,viewDC,0,0,SRCCOPY})
   elsif msg=WM_CHAR then
       if wParam=VK_ESCAPE then
           closeWindow(main)
           if id or object(lParam) then end if -- suppress warnings
       end if
   elsif msg=WM_KEYDOWN then
       if move(wParam) then
           mbmsg = ""
           if game_won() then
               mbmsg = "!!!YOU WON!!!\n\nAnother Go?"
           else
               add_rand(1)
               repaintWindow(main)
               if no_valid_moves() then
                   mbmsg = "You Lost.\n\nAnother Go?"
               end if
           end if
           if length(mbmsg) then
               if messageBox("Game Over",mbmsg,MB_YESNO)=IDYES then
                   newgame=1
               else
                   closeWindow(main)
               end if
           end if
       end if
   elsif msg=WM_GETMINMAXINFO then
       poke4(lParam+MINMAXINFO_ptMinTrackSize,{440,450}) 
   end if
   return 0

end function setHandler({main},routine_id("mainHandler"))

WinMain(main, SW_NORMAL)</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>

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>

Tcl

Text mode

<lang tcl>

  1. A minimal implementation of the game 2048 in Tcl.

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 http://wiki.tcl.tk/39566.