2048: Difference between revisions
(→{{header|C}}: Enable use of arrow keys) |
No edit summary |
||
Line 434: | Line 434: | ||
Keys: WASD (Slide Movement), N (New game), P (Exit)</pre> |
Keys: WASD (Slide Movement), N (New game), P (Exit)</pre> |
||
=={{header|BBC BASIC}}== |
|||
{{works with|BBC BASIC for Windows}} |
|||
<lang bbcbasic> SIZE = 4 : MAX = SIZE-1 |
|||
Won% = FALSE : Lost% = FALSE |
|||
@% = 5 |
|||
DIM Board(MAX,MAX),Stuck% 3 |
|||
PROCBreed |
|||
PROCPrint |
|||
REPEAT |
|||
Direction = GET-135 |
|||
IF Direction > 0 AND Direction < 5 THEN |
|||
Moved% = FALSE |
|||
PROCShift |
|||
PROCMerge |
|||
PROCShift |
|||
IF Moved% THEN PROCBreed : !Stuck%=0 ELSE ?(Stuck%+Direction-1)=-1 : Lost% = !Stuck%=-1 |
|||
PROCPrint |
|||
ENDIF |
|||
UNTIL Won% OR Lost% |
|||
IF Won% THEN PRINT "You WON! :-)" ELSE PRINT "You lost :-(" |
|||
END |
|||
REM ----------------------------------------------------------------------------------------------------------------------- |
|||
DEF PROCPrint |
|||
FOR i = 0 TO SIZE*SIZE-1 |
|||
IF Board(i DIV SIZE,i MOD SIZE) THEN PRINT Board(i DIV SIZE,i MOD SIZE); ELSE PRINT " _"; |
|||
IF i MOD SIZE = MAX THEN PRINT |
|||
NEXT |
|||
PRINT STRING$(SIZE,"-----") |
|||
ENDPROC |
|||
REM ---------------------------------------------------------------------------------------------------------------------- |
|||
DEF PROCShift |
|||
IF Direction = 2 OR Direction = 3 THEN loopend = MAX : step = -1 ELSE loopend = 0 : step = 1 |
|||
FOR row = loopend TO MAX-loopend STEP step |
|||
zeros = 0 |
|||
FOR col = loopend TO MAX-loopend STEP step |
|||
IF Direction < 3 THEN |
|||
IF Board(row,col) = 0 THEN zeros += step ELSE IF zeros THEN SWAP Board(row,col),Board(row,col-zeros) : Moved% = TRUE |
|||
ELSE |
|||
IF Board(col,row) = 0 THEN zeros += step ELSE IF zeros THEN SWAP Board(col,row),Board(col-zeros,row) : Moved% = TRUE |
|||
ENDIF |
|||
NEXT |
|||
NEXT |
|||
ENDPROC |
|||
REM ----------------------------------------------------------------------------------------------------------------------- |
|||
DEF PROCMerge |
|||
IF Direction = 1 THEN loopend = 0 : rowoff = 0 : coloff = 1 : step = 1 |
|||
IF Direction = 2 THEN loopend = MAX : rowoff = 0 : coloff = -1 : step = -1 |
|||
IF Direction = 3 THEN loopend = MAX : rowoff = -1 : coloff = 0 : step = -1 |
|||
IF Direction = 4 THEN loopend = 0 : rowoff = 1 : coloff = 0 : step = 1 |
|||
FOR row = loopend TO MAX-loopend-rowoff STEP step |
|||
FOR col = loopend TO MAX-loopend-coloff STEP step |
|||
IF Board(row,col) THEN IF Board(row,col) = Board(row+rowoff,col+coloff) THEN |
|||
Board(row,col) *= 2 : Board(row+rowoff,col+coloff) = 0 |
|||
Moved% = TRUE |
|||
IF NOT Won% THEN Won% = Board(row,col)=2048 |
|||
ENDIF |
|||
NEXT |
|||
NEXT |
|||
ENDPROC |
|||
REM ----------------------------------------------------------------------------------------------------------------------- |
|||
DEF PROCBreed |
|||
cell = RND(SIZE*SIZE)-1 |
|||
FOR i = 0 TO SIZE*SIZE-1 |
|||
z = (cell+i) MOD (SIZE*SIZE) |
|||
IF Board(z DIV SIZE,z MOD SIZE) = 0 THEN Board(z DIV SIZE,z MOD SIZE) = 2-(RND(10)=1)*2 : EXIT FOR |
|||
NEXT |
|||
ENDPROC</lang> |
|||
{{out}} |
|||
<pre> _ _ _ _ |
|||
_ _ _ _ |
|||
_ _ 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 :-(</pre> |
|||
=={{header|C}}== |
=={{header|C}}== |
Revision as of 11:57, 15 January 2017
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
<lang AutoHotkey>Grid := [], s := 16, w := h := S * 4.5 Gui, font, s%s% Gui, add, text, y1 loop, 4 { row := A_Index loop, 4 { col := A_Index if col = 1 Gui, add, button, v%row%_%col% xs y+1 w%w% h%h% -TabStop, % Grid[row,col] := 0 else Gui, add, button, v%row%_%col% x+1 yp w%w% h%h% -TabStop, % Grid[row,col] := 0 } } Gui, show,, 2048
- ------------------------------
Start: for row, obj in Grid for col, val in obj Grid[row,col] := 0
Grid[1,1]:=2 ShowGrid() return
- ------------------------------
GuiClose: ExitApp return
- ------------------------------
- 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 }
- ------------------------------</lang>
Batch File
<lang dos>::2048 Game Task from RosettaCode.org
- Batch File Implementation
@echo off setlocal enabledelayedexpansion cls
- begin_game
%== Set variables ==% set "score=0" set "won=0" set "SUP_score=0" for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do set /a "X_%%A%%B=0"
call :addtile call :addtile
%== Main game loop ==%
- main_loop
set "changed=0" call :display echo( echo Keys: WASD (Slide Movement^), N (New game^), P (Exit^)
%== Get Keypress ==% set "key=" for /f "delims=" %%? in ('xcopy /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%?" set "key=%key:~-1%"
%== Process keypress ==% if /i "!key!"=="W" ( for /l %%? in (1,1,4) do call :slide X_1%%? X_2%%? X_3%%? X_4%%? ) if /i "!key!"=="A" ( for /l %%? in (1,1,4) do call :slide X_%%?1 X_%%?2 X_%%?3 X_%%?4 ) if /i "!key!"=="S" ( for /l %%? in (1,1,4) do call :slide X_4%%? X_3%%? X_2%%? X_1%%? ) if /i "!key!"=="D" ( for /l %%? in (1,1,4) do call :slide X_%%?4 X_%%?3 X_%%?2 X_%%?1 ) if /i "!key!"=="N" goto :begin_game if /i "!key!"=="P" exit /b
%== Check if the board changed ==% if %changed% neq 0 call :addtile
%== Check if already won ==% if %won% equ 1 ( set "msg=Nice one... You WON^!^!" goto :gameover )
%== Check for lose condition ==% set /a "real_blanks=blank_count-1" if %real_blanks% leq 0 ( for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do set "TRY_%%A%%B=!X_%%A%%B!" set "TRY_changed=%changed%" & set "changed=0" set "SUP_score=1" for /l %%? in (1,1,4) do call :slide TRY_%%?1 TRY_%%?2 TRY_%%?3 TRY_%%?4 for /l %%? in (1,1,4) do call :slide TRY_1%%? TRY_2%%? TRY_3%%? TRY_4%%? if !changed! equ 0 ( set "msg=No moves are possible... Game Over :(" goto :gameover ) else (set "changed=!TRY_changed!" & set "SUP_score=0") ) goto main_loop
- ~~~~~~~~~~~~~~~~~~~~ Sub Procedures ~~~~~~~~~~~~~~~~~~~~::
%== Game Over xD ==%
- gameover
call :display echo( echo(!msg! echo( echo(Keys: N (New game^), P (Exit^)
- key_loop
set "key=" for /f "delims=" %%? in ('xcopy /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%?" set "key=%key:~-1%" if /i "!key!"=="N" goto :begin_game if /i "!key!"=="P" exit /b goto :key_loop
%== The main slider of numbers in tiles ==%
- slide
set "next=" set "slide_1=" set "slide_2=" for %%? in (%*) do if !%%?! neq 0 set "slide_1=!slide_1! !%%?!" for %%? in (!slide_1!) do ( set "scan=%%?" if "!scan!"=="!next!" ( set /a "next*=2" if !SUP_score! equ 0 set /a "score+=!next!" %== WINNING CONDITION!!! ==% if "!next!" equ "2048" set "won=1" set "scan=" ) set "slide_2=!slide_2! !next!" set "next=!scan!" ) set "slide_2=!slide_2! !next!" for /l %%? in (1,1,4) do set "final_%%?=0" set "cnt=0" & for %%? in (!slide_2!) do if !cnt! lss 4 ( set /a "cnt+=1" set "final_!cnt!=%%?" ) if not "!%1!!%2!!%3!!%4!"=="!final_1!!final_2!!final_3!!final_4!" set "changed=1" set "cnt=0" & for %%? in (%*) do ( set /a "cnt+=1" set /a "%%?=final_!cnt!" ) goto :EOF
%== Add number to tile ==%
- addtile
set "blank_list=" set "blank_count=0" for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do ( if !X_%%A%%B! equ 0 ( set "blank_list=!blank_list!X_%%A%%B" set /a blank_count+=1 ) ) set /a "pick_tile=(%random% %% %blank_count%)*4" set /a "rnd=%random%%%10+1" set "tile_new=!blank_list:~%pick_tile%,4!" if %rnd%==5 (set !tile_new!=4) else (set !tile_new!=2) goto :EOF
%== Display the table ==%
- display
cls echo 2048 Game in Batch echo( for /l %%A in (1,1,4) do ( for /l %%B in (1,1,4) do ( set "DX_%%A%%B=!X_%%A%%B!" if !tile_new!==X_%%A%%B (set "DX_%%A%%B= +!X_%%A%%B!") else ( if !X_%%A%%B! lss 1000 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! lss 100 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! lss 10 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! equ 0 set "DX_%%A%%B= " ) ) echo +----+----+----+----+ echo ^|!DX_%%A1!^|!DX_%%A2!^|!DX_%%A3!^|!DX_%%A4!^| ) echo +----+----+----+----+ echo( echo Score: %score% goto :EOF</lang>
- Output:
2048 Game in Batch +----+----+----+----+ | | +2| | | +----+----+----+----+ | 4| | | | +----+----+----+----+ | 4| | | | +----+----+----+----+ | 16| 4| | 2| +----+----+----+----+ Score: 60 Keys: WASD (Slide Movement), N (New game), P (Exit)
BBC BASIC
<lang bbcbasic> SIZE = 4 : MAX = SIZE-1
Won% = FALSE : Lost% = FALSE @% = 5 DIM Board(MAX,MAX),Stuck% 3
PROCBreed PROCPrint REPEAT Direction = GET-135 IF Direction > 0 AND Direction < 5 THEN Moved% = FALSE PROCShift PROCMerge PROCShift IF Moved% THEN PROCBreed : !Stuck%=0 ELSE ?(Stuck%+Direction-1)=-1 : Lost% = !Stuck%=-1 PROCPrint ENDIF UNTIL Won% OR Lost% IF Won% THEN PRINT "You WON! :-)" ELSE PRINT "You lost :-(" END
REM ----------------------------------------------------------------------------------------------------------------------- DEF PROCPrint FOR i = 0 TO SIZE*SIZE-1 IF Board(i DIV SIZE,i MOD SIZE) THEN PRINT Board(i DIV SIZE,i MOD SIZE); ELSE PRINT " _"; IF i MOD SIZE = MAX THEN PRINT NEXT PRINT STRING$(SIZE,"-----") ENDPROC
REM ---------------------------------------------------------------------------------------------------------------------- DEF PROCShift IF Direction = 2 OR Direction = 3 THEN loopend = MAX : step = -1 ELSE loopend = 0 : step = 1 FOR row = loopend TO MAX-loopend STEP step zeros = 0 FOR col = loopend TO MAX-loopend STEP step IF Direction < 3 THEN IF Board(row,col) = 0 THEN zeros += step ELSE IF zeros THEN SWAP Board(row,col),Board(row,col-zeros) : Moved% = TRUE ELSE IF Board(col,row) = 0 THEN zeros += step ELSE IF zeros THEN SWAP Board(col,row),Board(col-zeros,row) : Moved% = TRUE ENDIF NEXT NEXT ENDPROC
REM ----------------------------------------------------------------------------------------------------------------------- DEF PROCMerge IF Direction = 1 THEN loopend = 0 : rowoff = 0 : coloff = 1 : step = 1 IF Direction = 2 THEN loopend = MAX : rowoff = 0 : coloff = -1 : step = -1 IF Direction = 3 THEN loopend = MAX : rowoff = -1 : coloff = 0 : step = -1 IF Direction = 4 THEN loopend = 0 : rowoff = 1 : coloff = 0 : step = 1 FOR row = loopend TO MAX-loopend-rowoff STEP step FOR col = loopend TO MAX-loopend-coloff STEP step IF Board(row,col) THEN IF Board(row,col) = Board(row+rowoff,col+coloff) THEN Board(row,col) *= 2 : Board(row+rowoff,col+coloff) = 0 Moved% = TRUE IF NOT Won% THEN Won% = Board(row,col)=2048 ENDIF NEXT NEXT ENDPROC
REM ----------------------------------------------------------------------------------------------------------------------- DEF PROCBreed cell = RND(SIZE*SIZE)-1 FOR i = 0 TO SIZE*SIZE-1 z = (cell+i) MOD (SIZE*SIZE) IF Board(z DIV SIZE,z MOD SIZE) = 0 THEN Board(z DIV SIZE,z MOD SIZE) = 2-(RND(10)=1)*2 : EXIT FOR NEXT ENDPROC</lang>
- Output:
_ _ _ _ _ _ _ _ _ _ 2 _ _ _ _ _ -------------------- _ _ _ _ _ _ _ _ 2 _ _ _ _ 2 _ _ -------------------- 2 2 _ _ _ _ 2 _ _ _ _ _ _ _ _ _ -------------------- 4 2 _ _ 2 _ _ _ _ _ _ _ _ _ _ _ -------------------- . . . . 2 8 4 2 4 2 16 4 16 4 8 32 4 32 2 4 -------------------- You lost :-(
C
Supports limited colours through vt100 escape codes. Requires a posix machine for termios.h and unistd.h> headers. Provides simplistic animations when moving and merging blocks. <lang c>
- 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;
} </lang>
- Output:
Score: 1100 (+4) ------------------------- | 64 | 32 | 64 | 32 | | 32 | 16 | 2 | 8 | | 16 | 4 | 8 | 4 | | 4 | 2 | 4 | 2 | ------------------------- You lose!
C++
<lang cpp>
- 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" );
} </lang>
- Output:
SCORE: 2024 +------+------+------+------+ | 2 | 8 | 32 | 256 | +------+------+------+------+ | | | 4 | 32 | +------+------+------+------+ | | | 2 | 8 | +------+------+------+------+ | | | | 2 | +------+------+------+------+ (W)Up (S)Down (A)Left (D)Right
Common Lisp
Depends on Windows msvcrt.dll for _getch. Depends on quicklisp. Use arrow keys to make moves and press "Q" to quit. Tested with SBCL. <lang lisp>(ql:quickload '(cffi alexandria))
(defpackage :2048-lisp
(:use :common-lisp :cffi :alexandria))
(in-package :2048-lisp)
(defvar *lib-loaded* nil)
(unless *lib-loaded*
;; Load msvcrt.dll to access _getch. (define-foreign-library msvcrt (:windows (:default "msvcrt")))
(use-foreign-library msvcrt)
(defcfun "_getch" :int)
(setf *lib-loaded* t))
(defun read-arrow ()
"Get an arrow key from input as UP, DOWN, LEFT, or RIGHT, otherwise
return a char of whatever was pressed."
(let ((first-char (-getch))) (if (= 224 first-char) (case (-getch) (75 'left) (80 'down) (77 'right) (72 'up)) (code-char first-char))))
(defmacro swap (place1 place2)
"Exchange the values of two places." (let ((temp (gensym "TEMP"))) `(cl:let ((,temp ,place1)) (cl:setf ,place1 ,place2) (cl:setf ,place2 ,temp))))
(defun nflip (board &optional (left-right t))
"Flip the elements of a BOARD left and right or optionally up and down." (let* ((y-len (array-dimension board 0)) (x-len (array-dimension board 1)) (y-max (if left-right y-len (truncate y-len 2))) (x-max (if left-right (truncate x-len 2) x-len))) (loop for y from 0 below y-max for y-place = (- y-len 1 y) do (loop for x from 0 below x-max for x-place = (- x-len 1 x) do (if left-right (swap (aref board y x) (aref board y x-place)) (swap (aref board y x) (aref board y-place x))))) board))
(defun flip (board &optional (left-right t))
"Flip the elements of a BOARD left and right or optionally up and down.
Non-destructive version."
(nflip (copy-array board) left-right))
(defun transpose (board)
"Transpose the elements of BOARD into a new array." (let* ((y-len (array-dimension board 0)) (x-len (array-dimension board 1)) (new-board (make-array (reverse (array-dimensions board))))) (loop for y from 0 below y-len do (loop for x from 0 below x-len do (setf (aref new-board x y) (aref board y x)))) new-board))
(defun add-random-piece (board)
"Find a random empty spot on the BOARD to add a new piece.
Return T if successful, NIL otherwise."
(loop for x from 0 below (array-total-size board) unless (row-major-aref board x) count 1 into count and collect x into indices finally (unless (= 0 count) (setf (row-major-aref board (nth (random count) indices)) (if (= 0 (random 10)) 4 2)) (return t))))
(defun squash-line (line)
"Reduce a sequence of numbers from left to right according to
the rules of 2048. Return the score of squashing as well."
(let* ((squashed (reduce (lambda (acc x) (if (equal x (car acc)) (cons (list (* 2 x)) (cdr acc)) (cons x acc))) (nreverse (remove-if #'null line)) :initial-value nil)) (new-line (flatten squashed))) (list (append (make-list (- (length line) (length new-line))) new-line) (reduce #'+ (flatten (remove-if-not #'listp squashed))))))
(defun squash-board (board)
"Reduce each row of a board from left to right according to
the rules of 2048. Return the total score of squashing the board as well."
(let ((y-len (array-dimension board 0)) (x-len (array-dimension board 1)) (total 0)) (list (make-array (array-dimensions board) :initial-contents (loop for y from 0 below y-len for (line score) = (squash-line (make-array x-len :displaced-to board :displaced-index-offset (array-row-major-index board y 0))) collect line do (incf total score))) total)))
(defun make-move (board direction)
"Make a move in the given DIRECTION on a new board." ;; Move by always squashing right, but transforming the board as needed. (destructuring-bind (new-board score) (case direction (up (squash-board (flip (transpose board)))) (down (squash-board (flip (transpose board) nil))) (left (squash-board (nflip (flip board nil)))) (right (squash-board board))) (let ((new-board ;; Reverse the transformation. (case direction (up (transpose (nflip new-board))) (down (transpose (nflip new-board nil))) (left (nflip (nflip new-board nil))) (right new-board)))) (unless (equalp board new-board) (add-random-piece new-board) (list new-board score)))))
(defun winp (board winning-tile)
"Determine if a BOARD is in a winning state." (loop for x from 0 below (array-total-size board) for val = (row-major-aref board x) when (eql val winning-tile) do (return t)))
(defun game-overp (board)
"Determine if a BOARD is in a game over state." ;; If a move is simulated in every direction and nothing changes, ;; then we can assume there are no valid moves left. (notany (lambda (dir) (car (make-move board dir))) '(up down left right)))
(defun print-divider (cells cell-size)
"A print helper function for PRINT-BOARD." (dotimes (_ cells) (princ "+") (dotimes (_ cell-size) (princ "-"))) (princ "+") (terpri))
(defun print-board (board cell-size)
"Pretty print the given BOARD with a particular CELL-SIZE." (let* ((y-len (array-dimension board 0)) (x-len (array-dimension board 1)) (super-size (+ 2 cell-size))) (loop for y from 0 below y-len do (print-divider x-len super-size) (loop for x from 0 below x-len for val = (aref board y x) do (princ "|") (if val (format t " ~VD " cell-size val) (dotimes (_ super-size) (princ " ")))) (princ "|") (terpri)) (print-divider x-len super-size)))
(defun init-board ()
(let ((board (make-array '(4 4) :initial-element nil))) (setf (row-major-aref board (random (array-total-size board))) 2) board))
(defun prompt-input (board score &optional (check t))
(cond ((and check (winp board 2048)) (format t "You win!")) ((and check (game-overp board)) (format t "Game over...")) (t (let ((choice (read-arrow))) (cond ((symbolp choice) (destructuring-bind (&optional move (new-score 0)) (make-move board choice) (if move (prompt move (+ score new-score)) (prompt-input board score)))) ((find choice "qQ") (format t "Quitting.")) (t (prompt-input board score nil)))))))
(defun prompt (&optional (board (init-board)) (score 0))
(format t "~% Score: ~D~%" score) (print-board board 4) (prompt-input board score))</lang>
- Output:
* (2048-lisp::prompt) Score: 0 +------+------+------+------+ | | | | | +------+------+------+------+ | | | | | +------+------+------+------+ | | | 2 | | +------+------+------+------+ | | | | | +------+------+------+------+
Some time later...
Score: 336 +------+------+------+------+ | | 4 | 16 | 32 | +------+------+------+------+ | | | 4 | 16 | +------+------+------+------+ | | | 32 | | +------+------+------+------+ | | 2 | | | +------+------+------+------+
D
<lang d>import std.stdio, std.string, std.random; import core.stdc.stdlib: exit;
struct G2048 {
public void gameLoop() /*@safe @nogc*/ { addTile; while (true) { if (moved) addTile; drawBoard; if (done) break; waitKey; } writeln(win ? "You win!" : "Game Over!"); }
private:
static struct Tile { uint val = 0; bool blocked = false; }
enum moveDir { up, down, left, right } enum uint side = 4;
Tile[side][side] board; bool win = false, done = false, moved = true; uint score = 0;
void drawBoard() const /*@safe @nogc*/ { writeln("SCORE: ", score, "\n"); foreach (immutable y; 0 .. side) { write("+------+------+------+------+\n| "); foreach (immutable x; 0 .. side) { if (board[x][y].val) writef("%4d", board[x][y].val); else writef("%4s", " "); write(" | "); } writeln; } "+------+------+------+------+\n".writeln; }
void waitKey() /*@safe*/ { moved = false; "(W)Up (S)Down (A)Left (D)Right (Q)Quit: ".write; immutable c = readln.strip.toLower;
switch (c) { case "w": move(moveDir.up); break; case "a": move(moveDir.left); break; case "s": move(moveDir.down); break; case "d": move(moveDir.right); break; case "q": endGame; break; default: break; }
foreach (immutable y; 0 .. side) foreach (immutable x; 0 .. side) board[x][y].blocked = false; }
void endGame() const { writeln("Game ended with score: ", score); exit(0); }
void addTile() /*nothrow*/ @safe /*@nogc*/ { foreach (immutable y; 0 .. side) { foreach (immutable x; 0 .. side) { if (!board[x][y].val) { uint a, b; do { a = uniform(0, side); b = uniform(0, side); } while (board[a][b].val);
board[a][b].val = (uniform01 > 0.89) ? side : 2; if (canMove) return; } } } done = true; }
bool canMove() const pure nothrow @safe @nogc { foreach (immutable y; 0 .. side) foreach (immutable x; 0 .. side) if (!board[x][y].val) return true;
foreach (immutable y; 0 .. side) { foreach (immutable x; 0 .. side) { if (testAdd(x + 1, y, board[x][y].val) || testAdd(x - 1, y, board[x][y].val) || testAdd(x, y + 1, board[x][y].val) || testAdd(x, y - 1, board[x][y].val)) return true; } } return false; }
bool testAdd(in uint x, in uint y, in uint v) const pure nothrow @safe @nogc { if (x > 3 || y > 3) return false; return board[x][y].val == v; }
void moveVertically(in uint x, in uint y, in uint d) pure nothrow @safe @nogc { if (board[x][y + d].val && board[x][y + d].val == board[x][y].val && !board[x][y].blocked && !board[x][y + d].blocked) { board[x][y].val = 0; board[x][y + d].val *= 2; score += board[x][y + d].val; board[x][y + d].blocked = true; moved = true; } else if (!board[x][y + d].val && board[x][y].val) { board[x][y + d].val = board[x][y].val; board[x][y].val = 0; moved = true; }
if (d > 0) { if (y + d < 3) moveVertically(x, y + d, 1); } else { if (y + d > 0) moveVertically(x, y + d, -1); } }
void moveHorizontally(in uint x, in uint y, in uint d) pure nothrow @safe @nogc { if (board[x + d][y].val && board[x + d][y].val == board[x][y].val && !board[x][y].blocked && !board[x + d][y].blocked) { board[x][y].val = 0; board[x + d][y].val *= 2; score += board[x + d][y].val; board[x + d][y].blocked = true; moved = true; } else if (!board[x + d][y].val && board[x][y].val) { board[x + d][y].val = board[x][y].val; board[x][y].val = 0; moved = true; }
if (d > 0) { if (x + d < 3) moveHorizontally(x + d, y, 1); } else { if (x + d > 0) moveHorizontally(x + d, y, -1); } }
void move(in moveDir d) pure nothrow @safe @nogc { final switch (d) with(moveDir) { case up: foreach (immutable x; 0 .. side) foreach (immutable y; 1 .. side) if (board[x][y].val) moveVertically(x, y, -1); break; case down: foreach (immutable x; 0 .. side) foreach_reverse (immutable y; 0 .. 3) if (board[x][y].val) moveVertically(x, y, 1); break; case left: foreach (immutable y; 0 .. side) foreach (immutable x; 1 .. side) if (board[x][y].val) moveHorizontally(x, y, -1); break; case right: foreach (immutable y; 0 .. side) foreach_reverse (immutable x; 0 .. 3) if (board[x][y].val) moveHorizontally(x, y, 1); } }
}
void main() /*safe*/ {
G2048 g; g.gameLoop;
}</lang> The output is the same as the C++ version.
Elixir
<lang elixir>defmodule Game2048 do
@size 4 @range 0..@size-1 def play(goal \\ 2048), do: setup |> play(goal) defp play(board, goal) do show(board) cond do goal in Map.values(board) -> IO.puts "You win!" exit(:normal) 0 in Map.values(board) or combinable?(board) -> moved = move(board, keyin) if moved == board, do: play(board, goal), else: add_tile(moved) |> play(goal) true -> IO.puts "Game Over!" exit(:normal) end end defp setup do (for i <- @range, j <- @range, into: %{}, do: {{i,j},0}) |> add_tile |> add_tile end defp add_tile(board) do position = blank_space(board) |> Enum.random tile = if :rand.uniform(10)==1, do: 4, else: 2 %{board | position => tile} end defp blank_space(board) do for {key, 0} <- board, do: key end defp keyin do key = IO.gets("key in wasd or q: ") case String.first(key) do "w" -> :up "a" -> :left "s" -> :down "d" -> :right "q" -> exit(:normal) _ -> keyin end end defp move(board, :up) do Enum.reduce(@range, board, fn j,acc -> Enum.map(@range, fn i -> acc[{i,j}] end) |> move_and_combine |> Enum.with_index |> Enum.reduce(acc, fn {v,i},map -> Map.put(map, {i,j}, v) end) end) end defp move(board, :down) do Enum.reduce(@range, board, fn j,acc -> Enum.map(@size-1..0, fn i -> acc[{i,j}] end) |> move_and_combine |> Enum.reverse |> Enum.with_index |> Enum.reduce(acc, fn {v,i},map -> Map.put(map, {i,j}, v) end) end) end defp move(board, :left) do Enum.reduce(@range, board, fn i,acc -> Enum.map(@range, fn j -> acc[{i,j}] end) |> move_and_combine |> Enum.with_index |> Enum.reduce(acc, fn {v,j},map -> Map.put(map, {i,j}, v) end) end) end defp move(board, :right) do Enum.reduce(@range, board, fn i,acc -> Enum.map(@size-1..0, fn j -> acc[{i,j}] end) |> move_and_combine |> Enum.reverse |> Enum.with_index |> Enum.reduce(acc, fn {v,j},map -> Map.put(map, {i,j}, v) end) end) end defp move_and_combine(tiles) do (Enum.filter(tiles, &(&1>0)) ++ [0,0,0,0]) |> Enum.take(@size) |> case do [a,a,b,b] -> [a*2, b*2, 0, 0] [a,a,b,c] -> [a*2, b, c, 0] [a,b,b,c] -> [a, b*2, c, 0] [a,b,c,c] -> [a, b, c*2, 0] x -> x end end defp combinable?(board) do Enum.any?(for i <- @range, j <- 0..@size-2, do: board[{i,j}]==board[{i,j+1}]) or Enum.any?(for j <- @range, i <- 0..@size-2, do: board[{i,j}]==board[{i+1,j}]) end @frame String.duplicate("+----", @size) <> "+" @format (String.duplicate("|~4w", @size) <> "|") |> to_charlist # before 1.3 to_char_list defp show(board) do Enum.each(@range, fn i -> IO.puts @frame row = for j <- @range, do: board[{i,j}] IO.puts (:io_lib.fwrite @format, row) |> to_string |> String.replace(" 0|", " |") end) IO.puts @frame end
end
Game2048.play 512</lang>
- Output:
+----+----+----+----+ | | 2| | | +----+----+----+----+ | | | | | +----+----+----+----+ | 2| | | | +----+----+----+----+ | | | | | +----+----+----+----+ key in wasd or q: . . . +----+----+----+----+ | 2| 4| 2| | +----+----+----+----+ | 16| | | | +----+----+----+----+ | 8| 16| 32| 2| +----+----+----+----+ | 64| 256| 128| 4| +----+----+----+----+ key in wasd or q: q
Go
<lang Go>package main
import ( "bufio" "fmt" "log" "math/rand" "os" "os/exec" "strconv" "strings" "text/template" "time" "unicode"
"golang.org/x/crypto/ssh/terminal" )
const maxPoints = 2048 const ( fieldSizeX = 4 fieldSizeY = 4 ) const tilesAtStart = 2 const probFor2 = 0.9
type button int
const ( _ button = iota up down right left quit )
var labels = func() map[button]rune { m := make(map[button]rune, 4) m[up] = 'W' m[down] = 'S' m[right] = 'D' m[left] = 'A' return m }() var keybinding = func() map[rune]button { m := make(map[rune]button, 8) for b, r := range labels { m[r] = b if unicode.IsUpper(r) { r = unicode.ToLower(r) } else { r = unicode.ToUpper(r) } m[r] = b } m[0x03] = quit return m }()
var model = struct { Score int Field [fieldSizeY][fieldSizeX]int }{}
var view = func() *template.Template { maxWidth := 1 for i := maxPoints; i >= 10; i /= 10 { maxWidth++ }
w := maxWidth + 3 r := make([]byte, fieldSizeX*w+1) for i := range r { if i%w == 0 { r[i] = '+' } else { r[i] = '-' } } rawBorder := string(r)
v, err := template.New("").Parse(`SCORE: Template:.Score Template:Range .Field ` + rawBorder + ` |Template:Range . Template:If .Template:Printf "%` + strconv.Itoa(maxWidth) + `d" .Template:Else` + strings.Repeat(" ", maxWidth) + `Template:End |Template:EndTemplate:End ` + rawBorder + `
(` + string(labels[up]) + `)Up (` + string(labels[down]) + `)Down (` + string(labels[left]) + `)Left (` + string(labels[right]) + `)Right `) check(err) return v }()
func check(err error) { if err != nil { log.Panicln(err) } }
func clear() { c := exec.Command("clear") c.Stdout = os.Stdout check(c.Run()) }
func draw() { clear() check(view.Execute(os.Stdout, model)) }
func addRandTile() (full bool) { free := make([]*int, 0, fieldSizeX*fieldSizeY)
for x := 0; x < fieldSizeX; x++ { for y := 0; y < fieldSizeY; y++ { if model.Field[y][x] == 0 { free = append(free, &model.Field[y][x]) } } }
val := 4 if rand.Float64() < probFor2 { val = 2 } *free[rand.Intn(len(free))] = val
return len(free) == 1 }
type point struct{ x, y int }
func (p point) get() int { return model.Field[p.y][p.x] } func (p point) set(n int) { model.Field[p.y][p.x] = n } func (p point) inField() bool { return p.x >= 0 && p.y >= 0 && p.x < fieldSizeX && p.y < fieldSizeY } func (p *point) next(n point) { p.x += n.x; p.y += n.y }
func controller(key rune) (gameOver bool) { b := keybinding[key]
if b == 0 { return false } if b == quit { return true }
var starts []point var next point
switch b { case up: next = point{0, 1} starts = make([]point, fieldSizeX) for x := 0; x < fieldSizeX; x++ { starts[x] = point{x, 0} } case down: next = point{0, -1} starts = make([]point, fieldSizeX) for x := 0; x < fieldSizeX; x++ { starts[x] = point{x, fieldSizeY - 1} } case right: next = point{-1, 0} starts = make([]point, fieldSizeY) for y := 0; y < fieldSizeY; y++ { starts[y] = point{fieldSizeX - 1, y} } case left: next = point{1, 0} starts = make([]point, fieldSizeY) for y := 0; y < fieldSizeY; y++ { starts[y] = point{0, y} } }
moved := false winning := false
for _, s := range starts { n := s move := func(set int) { moved = true s.set(set) n.set(0) } for n.next(next); n.inField(); n.next(next) { if s.get() != 0 { if n.get() == s.get() { score := s.get() * 2 model.Score += score winning = score >= maxPoints
move(score) s.next(next) } else if n.get() != 0 { s.next(next) if s.get() == 0 { move(n.get()) } } } else if n.get() != 0 { move(n.get()) } } }
if !moved { return false }
lost := false if addRandTile() { lost = true Out: for x := 0; x < fieldSizeX; x++ { for y := 0; y < fieldSizeY; y++ { if (y > 0 && model.Field[y][x] == model.Field[y-1][x]) || (x > 0 && model.Field[y][x] == model.Field[y][x-1]) { lost = false break Out } } } }
draw()
if winning { fmt.Println("You win!") return true } if lost { fmt.Println("Game Over") return true }
return false }
func main() { oldState, err := terminal.MakeRaw(0) check(err) defer terminal.Restore(0, oldState)
rand.Seed(time.Now().Unix())
for i := tilesAtStart; i > 0; i-- { addRandTile() } draw()
stdin := bufio.NewReader(os.Stdin)
readKey := func() rune { r, _, err := stdin.ReadRune() check(err) return r }
for !controller(readKey()) { } } </lang>
J
Solution <lang j>NB. 2048.ijs script NB. ========================================================= NB. 2048 game engine
require 'guid' ([ 9!:1) _2 (3!:4) , guids 1 NB. randomly set initial random seed
coclass 'g2048' Target=: 2048
new2048=: verb define
Gridsz=: 4 4 Points=: Score=: 0 Grid=: newnum^:2 ] Gridsz $ 0
)
newnum=: verb define
num=. 2 4 {~ 0.1 > ?0 NB. 10% chance of 4 idx=. 4 $. $. 0 = y NB. indicies of 0s if. #idx do. NB. handle full grid idx=. ,/ ({~ 1 ? #) idx NB. choose an index num (<idx)} y else. return. y end.
)
mskmerge=: [: >/\.&.|. 2 =/\ ,&_1 mergerow=: ((* >:) #~ _1 |. -.@]) mskmerge scorerow=: +/@(+: #~ mskmerge)
compress=: -.&0 toLeft=: 1 :'4&{.@(u@compress)"1' toRight=: 1 : '_4&{.@(u@compress&.|.)"1' toUp=: 1 : '(4&{.@(u@compress)"1)&.|:' toDown=: 1 : '(_4&{.@(u@compress&.|.)"1)&.|:'
move=: conjunction define
Points=: +/@, v Grid update newnum^:(Grid -.@-: ]) u Grid
)
noMoves=: (0 -.@e. ,)@(mergerow toRight , mergerow toLeft , mergerow toUp ,: mergerow toDown) hasWon=: Target e. ,
eval=: verb define
Score=: Score + Points isend=. (noMoves , hasWon) y msg=. isend # 'You lost!!';'You Won!!' if. -. isend=. +./ isend do. Points=: 0 msg=. 'Score is ',(": Score) end. isend;msg
)
showGrid=: echo
NB. ========================================================= NB. Console user interface
g2048Con_z_=: conew&'g2048con'
coclass 'g2048con' coinsert 'g2048'
create=: verb define
echo Instructions startnew y
)
destroy=: codestroy quit=: destroy
startnew=: update@new2048
left=: 3 :'mergerow toLeft move (scorerow toLeft)' right=: 3 :'mergerow toRight move (scorerow toRight)' up=: 3 :'mergerow toUp move (scorerow toUp)' down=: 3 :'mergerow toDown move (scorerow toDown)'
update=: verb define
Grid=: y NB. update global Grid 'isend msg'=. eval y echo msg showGrid y if. isend do. destroy end. empty
)
Instructions=: noun define
2048
Object:
Create the number 2048 by merging numbers.
How to play:
When 2 numbers the same touch, they merge. - move numbers using the commands below: right__grd left__grd up__grd down__grd - quit a game: quit__grd - start a new game: grd=: g2048Con
)</lang> Usage <lang j> grd=: g2048Con
Score is 0 0 0 0 2 0 2 0 0 0 0 0 0 0 0 0 0
right__grd
Score is 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0 2
down__grd
Score is 4 0 0 0 0 0 0 0 0 0 0 4 4 0 0 0 2 ...</lang>
Java
<lang java>import java.awt.*; import java.awt.event.*; import java.util.Random; import javax.swing.*;
public class Game2048 extends JPanel {
enum State { start, won, running, over }
final Color[] colorTable = { new Color(0x701710), new Color(0xFFE4C3), new Color(0xfff4d3), new Color(0xffdac3), new Color(0xe7b08e), new Color(0xe7bf8e), new Color(0xffc4c3), new Color(0xE7948e), new Color(0xbe7e56), new Color(0xbe5e56), new Color(0x9c3931), new Color(0x701710)};
final static int target = 2048;
static int highest; static int score;
private Color gridColor = new Color(0xBBADA0); private Color emptyColor = new Color(0xCDC1B4); private Color startColor = new Color(0xFFEBCD);
private Random rand = new Random();
private Tile[][] tiles; private int side = 4; private State gamestate = State.start; private boolean checkingAvailableMoves;
public Game2048() { setPreferredSize(new Dimension(900, 700)); setBackground(new Color(0xFAF8EF)); setFont(new Font("SansSerif", Font.BOLD, 48)); setFocusable(true);
addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { startGame(); repaint(); } });
addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: moveUp(); break; case KeyEvent.VK_DOWN: moveDown(); break; case KeyEvent.VK_LEFT: moveLeft(); break; case KeyEvent.VK_RIGHT: moveRight(); break; } repaint(); } }); }
@Override public void paintComponent(Graphics gg) { super.paintComponent(gg); Graphics2D g = (Graphics2D) gg; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawGrid(g); }
void startGame() { if (gamestate != State.running) { score = 0; highest = 0; gamestate = State.running; tiles = new Tile[side][side]; addRandomTile(); addRandomTile(); } }
void drawGrid(Graphics2D g) { g.setColor(gridColor); g.fillRoundRect(200, 100, 499, 499, 15, 15);
if (gamestate == State.running) {
for (int r = 0; r < side; r++) { for (int c = 0; c < side; c++) { if (tiles[r][c] == null) { g.setColor(emptyColor); g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7); } else { drawTile(g, r, c); } } } } else { g.setColor(startColor); g.fillRoundRect(215, 115, 469, 469, 7, 7);
g.setColor(gridColor.darker()); g.setFont(new Font("SansSerif", Font.BOLD, 128)); g.drawString("2048", 310, 270);
g.setFont(new Font("SansSerif", Font.BOLD, 20));
if (gamestate == State.won) { g.drawString("you made it!", 390, 350);
} else if (gamestate == State.over) g.drawString("game over", 400, 350);
g.setColor(gridColor); g.drawString("click to start a new game", 330, 470); g.drawString("(use arrow keys to move tiles)", 310, 530); } }
void drawTile(Graphics2D g, int r, int c) { int value = tiles[r][c].getValue();
g.setColor(colorTable[(int) (Math.log(value) / Math.log(2)) + 1]); g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7); String s = String.valueOf(value);
g.setColor(value < 128 ? colorTable[0] : colorTable[1]);
FontMetrics fm = g.getFontMetrics(); int asc = fm.getAscent(); int dec = fm.getDescent();
int x = 215 + c * 121 + (106 - fm.stringWidth(s)) / 2; int y = 115 + r * 121 + (asc + (106 - (asc + dec)) / 2);
g.drawString(s, x, y); }
private void addRandomTile() { int pos = rand.nextInt(side * side); int row, col; do { pos = (pos + 1) % (side * side); row = pos / side; col = pos % side; } while (tiles[row][col] != null);
int val = rand.nextInt(10) == 0 ? 4 : 2; tiles[row][col] = new Tile(val); }
private boolean move(int countDownFrom, int yIncr, int xIncr) { boolean moved = false;
for (int i = 0; i < side * side; i++) { int j = Math.abs(countDownFrom - i);
int r = j / side; int c = j % side;
if (tiles[r][c] == null) continue;
int nextR = r + yIncr; int nextC = c + xIncr;
while (nextR >= 0 && nextR < side && nextC >= 0 && nextC < side) {
Tile next = tiles[nextR][nextC]; Tile curr = tiles[r][c];
if (next == null) {
if (checkingAvailableMoves) return true;
tiles[nextR][nextC] = curr; tiles[r][c] = null; r = nextR; c = nextC; nextR += yIncr; nextC += xIncr; moved = true;
} else if (next.canMergeWith(curr)) {
if (checkingAvailableMoves) return true;
int value = next.mergeWith(curr); if (value > highest) highest = value; score += value; tiles[r][c] = null; moved = true; break; } else break; } }
if (moved) { if (highest < target) { clearMerged(); addRandomTile(); if (!movesAvailable()) { gamestate = State.over; } } else if (highest == target) gamestate = State.won; }
return moved; }
boolean moveUp() { return move(0, -1, 0); }
boolean moveDown() { return move(side * side - 1, 1, 0); }
boolean moveLeft() { return move(0, 0, -1); }
boolean moveRight() { return move(side * side - 1, 0, 1); }
void clearMerged() { for (Tile[] row : tiles) for (Tile tile : row) if (tile != null) tile.setMerged(false); }
boolean movesAvailable() { checkingAvailableMoves = true; boolean hasMoves = moveUp() || moveDown() || moveLeft() || moveRight(); checkingAvailableMoves = false; return hasMoves; }
public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setTitle("2048"); f.setResizable(true); f.add(new Game2048(), BorderLayout.CENTER); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); }); }
}
class Tile {
private boolean merged; private int value;
Tile(int val) { value = val; }
int getValue() { return value; }
void setMerged(boolean m) { merged = m; }
boolean canMergeWith(Tile other) { return !merged && other != null && !other.merged && value == other.getValue(); }
int mergeWith(Tile other) { if (canMergeWith(other)) { value *= 2; merged = true; return value; } return -1; }
}</lang>
Kotlin
Stateless with focus on clarity rather than conciseness.
<lang kotlin> 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("+----+----+----+----+")
}</lang>
Sample output:
New Grid: +----+----+----+----+ | 2| | | | +----+----+----+----+ | | | | 2| +----+----+----+----+ | 4| 16| | | +----+----+----+----+ | 16| 4| 2| | +----+----+----+----+ Direction?
Perl 6
Uses termios to set the terminal options, so only compatible with POSIX terminals. This version does not include a specific "win" or "lose" condition. (though it would be trivial to add them.) You can continue to play this even after getting a 2048 tile; and if there is no valid move you can make, you can't do anything but quit.
<lang perl6>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».¢er) ~ '│' }
sub center ($s){
my $c = cell - $s.chars; my $pad = ' ' x ceiling($c/2); my $tile = sprintf "%{cell}s", "$s$pad"; my $idx = $s ?? $s.log(2) !! 0; ansi ?? "\e[{@ANSI[$idx]}m$tile\e[0m" !! $tile;
}
sub draw-board {
run('clear'); print qq:to/END/;
Press direction arrows to move.
Press q to quit.
$top { join "\n\t$mid\n\t", map { .&row }, @board } $bot
Score: $score
END }
sub squash (@c) {
my @t = grep { .chars }, @c; map { combine(@t[$_], @t[$_+1]) if @t[$_] && @t[$_+1] == @t[$_] }, ^@t-1; @t = grep { .chars }, @t; @t.push: while @t < n; @t;
}
sub combine ($v is rw, $w is rw) { $v += $w; $w = ; $score += $v; }
multi sub move('up') {
map { @board[*;$_] = squash @board[*;$_] }, ^n;
}
multi sub move('down') {
map { @board[*;$_] = reverse squash reverse @board[*;$_] }, ^n;
}
multi sub move('left') {
map { @board[$_] = squash @board[$_] }, ^n;
}
multi sub move('right') {
map { @board[$_] = reverse squash reverse @board[$_] }, ^n;
}
sub another {
my @empties; for @board.kv -> $r, @row { @empties.push(($r, $_)) for @row.grep(:k, ); } my ( $x, $y ) = @empties.roll; @board[$x; $y] = (flat 2 xx 9, 4).roll;
}
sub save () { join '|', flat @board».list }
loop {
another if $save ne save(); draw-board; $save = save();
# Read up to 4 bytes from keyboard buffer. # Page navigation keys are 3-4 bytes each. # Specifically, arrow keys are 3. my $key = $*IN.read(4).decode;
move %dir{$key} if so %dir{$key}; last if $key eq 'q'; # (q)uit
}</lang> Sample output:
Press direction arrows to move. Press q to quit. ┌──────┬──────┬──────┬──────┐ │ 4 │ 2 │ │ │ ├──────┼──────┼──────┼──────┤ │ 16 │ 8 │ │ │ ├──────┼──────┼──────┼──────┤ │ 64 │ 32 │ 16 │ │ ├──────┼──────┼──────┼──────┤ │ 128 │ 512 │ 128 │ 64 │ └──────┴──────┴──────┴──────┘ Score: 6392
Phix
Faithful desktop gui (windows only) reproduction of the above link (https://gabrielecirulli.github.io/2048/) Now I just got figure out how to win... <lang Phix>include ..\arwen\arwen.ew include ..\arwen\axtra.ew
constant main = create(Window, "2048", 0, 0, 20, 20, 520, 540, 0),
mainDC = getPrivateDC(main), viewDC = c_func(xCreateCompatibleDC, {NULL})
constant TA_CENTER = 6 {} = c_func(xSetTextAlign,{viewDC,TA_CENTER}) constant hFont40 = createFontForDC(viewDC, "Calibri", 40, Bold) constant hFont32 = createFontForDC(viewDC, "Calibri", 32, Bold)
constant tile_colours = {#B4C0CC, -- blank
#DAE4EE, -- 2 #C8E0ED, -- 4 #79B1F2, -- 8 #6395F5, -- 16 #5F7CF6, -- 32 #3B5EF6, -- 64 #72CFED, -- 128 #61CCED, -- 256 #50C8ED, -- 512 #3FC5ED, -- 1024 #2EC2ED} -- 2048
-- the 4x4 board. -- note that values are [1..12] for [blank,2,4,8,..2048]. -- (merging two eights is not 8+8->16 but 4+1->5, internally) sequence board
integer newgame = 1
procedure add_rand(integer count) -- (nb infinite loop if board is full) integer x, y
while count do x = rand(4) y = rand(4) if board[y][x]=1 then -- blank board[y][x] = 2+(rand(10)=10) count -= 1 end if end while
end procedure
integer valid = 0 integer prev, nxt, bxy
procedure move_x(integer x, integer y, integer d)
bxy = board[x][y] if bxy!=1 then if bxy=prev then board[x][y] = 1 bxy += 1 board[x][nxt] = bxy nxt += d prev = 13 valid = 1 else if prev=1 or y!=nxt then if prev!=1 and prev!=13 then nxt += d end if if y!=nxt then board[x][y] = 1 board[x][nxt] = bxy valid = 1 end if end if prev = bxy end if end if
end procedure
procedure move_y(integer x, integer y, integer d)
bxy = board[x][y] if bxy!=1 then if bxy=prev then board[x][y] = 1 bxy += 1 board[nxt][y] = bxy nxt += d prev = 13 valid = 1 else if prev=1 or x!=nxt then if prev!=1 and prev!=13 then nxt += d end if if x!=nxt then board[x][y] = 1 board[nxt][y] = bxy valid = 1 end if end if prev = bxy end if end if
end procedure
function move(integer key) -- a non-zero result means it changed something.
valid = 0 if key=VK_LEFT then for x=1 to 4 do prev = 13 nxt = 1 for y=1 to 4 do move_x(x,y,+1) end for end for elsif key=VK_DOWN then for y=1 to 4 do prev = 13 nxt = 4 for x=4 to 1 by -1 do move_y(x,y,-1) end for end for elsif key=VK_RIGHT then for x=1 to 4 do prev = 13 nxt = 4 for y=4 to 1 by -1 do move_x(x,y,-1) end for end for elsif key=VK_UP then for y=1 to 4 do prev = 13 nxt = 1 for x=1 to 4 do move_y(x,y,+1) end for end for end if return valid
end function
function game_won()
for i=1 to length(board) do if find(12,board[i]) then return 1 end if end for return 0
end function
constant valid_keys = {VK_LEFT,VK_DOWN,VK_RIGHT,VK_UP}
function no_valid_moves() sequence saved_board = board
for i=1 to length(valid_keys) do if move(valid_keys[i]) then board = saved_board return 0 -- OK end if end for return 1 -- game over...
end function
integer dw = 0, dh = 0 -- client area width and height atom bmView integer vwX = 0, vwY = 0 -- actual size of the view bitmap
integer ox,oy, -- top tight coords
os,ts, -- overall and tile size ts2 -- half tile, for number positioning
function mainHandler(integer id, integer msg, atom wParam, object lParam) integer tx, ty, bxy string mbmsg
if msg=WM_SIZE then {{},{},dw,dh} = getClientRect(main) if dw>vwX or dh>vwY then -- we need a bigger bitmap bmView = c_func(xCreateCompatibleBitmap, {mainDC, dw, dh}) {} = deleteObject(selectObject(viewDC,bmView)) {vwX,vwY} = {dw,dh} end if if dw>=dh then ox = floor((dw-dh)/2) oy = 0 os = dh else ox = 0 oy = floor((dh-dw)/2) os = dw end if ts = floor((os-10)/4-7) ts2 = floor(ts/2+5)-10 elsif msg=WM_PAINT then if newgame then board = repeat(repeat(1,4),4) add_rand(2) newgame = 0 end if setPenColor(#EFF8FA) drawRectangleh(viewDC, True, 0, 0, dw, dh) setPenColor(#A0ADBB) drawRoundRecth(viewDC, True, ox+5, oy+5, ox+os-5, oy+os-5, 10, 10) tx = ox+15 for y=1 to 4 do ty = oy+15 for x=1 to 4 do bxy = board[x][y] setPenColor(tile_colours[bxy]) drawRoundRecth(viewDC, True, tx, ty, tx+ts-10, ty+ts-10, 5, 5) if bxy>1 then setPenColor(iff(bxy<=3?#656E77:#F2F6F9)) {} = selectObject(viewDC,iff(bxy>10?hFont32:hFont40)) wPuts2(viewDC, tx+ts2, ty+ts2-25-iff(bxy<11?7:0), power(2,bxy-1)) end if ty += ts+5 end for tx += ts+5 end for void = c_func(xBitBlt,{mainDC,0,0,dw,dh,viewDC,0,0,SRCCOPY}) elsif msg=WM_CHAR then if wParam=VK_ESCAPE then closeWindow(main) if id or object(lParam) then end if -- suppress warnings end if elsif msg=WM_KEYDOWN then if move(wParam) then mbmsg = "" if game_won() then mbmsg = "!!!YOU WON!!!\n\nAnother Go?" else add_rand(1) repaintWindow(main) if no_valid_moves() then mbmsg = "You Lost.\n\nAnother Go?" end if end if if length(mbmsg) then if messageBox("Game Over",mbmsg,MB_YESNO)=IDYES then newgame=1 else closeWindow(main) end if end if end if elsif msg=WM_GETMINMAXINFO then poke4(lParam+MINMAXINFO_ptMinTrackSize,{440,450}) end if return 0
end function setHandler({main},routine_id("mainHandler"))
WinMain(main, SW_NORMAL)</lang>
PicoLisp
<lang PicoLisp>(load "@lib/simul.l")
(seed (in "/dev/urandom" (rd 8)))
(setq *G (grid 4 4) *D NIL)
(de cell ()
(use This (while (get (setq This (intern (pack (char (+ 96 (rand 1 4))) (rand 1 4) ) ) ) 'N ) ) (=: N (if (> 90 (rand 1 100)) 2 4) ) ) (setq *D (fish '((This) (: N)) *G)) )
(de redraw (G S D)
# zeroize *G (mapc '((I) (mapc '((This) (=: N NIL)) I) ) *G ) # draw again (mapc '((X This) (while (and This X) (=: N (pop 'X)) (setq This (D This)) ) ) G S ) )
(de summ (Lst)
(mapcar '((L) (make (while L (ifn (= (car L) (cadr L)) (link (car L)) (link (+ (car L) (cadr L))) (pop 'L) ) (pop 'L) ) ) ) Lst ) )
(de vertical ()
(mapcar '((X) (extract '((This) (: N)) X)) *G ) )
(de horizontal ()
(mapcar '((This) (make (while This (when (: N) (link @)) (setq This (east This)) ) ) ) (car *G) ) )
(de finish? ()
(nor (fish '((This) (when (atom This) (= NIL (: N))) ) *G ) (find '((L) (find '((This) (when (: N) (find '((D) (= (: N) (get (D This) 'N)) ) (quote north south west east) ) ) ) L ) ) *G ) ) )
(de board (D)
(space 3) (prin '+) (for I G (prin (if (D (car I)) " +" "---+")) ) (prinl) )
(de display ()
(let G (mapcar reverse *G) (board north) (while (caar G) (space 3) (prin '|) (for I G (with (car I) (prin (if (: N) (align 3 (: N)) " ") (if (east This) " " '|) ) ) ) (prinl) (board south) (map pop G) ) (do 2 (prinl) ) ) )
(do 2
(cell) )
(display) (loop
(case (pack (make (link (key)) (while (key 100) (link @) ) ) ) ("^[[D" #left (redraw (summ (horizontal)) '(a1 a2 a3 a4) east) ) ("^[[C" #rigth (redraw (summ (mapcar reverse (horizontal))) '(d1 d2 d3 d4) west) ) ("^[[B" #down (redraw (summ (vertical)) '(a1 b1 c1 d1) north) ) ("^[[A" #up (redraw (summ (mapcar reverse (vertical))) '(a4 b4 c4 d4) south) ) ) (when (diff *D (fish '((This) (: N)) *G)) (cell) ) (display) (T (finish?) (println 'Finish)) (T (fish '((This) (= 512 (: N))) *G) (println 'Maximum) ) )
(bye)</lang>
Python
<lang python>
- !/usr/bin/env python3
import curses from random import randrange, choice # generate and place new tile from collections import defaultdict
letter_codes = [ord(ch) for ch in 'WASDRQwasdrq'] actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit'] actions_dict = dict(zip(letter_codes, actions * 2))
def get_user_action(keyboard): char = "N" while char not in actions_dict: char = keyboard.getch() return actions_dict[char]
def transpose(field): return [list(row) for row in zip(*field)]
def invert(field): return [row[::-1] for row in field]
class GameField(object): def __init__(self, height=4, width=4, win=2048): self.height = height self.width = width self.win_value = 2048 self.score = 0 self.highscore = 0 self.reset()
def reset(self): if self.score > self.highscore: self.highscore = self.score self.score = 0 self.field = [[0 for i in range(self.width)] for j in range(self.height)] self.spawn() self.spawn()
def move(self, direction): def move_row_left(row): def tighten(row): # squeese non-zero elements together new_row = [i for i in row if i != 0] new_row += [0 for i in range(len(row) - len(new_row))] return new_row
def merge(row): pair = False new_row = [] for i in range(len(row)): if pair: new_row.append(2 * row[i]) self.score += 2 * row[i] pair = False else: if i + 1 < len(row) and row[i] == row[i + 1]: pair = True new_row.append(0) else: new_row.append(row[i]) assert len(new_row) == len(row) return new_row return tighten(merge(tighten(row)))
moves = {} moves['Left'] = lambda field: \ [move_row_left(row) for row in field] moves['Right'] = lambda field: \ invert(moves['Left'](invert(field))) moves['Up'] = lambda field: \ transpose(moves['Left'](transpose(field))) moves['Down'] = lambda field: \ transpose(moves['Right'](transpose(field)))
if direction in moves: if self.move_is_possible(direction): self.field = moves[direction](self.field) self.spawn() return True else: return False
def is_win(self): return any(any(i >= self.win_value for i in row) for row in self.field)
def is_gameover(self): return not any(self.move_is_possible(move) for move in actions)
def draw(self, screen): help_string1 = '(W)Up (S)Down (A)Left (D)Right' help_string2 = ' (R)Restart (Q)Exit' gameover_string = ' GAME OVER' win_string = ' YOU WIN!' def cast(string): screen.addstr(string + '\n')
def draw_hor_separator(): top = '┌' + ('┬──────' * self.width + '┐')[1:] mid = '├' + ('┼──────' * self.width + '┤')[1:] bot = '└' + ('┴──────' * self.width + '┘')[1:] separator = defaultdict(lambda: mid) separator[0], separator[self.height] = top, bot if not hasattr(draw_hor_separator, "counter"): draw_hor_separator.counter = 0 cast(separator[draw_hor_separator.counter]) draw_hor_separator.counter += 1
def draw_row(row): cast(.join('│{: ^5} '.format(num) if num > 0 else '| ' for num in row) + '│')
screen.clear() cast('SCORE: ' + str(self.score)) if 0 != self.highscore: cast('HGHSCORE: ' + str(self.highscore)) for row in self.field: draw_hor_separator() draw_row(row) draw_hor_separator() if self.is_win(): cast(win_string) else: if self.is_gameover(): cast(gameover_string) else: cast(help_string1) cast(help_string2)
def spawn(self): new_element = 4 if randrange(100) > 89 else 2 (i,j) = choice([(i,j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0]) self.field[i][j] = new_element
def move_is_possible(self, direction): def row_is_left_movable(row): def change(i): # true if there'll be change in i-th tile if row[i] == 0 and row[i + 1] != 0: # Move return True if row[i] != 0 and row[i + 1] == row[i]: # Merge return True return False return any(change(i) for i in range(len(row) - 1))
check = {} check['Left'] = lambda field: \ any(row_is_left_movable(row) for row in field)
check['Right'] = lambda field: \ check['Left'](invert(field))
check['Up'] = lambda field: \ check['Left'](transpose(field))
check['Down'] = lambda field: \ check['Right'](transpose(field))
if direction in check: return check[direction](self.field) else: return False
def main(stdscr): curses.use_default_colors() game_field = GameField(win=32) state_actions = {} # Init, Game, Win, Gameover, Exit def init(): game_field.reset() return 'Game'
state_actions['Init'] = init
def not_game(state): game_field.draw(stdscr) action = get_user_action(stdscr) responses = defaultdict(lambda: state) responses['Restart'], responses['Exit'] = 'Init', 'Exit' return responses[action]
state_actions['Win'] = lambda: not_game('Win') state_actions['Gameover'] = lambda: not_game('Gameover')
def game(): game_field.draw(stdscr) action = get_user_action(stdscr) if action == 'Restart': return 'Init' if action == 'Exit': return 'Exit' if game_field.move(action): # move successful if game_field.is_win(): return 'Win' if game_field.is_gameover(): return 'Gameover' return 'Game'
state_actions['Game'] = game
state = 'Init' while state != 'Exit': state = state_actions[state]()
curses.wrapper(main) </lang>
R
orginal R package : https://github.com/ThinkRstat/r2048 <lang R> GD <- function(vec) {
c(vec[vec != 0], vec[vec == 0])
} DG <- function(vec) {
c(vec[vec == 0], vec[vec != 0])
}
DG_ <- function(vec, v = TRUE) {
if (v) print(vec) rev(GD_(rev(vec), v = FALSE))
}
GD_ <- function(vec, v = TRUE) {
if (v) { print(vec) } vec2 <- GD(vec) # on cherche les 2 cote a cote pos <- which(vec2 == c(vec2[-1], 9999)) # put pas y avoir consécutif dans pos pos[-1][which(abs(pos - c(pos[-1], 999)) == 1)] av <- which(c(0, c(pos[-1], 9) - pos) == 1) if (length(av) > 0) { pos <- pos[-av] } vec2[pos] <- vec2[pos] + vec2[pos + 1] vec2[pos + 1] <- 0 GD(vec2)
}
H_ <- function(base) {
apply(base, MARGIN = 2, FUN = GD_, v = FALSE)
} B_ <- function(base) {
apply(base, MARGIN = 2, FUN = DG_, v = FALSE)
} G_ <- function(base) {
t(apply(base, MARGIN = 1, FUN = GD_, v = FALSE))
} D_ <- function(base) {
t(apply(base, MARGIN = 1, FUN = DG_, v = FALSE))
}
H <- function(base) {
apply(base, MARGIN = 2, FUN = GD, v = FALSE)
} B <- function(base) {
apply(base, MARGIN = 2, FUN = DG, v = FALSE)
} G <- function(base) {
t(apply(base, MARGIN = 1, FUN = GD, v = FALSE))
} D <- function(base) {
t(apply(base, MARGIN = 1, FUN = DG, v = FALSE))
}
add2or4 <- function(base, p = 0.9) {
lw <- which(base == 0) if (length(lw) > 1) { tirage <- sample(lw, 1) } else { tirage <- lw } base[tirage] <- sample(c(2, 4), 1, prob = c(p, 1 - p)) base
} print.dqh <- function(base) {
cat("\n\n") for (i in 1:nrow(base)) { cat(paste(" ", base[i, ], " ")) cat("\n") } cat("\n")
}
- -*- 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") }
}
</lang>
Ruby
inspired by the Perl6 version <lang ruby>
- !/usr/bin/ruby
require 'io/console'
class Board
def initialize size=4, win_limit=2048, cell_width = 6 @size = size; @cw = cell_width; @win_limit = win_limit @board = Array.new(size) {Array.new(size, 0)} @moved = true; @score = 0; @no_more_moves = false spawn end
def draw print "\n\n" if @r_vert print ' ' if @r_hori print '┌' + (['─' * @cw] * @size).join('┬') + '┐' @board.each do |row| print "\n" formated = row.map {|num| num == 0 ? ' ' * @cw : format(num)} print ' ' if @r_hori puts '│' + formated.join('│') + '│' print ' ' if @r_hori print '├' + ([' ' * @cw] * @size).join('┼') + '┤' end print "\r" print ' ' if @r_hori puts '└' + (['─' * @cw] * @size).join('┴') + '┘' end
def move direction case direction when :up @board = column_map {|c| logic(c)} @r_vert = false if $rumble when :down @board = column_map {|c| logic(c.reverse).reverse} @r_vert = true if $rumble when :left @board = row_map {|r| logic(r)} @r_hori = false if $rumble when :right @board = row_map {|r| logic(r.reverse).reverse} @r_hori = true if $rumble end spawn @moved = false end
def print_score puts "Your Score is #@score." puts "Congratulations, you have won!" if to_enum.any? {|e| e >= @win_limit} end
def no_more_moves?; @no_more_moves; end def won?; to_enum.any? {|e| e >= @win_limit}; end def reset!; initialize @size, @win_limit, @cw; end
private
def set x, y, val @board[y][x] = val end
def spawn free_pos = to_enum.select{|elem,x,y| elem == 0}.map{|_,x,y| [x,y]} unless free_pos.empty? set *free_pos.sample, rand > 0.1 ? 2 : 4 if @moved else snap = @board unless @stop @stop = true %i{up down left right}.each{|s| move(s)} @no_more_moves = true if snap.flatten == @board.flatten @board = snap @stop = false end end end
def logic list jump = false result = list.reduce([]) do |res, val| if res.last == val && !jump
res[-1] += val @score += val
jump = true elsif val != 0
res.push val
jump = false end res end result += [0] * (@size - result.length) @moved ||= list != result result end
def column_map xboard = @board.transpose xboard.map!{|c| yield c } xboard.transpose end
def row_map @board.map {|r| yield r } end
def to_enum @enum ||= Enumerator.new(@size * @size) do |yielder| (@size*@size).times do |i|
yielder.yield (@board[i / @size][i % @size]), (i % @size), (i / @size )
end end @enum.rewind end
def format(num) if $color cstart = "\e[" + $colors[Math.log(num, 2)] + "m" cend = "\e[0m" else cstart = cend = "" end cstart + num.to_s.center(@cw) + cend end
end
$color = true $colors = %W{0 1;97 1;93 1;92 1;96 1;91 1;95 1;94 1;30;47 1;43 1;42 1;46 1;41 1;45 1;44 1;33;43 1;33;42 1;33;41 1;33;44} $rumble = false
$check_score = true unless ARGV.empty?
puts "Usage: #$0 [gridsize] [score-threshold] [padwidth] [--no-color] [--rumble]"; exit if %W[-h --help].include?(ARGV[0]) args = ARGV.map(&:to_i).reject{|n| n == 0} b = Board.new(*args) unless args.empty? $rumble = true if ARGV.any?{|a| a =~ /rumble/i } $color = false if ARGV.any?{|a| a =~ /no.?color/i}
end
b ||= Board.new puts "\e[H\e[2J" b.draw puts "Press h for help, q to quit" loop do
input = STDIN.getch if input == "\e" 2.times {input << STDIN.getch} end
case input when "\e[A", "w" then b.move(:up) when "\e[B", "a" then b.move(:down) when "\e[C", "s" then b.move(:right) when "\e[D", "d" then b.move(:left) when "q","\u0003","\u0004" then b.print_score; exit
when "h" puts <<-EOM.gsub(/^\s*/, ) ┌─ ─┐ │Use the arrow-keys or WASD on your keyboard to push board in the given direction. │Tiles with the same number merge into one. │Get a tile with a value of #{ARGV[1] || 2048} to win. │In case you cannot move or merge any tiles anymore, you loose. │You can start this game with different settings by providing commandline argument: │For instance: │ %> #$0 6 8192 --rumble └─ ─┘ PRESS q TO QUIT (or Ctrl-C or Ctrl-D) EOM input = STDIN.getch end
puts "\e[H\e[2J" b.draw
if b.no_more_moves? or $check_score && b.won? b.print_score if b.no_more_moves? puts "No more moves possible" puts "Again? (y/n)" exit if STDIN.gets.chomp.downcase == "n" $check_score = true b.reset! puts "\e[H\e[2J" b.draw else puts "Continue? (y/n)" exit if STDIN.gets.chomp.downcase == "n" $check_score = false puts "\e[H\e[2J" b.draw end end
end </lang>
Rust
Text mode
A simple implementation in rust. The user has to input an endline since i did not find a way to read a key press <lang rust> use std::io::{self,BufRead}; extern crate rand;
enum Usermove {
Up, Down, Left, Right,
}
fn print_game(field :& [[u32;4];4] ){
println!("{:?}",&field[0] ); println!("{:?}",&field[1] ); println!("{:?}",&field[2] ); println!("{:?}",&field[3] );
}
fn get_usermove()-> Usermove {
let umove: Usermove ; loop{ let mut input = String::new(); io::stdin().read_line(&mut input).unwrap();
match input.chars().nth(0){ Some('a') =>{umove = Usermove::Left ;break }, Some('w') =>{umove = Usermove::Up ;break }, Some('s') =>{umove = Usermove::Down ;break }, Some('d') =>{umove = Usermove::Right;break }, _ => {println!("input was {}: invalid character should be a,s,w or d ",input.chars().nth(0).unwrap());} , } } umove
}
//this function inplements the user moves. //for every element it looks if the element is zero // if the element is zero it looks against the direction of the movement if any //element is not zero then it will move it to the element its place then it will look for //a matching element // if the element is not zero then it will look for a match if no match is found // then it will look for the next element
fn do_game_step(step : &Usermove, field:&mut [[u32;4];4]){
match *step { Usermove::Left =>{ for array in field{ for col in 0..4 { for testcol in (col+1)..4 { if array[testcol] != 0 { if array[col] == 0 { array[col] += array[testcol]; array[testcol] = 0; } else if array[col] == array[testcol] { array[col] += array[testcol]; array[testcol] = 0; break; } else { break } } } } } } , Usermove::Right=>{ for array in field{ for col in (0..4).rev() { for testcol in (0..col).rev() { if array[testcol] != 0 { if array[col] == 0 { array[col] += array[testcol]; array[testcol] = 0; } else if array[col] == array[testcol] { array[col] += array[testcol]; array[testcol] = 0; break; }else { break; } } } } } } , Usermove::Down =>{ for col in 0..4 { for row in (0..4).rev() { for testrow in (0..row).rev() { if field[testrow][col] != 0 { if field[row][col] == 0 { field[row][col] += field[testrow][col]; field[testrow][col] = 0; } else if field[row][col] == field[testrow][col] { field[row][col] += field[testrow][col]; field[testrow][col] = 0; break; }else { break; }
} } } } } , Usermove::Up =>{ for col in 0..4 { for row in 0..4{ for testrow in (row+1)..4 { if field[testrow][col] != 0 { if field[row][col] == 0 { field[row][col] += field[testrow][col]; field[testrow][col] = 0; } else if field[row][col] == field[testrow][col] { field[row][col] += field[testrow][col]; field[testrow][col] = 0; break; }else { break; } } } } } }, }
}
fn spawn( field: &mut [[u32;4];4]){
loop{ let x = rand::random::<usize>(); if field[x % 4][(x/4)%4] == 0 { if x % 10 == 0 { field[x % 4][(x/4)%4]= 4; }else{ field[x % 4][(x/4)%4]= 2; } break; } }
}
fn main() {
let mut field : [[u32; 4];4] = [[0;4];4]; let mut test : [[u32; 4];4] ; 'gameloop:loop { //check if there is still an open space test=field.clone(); spawn(&mut field); //if all possible moves do not yield a change then there is no valid move left //and it will be game over for i in [Usermove::Up,Usermove::Down,Usermove::Left,Usermove::Right].into_iter(){ do_game_step(i, &mut test); if test != field{ break;//found a valid move } match *i{ Usermove::Right=> { println!("No more valid move, you lose"); break 'gameloop; }, _=>{}, } } print_game(&field); println!("move the blocks");
test=field.clone(); while test==field { do_game_step(&get_usermove(), &mut field); }
for row in field.iter(){ if row.iter().any(|x| *x == 2048){ print_game(&field ); println!("You Won!!"); break; } } }
} </lang>
Tcl
Text mode
<lang tcl>
- 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 </lang>
Tk
XPL0
<lang XPL0>include c:\cxpl\codes; \intrinsic 'code' declarations int Box(16), Moved;
proc ShiftTiles(I0, DI); \Shift tiles, add adjacents, shift again int I0, DI; int Done, M, N, I; [Done:= false; loop [for M:= 1 to 3 do \shift all tiles in a single row or column
[I:= I0; for N:= 1 to 3 do [if Box(I)=0 & Box(I+DI)#0 then [Box(I):= Box(I+DI); Box(I+DI):= 0; Moved:= true]; I:= I+DI; ]; ]; if Done then return; Done:= true; I:= I0; \add identical adjacent tiles into a new tile for N:= 1 to 3 do [if Box(I)=Box(I+DI) & Box(I)#0 then [Box(I):= Box(I)+1; Box(I+DI):= 0; Moved:= true]; I:= I+DI; ]; ]; \loop back to close any gaps that were opened
]; \ShiftTiles
int I, J, X, Y, C; [Clear; for I:= 0 to 15 do Box(I):= 0; \empty the box of tiles loop [repeat I:= Ran(16) until Box(I)=0; \in a random empty location
Box(I):= if Ran(10) then 1 else 2; \insert a 2^1=2 or 2^2=4 for I:= 0 to 15 do \show board with its tiles [X:= ((I&3)+5)*6; \get coordinates of tile Y:= I>>2*3+6; Attrib(((Box(I)+1)&7)<<4 + $F);\set color based on tile value for J:= 0 to 2 do \draw a square (6*8x3*16) [Cursor(X, Y+J); Text(6, " "); ]; if Box(I)#0 then \box contains a tile [J:= 1; \center numbers somewhat if Box(I) <= 9 then J:= 2; if Box(I) <= 3 then J:= 3; Cursor(X+J, Y+1); IntOut(6, 1<<Box(I)); ]; ]; Moved:= false; \a tile must move to continue repeat repeat C:= ChIn(1) until C#0; \get key scan code, or ASCII for I:= 3 downto 0 do \for all rows or columns [case C of $4B: ShiftTiles(I*4, 1); \left arrow $4D: ShiftTiles(I*4+3, -1); \right arrow $50: ShiftTiles(I+12, -4); \down arrow $48: ShiftTiles(I, 4); \up arrow $1B: [Clear; exit] \Esc other []; \ignore all other keys ]; until Moved; ];
]</lang>