2048
Implement a 2D sliding block puzzle game where blocks with numbers are combined to add their values.
You are encouraged to solve this task according to the task description, using any language you may know.
The rules are that each turn the player must perform a valid move shifting all tiles in one direction (up, down, left or right). A move is valid when at least one tile can be moved in that direction. When moved against each other tiles with the same number on them combine into one. A new tile with the value of 2 is spawned at the end of each turn if there is an empty spot for it. To win the player must create a tile with the number 2048. The player loses if no valid moves are possible.
The name comes from the popular open-source implementation of this game mechanic, 2048.
Requirements:
- "Non-greedy" movement. The tiles that were created by combining other tiles should not be combined again during the same turn (move). That is to say that moving the tile row of
[2][2][2][2]
to the right should result in
......[4][4]
and not
.........[8]
- "Move direction priority". If more than one variant of combining is possible, move direction shows one that will take effect. For example, moving the tile row of
...[2][2][2]
to the right should result in
......[2][4]
and not
......[4][2]
- Adding a new tile on a blank space. Most of the time new "2" is to be added and occasionally (10% of the time) - "4"
- Check for valid moves. The player shouldn't be able to skip their turn by trying a move that doesn't change the board.
- Win condition.
- Lose condition.
Batch File
<lang dos>::2048 Game Task from RosettaCode.org
- Batch File Implementation
@echo off setlocal enabledelayedexpansion cls
- begin_game
%== Set variables ==% set "score=0" set "won=0" set "SUP_score=0" for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do set /a "X_%%A%%B=0"
call :addtile call :addtile
%== Main game loop ==%
- main_loop
set "changed=0" call :display echo( echo Keys: WASD (Slide Movement^), N (New game^), P (Exit^)
%== Get Keypress ==% set "key=" for /f "delims=" %%? in ('xcopy /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%?" set "key=%key:~-1%"
%== Process keypress ==% if /i "!key!"=="W" ( for /l %%? in (1,1,4) do call :slide X_1%%? X_2%%? X_3%%? X_4%%? ) if /i "!key!"=="A" ( for /l %%? in (1,1,4) do call :slide X_%%?1 X_%%?2 X_%%?3 X_%%?4 ) if /i "!key!"=="S" ( for /l %%? in (1,1,4) do call :slide X_4%%? X_3%%? X_2%%? X_1%%? ) if /i "!key!"=="D" ( for /l %%? in (1,1,4) do call :slide X_%%?4 X_%%?3 X_%%?2 X_%%?1 ) if /i "!key!"=="N" goto :begin_game if /i "!key!"=="P" exit /b
%== Check if the board changed ==% if %changed% neq 0 call :addtile
%== Check if already won ==% if %won% equ 1 ( set "msg=Nice one... You WON^!^!" goto :gameover )
%== Check for lose condition ==% set /a "real_blanks=blank_count-1" if %real_blanks% leq 0 ( for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do set "TRY_%%A%%B=!X_%%A%%B!" set "TRY_changed=%changed%" & set "changed=0" set "SUP_score=1" for /l %%? in (1,1,4) do call :slide TRY_%%?1 TRY_%%?2 TRY_%%?3 TRY_%%?4 for /l %%? in (1,1,4) do call :slide TRY_1%%? TRY_2%%? TRY_3%%? TRY_4%%? if !changed! equ 0 ( set "msg=No moves are possible... Game Over :(" goto :gameover ) else (set "changed=!TRY_changed!" & set "SUP_score=0") ) goto main_loop
- ~~~~~~~~~~~~~~~~~~~~ Sub Procedures ~~~~~~~~~~~~~~~~~~~~::
%== Game Over xD ==%
- gameover
call :display echo( echo(!msg! echo( echo(Keys: N (New game^), P (Exit^)
- key_loop
set "key=" for /f "delims=" %%? in ('xcopy /w "%~f0" "%~f0" 2^>nul') do if not defined key set "key=%%?" set "key=%key:~-1%" if /i "!key!"=="N" goto :begin_game if /i "!key!"=="P" exit /b goto :key_loop
%== The main slider of numbers in tiles ==%
- slide
set "next=" set "slide_1=" set "slide_2=" for %%? in (%*) do if !%%?! neq 0 set "slide_1=!slide_1! !%%?!" for %%? in (!slide_1!) do ( set "scan=%%?" if "!scan!"=="!next!" ( set /a "next*=2" if !SUP_score! equ 0 set /a "score+=!next!" %== WINNING CONDITION!!! ==% if "!next!" equ "2048" set "won=1" set "scan=" ) set "slide_2=!slide_2! !next!" set "next=!scan!" ) set "slide_2=!slide_2! !next!" for /l %%? in (1,1,4) do set "final_%%?=0" set "cnt=0" & for %%? in (!slide_2!) do if !cnt! lss 4 ( set /a "cnt+=1" set "final_!cnt!=%%?" ) if not "!%1!!%2!!%3!!%4!"=="!final_1!!final_2!!final_3!!final_4!" set "changed=1" set "cnt=0" & for %%? in (%*) do ( set /a "cnt+=1" set /a "%%?=final_!cnt!" ) goto :EOF
%== Add number to tile ==%
- addtile
set "blank_list=" set "blank_count=0" for /l %%A in (1,1,4) do for /l %%B in (1,1,4) do ( if !X_%%A%%B! equ 0 ( set "blank_list=!blank_list!X_%%A%%B" set /a blank_count+=1 ) ) set /a "pick_tile=(%random% %% %blank_count%)*4" set /a "rnd=%random%%%10+1" set "tile_new=!blank_list:~%pick_tile%,4!" if %rnd%==5 (set !tile_new!=4) else (set !tile_new!=2) goto :EOF
%== Display the table ==%
- display
cls echo 2048 Game in Batch echo( for /l %%A in (1,1,4) do ( for /l %%B in (1,1,4) do ( set "DX_%%A%%B=!X_%%A%%B!" if !tile_new!==X_%%A%%B (set "DX_%%A%%B= +!X_%%A%%B!") else ( if !X_%%A%%B! lss 1000 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! lss 100 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! lss 10 set "DX_%%A%%B= !DX_%%A%%B!" if !X_%%A%%B! equ 0 set "DX_%%A%%B= " ) ) echo +----+----+----+----+ echo ^|!DX_%%A1!^|!DX_%%A2!^|!DX_%%A3!^|!DX_%%A4!^| ) echo +----+----+----+----+ echo( echo Score: %score% goto :EOF</lang>
- Output:
2048 Game in Batch +----+----+----+----+ | | +2| | | +----+----+----+----+ | 4| | | | +----+----+----+----+ | 4| | | | +----+----+----+----+ | 16| 4| | 2| +----+----+----+----+ Score: 60 Keys: WASD (Slide Movement), N (New game), P (Exit)
C
Supports limited colours through vt100 escape codes. Requires a posix machine for termios.h and unistd.h> headers. Provides simplistic animations when moving and merging blocks. <lang c>
- 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; default: found_valid_key = 0; break; } } while (!found_valid_key);
do_tick(direction); if (game.have_moved != 0){ do_newblock(); } do_draw();
switch (do_check_end_condition()) { case -1: goto game_lose; case 1: goto game_win; case 0: break; } }
if (0)
game_lose: printf("You lose!\n");
if (0)
game_win: printf("You win!\n");
if (0)
game_quit:
/* Restore terminal settings */ tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return 0;
} </lang>
- Output:
Score: 1100 (+4) ------------------------- | 64 | 32 | 64 | 32 | | 32 | 16 | 2 | 8 | | 16 | 4 | 8 | 4 | | 4 | 2 | 4 | 2 | ------------------------- You lose!
C++
<lang cpp>
- 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 @goal 2048 def play, do: setup |> play defp play(board) 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), else: add_tile(moved) |> play 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 Enum.filter_map(board, fn {_,v} -> v==0 end, fn {k,_} -> k end) 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(@size-1..0, [], fn j,acc -> [Enum.map(@range, fn i -> board[{i,j}] end) | acc] end) |> move_and_combine |> Enum.with_index |> Enum.reduce(%{}, fn {row,j},acc -> Enum.with_index(row) |> Enum.reduce(acc, fn {v,i},map -> Map.put(map, {i,j}, v) end) end) end defp move(board, :down) do Enum.reduce(@size-1..0, [], fn j,acc -> [Enum.map(@size-1..0, fn i -> board[{i,j}] end) | acc] end) |> move_and_combine |> Enum.with_index |> Enum.reduce(%{}, fn {row,j},acc -> Enum.with_index(row) |> Enum.reduce(acc, fn {v,i},map -> Map.put(map, {@size-i-1,j}, v) end) end) end defp move(board, :left) do Enum.reduce(@size-1..0, [], fn i,acc -> [Enum.map(@range, fn j -> board[{i,j}] end) | acc] end) |> move_and_combine |> Enum.with_index |> Enum.reduce(%{}, fn {row,i},acc -> Enum.with_index(row) |> Enum.reduce(acc, fn {v,j},map -> Map.put(map, {i,j}, v) end) end) end defp move(board, :right) do Enum.reduce(@size-1..0, [], fn i,acc -> [Enum.map(@size-1..0, fn j -> board[{i,j}] end) | acc] end) |> move_and_combine |> Enum.with_index |> Enum.reduce(%{}, fn {row,i},acc -> Enum.with_index(row) |> Enum.reduce(acc, fn {v,j},map -> Map.put(map, {i,@size-j-1}, v) end) end) end defp move_and_combine(tiles) do Enum.map(tiles, fn row -> (Enum.filter(row, &(&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) 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 board end
end
Game2048.play</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> package com.sbg.g2048.stateless
import java.io.BufferedReader import java.io.InputStreamReader
const val positiveGameOverMessage = "So sorry, but you won the game." const val negativeGameOverMessage = "So sorry, but you lost the game."
fun main(args: Array<String>) {
val grid = arrayOf( arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0), arrayOf(0, 0, 0, 0) )
val gameOverMessage = run2048(grid) println(gameOverMessage)
}
fun run2048(grid: Array<Array<Int>>): String {
if (isGridSolved(grid)) return positiveGameOverMessage else if (isGridFull(grid)) return negativeGameOverMessage
val populatedGrid = spawnNumber(grid) display(populatedGrid)
return run2048(manipulateGrid(populatedGrid, waitForValidInput()))
}
fun isGridSolved(grid: Array<Array<Int>>): Boolean = grid.any { row -> row.contains(2048) } fun isGridFull(grid: Array<Array<Int>>): Boolean = grid.all { row -> !row.contains(0) }
fun spawnNumber(grid: Array<Array<Int>>):Array<Array<Int>> {
val coordinates = locateSpawnCoordinates(grid) val number = generateNumber()
return updateGrid(grid, coordinates, number)
}
fun locateSpawnCoordinates(grid: Array<Array<Int>>): Pair<Int, Int> {
val emptyCells = arrayListOf<Pair<Int, Int>>() grid.forEachIndexed { x, row -> row.forEachIndexed { y, cell -> if (cell == 0) emptyCells.add(Pair(x, y)) } }
return emptyCells[(Math.random() * (emptyCells.size()-1)).toInt()]
} fun generateNumber(): Int = if (Math.random() > 0.10) 2 else 4
fun updateGrid(grid: Array<Array<Int>>, at: Pair<Int, Int>, value: Int): Array<Array<Int>> {
val updatedGrid = grid.copyOf() updatedGrid[at.first][at.second] = value return updatedGrid
}
fun waitForValidInput():String {
val input = waitForInput() return if (isValidInput(input)) input else waitForValidInput()
} fun isValidInput(input: String): Boolean = arrayOf("a", "s", "d", "w").contains(input)
fun waitForInput(): String {
val br = BufferedReader(InputStreamReader(System.`in`)); println("Direction? ") return br.readLine()
}
fun manipulateGrid(grid: Array<Array<Int>>, input: String): Array<Array<Int>> = when (input) {
"a" -> shiftCellsLeft(grid) "s" -> shiftCellsDown(grid) "d" -> shiftCellsRight(grid) "w" -> shiftCellsUp(grid) else -> throw IllegalArgumentException("Expected one of [a, s, d, w]")
}
fun shiftCellsLeft(grid: Array<Array<Int>>): Array<Array<Int>> =
grid.map { row -> mergeAndOrganizeCells(row) }.toTypedArray()
fun shiftCellsRight(grid: Array<Array<Int>>): Array<Array<Int>> =
grid.map { row -> mergeAndOrganizeCells(row.reversed().toTypedArray()).reversed().toTypedArray() }.toTypedArray()
fun shiftCellsUp(grid: Array<Array<Int>>): Array<Array<Int>> {
val rows: Array<Array<Int>> = arrayOf( arrayOf(grid[0][0], grid[1][0], grid[2][0], grid[3][0]), arrayOf(grid[0][1], grid[1][1], grid[2][1], grid[3][1]), arrayOf(grid[0][2], grid[1][2], grid[2][2], grid[3][2]), arrayOf(grid[0][3], grid[1][3], grid[2][3], grid[3][3]) )
val updatedGrid = grid.copyOf()
rows.map { row -> mergeAndOrganizeCells(row) }.forEachIndexed { rowIdx, row -> updatedGrid[0][rowIdx] = row[0] updatedGrid[1][rowIdx] = row[1] updatedGrid[2][rowIdx] = row[2] updatedGrid[3][rowIdx] = row[3] }
return updatedGrid
}
fun shiftCellsDown(grid: Array<Array<Int>>): Array<Array<Int>> {
val rows: Array<Array<Int>> = arrayOf( arrayOf(grid[3][0], grid[2][0], grid[1][0], grid[0][0]), arrayOf(grid[3][1], grid[2][1], grid[1][1], grid[0][1]), arrayOf(grid[3][2], grid[2][2], grid[1][2], grid[0][2]), arrayOf(grid[3][3], grid[2][3], grid[1][3], grid[0][3]) )
val updatedGrid = grid.copyOf()
rows.map { row -> mergeAndOrganizeCells(row) }.forEachIndexed { rowIdx, row -> updatedGrid[3][rowIdx] = row[0] updatedGrid[2][rowIdx] = row[1] updatedGrid[1][rowIdx] = row[2] updatedGrid[0][rowIdx] = row[3] }
return updatedGrid
}
fun mergeAndOrganizeCells(row: Array<Int>): Array<Int> = organize(merge(row.copyOf()))
fun merge(row: Array<Int>, idxToMatch: Int = 0, idxToCompare: Int = 1): Array<Int> {
if (idxToMatch >= row.size) return row if (idxToCompare >= row.size) return merge(row, idxToMatch + 1, idxToMatch + 2) if (row[idxToMatch] == 0) return merge(row, idxToMatch + 1, idxToMatch + 2)
if (row[idxToMatch] == row[idxToCompare]) { row[idxToMatch] *= 2 row[idxToCompare] = 0
return merge(row, idxToMatch + 1, idxToMatch + 2) } else { return if (row[idxToCompare] != 0) merge(row, idxToMatch + 1, idxToMatch + 2) else merge(row, idxToMatch, idxToCompare + 1) }
}
fun organize(row: Array<Int>, idxToMatch: Int = 0, idxToCompare: Int = 1): Array<Int> {
if (idxToMatch >= row.size) return row if (idxToCompare >= row.size) return organize(row, idxToMatch + 1, idxToMatch + 2) if (row[idxToMatch] != 0) return organize(row, idxToMatch + 1, idxToMatch + 2)
if (row[idxToCompare] != 0) { row[idxToMatch] = row[idxToCompare] row[idxToCompare] = 0
return organize(row, idxToMatch + 1, idxToMatch + 2) } else { return organize(row, idxToMatch, idxToCompare + 1) }
}
fun display(grid: Array<Array<Int>>) {
val prettyPrintableGrid = grid.map { row -> row.map { cell -> if (cell == 0) " " else java.lang.String.format("%4d", cell) } }
println("New Grid:") prettyPrintableGrid.forEach { row -> println("+----+----+----+----+") row.forEach { print("|$it") } println("|") } println("+----+----+----+----+")
}</lang>
Sample output:
New Grid: +----+----+----+----+ | 2| | | | +----+----+----+----+ | | | | 2| +----+----+----+----+ | 4| 16| | | +----+----+----+----+ | 16| 4| 2| | +----+----+----+----+ Direction?
Perl 6
Uses termios to set the terminal options, so only compatible with POSIX terminals. This version does not include a specific "win" or "lose" condition. (though it would be trivial to add them.) You can continue to play this even after getting a 2048 tile; and if there is no valid move you can make, you can't do anything but quit.
<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>inc c:\cxpl\codes; \intrinsic 'code' declarations int Board(4,4), Score, Best, Moved; def Esc=$1B, UpArrow=$48, DnArrow=$50, LtArrow=$4B, RtArrow=$4D; \scan codes
proc DrawTile(X0, Y0, Power);\Draw a numbered tile or empty location
int X0, Y0, Power; \character coordinates and tile's power of 2
int Y;
[Attrib((rem(Power/7)+1)<<4); \color attribute: $10, $20 ... $60, $70, $10 ...
for Y:= Y0 to Y0+2 do
[Cursor(X0, Y); Text(6, " ")];
if Power > 0 then \display tile's number
[Cursor(X0+1, Y0+1); if Power <= 9 then ChOut(6, ^ );\center numbers, somewhat if Power <= 3 then ChOut(6, ^ ); IntOut(6, 1<<Power); \2^Power ];
]; \DrawTile
proc MoveTiles(X0, Y0, DX, DY); \Shift tiles, add adjacents, shift again
int X0, Y0, DX, DY;
int M, N, X, Y;
[for M:= 1 to 3 do \shift all tiles in a single row or column
[X:= X0; Y:= Y0; for N:= 1 to 3 do [if Board(X,Y)=0 & Board(X+DX,Y+DY)#0 then [Board(X,Y):= Board(X+DX,Y+DY); Board(X+DX,Y+DY):= 0; Moved:= true]; X:= X+DX; Y:= Y+DY; ]; ];
X:= X0; Y:= Y0; \add identical adjacent tiles into a new tile for N:= 1 to 3 do
[if Board(X,Y)=Board(X+DX,Y+DY) & Board(X,Y)#0 then [Board(X,Y):= Board(X,Y)+1; Board(X+DX,Y+DY):= 0; Score:= Score + 1<<Board(X,Y); Moved:= true]; X:= X+DX; Y:= Y+DY; ];
X:= X0; Y:= Y0; \shift into any locations that were emptied for N:= 1 to 3 do
[if Board(X,Y) = 0 then [Board(X,Y):= Board(X+DX,Y+DY); Board(X+DX,Y+DY):= 0]; X:= X+DX; Y:= Y+DY; ];
]; \MoveTiles
proc PlayGame; \Play one game
int X, Y, C;
[Clear;
for Y:= 0 to 3 do \initialize board to empty
for X:= 0 to 3 do Board(X,Y):= 0;
Score:= 0; Moved:= true; \force first tile to be inserted
loop [for Y:= 0 to 3 do \is there an empty location?
for X:= 0 to 3 do if Board(X,Y) = 0 then Y:= 5; if Y = 4 then \no empty location found [Cursor(0, 14); Text(0, "Game Over"); quit]; if Moved then \insert a new tile (2^1=2 or 2^2=4) [repeat X:= Ran(4); Y:= Ran(4) until Board(X,Y) = 0; Board(X,Y):= if Ran(10) then 1 else 2];
for Y:= 0 to 3 do \draw board with its tiles for X:= 0 to 3 do DrawTile(X*6, Y*3+1, Board(X,Y));
Attrib($07); \display Score and legends Cursor(0, 0); Text(6, "Score: "); IntOut(6, Score); if Score > Best then Best:= Score; Cursor(13, 0); Text(6, "Best: "); IntOut(6, Best); Cursor(4, 13); Text(6, "^{ ^x ^y ^z Esc");
for Y:= 0 to 3 do \search for winning location (2^11 = 2048) for X:= 0 to 3 do if Board(X,Y) = 11 then [Cursor(0, 14); Text(0, "You Win!"); quit];
Moved:= false; repeat C:= ChIn(1) until C#0; case C of LtArrow: for Y:= 0 to 3 do MoveTiles(0, Y, +1, 0); RtArrow: for Y:= 0 to 3 do MoveTiles(3, Y, -1, 0); DnArrow: for X:= 0 to 3 do MoveTiles(X, 3, 0, -1); UpArrow: for X:= 0 to 3 do MoveTiles(X, 0, 0, +1); Esc, ^q, ^Q: [Clear; exit] other []; ]; \loop
X:= ChIn(1); \wait for keystroke before starting next game ]; \PlayGame
[Best:= 0;
loop PlayGame;
]</lang>