# Create a simple 2048 game clone

2048 is an incredibly fun and addictive game in which the goal is to create a tile with 2048 on it.

Here's a short description of the game:

Pressing an arrow key will slide all the blocks in the stage in that direction. For example, if x represents a block, and you pressed the up arrow in this case:

...x
.x..
..x.
xx..


Then the board would become

xxxx
.x..
....
....


Furthermore, the blocks are numbered, starting at 2. If two of the same numbered blocks are moved together, they will merge into the next number. For example, pressing "up" on this board:

.2..
..22
.2..
....


Would create this:

.422
....
....
....


And then after pressing "right," it would become ..44, and therefore pressing right again would create an "8" block, and so on.

Each turn, a new "2" block is created on a random open square. (It's actually not always a "2," but for simplicity let's keep it at that.) If there are no possible moves left (i.e., the board is full and you can't merge anything), the game is lost, and if a 2048 block is created, you win!

Your challenge is to recreate this game, golfed!

• For edge-cases, such as pressing "right" on this board:

....
.222
....
....


you must merge the tiles closest to the edge of the arrow key pressed. For example, this would become ..24, because the second and third "2"s are the closest to the right edge.

• If the player presses an invalid move (such as "up" on a board like 2.2. / .... / .... / ....), you must ignore the move.

• Moves can be accepted in any way, such as clicking, arrow keys, ULRD, etc.

• The tiles must all be the same shape—a "1024" tile should be the same size as a "2" tile.

• The tiles must be separated in some way. For example, |1024| 2|1024|1024| is a valid example of a row (assuming that the tiles are squares), while 1024 210241024 is not.

• You must output "you win" if the player creates a 2048 tile, or "you lose" if there are no valid moves left.

• This is , so the shortest code in bytes will win!

• Instead of accepting either rows of single characters (first five examples) and/or separated tiles (last one), why not assign ascending powers of two to individual characters to normalize the output? Commented Mar 14, 2014 at 3:41
• @millinon Because it's supposed to be as similar as possible to the actual 2048 game (it would make the title meaningless), and it would remove some of the challenge. Commented Mar 14, 2014 at 3:50
• "If there are no possible merges left, the game is lost" Shouldn't there be an "and the board is full" in there somewhere? Commented Mar 14, 2014 at 8:25
• Funny how all your questons get a ton of votes, but not many answers. Commented Mar 14, 2014 at 13:02
• Now its your fault I´ve been playing 2048 the whole weekend and today the productivity of our office has dropped to 0.
– asr
Commented Mar 17, 2014 at 10:09

### GolfScript, 323 characters

1,16*{16,{1$=!},.,rand=)/()2++\[]*+4/{.['-----']4*'+'*'++'1/*n+:^\{'|'' ':X'|'+4*n++:Y.@{X\+-5>'|'\+}%'|'+n+Y+Y+^+}%+puts}:P~"wasd":Q{1$.@{Q\?[{zip}{}{-1%zip-1%}{{-1%}%}]=\1$~{0-[{.2$={+)}*}*0...]4<{1|(}%}%\~}:^~=!},:F{0{;'"#{'9.?rand';STDIN.getc()}"'++~.F\?0<}do^[]*.2048?0<.!{'you win'@@}*}{'you lose'@@*0}if}do 4/P;


The keys you have to use are wasd but you can change the constant in the code to whatever keys you like. It gracefully ignores any unknown key or illegal move. Unfortunately you have to press enter after each key.

### 256 character solution

1,16*{16,{1$=!},.,rand=)/())+\[]*+4/{.{{.9>7*+48+}%n+}%puts}:P~"wasd":Q{1$.@{Q\?[{zip}{}{-1%zip-1%}{{-1%}%}]=\1$~{0-[{.2$={;21+}*}*0...]4<{20%}%}%\~}:^~=!},:F{0{;'"#{'9.?rand';STDIN.getc()}"'++~.F\?0<}do^[]*.10?0<.!{'you win'@@}*}{'you lose'@@*0}if}do 4/P;


Here the output is hexadecimal logarithmic, i.e. 0 stands for ., 1 for 2, 2 for 4, 3 for 8 ... B for 2048. Since we all know hexadecimals and the powers of two this shouldn't be a limitation at all.

Example run (first moves):

0000
0000
0010
0000
w
0011
0000
0000
0000
a
2000
0000
0001
0000
s
0000
0000
0100
2001
d
0000
0000
1001
0021
w
1022
0000
0001
0000
a
1301
0000
1000
0000
s
0000
1000
0000
2301
d
0000
1001
0000
0231

• Tiles should be seperated though, but nice job. Commented Mar 14, 2014 at 15:55
• @TeunPronk I'm working on it. Unfortunately it breaks the nice power of two score. Commented Mar 14, 2014 at 15:57
• +unlimited for being exactly 256 chars Commented Mar 14, 2014 at 16:25
• yay...2^7 characters!! Commented Mar 14, 2014 at 16:36
• Nice! Didn't know how to do input in golfscript before. Personally I like the 256 char solution more, easier to read. And I would say the tiles are separated because each tile is only 1 character long. there is never any ambiguity. Commented Mar 14, 2014 at 19:14

# J - 240230 216 char

This counts the trailing newline, because you need it to start running the game at the console. Play by typing one of udlr and then hitting Enter. Moves that do nothing to the board are handled properly, but the program will get mad at you if you give it any bad input.

'you ',>losewin{~$:@".@('o b=:2 t}^:(1-b-:])s ',' b',~1!:1@1:)w@.((w=:2048 e.,)+(s d-:s u=:l d=:r(&.|:))*s r-:(s=:[:p(2(=/(0,+/^:)@{.,}.),)/@p=:\:=&0)l=:&.|.r=:"1)(o=:[2:1!:2~4<@":"0])b=:2(t=:(?&#{])[:I.0=,)}4 4$0


The readable version and explanation are a little too large to fit comfortably into the post: you can find a writeup at this link.

Usage:

   'you ',>losewin{~$:@".@('o b=:2 t}^:(1-b-:])s ',' b',~1!:1@1:)w@.((w=:2048 e.,)+(s d-:s u=:l d=:r(&.|:))*s r-:(s=:[:p(2(=/(0,+/^:)@{.,}.),)/@p=:\:=&0)l=:&.|.r=:"1)(o=:[2:1!:2~4<@":"0])b=:2(t=:(?&#{])[:I.0=,)}4 4$0
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   2|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
r
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   2|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   2|   0|   0|
+----+----+----+----+
u
+----+----+----+----+
|   0|   2|   0|   2|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   2|
+----+----+----+----+
d
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   2|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   2|   0|   4|
+----+----+----+----+
l
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   2|   0|   0|   0|
+----+----+----+----+
|   0|   0|   2|   0|
+----+----+----+----+
|   2|   4|   0|   0|
+----+----+----+----+
d
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   2|   0|   0|
+----+----+----+----+
|   4|   4|   2|   0|
+----+----+----+----+
r
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   2|
+----+----+----+----+
|   0|   0|   0|   2|
+----+----+----+----+
|   0|   0|   8|   2|
+----+----+----+----+
d
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   0|   0|   0|
+----+----+----+----+
|   0|   2|   0|   2|
+----+----+----+----+
|   0|   0|   8|   4|
+----+----+----+----+

• Great. Unfortunately the squares size differs when the contents change and that was one requirement stated in the question. Commented Mar 14, 2014 at 18:52
• @Howard I'm of the persuasion that making the tiles rigidly squarelike is a lot uglier than letting them be, but there, it's fixed. Commented Mar 14, 2014 at 19:16

# APL (Dyalog APL), 153150167156155 151 bytes

'you ',{2048∊⍵:'win'
(z/∊n)[?+/z←0=∊n←⍵]←2
⎕←4↑¨⍕¨n
1∊(⍉2=⌿n),(2=/n),0=n:∇{⍵≡r←R⍣¯1⎕UCS{4↑⍺⍺t⎕R(1⌽∪¨t←2/¨⍺⍺2*⍳16)⍺⍺⍵~0}¨(R←↓⍉∘⌽⍣⎕)⍵:∇⍵⋄r}n
'lose'}4 4⍴0


Prompts for move; 0=Left, 1=Up 2=Right, 3=Down

Try it online!

### Ungolfed, with whitespace

C ← ⎕UCS
'you ', {
2048 ∊ ⍵: 'win'
n ← ⍵
z ← 0 = ∊n
(z / ∊n)[? +/z] ← 2
⎕ ← 4 ↑¨ ⍕¨n
1 ∊ (⍉ 2 =⌿ n) , (2 =/ n) , 0 = n: ∇ {
R←↓⍉∘⌽⍣⎕
r ← R⍣¯1 ⎕UCS {
t ← 2 /¨ ⍺⍺ 2 * ⍳16
4 ↑ ⍺⍺ t ⎕R (1 ⌽ ∪¨t) ⍺⍺ ⍵ ~ 0
}¨ R ⍵
⍵ ≡ r: ∇ ⍵
r
} n
'lose'
} 4 4⍴0


### Explanation

Blank lines indicate a newline, while blocks of lines belong on a single line

'you ', "you" prepended to the result of the function {...

2048 ∊ ⍵: 'win' if the argument (the board) contains 2048, then return "win"

n ← ⍵ assign the right argument to n (so it may be modified in-place)

z ← 0 = ∊nz gets the Boolean list where flattened z is 0

(z / ∊n)z filters the flattened n (to get only the zeros)
[? +/z] use a random number between 1 and the count of zeros (the sum of the Boolean z) to select which zero-containing position
← 2 gets a 2

⎕ ← output the
4 ↑¨ each extended-to-length-4 (with spaces)
⍕¨n string representation of each number

1 ∊ if any of the following are found;
(⍉2=⌿n) pairwise vertical equalities (transposed to be concatenable)
, or (concatenated to)
(2=/n) pairwise horizontal equalities
, or (concatenated to)
0=n zeros
: then:
∇ call this function on the result of the function {...

R ←R (for Rows of Rotated) is
↓ the split-into-rows
⍉∘⌽⍣⎕ prompt for numeric input, then rotate (literally mirror horizontally, then transpose) the argument (the board) –90° times the inputted number

r ←r (for result) gets
R⍣¯1 the inverse of R (rotate back, then merge rows into matrix) applied to the result of the function ⎕UCS{... (where ⍺⍺ means convert to/from UCS points)

t ←t gets
2 /¨ two of each of
⍺⍺ the characters corresponding to the code points
2 * ⍳16 2¹, 2², 2³,... 2¹⁶

4↑ pad with zeros on the right until length 4
C convert the following UCS characters to code points
t ⎕R (1 ⌽ ∪¨t) regex replace each pair t with the unique characters from the next t on the right
C ⍵~0 the argument (board) without zeros (compacted to the left), then converted to UCS code points

}¨ [end of function] applied to each of
R ⍵R applied to the argument (the board)

⍵ ≡ r: if the argument (the old board state) is identical to r (new board state), then:
∇⍵ call this function on the unmodified argument (because the user made an invalid move), else

r return r (the new board state)

} n [end of function] applied to n (the new board state), else

'lose' return "lose"

} 4 4 ⍴ 0 [end of function] applied to a 4-by-4 table of zeros (the initial state)

# Mathematica, 484 chars

Very ugly.

m@l=#//.{{x___,0,a_/;a>0,y___}:>{x,a,0,y},{x___,a_/;a>0,a_,y___}:>{x,2 h@a,0,y}}/.h@a_:>a&;{m@u,m@d,m@r}=Composition[#,m@#2,#]&@@@{{Thread,l},{Reverse,u},{Thread,d}};a=ReplacePart[#,RandomChoice@Position[#,0]->2]&;g=a@ConstantArray[0,{4,4}];EventHandler[Dynamic[GraphicsGrid@Map[Graphics@Text@#&,g,{2}]],(#2<>"ArrowKeyDown":>If[g!=m[#]@g,g=a[m[#]@g];Which[And@@(g==m[#]@g&/@{u,l,r,d}),Print@"you lose",!FreeQ[g,2048],Print@"you win"]])&@@@{{l,"Left"},{r,"Right"},{u,"Up"},{d,"Down"}}]


Controlled by arrow keys.

Ungolfed:

move[Left] = # //. {{x___, 1, a_ /; a > 1, y___} :> {x, a, 1, y},
{x___, a_ /; a > 1, a_, y___} :> {x, 2 Hold@a, 1, y}} /.
Hold@a_ :> a &;
move[Up] = Composition[Transpose, move[Left], Transpose];
move[Down] = Composition[Reverse, move[Up], Reverse];
move[Right] = Composition[Transpose, move[Down], Transpose];
addTile = ReplacePart[#, RandomChoice@Position[#, 1] -> 2] &;
keyDown = If[grid != move[#][grid], grid = addTile[move[#][grid]];
Which[And @@ (grid == move[#][grid] & /@ {Left, Right, Up, Down}),
status = "Can't move...",
! FreeQ[grid, 2048], status = "2048!"]] &;
status = "";
EventHandler[Dynamic[ArrayPlot[Log2@grid/11,
ColorFunction -> "StarryNightColors",
ColorFunctionScaling -> False,
Mesh -> All,
Epilog -> {MapIndexed[
Text[Style[#1, "Section"] //. 1 -> "", #2 - {0.5, 0.5}] &,
Transpose@Reverse@grid, {2}],
Text[Style[status, "Section"], {2, 2}]}]],
{"LeftArrowKeyDown" :> keyDown[Left],
"RightArrowKeyDown" :> keyDown[Right],
"UpArrowKeyDown" :> keyDown[Up],
"DownArrowKeyDown" :> keyDown[Down]}]


• I like the looks of the ungolfed one! Commented Mar 22, 2014 at 6:31

# Delphi XE3 (Waaay to many {whispers} 2.979 bytes -> ungolfed 4.560 bytes)

I like to call this "2048 code the novel"
Used more bytes than I like but it works and it was fun to do.

Im still going to try make it shorter later on.

### Golfed

uses System.SysUtils,Windows;type TDir=(dUp,dDown,dLeft,dRight,dInv);const t='_____________________________';er='|      |      |      |      |';nr='| %s | %s | %s | %s |';br='|______|______|______|______|';fn='%d';procedure mycls;var S:String;H:DWORD;CO:_COORD;begin H:=GetStdHandle(STD_OUTPUT_HANDLE);CO.X:=0;CO.Y:=0;SetConsoleCursorPosition(H,CO);S:=StringOfChar(Chr(32),2000);Writeln(S);SetConsoleCursorPosition(H,CO);end;var a:array[1..4,1..4]of integer;c,rx,ry,i,j:int8;m:string;GameOver,gs:boolean;function hz:boolean;var b,q:int8;begin for b:=1to 4do for q:=1to 4do if a[b,q]=0 then exit(true);end;function HM:boolean;var b,q:int8;begin if hz then exit(true);for b:=1to 4do for q:=1to 4do begin c:=a[b,q];if c in [a[b-1,q],a[b+1,q],a[b,q-1],a[b,q+1]] then result:=true;end;end;procedure rn(out n,m:int8);var z:int8;begin z:=0;repeat n:=Random(4)+1;m:=Random(4)+1;z:=z+1;until(a[n,m]=0)and(z>=3);end;function gn(n:integer):string;begin if n=0 then exit('    ');Result:=IntToStr(n).PadLeft(4,' ');end;procedure pm(d:TDir;score:boolean);var b,q,z:int8;begin case d of dUp:for z:=1to 3do for b:=1to 4do for q:=1to 3do begin if score then begin if a[q,b]=a[q+1,b] then begin a[q,b]:=a[q,b]+a[q+1,b];a[q+1,b]:=0;end;end else if a[q,b]=0 then begin a[q,b]:=a[q+1,b];a[q+1,b]:=0;end;end;dDown:for z:=1to 3do for b:=1to 4do for q:=2to 4do begin if score then begin if a[q,b]=a[q-1,b] then begin a[q,b]:=a[q,b]+a[q-1,b];a[q-1,b]:=0;end;end else if a[q,b]=0 then begin a[q-1,b]:=a[q,b];a[q-1,b]:=0;end;end;dLeft:for z:=1to 3do for q:=1to 4do for b:=1to 3do begin if score then begin if a[q,b]=a[q,b+1] then a[q,b]:=a[q,b]+a[q,b+1];a[q,b+1]:=0;end else if a[q,b]=0 then begin a[q,b]:=a[q,b+1];a[q,b+1]:=0;end;end;dRight:for z:=1to 3do for q:=1to 4do for b:=2to 4do begin if score then begin if a[q,b]=a[q,b-1] then begin a[q,b]:=a[q,b]+a[q,b-1];a[q,b-1]:=0;end;end else if a[q,b]=0 then begin a[q,b]:=a[q,b-1];a[q,b-1]:=0;end;end;end;end;function gd(s:string):TDir;begin s:=lowercase(s);if s='u'then exit(dUp);if s='d'then exit(dDown);if s='l'then exit(dLeft);if s='r'then exit(dRight);exit(dInv)end;procedure dg;var z:int8;begin writeln(t);for z:=1to 4do begin writeln(er);Writeln(Format(nr,[gn(a[z,1]),gn(a[z,2]),gn(a[z,3]),gn(a[z,4])]));Writeln(br);end;end;function hw:boolean;var b,q:int8; begin for b:=1to 4do for q:=1to 4do if a[b,q]=2048 then result:=true;end;function dm:boolean;var d:Tdir;begin d:=gd(m);if d=dInv then if not gs then exit(false)else exit(true);pm(d,false);pm(d,true);pm(d,false);exit(true);end;begin gs:=true;m:='';for j:=1to 4do for i:=1to 4do begin a[i,j]:=0;end;rx:=0;ry:=0;rn(rx,ry);a[rx,ry]:=2;repeat if (dm) then begin if hz then begin rn(rx,ry);a[rx,ry]:=2;end;gs:=false;end;mycls;GameOver:=true;if hw then WriteLn('You have won!')else if HM then begin GameOver:=false;dg;writeln('Direction: [U]=up, [D]=Down, [L]=Left, [R]=Right');readln(m);end else WriteLn('Game Over, no more possible moves :('#13#10'Try again next time')until GameOver;readln;end.


### Ungolfed

uses
System.SysUtils,Windows;
type
TDir=(dUp,dDown,dLeft,dRight,dInv);
const
t='_____________________________';
er='|      |      |      |      |';
nr='| %s | %s | %s | %s |';
br='|______|______|______|______|';
fn='%d';
procedure mycls;
var
S:String;
H:DWORD;
CO:_COORD;
begin
H:=GetStdHandle(STD_OUTPUT_HANDLE);
CO.X:=0;
CO.Y:=0;
SetConsoleCursorPosition(H,CO);
S:=StringOfChar(Chr(32),2000);
Writeln(S);
SetConsoleCursorPosition(H,CO);
end;
var
a:array[1..4,1..4]of integer;
c,rx,ry,i,j:int8;
m:string;
GameOver,gs:boolean;
function hz:boolean;
var b,q:int8;
begin
for b:=1to 4do
for q:=1to 4do
if a[b,q]=0 then exit(true);
end;
function HM:boolean;
var b,q:int8;
begin
if hz then exit(true);
for b:=1to 4do
for q:=1to 4do
begin
c:=a[b,q];
if c in [a[b-1,q],a[b+1,q],a[b,q-1],a[b,q+1]] then
result:=true;
end;
end;
procedure rn(out n,m:int8);
var z:int8;
begin
z:=0;
repeat
n:=Random(4)+1;
m:=Random(4)+1;
z:=z+1;
until(a[n,m]=0)and(z>=3);
end;
function gn(n:integer):string;
begin
if n=0 then exit('    ');
end;
procedure pm(d:TDir;score:boolean);
var
b,q,z:int8;
begin
case d of
dUp:
for z:=1to 3do
for b:=1to 4do
for q:=1to 3do
begin
if score then
begin
if a[q,b]=a[q+1,b] then
begin
a[q,b]:=a[q,b]+a[q+1,b];a[q+1,b]:=0;
end;
end
else
if a[q,b]=0 then
begin
a[q,b]:=a[q+1,b];a[q+1,b]:=0;
end;
end;
dDown:
for z:=1to 3do
for b:=1to 4do
for q:=2to 4do
begin
if score then
begin
if a[q,b]=a[q-1,b] then
begin
a[q,b]:=a[q,b]+a[q-1,b];a[q-1,b]:=0;
end;
end
else
if a[q,b]=0 then
begin
a[q-1,b]:=a[q,b];
a[q-1,b]:=0;
end;
end;
dLeft:
for z:=1to 3do
for q:=1to 4do
for b:=1to 3do
begin
if score then
begin
if a[q,b]=a[q,b+1] then
a[q,b]:=a[q,b]+a[q,b+1];a[q,b+1]:=0;
end
else
if a[q,b]=0 then
begin
a[q,b]:=a[q,b+1];a[q,b+1]:=0;
end;
end;
dRight:
for z:=1to 3do
for q:=1to 4do
for b:=2to 4do
begin
if score then
begin
if a[q,b]=a[q,b-1] then
begin
a[q,b]:=a[q,b]+a[q,b-1];a[q,b-1]:=0;
end;
end
else
if a[q,b]=0 then
begin
a[q,b]:=a[q,b-1];a[q,b-1]:=0;
end;
end;
end;
end;

function gd(s:string):TDir;
begin
s:=lowercase(s);
if s='u'then exit(dUp);
if s='d'then exit(dDown);
if s='l'then exit(dLeft);
if s='r'then exit(dRight);
exit(dInv)
end;
procedure dg;
var z:int8;
begin
writeln(t);
for z:=1to 4do
begin
writeln(er);
Writeln(Format(nr,[gn(a[z,1]),gn(a[z,2]),gn(a[z,3]),gn(a[z,4])]));
Writeln(br);
end;
end;
function hw:boolean;
var b,q:int8;
begin
for b:=1to 4do
for q:=1to 4do
if a[b,q]=2048 then
result:=true;
end;
function dm:boolean;
var
d:Tdir;
begin
d:=gd(m);
if d=dInv then if not gs then exit(false)else exit(true);
pm(d,false);
pm(d,true);
pm(d,false);
exit(true);
end;
begin
gs:=true;m:='';
for j:=1to 4do
for i:=1to 4do
begin
a[i,j]:=0;
end;
rx:=0;ry:=0;
rn(rx,ry);
a[rx,ry]:=2;
repeat
if (dm) then
begin
if hz then
begin
rn(rx,ry);
a[rx,ry]:=2;
end;
gs:=false;
end;
mycls;
GameOver:=true;
if hw then
WriteLn('You have won!')
else if HM then
begin
GameOver:=false;
dg;
writeln('Direction: [U]=up, [D]=Down, [L]=Left, [R]=Right');
end
else
WriteLn('Game Over, no more possible moves :('#13#10'Try again next time')
until GameOver;
end.

• 2.979 bytes? Is it supposed to be kilobytes?
– user10766
Commented Mar 14, 2014 at 16:54
• Some places in the world use commas in numbers the way we use periods, I would not be all that surprised if the reverse was also true. Commented Mar 15, 2014 at 4:55
• @undergroundmonorail True, in Europe the number one thousand and twelve comma six is often written as 1.012,6 instead of the American 1,012.6 :) Commented Mar 22, 2014 at 6:29
• CodeGolf taught me that you can interchange , and . in numbers! Commented Apr 8, 2014 at 9:52
• Good thing this question doesn't have a leaderboard... Commented Aug 5, 2016 at 1:13

## C (C89 standard), 881 bytes

Use wasd keys to move. Compiles in GCC and clang by default, unless set to C99 standard (I guess). Uses termios.h, worked in Linux and MacOS X. Not sure about Windows.

#include<termios.h>
#define R return
#define H while
char t[17],*Q,*W="adws",D,x,y,X;m(x,y){R!D?x+y*4:D==1?3-x+y*4:D==2?y+x*4:y+(3-x)*4;}c(){for(y=0;y<3;++y)for(x=0;x<3;++x){D=t[x+y*4];if(t[x+1+y*4]==D||t[x+4+y*4]==D)x=y=9;}R y>4;}d(){if(strlen(t)==16)R 0;H(t[x=(rand()&15)]);R t[x]=1;}r(x){putchar(x);}b(){y=0;r(10);H(y<21)r(y++%5?45:43);r(10);}f(){for(x=0;x<17;++x)if(X=(t[x]==11))x=32;R x<18;}struct termios z;main(){srand(time(tcgetattr(0,&z)));z.c_lflag&=~ICANON;tcsetattr(0,0,&z);H(f()&&(d()||c())){x=0;H(x<16){if(!(x&3)){b();r('|');}if(y=t[x++])printf("%4u|",1<<y);else printf("    |");}b();r(10);H(!(Q=strchr(W,getchar())));D=Q-W;for(y=0;y<4;++y)for(X=0,x=1;x<4;++x)if(t[m(x,y)]){if(t[m(x,y)]==t[m(X,y)]&&t[m(X,y)]++)t[m(x,y)]=0;X=x;}do{for(y=0;y<4;++y)for(x=0;x<3;++x)if(!t[m(x,y)]&&(X=t[m(x+1,y)])){t[m(x,y)]=X;t[m(x+1,y)]=0;x=y=9;}}H(y>4);}puts(X?"you win":"you lose");}


Broken in some lines:

#include<termios.h>
#define R return
#define H while
c(){for(y=0;y<3;++y)for(x=0;x<3;++x){D=t[x+y*4];if(t[x+1+y*4]==D||t[x+4+y*4]==D)x=y=9;}R y>4;}
d(){if(strlen(t)==16)R 0;H(t[x=(rand()&15)]);R t[x]=1;}
r(x){putchar(x);}
b(){y=0;r(10);H(y<21)r(y++%5?45:43);r(10);}
f(){for(x=0;x<17;++x)if(X=(t[x]==11))x=32;R x<18;}
struct termios z;
main(){srand(time(tcgetattr(0,&z)));z.c_lflag&=~ICANON;tcsetattr(0,0,&z);
H(f()&&(d()||c())){x=0;H(x<16){if(!(x&3)){b();r('|');}if(y=t[x++])printf("%4u|",1<<y);else printf("    |");}
b();r(10);H(!(Q=strchr(W,getchar())));D=Q-W;for(y=0;y<4;++y)for(X=0,x=1;x<4;++x)
if(t[m(x,y)]){if(t[m(x,y)]==t[m(X,y)]&&t[m(X,y)]++)t[m(x,y)]=0;X=x;}
do{for(y=0;y<4;++y)for(x=0;x<3;++x)if(!t[m(x,y)]&&(X=t[m(x+1,y)]))
{t[m(x,y)]=X;t[m(x+1,y)]=0;x=y=9;}}H(y>4);}puts(X?"you win":"you lose");}


The looks:

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


It can be improved for sure.

# Java: 1346 1269

Edit So although this contest is done, sometimes I can't let go when improvements can be made. This version boasts a leaner, meaner, in-place folding function, the eviction of most boolean types except where it would be more verbose without them, and slightly smaller cells (4x4 instead of 5x5) to cut out a few extra characters there.

So this was a fun one. Thanks for posting! Ungolfed, this was 2856 bytes, golfed I shrunk it to 1346 bytes. Being Java, I'm not going to win, but I wanted to put in a good showing. Figuring out a "general" roll-up function to handle tile joins and moves was fun -- you can of course check out my "progress" by looking through the commits on my github repository for this solution.

Golfed:

import java.util.*;class T{public static void main(String[]a){(new T()).s();}int[][]b=new int[4][4];int d,p,i,j,x,y,v,q,r;boolean I,V;void s(){p();do{d();do{char a=(new Scanner(System.in)).nextLine().charAt(0);y=a=='u'?f(0,1):a=='d'?f(1,1):a=='l'?f(0,0):a=='r'?f(1,0):0;}while(y<1);p();}while((x=n())>0);d();c("you "+(x<0?"win":"lose"));}int h(){for(int[]y:b)for(int x:y)if(x<2)return 1;return 0;}int n(){for(y=0;y<4;y++){for(x=0;x<4;x++){i=b[y][x];if(x<3&&i==b[y][x+1]||y<3&&i==b[y+1][x])return 1;if(i>2047)return -1;}}return h();}int f(int w,int z){I=w>0;V=z>0;for(i=d=0;i<4;i++){p=I?3:0;for(j=1;j<4;){v=V?i:j;x=I?3-v:v;v=V?j:i;y=I?3-v:v;q=V?x:p;r=V?p:y;if(b[y][x]==0||p==(V?y:x))j++;else if(b[r][q]==0){d+=b[r][q]=b[y][x];b[y][x]=0;j++;}else if(b[r][q]==b[y][x]){d+=b[r][q]*=2;b[y][x]=0;p+=I?-1:1;j++;}else p+=I?-1:1;}}return d;}int v(){return(new Random()).nextInt(4);}void p(){if(h()<1)return;do{x=v();y=v();}while(b[x][y]>0);b[x][y]=2;}void c(String a){System.out.println(a);}String l(char n,char m){String s=""+n;for(i=0;i<4;i++){for(j=0;j<4;j++)s+=m;s+=n;}return s;}void d(){c(l('+','-'));String p[]=new String[5];for(int[]y:b){p[0]=p[1]=p[3]=l('|',' ');p[2]="";for(x=0;x<4;)p[2]+=String.format("|%4d",y[x++]);p[2]+="|";p[4]=l('+','-');for(String q:p)c(q);}}}


Ungolfed: (Check my github repository for this project for an updated version, including an assertions-based test for new folding functions)

import java.util.*;
class Twe {
public static void main(String[] a){
(new Twe()).start();
}
int[][] board=new int[4][4];
void start(){
int x;
placeTwo();
do{
drawBoard();
resolve();
placeTwo();
}while((x=notDone())>0);
drawBoard();
wrapup(x);
}
int hasFree(){
for(int[]y:board)
for(int x:y)
if(x<2)return 1;
return 0;
}
int notDone(){
int moves,x,y;
for(moves=y=0;y<4;y++){
for(x=0;x<4;x++){
else if(x<3&&board[y][x]==board[y][x+1]||
y<3&&board[y][x]==board[y+1][x])moves++;
if(board[y][x]>2047)return -1;
}
}
return hasFree()+moves;
}
void wrapup(int k){
if(k<0){
chalk("you win",true);
}else{
chalk("you lose",true);
}
}
void resolve(){
do{
switch((new Scanner(System.in)).nextLine().charAt(0)){
case 'u':
if (fold(false,true)>0)return;
break;
case 'd':
if (fold(true, true)>0)return;
break;
case 'l':
if (fold(false,false)>0)return;
break;
case 'r':
if (fold(true,false)>0)return;
break;
case 'z':
board[0][0]=2048; // instant win;
return;
}
} while(true);
}
// false,true  = up
// true, true  = down
// false,false = left
// true, false = right
int fold(boolean inv, boolean vert){
int didMove=0;
int nextSpot,x,y,v,q,r;
int[][] nb = new int[4][4];
for(int i=0;i<4;i++){
nextSpot=inv?3:0;
for(int j=0;j<4;j++){
v=vert?i:j;
x=inv?3-v:v;
v=vert?j:i;
y=inv?3-v:v;
q=vert?x:nextSpot;
r=vert?nextSpot:y;
if(board[y][x]>0){
if(nb[r][q]<1){
nb[r][q]=board[y][x];
didMove+=(inv?-1:1)*(vert?y-r:x-q);
}else if(nb[r][q]==board[y][x]){
nb[r][q]*=2;
nextSpot+=inv?-1:1;
didMove++;
}else{
nextSpot+=inv?-1:1;//suckage
q=vert?x:nextSpot;
r=vert?nextSpot:y;
nb[r][q]=board[y][x];
didMove+=(inv?-1:1)*(vert?y-r:x-q);
}
}
}
}
board=nb;
return didMove;
}
int vec(){
return (new Random()).nextInt(4);
}
void placeTwo(){
if (hasFree()<1) return;
int x,y;
do{
x=vec();y=vec();
}while(board[x][y]>0);
board[x][y]=2;
}
void chalk(String a, boolean nl){
System.out.print(a+(nl?"\n":""));
}
String fill(char node, char mid){
String str = ""+node;
for(int i=0;i<4;i++){
for(int j=0;j<5;j++)
str+=mid;
str+=node;
}
return str;
}
void drawBoard(){
chalk(fill('+','-'),true);
String p[] = new String[6];
for(int[]y:board){
p[0]=p[1]=p[3]=p[4]=fill('|',' ');
p[2]="";
for(int x=0;x<4;){
}
p[2]+="|";
p[5]=fill('+','-');
for (String q:p){
chalk(q,true);
}
}
}
return String.format("|%5d",a);
}
}


Using the program is simple. To build and run:

javac T.java
java T


Press u to fold up, r to fold right, d to fold down, l to fold left. Any other key is ignored, invalid (no result) moves are ignored. Being Java, press enter after each key to flush the line buffer. As required by the rules, if you win the program outputs you win, if you lose the program outputs you lose. New 2's are placed randomly in the open tiles. Merges follow the stated rules. Cells are 4x4 characters, with a border surrounding each cell. Of course, leave me a comment if I've messed something up and I'll be certain to fix it.

Example output:

\$ java T
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
u
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   2|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
l
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
u
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   4|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   2|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
l
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   4|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   4|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   2|
|    |    |    |    |
+----+----+----+----+
u
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   8|   2|   0|   2|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
l
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   8|   4|   2|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
l
u
d
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   2|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   8|   4|   2|   0|
|    |    |    |    |
+----+----+----+----+
d
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   2|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   8|   4|   4|   0|
|    |    |    |    |
+----+----+----+----+
l
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   2|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   8|   8|   0|   0|
|    |    |    |    |
+----+----+----+----+
l
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   2|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   2|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|  16|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
d
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   2|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   4|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|  16|   2|   0|   0|
|    |    |    |    |
+----+----+----+----+
d
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   0|   0|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|   4|   2|   0|   0|
|    |    |    |    |
+----+----+----+----+
|    |    |    |    |
|    |    |    |    |
|  16|   4|   0|   0|
|    |    |    |    |
+----+----+----+----+


### Lua, 622616615612592590 575 bytes

Queries stdin for 1,2,3,4 correlating with left, down, right, up (made for numpad input); crashes if you give it bad input.

Blank squares are printed as '0's.

b={('\0'):rep(16):byte(1,16)}c=1 a=print h=unpack while{}do if c then c=z
e={}for i=1,16 do e[#e+1]=0==b[i]and i or z end _=#e==0 and
a'you lose'b[e[math.random(#e)]]=2 l=('+----'):rep(4)..'+\n'm=l:gsub('-',' ')for
i=1,13,4 do a(l..m..m..m..('+%4d+%4d+%4d+%4d+'):format(h(b,i)))end a(l)end c=z
u,v,w=h({4,1,-4,1,-4,16,4,-1,1,1,4,-4},3*io.read()-2)for i=1,4 do o=-1 for j=2,4
do if 0~=b[u*i+v*j+w]then for k=1-j,o do p=u*i-v*k+w q=p+v r=b[p]if r==0 then
b[p]=b[q]b[q]=r c=1 o=k elseif r==b[q]then _=r==1024 and a'you win'b[p]=r*2
b[q]=0 c=1 o=k-1 break end end end end end end


Example output:

+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   2+   0+   0+   0+
+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   0+   0+   0+   0+
+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   0+   0+   0+   0+
+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   0+   0+   0+   0+
+----+----+----+----+

2
+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   0+   2+   0+   0+
+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   0+   0+   0+   0+
+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   0+   0+   0+   0+
+----+----+----+----+
+    +    +    +    +
+    +    +    +    +
+    +    +    +    +
+   2+   0+   0+   0+
+----+----+----+----+


# Clojure: 599

run it in a REPL

golfed

(defn i[b](let[f(into[](flatten b))z(seq(keep-indexed #(when(zero? %2)%1)f))]
(cond(some #{2048}f)(pr "you win")(empty? z)(pr "game over"):e(partition 4
(assoc f(rand-nth z)2)))))(def r #(remove zero? %))(loop[b(i (partition 4
(repeat 16 0)))](when((fn[v](doseq[l(for[a v](apply str(map #(format "%5d " %)a)))]
(println l "\n"))v)b)(-> b((apply comp(assoc(into[](repeat 5(fn[v]
(map(fn[n](map #(nth % n)v))[3 2 1 0]))))({"h"4 "k"3 "l"2 "j"1}(read-line))
(partial map(comp #(take 4(concat % [0 0 0 0]))(fn[v]
(r(reduce #(let[l(last %1)](if(= %2 l)(conj(pop %1)(+ l %2) 0)
(conj %1 %2)))[]v)))r)))))i recur)))


ungolfed

(defn insert-2 [board]
(let [flat (into [] (flatten board))
zero-indices (seq (keep-indexed
#(when (zero? %2) %1)
flat))]
(cond
(some #{2048} flat) (pr "you win")
(empty? zero-indices) (pr "game over")
:else (partition 4 (assoc flat (rand-nth zero-indices) 2)))))

(defn rotate [board]
(map
(fn [n]
(map #(nth % n)
board))
[3 2 1 0]))

(defn remove-zeros [row]
(remove zero? row))

(defn fill [row]
(take 4 (concat row [0 0 0 0])))

(defn sum-up [acc x]
(let [l (last acc)]
(if (= x l)
(conj (pop acc) (+ l x) 0)
(conj acc x))))

(defn sum-pairs [v]
(remove-zeros (reduce sum-up [] v)))

(defn render [v]
(doseq [line (for [row v]
(apply str (map #(format "%5d " %) row)))]
(println line "\n")) v)

(defn slide [board]
(map (comp
fill
sum-pairs
remove-zeros
) board))

(loop [board (insert-2 (partition 4 (repeat 16 0)))]
(when (render board)
(let [input ({"h" 4 "k" 3 "l" 2 "j" 1} (read-line))
command (apply comp
(assoc (into [] (repeat 5 rotate))
input slide))] ;; (comp rotate rotate slide rotate rotate)
(-> board command insert-2 recur))))
`