2048

From Rosetta Code
Revision as of 00:08, 23 June 2016 by Thundergnat (talk | contribs) (Undo revision 229566 by Thundergnat (talk))
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 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

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

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

   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>

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>

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

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

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>

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", "a" then b.move(:down)
 when "\e[C", "s" then b.move(:right)
 when "\e[D", "d" 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 <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.