2048

From Rosetta Code
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.

AutoHotkey[edit]

Grid := [], s := 16, w := h := S * 4.5
Gui, font, s%s%
Gui, add, text, y1
loop, 4
{
row := A_Index
loop, 4
{
col := A_Index
if col = 1
Gui, add, button, v%row%_%col% xs y+1 w%w% h%h% -TabStop, % Grid[row,col] := 0
else
Gui, add, button, v%row%_%col% x+1 yp w%w% h%h% -TabStop, % Grid[row,col] := 0
}
}
Gui, show,, 2048
;------------------------------
 
Start:
for row, obj in Grid
for col, val in obj
Grid[row,col] := 0
 
Grid[1,1]:=2
ShowGrid()
return
 
;------------------------------
GuiClose:
ExitApp
return
;------------------------------
#IfWinActive, 2048
;------------------------------
up::
move := false
loop, 4
{
col := A_Index
Loop, 3
{
row := A_Index
if Grid[row, col] && (Grid[row, col] = Grid[row+1, col])
Grid[row, col] *=2 , Grid[row+1, col] := 0, move := true
}
}
 
loop, 4
{
row := A_Index
loop, 4
{
col := A_Index
loop, 4
if !Grid[row, col]
loop, 3
if !Grid[row, col] && Grid[row+A_Index, col]
{
Grid[row, col] := Grid[row+A_Index, col] , Grid[row+A_Index, col] := 0, move := true
if (Grid[row, col] = Grid[row-1, col])
Grid[row-1, col] *=2 , Grid[row, col] := 0, move := true
}
}
}
gosub, AddNew
return
;------------------------------
Down::
move := false
loop, 4
{
col := A_Index
Loop, 3
{
row := 5-A_Index
if Grid[row, col] && (Grid[row, col] = Grid[row-1, col])
Grid[row, col] *=2 , Grid[row-1, col] := 0, move := true
}
}
 
loop, 4
{
row := 5-A_Index
loop, 4
{
col := A_Index
loop, 4
if !Grid[row, col]
loop, 3
if !Grid[row, col] && Grid[row-A_Index, col]
{
Grid[row, col] := Grid[row-A_Index, col] , Grid[row-A_Index, col] := 0, move := true
if (Grid[row, col] = Grid[row+1, col])
Grid[row+1, col] *=2 , Grid[row, col] := 0, move := true
}
}
}
gosub, AddNew
return
;------------------------------
Left::
move := false
loop, 4
{
row := A_Index
Loop, 3
{
col := A_Index
if Grid[row, col] && (Grid[row, col] = Grid[row, col+1])
Grid[row, col] *=2 , Grid[row, col+1] := 0, move := true
}
}
 
loop, 4
{
col := A_Index
loop, 4
{
row := A_Index
loop, 4
if !Grid[row, col]
loop, 3
if !Grid[row, col] && Grid[row, col+A_Index]
{
Grid[row, col] := Grid[row, col+A_Index] , Grid[row, col+A_Index] := 0, move := true
if (Grid[row, col] = Grid[row, col-1])
Grid[row, col-1] *=2 , Grid[row, col] := 0, move := true
}
 
}
}
gosub, AddNew
return
;------------------------------
Right::
move := false
loop, 4
{
row := A_Index
Loop, 3
{
col := 5-A_Index
if Grid[row, col] && (Grid[row, col] = Grid[row, col-1])
Grid[row, col] *=2 , Grid[row, col-1] := 0, move := true
}
}
 
loop, 4
{
col := 5-A_Index
loop, 4
{
row := A_Index
loop, 4
if !Grid[row, col]
loop, 3
if !Grid[row, col] && Grid[row, col-A_Index]
{
Grid[row, col] := Grid[row, col-A_Index] , Grid[row, col-A_Index] := 0, move := true
if (Grid[row, col] = Grid[row, col+1])
Grid[row, col+1] *=2 , Grid[row, col] := 0, move := true
}
}
}
gosub, AddNew
return
 
;------------------------------
#IfWinActive
;------------------------------
AddNew:
if EndOfGame()
{
MsgBox Done `nPress OK to retry
goto start
}
return
 
;------------------------------
EndOfGame(){
global
if Move
AddRandom()
ShowGrid()
for row, obj in Grid
for col, val in obj
if !grid[row,col]
return 0
 
for row, obj in Grid
for col, val in obj
if (grid[row,col] = grid[row+1,col]) || (grid[row,col] = grid[row-1,col]) || (grid[row,col] = grid[row,col+1]) || (grid[row,col] = grid[row,col-1])
return 0
return 1
}
 
;------------------------------
ShowGrid(){
global Grid
for row, obj in Grid
for col, val in obj
{
GuiControl,, %row%_%col%, %val%
if val
GuiControl, Show, %row%_%col%
else
GuiControl, Hide, %row%_%col%
}
}
 
;------------------------------
AddRandom(){
global Grid
ShowGrid()
Sleep, 200
for row, obj in Grid
for col, val in obj
if !grid[row,col]
list .= (list?"`n":"") row "," col
Sort, list, random
Rnd := StrSplit(list, "`n").1
Grid[StrSplit(rnd, ",").1, StrSplit(rnd, ",").2] := 2
}
;------------------------------

Batch File[edit]

::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
Output:
2048 Game in Batch

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

Score: 60

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

BBC BASIC[edit]

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

C[edit]

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

 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
 
#define D_INVALID -1
#define D_UP 1
#define D_DOWN 2
#define D_RIGHT 3
#define D_LEFT 4
 
const long values[] = {
0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048
};
 
const char *colors[] = {
"39", "31", "32", "33", "34", "35", "36", "37", "91", "92", "93", "94"
};
 
struct gamestate_struct__ {
int grid[4][4];
int have_moved;
long total_score;
long score_last_move;
int blocks_in_play;
} game;
 
struct termios oldt, newt;
 
void do_draw(void)
{
printf("\033[2J\033[HScore: %ld", game.total_score);
if (game.score_last_move)
printf(" (+%ld)", game.score_last_move);
printf("\n");
 
for (int i = 0; i < 25; ++i)
printf("-");
printf("\n");
 
for (int y = 0; y < 4; ++y) {
printf("|");
for (int x = 0; x < 4; ++x) {
if (game.grid[x][y])
printf("\033[7m\033[%sm%*zd \033[0m|", colors[game.grid[x][y]],
4, values[game.grid[x][y]]);
else
printf("%*s |", 4, "");
}
printf("\n");
}
 
for (int i = 0; i < 25; ++i) {
printf("-");
}
printf("\n");
}
 
void do_merge(int d)
{
/* These macros look pretty scary, but mainly demonstrate some space saving */
#define MERGE_DIRECTION(_v1, _v2, _xs, _xc, _xi, _ys, _yc, _yi, _x, _y) \
do { \
for (int _v1 = _xs; _v1 _xc; _v1 += _xi) { \
for (int _v2 = _ys; _v2 _yc; _v2 += _yi) { \
if (game.grid[x][y] && (game.grid[x][y] == \
game.grid[x + _x][y + _y])) { \
game.grid[x][y] += (game.have_moved = 1); \
game.grid[x + _x][y + _y] = (0 * game.blocks_in_play--);\
game.score_last_move += values[game.grid[x][y]]; \
game.total_score += values[game.grid[x][y]]; \
} \
} \
} \
} while (0)

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

 
switch (d) {
case D_LEFT:
GRAVITATE_DIRECTION(x, y, 0, < 3, 1, 0, < 4, 1, 1, 0);
break;
case D_RIGHT:
GRAVITATE_DIRECTION(x, y, 3, > 0, -1, 0, < 4, 1, -1, 0);
break;
case D_DOWN:
GRAVITATE_DIRECTION(y, x, 3, > 0, -1, 0, < 4, 1, 0, -1);
break;
case D_UP:
GRAVITATE_DIRECTION(y, x, 0, < 3, 1, 0, < 4, 1, 0, 1);
break;
}
 
#undef GRAVITATE_DIRECTION
}
 
int do_check_end_condition(void)
{
int ret = -1;
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 4; ++y) {
if (values[game.grid[x][y]] == 2048)
return 1;
if (!game.grid[x][y] ||
((x + 1 < 4) && (game.grid[x][y] == game.grid[x + 1][y])) ||
((y + 1 < 4) && (game.grid[x][y] == game.grid[x][y + 1])))
ret = 0;
}
}
return ret;
}
 
int do_tick(int d)
{
game.have_moved = 0;
do_gravity(d);
do_merge(d);
do_gravity(d);
return game.have_moved;
}
 
void do_newblock(void) {
if (game.blocks_in_play >= 16) return;
 
int bn = rand() % (16 - game.blocks_in_play);
int pn = 0;
 
for (int x = 0; x < 4; ++x) {
for (int y = 0; y < 4; ++y) {
if (game.grid[x][y])
continue;
 
if (pn == bn){
game.grid[x][y] = rand() % 10 ? 1 : 2;
game.blocks_in_play += 1;
return;
}
else {
++pn;
}
}
}
}
 
int main(void)
{
/* Initialize terminal settings */
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
 
srand(time(NULL));
memset(&game, sizeof(game), 0);
do_newblock();
do_newblock();
do_draw();
 
while (1) {
int found_valid_key, direction, value;
do {
found_valid_key = 1;
direction = D_INVALID;
value = getchar();
switch (value) {
case 'h': case 'a':
direction = D_LEFT;
break;
case 'l': case 'd':
direction = D_RIGHT;
break;
case 'j': case 's':
direction = D_DOWN;
break;
case 'k': case 'w':
direction = D_UP;
break;
case 'q':
goto game_quit;
break;
case 27:
if (getchar() == 91) {
value = getchar();
switch (value) {
case 65:
direction = D_UP;
break;
case 66:
direction = D_DOWN;
break;
case 67:
direction = D_RIGHT;
break;
case 68:
direction = D_LEFT;
break;
default:
found_valid_key = 0;
break;
}
}
break;
default:
found_valid_key = 0;
break;
}
} while (!found_valid_key);
 
do_tick(direction);
if (game.have_moved != 0){
do_newblock();
}
do_draw();
 
switch (do_check_end_condition()) {
case -1:
goto game_lose;
case 1:
goto game_win;
case 0:
break;
}
}
 
if (0)
game_lose:
printf("You lose!\n");
goto game_quit;
if (0)
game_win:
printf("You win!\n");
goto game_quit;
if (0)
game_quit:
 
/* Restore terminal settings */
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return 0;
}
 
Output:
Score: 1100 (+4)
-------------------------
|  64 |  32 |  64 |  32 |
|  32 |  16 |   2 |   8 |
|  16 |   4 |   8 |   4 |
|   4 |   2 |   4 |   2 |
-------------------------
You lose!

C++[edit]

 
#include <time.h>
#include <iostream>
#include <string>
#include <iomanip>
#include <cstdlib>
 
typedef unsigned int uint;
using namespace std;
enum movDir { UP, DOWN, LEFT, RIGHT };
 
class tile
{
public:
tile() : val( 0 ), blocked( false ) {}
uint val;
bool blocked;
};
 
class g2048
{
public:
g2048() : done( false ), win( false ), moved( true ), score( 0 ) {}
void loop()
{
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" );
}
 
Output:
SCORE: 2024

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

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

Common Lisp[edit]

Depends on Windows msvcrt.dll for _getch. Depends on quicklisp. Use arrow keys to make moves and press "Q" to quit. Tested with SBCL.

(ql:quickload '(cffi alexandria))
 
(defpackage :2048-lisp
(:use :common-lisp :cffi :alexandria))
 
(in-package :2048-lisp)
 
(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))
Output:
* (2048-lisp::prompt)

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

Some time later...

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

D[edit]

Translation of: C++
import std.stdio, std.string, std.random;
import core.stdc.stdlib: exit;
 
struct G2048 {
public void gameLoop() [email protected] @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 [email protected] @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() [email protected]*/ {
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 [email protected]*/ {
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;
}

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

Elixir[edit]

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

Go[edit]

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: {{.Score}}
{{range .Field}}
`
+ rawBorder + `
|{{range .}} {{if .}}{{printf "%`
+ strconv.Itoa(maxWidth) + `d" .}}{{else}}` +
strings.Repeat(" ", maxWidth) + `{{end}} |{{end}}{{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()) {
}
}
 

J[edit]

Solution

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&{.@([email protected])"1'
toRight=: 1 : '_4&{.@([email protected]&.|.)"1'
toUp=: 1 : '(4&{.@([email protected])"1)&.|:'
toDown=: 1 : '(_4&{.@([email protected]&.|.)"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=: [email protected]
 
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 ''
)

Usage

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

Java[edit]

Game 2048 java2.png
Works with: Java version 8
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;
}
}

Kotlin[edit]

Stateless with focus on clarity rather than conciseness.

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(::mergeAndOrganizeCells).toTypedArray()
 
fun shiftCellsRight(grid: Array<Array<Int>>): Array<Array<Int>> =
grid.map { row -> mergeAndOrganizeCells(row.reversed().toTypedArray()).reversed().toTypedArray() }.toTypedArray()
 
fun shiftCellsUp(grid: Array<Array<Int>>): Array<Array<Int>> {
val rows: Array<Array<Int>> = arrayOf(
arrayOf(grid[0][0], grid[1][0], grid[2][0], grid[3][0]),
arrayOf(grid[0][1], grid[1][1], grid[2][1], grid[3][1]),
arrayOf(grid[0][2], grid[1][2], grid[2][2], grid[3][2]),
arrayOf(grid[0][3], grid[1][3], grid[2][3], grid[3][3])
)
 
val updatedGrid = grid.copyOf()
 
rows.map(::mergeAndOrganizeCells).forEachIndexed { rowIdx, row ->
updatedGrid[0][rowIdx] = row[0]
updatedGrid[1][rowIdx] = row[1]
updatedGrid[2][rowIdx] = row[2]
updatedGrid[3][rowIdx] = row[3]
}
 
return updatedGrid
}
 
fun shiftCellsDown(grid: Array<Array<Int>>): Array<Array<Int>> {
val rows: Array<Array<Int>> = arrayOf(
arrayOf(grid[3][0], grid[2][0], grid[1][0], grid[0][0]),
arrayOf(grid[3][1], grid[2][1], grid[1][1], grid[0][1]),
arrayOf(grid[3][2], grid[2][2], grid[1][2], grid[0][2]),
arrayOf(grid[3][3], grid[2][3], grid[1][3], grid[0][3])
)
 
val updatedGrid = grid.copyOf()
 
rows.map(::mergeAndOrganizeCells).forEachIndexed { rowIdx, row ->
updatedGrid[3][rowIdx] = row[0]
updatedGrid[2][rowIdx] = row[1]
updatedGrid[1][rowIdx] = row[2]
updatedGrid[0][rowIdx] = row[3]
}
 
return updatedGrid
}
 
fun mergeAndOrganizeCells(row: Array<Int>): Array<Int> = organize(merge(row.copyOf()))
 
fun merge(row: Array<Int>, idxToMatch: Int = 0, idxToCompare: Int = 1): Array<Int> {
if (idxToMatch >= row.size)
return row
if (idxToCompare >= row.size)
return merge(row, idxToMatch + 1, idxToMatch + 2)
if (row[idxToMatch] == 0)
return merge(row, idxToMatch + 1, idxToMatch + 2)
 
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("+----+----+----+----+")
}

Sample output:

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

Perl 6[edit]

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

Works with: Rakudo version 2017.03
use Term::termios;
 
constant $saved = Term::termios.new(fd => 1).getattr;
constant $termios = Term::termios.new(fd => 1).getattr;
# raw mode interferes with carriage returns, so
# set flags needed to emulate it manually
$termios.unset_iflags(<BRKINT ICRNL ISTRIP IXON>);
$termios.unset_lflags(< ECHO ICANON IEXTEN ISIG>);
$termios.setattr(:DRAIN);
 
# 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[[email protected][$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
}

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[edit]

Faithful desktop gui (windows only) reproduction of the above link (https://gabrielecirulli.github.io/2048/) Now I just got figure out how to win...

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)


PHP[edit]

Works from PHP5 and upwards in CLI mode.

 
<?php
 
$game = new Game();
 
while(true) {
$game->cycle();
}
 
class Game {
private $field;
private $fieldSize;
private $command;
private $error;
private $lastIndexX, $lastIndexY;
private $score;
private $finishScore;
 
function __construct() {
$this->field = array();
$this->fieldSize = 4;
$this->finishScore = 2048;
$this->score = 0;
$this->addNumber();
$this->render();
}
 
public function cycle() {
$this->command = strtolower($this->readchar('Use WASD, q exits'));
$this->cls();
 
if($this->processCommand()) {
$this->addNumber();
} else {
if(count($this->getFreeList()) == 0 ) {
$this->error = 'No options left!, You Lose!!';
} else {
$this->error = 'Invalid move, try again!';
}
}
$this->render();
}
 
private function readchar($prompt) {
readline_callback_handler_install($prompt, function() {});
$char = stream_get_contents(STDIN, 1);
readline_callback_handler_remove();
return $char;
}
 
/**
* Insert a number in an empty spot on the field
*/

private function addNumber() {
$freeList = $this->getFreeList();
if(count($freeList) == 0) {
return;
}
$index = mt_rand(0, count($freeList)-1);
$nr = (mt_rand(0,9) == 0)? 4 : 2;
$this->field[$freeList[$index]['x']][$freeList[$index]['y']] = $nr;
return;
}
 
/**
* @return array(array('x' => <x>, 'y' => <y>)) with empty positions in the field
*/

private function getFreeList() {
$freeList = array();
for($y =0; $y< $this->fieldSize;$y++) {
for($x=0; $x < $this->fieldSize; $x++) {
if(!isset($this->field[$x][$y])) {
$freeList[] = array('x' => $x, 'y' => $y);
} elseif($this->field[$x][$y] == $this->finishScore) {
$this->error = 'You Win!!';
}
}
}
return $freeList;
}
 
/**
* Process a command:
* @return is the command valid (Did it cause a change in the field)
*/

private function processCommand() {
if(!in_array($this->command, array('w','a','s','d','q'))) {
$this->error = 'Invalid Command';
return false;
}
if($this->command == 'q') {
echo PHP_EOL. 'Bye!'. PHP_EOL;
exit;
}
 
// Determine over which axis and in which direction we move:
$axis = 'x';
$sDir = 1;
 
switch($this->command) {
case 'w':
$axis = 'y';
$sDir = -1;
break;
case 'a':
$sDir = -1;
break;
case 's':
$axis = 'y';
break;
case 'd':
break;
}
 
$done = 0;
// shift all numbers in that direction
$done += $this->shift($axis, $sDir);
// merge equal numbers in opposite direction
$done += $this->merge($axis, $sDir * -1);
// shift merged numbers in that direction
$done += $this->shift($axis, $sDir);
return $done >0;
}
 
private function shift($axis, $dir) {
$totalDone = 0;
for($i = 0; $i <$this->fieldSize; $i++) {
$done = 0;
foreach($this->iterate($axis,$dir) as $xy) {
if($xy['vDest'] === NULL && $xy['vSrc'] !== NULL) {
$this->field[$xy['dX']][$xy['dY']] = $xy['vSrc'];
$this->field[$xy['sX']][$xy['sY']] = NULL;
$done++;
}
}
$totalDone += $done;
if($done == 0) {
// nothing to shift anymore
break;
}
}
return $totalDone;
}
 
private function merge($axis, $dir) {
$done = 0;
foreach($this->iterate($axis,$dir) as $xy) {
if($xy['vDest'] !== NULL && $xy['vDest'] === $xy['vSrc']) {
$this->field[$xy['sX']][$xy['sY']] += $xy['vDest'];
$this->field[$xy['dX']][$xy['dY']] = NULL;
$this->score += $this->field[$xy['sX']][$xy['sY']];
$done ++;
}
}
return $done;
}
 
/**
* @return array List of src, dest pairs and their values to iterate over.
*/

private function iterate($axis, $dir) {
$res = array();
for($y = 0; $y < $this->fieldSize; $y++) {
for($x=0; $x < $this->fieldSize; $x++) {
$item = array('sX'=> $x,'sY' => $y, 'dX' => $x, 'dY' => $y, 'vDest' => NULL,'vSrc' => NULL);
 
if($axis == 'x') {
$item['dX'] += $dir;
} else {
$item['dY'] += $dir;
}
 
if($item['dX'] >= $this->fieldSize || $item['dY'] >=$this->fieldSize || $item['dX'] < 0 || $item['dY'] < 0) {
continue;
}
 
$item['vDest'] = (isset($this->field[$item['dX']][$item['dY']]))? $this->field[$item['dX']][$item['dY']] : NULL;
$item['vSrc'] = (isset($this->field[$item['sX']][$item['sY']]))? $this->field[$item['sX']][$item['sY']] : NULL;
$res[] = $item;
}
}
if($dir < 0) {
$res = array_reverse($res);
}
return $res;
}
 
/// RENDER ///
 
/**
* Clear terminal screen
*/

private function cls() {
echo chr(27).chr(91).'H'.chr(27).chr(91).'J';
}
 
private function render() {
echo $this->finishScore . '! Current score: '. $this->score .PHP_EOL;
 
if(!empty($this->error)) {
echo $this->error . PHP_EOL;
$this->error = NULL;
}
$this->renderField();
}
 
private function renderField() {
$width = 5;
$this->renderVSeperator($width);
for($y =0; $y < $this->fieldSize; $y ++) {
for($x = 0;$x < $this->fieldSize; $x++) {
echo '|';
if(!isset($this->field[$x][$y])) {
echo str_repeat(' ', $width);
continue;
}
printf('%'.$width.'s', $this->field[$x][$y]);
}
echo '|'. PHP_EOL;
$this->renderVSeperator($width);
}
}
 
private function renderVSeperator($width) {
echo str_repeat('+'. str_repeat('-', $width), $this->fieldSize) .'+' .PHP_EOL;
}
 
}
 


PicoLisp[edit]

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

Pony[edit]

Works with: ponyc version 0.10.0
 
use "term"
use "random"
use "time"
 
interface EdgeRow
fun val row() : Iterator[U32] ref
fun val inc() : I32
 
primitive TopRow is EdgeRow
fun row() : Iterator[U32] ref => let r : Array[U32] box = [0,1,2,3]
r.values()
fun inc() : I32 => 4
 
primitive LeftRow is EdgeRow
fun row() : Iterator[U32] ref => let r : Array[U32] box = [0,4,8,12]
r.values()
fun inc() : I32 => 1
 
primitive RightRow is EdgeRow
fun row() : Iterator[U32] ref => let r : Array[U32] box = [3,7,11,15]
r.values()
fun inc() : I32 => -1
 
primitive BottomRow is EdgeRow
fun row() : Iterator[U32] ref => let r : Array[U32] box = [12,13,14,15]
r.values()
fun inc() : I32 => -4
 
primitive LEFT
primitive RIGHT
primitive UP
primitive DOWN
type Move is (LEFT|RIGHT|UP|DOWN)
 
class KeyboardHandler is ANSINotify
let _game : Game tag
new iso create(game : Game tag) => _game = game
 
fun ref apply(term: ANSITerm ref, input: U8 val) =>
if input == 113 then
_game.quit()
term.dispose()
end
fun ref left(ctrl: Bool, alt: Bool, shift: Bool) => _game.move(LEFT)
fun ref down(ctrl: Bool, alt: Bool, shift: Bool) => _game.move(DOWN)
fun ref up(ctrl: Bool, alt: Bool, shift: Bool) => _game.move(UP)
fun ref right(ctrl: Bool, alt: Bool, shift: Bool) => _game.move(RIGHT)
 
type ROW is (U32,U32,U32,U32)
 
primitive Merger
fun tag apply(r : ROW) : ROW =>
match r
| (0,0,0,_) => (r._4,0,0,0)
| (0,0,_,r._3) => (r._3<<1,0,0,0)
| (0,0,_,_) => (r._3,r._4,0,0)
| (0,_,r._2,_) => (r._2<<1,r._4,0,0)
| (0,_,0,r._2) => (r._2<<1,0,0,0)
| (0,_,0,_) => (r._2,r._4,0,0)
| (0,_,_,r._3) => (r._2,r._3<<1,0,0)
| (0,_,_,_) => (r._2,r._3,r._4,0)
| (_, r._1, _, r._3) => (r._1<<1, r._3<<1, 0, 0)
| (_, r._1, 0, _) => (r._1<<1, r._4, 0, 0)
| (_, r._1, _, _) => (r._1<<1, r._3, r._4, 0)
| (_, 0,r._1, _) => (r._1<<1,r._4,0,0)
| (_, 0,0, r._1) => (r._1<<1,0,0,0)
| (_, 0,0, _) => (r._1,r._4,0,0)
| (_, 0,_, r._3) => (r._1, r._3<<1,0,0)
| (_, 0,_, _) => (r._1, r._3,r._4,0)
| (_,_,r._2,_) => (r._1, r._2<<1,r._4,0)
| (_,_,0,r._2) => (r._1, r._2<<1,0,0)
| (_,_,0,_) => (r._1, r._2,r._4,0)
| (_,_,_,r._3) => (r._1, r._2,r._3<<1,0)
else
r
end
/**
* Game actor
*/
actor Game
embed _grid : Array[U32] = Array[U32].init(0, 16)
let _rand : Random = MT(Time.millis())
let _env : Env
let _board : String ref = recover String(1024) end
 
new create(env: Env)=>
_env = env
_add_block()
_add_block()
_draw()
 
fun _merge(start : U32, inc : I32) : (ROW | None) =>
var st = start.i32()
let rval : ROW = (_get(st), _get(st + inc),
_get(st + (inc * 2)), _get(st + (inc * 3)))
let rout = Merger(rval)
if rout is rval then None else rout end
 
fun ref _update(start : U32, inc : I32) : Bool =>
match _merge(start, inc)
| let rout : ROW =>
var st = start.i32()
_set(st, rout._1)
_set(st + inc, rout._2)
_set(st + (inc * 2), rout._3)
_set(st + (inc * 3), rout._4)
true
else
false
end
 
fun ref _shift_to(edge : EdgeRow val) : Bool =>
var updated = false
for r in edge.row() do
if _update(r, edge.inc()) then
updated = true
end
end
updated
 
fun _fmt(i : U32) : String =>
match i
| 0 => " __ "
| 2 => "\x1B[31m 2 \x1B[0m"
| 4 => "\x1B[32m 4 \x1B[0m"
| 8 => "\x1B[33m 8 \x1B[0m"
| 16 => "\x1B[34m 16 \x1B[0m"
| 32 => "\x1B[35m 32 \x1B[0m"
| 64 => "\x1B[36m 64 \x1B[0m"
| 128 => "\x1B[37m128 \x1B[0m"
| 256 => "\x1B[41m\x1B[37m256 \x1B[0m"
| 512 => "\x1B[42m\x1B[37m512 \x1B[0m"
| 1024 => "\x1B[43m\x1B[37m1024\x1B[0m"
| 2048 => "\x1B[47m\x1B[35m\x1B[1m\x1B[5m2048\x1B[0m"
else
i.string()
end
 
fun ref _draw() =>
let s : String ref = _board
s.truncate(0)
var i : U32 = 0
repeat
if (i % 4) == 0 then
s.append("---------------------\n")
end
s.append(_fmt(_get(i)))
s.append(" ")
i = i + 1
if (i % 4) == 0 then
s.append("\n")
end
until i==16 end
_env.out.print(s.string())
_env.out.print("Arrow keys to move. Press (q)uit key to quit.")
 
fun ref _set(i:(I32|U32), v : U32) =>
try
_grid.update(i.usize(),v)
else
_env.out.print("cant update!")
end
 
fun _count() : U64 =>
var c : U64 = 0
for v in _grid.values() do
c = c + if v == 0 then 0 else 1 end
end
c
 
fun ref _add_block() =>
let c = _count()
if c == 16 then return end
 
var hit = _rand.int(16 - c)
var i : U32 = 0
while i < 16 do
if (_get(i) == 0) then
if hit == 0 then
_set(i, if _rand.int(10) > 0 then 2 else 4 end)
break
end
hit = hit - 1
end
i = i + 1
end
 
fun _get(i : (I32|U32)) : U32 => try _grid(i.usize()) else 0 end
 
fun _win() : Bool =>
for v in _grid.values() do
if v == 2048 then return true end
end
false
 
fun _no_moves(edge : EdgeRow val) : Bool =>
for r in edge.row() do
match _merge(r, edge.inc())
| let rout : ROW =>
if (rout._1 == 0) or (rout._2 == 0) or
(rout._3 == 0) or (rout._4 == 0) then
return false
end
end
end
true
 
fun _lose() : Bool =>
(_grid.size() >= 16) and
_no_moves(LeftRow) and
_no_moves(RightRow) and
_no_moves(TopRow) and
_no_moves(BottomRow)
 
be quit()=>
_env.out.print("Exiting.. some terminals may require <ctrl-c>")
_env.exitcode(0)
_env.input.dispose()
 
be move(m: Move) =>
let updated =
match m
| LEFT => _shift_to(LeftRow)
| RIGHT => _shift_to(RightRow)
| UP => _shift_to(TopRow)
| DOWN => _shift_to(BottomRow)
else
false
end
 
if _win() then
_draw()
_env.out.print("You win :)")
quit()
else
if updated then
_add_block()
_draw()
end
if _lose() then
_env.out.print("You lose :(")
quit()
end
end
 
actor Main
new create(env: Env) =>
// unit test
ifdef "test" then
TestMain(env)
return
end
// else game
let input : Stdin tag = env.input
env.out.print("Welcome to ponylang-2048...")
let game = Game(env)
let term = ANSITerm(KeyboardHandler(game), input)
 
let notify : StdinNotify iso = object iso
let term: ANSITerm = term
let _in: Stdin tag = input
fun ref apply(data: Array[U8] iso) => term(consume data)
fun ref dispose() => _in.dispose()
end
 
input(consume notify)
 

Python[edit]

 
#!/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)
 

R[edit]

orginal R package : https://github.com/ThinkRstat/r2048

 
GD <- function(vec) {
c(vec[vec != 0], vec[vec == 0])
}
DG <- function(vec) {
c(vec[vec == 0], vec[vec != 0])
}
 
DG_ <- function(vec, v = TRUE) {
if (v)
print(vec)
rev(GD_(rev(vec), v = FALSE))
}
 
GD_ <- function(vec, v = TRUE) {
if (v) {
print(vec)
}
vec2 <- GD(vec)
# on cherche les 2 cote a cote
pos <- which(vec2 == c(vec2[-1], 9999))
# put pas y avoir consécutif dans pos
pos[-1][which(abs(pos - c(pos[-1], 999)) == 1)]
av <- which(c(0, c(pos[-1], 9) - pos) == 1)
if (length(av) > 0) {
pos <- pos[-av]
}
vec2[pos] <- vec2[pos] + vec2[pos + 1]
vec2[pos + 1] <- 0
GD(vec2)
 
}
 
H_ <- function(base) {
apply(base, MARGIN = 2, FUN = GD_, v = FALSE)
}
B_ <- function(base) {
apply(base, MARGIN = 2, FUN = DG_, v = FALSE)
}
G_ <- function(base) {
t(apply(base, MARGIN = 1, FUN = GD_, v = FALSE))
}
D_ <- function(base) {
t(apply(base, MARGIN = 1, FUN = DG_, v = FALSE))
}
 
H <- function(base) {
apply(base, MARGIN = 2, FUN = GD, v = FALSE)
}
B <- function(base) {
apply(base, MARGIN = 2, FUN = DG, v = FALSE)
}
G <- function(base) {
t(apply(base, MARGIN = 1, FUN = GD, v = FALSE))
}
D <- function(base) {
t(apply(base, MARGIN = 1, FUN = DG, v = FALSE))
}
 
add2or4 <- function(base, p = 0.9) {
lw <- which(base == 0)
if (length(lw) > 1) {
tirage <- sample(lw, 1)
} else {
tirage <- lw
}
base[tirage] <- sample(c(2, 4), 1, prob = c(p, 1 - p))
base
}
print.dqh <- function(base) {
cat("\n\n")
for (i in 1:nrow(base)) {
cat(paste(" ", base[i, ], " "))
cat("\n")
}
cat("\n")
}
 
 
 
# -*- coding: utf-8 -*-
#' @encoding UTF-8
#' @title run_2048
#' @description The 2048 game
#' @param nrow nomber of row
#' @param ncol numver of col
#' @param p probability to obtain a 2 (1-p) is the probability to obtain a 4
#' @examples
#' \dontrun{
#' run_2048()
#' }
#' @export
 
 
run_2048 <- function(nrow, ncol, p = 0.9) {
 
 
 
help <- function() {
cat(" *** KEY BINDING *** \n\n")
cat("press ECHAP to quit\n\n")
cat("choose moove E (up) ; D (down) ; S (left); F (right) \n")
cat("choose moove 8 (up) ; 2 (down) ; 4 (left); 6 (right) \n")
cat("choose moove I (up) ; K (down) ; J (left); L (right) \n\n\n")
 
}
 
 
if (missing(nrow) & missing(ncol)) {
nrow <- ncol <- 4
}
if (missing(nrow)) {
nrow <- ncol
}
if (missing(ncol)) {
ncol <- nrow
}
 
base <- matrix(0, nrow = nrow, ncol = ncol)
 
while (length(which(base == 2048)) == 0) {
base <- add2or4(base, p = p)
# print(base)
 
class(base) <- "dqh"
print(base)
flag <- sum((base == rbind(base[-1, ], 0)) + (base == rbind(0,
base[-nrow(base), ])) + (base == cbind(base[, -1], 0)) + (base ==
cbind(0, base[, -nrow(base)])))
if (flag == 0) {
 
break
}
 
y <- character(0)
while (length(y) == 0) {
cat("\n", "choose moove E (up) ; D (down) ; s (left); f (right) OR H for help",
"\n") # prompt
y <- scan(n = 1, what = "character")
}
 
 
baseSAVE <- base
base <- switch(EXPR = y, E = H_(base), D = B_(base), S = G_(base),
F = D_(base), e = H_(base), d = B_(base), s = G_(base), f = D_(base),
`8` = H_(base), `2` = B_(base), `4` = G_(base), `6` = D_(base),
H = help(), h = help(), i = H_(base), k = B_(base), j = G_(base),
l = D_(base), I = H_(base), K = B_(base), J = G_(base), L = D_(base))
if (is.null(base)) {
cat(" wrong KEY \n")
base <- baseSAVE
}
 
 
 
}
 
if (sum(base >= 2048) > 1) {
cat("YOU WIN ! \n")
} else {
cat("YOU LOOSE \n")
}
}
 
 


Ruby[edit]

inspired by the Perl6 version

 
#!/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 [email protected]"
puts "Congratulations, you have won!" if to_enum.any? {|e| e >= @win_limit}
end
 
def no_more_moves?; @no_more_moves; end
def won?; to_enum.any? {|e| e >= @win_limit}; end
def reset!; initialize @size, @win_limit, @cw; end
 
private
 
def set x, y, val
@board[y][x] = val
end
 
def spawn
free_pos = to_enum.select{|elem,x,y| elem == 0}.map{|_,x,y| [x,y]}
unless free_pos.empty?
set *free_pos.sample, rand > 0.1 ? 2 : 4 if @moved
else
snap = @board
unless @stop
@stop = true
%i{up down left right}.each{|s| move(s)}
@no_more_moves = true if snap.flatten == @board.flatten
@board = snap
@stop = false
end
end
end
 
def logic list
jump = false
result =
list.reduce([]) do |res, val|
if res.last == val && !jump
res[-1] += val
@score += val
jump = true
elsif val != 0
res.push val
jump = false
end
res
end
result += [0] * (@size - result.length)
@moved ||= list != result
result
end
 
def column_map
xboard = @board.transpose
xboard.map!{|c| yield c }
xboard.transpose
end
 
def row_map
@board.map {|r| yield r }
end
 
def to_enum
@enum ||= Enumerator.new(@size * @size) do |yielder|
(@size*@size).times do |i|
yielder.yield (@board[i / @size][i % @size]), (i % @size), (i / @size )
end
end
@enum.rewind
end
 
def format(num)
if $color
cstart = "\e[" + $colors[Math.log(num, 2)] + "m"
cend = "\e[0m"
else
cstart = cend = ""
end
cstart + num.to_s.center(@cw) + cend
end
end
 
$color = true
$colors = %W{0 1;97 1;93 1;92 1;96 1;91 1;95 1;94 1;30;47 1;43 1;42
1;46 1;41 1;45 1;44 1;33;43 1;33;42 1;33;41 1;33;44}
$rumble = false
 
$check_score = true
unless ARGV.empty?
puts "Usage: #$0 [gridsize] [score-threshold] [padwidth] [--no-color] [--rumble]"; exit if %W[-h --help].include?(ARGV[0])
args = ARGV.map(&:to_i).reject{|n| n == 0}
b = Board.new(*args) unless args.empty?
$rumble = true if ARGV.any?{|a| a =~ /rumble/i }
$color = false if ARGV.any?{|a| a =~ /no.?color/i}
end
 
b ||= Board.new
puts "\e[H\e[2J"
b.draw
puts "Press h for help, q to quit"
loop do
input = STDIN.getch
if input == "\e"
2.times {input << STDIN.getch}
end
 
case input
when "\e[A", "w" then b.move(:up)
when "\e[B", "s" then b.move(:down)
when "\e[C", "d" then b.move(:right)
when "\e[D", "a" then b.move(:left)
 
when "q","\u0003","\u0004" then b.print_score; exit
 
when "h"
puts <<-EOM.gsub(/^\s*/, '')
┌─ ─┐
│Use the arrow-keys or WASD on your keyboard to push board in the given direction.
│Tiles with the same number merge into one.
│Get a tile with a value of #{ARGV[1] || 2048} to win.
In case you cannot move or merge any tiles anymore, you loose.
│You can start this game with different settings by providing commandline argument:
For instance:
%> #$0 6 8192 --rumble
└─ ─┘
PRESS q TO QUIT (or Ctrl-C or Ctrl-D)
EOM
input = STDIN.getch
end
 
puts "\e[H\e[2J"
b.draw
 
if b.no_more_moves? or $check_score && b.won?
b.print_score
if b.no_more_moves?
puts "No more moves possible"
puts "Again? (y/n)"
exit if STDIN.gets.chomp.downcase == "n"
$check_score = true
b.reset!
puts "\e[H\e[2J"
b.draw
else
puts "Continue? (y/n)"
exit if STDIN.gets.chomp.downcase == "n"
$check_score = false
puts "\e[H\e[2J"
b.draw
end
end
end
 

Rust[edit]

Text mode[edit]

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

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

Tcl[edit]

Text mode[edit]

 
# A minimal implementation of the game 2048 in Tcl.
# For a maintained version with expanded functionality see
# https://tcl.wiki/40557.
package require Tcl 8.5
package require struct::matrix
package require struct::list
 
# Board size.
set size 4
 
# Iterate over all cells of the game board and run script for each.
#
# The game board is a 2D matrix of a fixed size that consists of elements
# called "cells" that each can contain a game tile (corresponds to numerical
# values of 2, 4, 8, ..., 2048) or nothing (zero).
#
# - cellList is a list of cell indexes (coordinates), which are
# themselves lists of two numbers each. They each represent the location
# of a given cell on the board.
# - varName1 are varName2 are names of the variables the will be assigned
# the index values.
# - cellVarName is the name of the variable that at each step of iteration
# will contain the numerical value of the present cell. Assigning to it will
# change the cell's value.
# - 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
}
}
 
# Generate a list of cell indexes for all cells on the board, i.e.,
# {{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
}
 
# 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}
}
 
# Return 1 if the predicate pred is true when applied to all items on the list
# 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
}
 
# Check if list represents valid cell coordinates.
proc valid-cell? cell {
map-and $cell valid-index
}
 
# Get the value of a game board cell.
proc cell-get cell {
board get cell {*}$cell
}
 
# Set the value of a game board cell.
proc cell-set {cell value} {
board set cell {*}$cell $value
}
 
# Filter a list of board cell indexes cellList to only have those indexes
# that correspond to empty board cells.
proc empty {cellList} {
 ::struct::list filterfor x $cellList {[cell-get $x] == 0}
}
 
# Pick a random item from the given list.
proc pick list {
lindex $list [expr {int(rand() * [llength $list])}]
}
 
# 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
}
 
# 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
}
 
# If checkOnly is false try to shift all cells one step in the direction of
# 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
}
 
# Is it possible to move any tiles in the direction of directionVect?
proc can-move? {directionVect} {
move-all $directionVect 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
}
}
}
 
# Check lose condition. The player loses when the win condition isn't met and
# 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
}
}
 
# 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
 

Tk[edit]

See https://tcl.wiki/39566.

XPL0[edit]

include c:\cxpl\codes;  \intrinsic 'code' declarations
int Box(16), Moved;
 
proc ShiftTiles(I0, DI); \Shift tiles, add adjacents, shift again
int I0, DI;
int Done, M, N, I;
[Done:= false;
loop [for M:= 1 to 3 do \shift all tiles in a single row or column
[I:= I0;
for N:= 1 to 3 do
[if Box(I)=0 & Box(I+DI)#0 then
[Box(I):= Box(I+DI); Box(I+DI):= 0; Moved:= true];
I:= I+DI;
];
];
if Done then return;
Done:= true;
I:= I0; \add identical adjacent tiles into a new tile
for N:= 1 to 3 do
[if Box(I)=Box(I+DI) & Box(I)#0 then
[Box(I):= Box(I)+1; Box(I+DI):= 0; Moved:= true];
I:= I+DI;
];
]; \loop back to close any gaps that were opened
]; \ShiftTiles
 
int I, J, X, Y, C;
[Clear;
for I:= 0 to 15 do Box(I):= 0; \empty the box of tiles
loop [repeat I:= Ran(16) until Box(I)=0; \in a random empty location
Box(I):= if Ran(10) then 1 else 2; \insert a 2^1=2 or 2^2=4
for I:= 0 to 15 do \show board with its tiles
[X:= ((I&3)+5)*6; \get coordinates of tile
Y:= I>>2*3+6;
Attrib(((Box(I)+1)&7)<<4 + $F);\set color based on tile value
for J:= 0 to 2 do \draw a square (6*8x3*16)
[Cursor(X, Y+J);
Text(6, " ");
];
if Box(I)#0 then \box contains a tile
[J:= 1; \center numbers somewhat
if Box(I) <= 9 then J:= 2;
if Box(I) <= 3 then J:= 3;
Cursor(X+J, Y+1);
IntOut(6, 1<<Box(I));
];
];
Moved:= false; \a tile must move to continue
repeat repeat C:= ChIn(1) until C#0; \get key scan code, or ASCII
for I:= 3 downto 0 do \for all rows or columns
[case C of
$4B: ShiftTiles(I*4, 1); \left arrow
$4D: ShiftTiles(I*4+3, -1); \right arrow
$50: ShiftTiles(I+12, -4); \down arrow
$48: ShiftTiles(I, 4); \up arrow
$1B: [Clear; exit] \Esc
other []; \ignore all other keys
];
until Moved;
];
]