2048
Implement a 2D sliding block puzzle game where blocks with numbers are combined to add their values.
The rules are that each turn the player must perform a valid move shifting all tiles in one direction (up, down, left or right). A move is valid when at least one tile can be moved in that direction. When moved against each other tiles with the same number on them combine into one. A new tile with the value of 2 is spawned at the end of each turn if there is an empty spot for it. To win the player must create a tile with the number 2048. The player loses if no valid moves are possible.
The name comes from the popular open-source implementation of this game mechanic, 2048.
Requirements:
- "Non-greedy" movement. The tiles that were created by combining other tiles should not be combined again during the same turn (move). That is to say that moving the tile row of
[2][2][2][2]
to the right should result in
......[4][4]
and not
.........[8]
- "Move direction priority". If more than one variant of combining is possible, move direction shows one that will take effect. For example, moving the tile row of
...[2][2][2]
to the right should result in
......[2][4]
and not
......[4][2]
- Adding a new tile on a blank space. Most of the time new "2" is to be added and occasionally (10% of the time) - "4"
- Check for valid moves. The player shouldn't be able to skip their turn by trying a move that doesn't change the board.
- Win condition.
- Lose condition.
Batch File
This code uses XCOPY.EXE as key input. Check HERE for details. <lang dos>::
- 2048 Task from Rosetta Code Wiki
- Batch File Implementation
- Directly OPEN the Batch File to play.
- I think this code is slow in some systems and needs improvements...
@echo off title 2048 Game setlocal enabledelayedexpansion
- GENERATING A GAME
- begin
set score=0 set won= set lose= for %%a in (1,2,3,4) do (for %%b in (1,2,3,4) do set a%%a%%b=0) set list=a11a12a13a14a21a22a23a24a31a32a33a34a41a42a43a44 set blanktiles=16 call :addtile call :addtile
- /GENERATING A GAME
- MAIN GAME LOOP
- gameplay
call :display echo.Your Move: set "k=" for /F "usebackq delims=" %%L in (`xcopy /L /w "%~f0" "%~f0" 2^>NUL`) do (
if not defined k set "k=%%L"
) set k=%k:~-1% set moved=0&set list=&set blanktiles=0&set x1=&set test=0&set inp= if /i "!k!"=="n" (goto :begin) if /i "!k!"=="p" (exit) if /i "!k!"=="s" (set inp=1) if /i "!k!"=="w" (set inp=2) if /i "!k!"=="a" (set inp=3) if /i "!k!"=="d" (set inp=4) if not "!inp!"=="" (for %%a in (a1 a2 a3 a4) do (set x=%%a&call :proc%inp%)) if !won!==1 (set "msg=Congrats^! You SOLVED the 2048 puzzle^!"&goto :res) if !moved!==0 ( if !blanktiles!==0 ( if "!x1!"=="" ( set test=1&set lose=1 for %%a in (a1 a2 a3 a4) do (set x=%%a&call :adder3) if !lose!==1 (set "msg=Game Over due to Out of Moves... Sorry :("&goto :res) ) else ( set test=1&set lose=1 for %%a in (a1 a2 a3 a4) do (set x=%%a&call :adder1) if !lose!==1 (set "msg=Game Over due to Out of Moves... Sorry :("&goto :res) ) ) goto :gameplay ) call :addtile goto :gameplay
- /MAIN GAME LOOP
- ADDTILE FUNCTION
- addtile
set /a rnd1=(%random%%%%blanktiles%)*3 set /a rnd2=%random%%%10 set pick=!list:~%rnd1%,3! set %pick%=2 if %rnd2%==1 set %pick%=4 set list=!list:%pick%=! set /a blanktiles-=1 goto :EOF
- /ADDTILE FUNCTION
- PROCESSOR DOWN
- proc1
set cyc=4
- cyc1
if !%x%%cyc%!==0 ( set /a tm=%cyc%-1 for /l %%b in (!tm!,-1,1) do ( if not !%x%%%b!==0 ( set "%x%%cyc%=!%x%%%b!" set "%x%%%b=0" set moved=1 goto :break1 ) ) )
- break1
if %cyc%==2 (goto :adder1) else (set /a cyc-=1&goto :cyc1)
- adder1
if !%x%3!==!%x%4! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%4*=2 set %x%3=!%x%2! set %x%2=!%x%1! set %x%1=0 set /a "score+=!%x%4!" if !%x%4!==2048 (set won=1) if not !%x%4!==0 (set moved=1) ) if !%x%2!==!%x%3! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%3*=2 set %x%2=!%x%1! set %x%1=0 set /a "score+=!%x%3!" if !%x%3!==2048 (set won=1) if not !%x%3!==0 (set moved=1) ) else ( if !%x%1!==!%x%2! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%2*=2 set %x%1=0 set /a "score+=!%x%2!" if !%x%2!==2048 (set won=1) if not !%x%2!==0 (set moved=1) ) ) if !test!==0 ( if !%x%1!==0 (set list=!list!%x%1&set /a blanktiles+=1) if !%x%2!==0 (set list=!list!%x%2&set /a blanktiles+=1) if !%x%3!==0 (set list=!list!%x%3&set /a blanktiles+=1) if !%x%4!==0 (set list=!list!%x%4&set /a blanktiles+=1) ) goto :EOF
- /PROCESSOR DOWN
- PROCESSOR UP
- proc2
set cyc=1
- cyc2
if !%x%%cyc%!==0 ( set /a tm=%cyc%+1 for /l %%b in (!tm!,1,4) do ( if not !%x%%%b!==0 ( set "%x%%cyc%=!%x%%%b!" set "%x%%%b=0" set moved=1 goto :break2 ) ) )
- break2
if %cyc%==3 (goto :adder2) else (set /a cyc+=1&goto :cyc2)
- adder2
if !%x%1!==!%x%2! ( set /a %x%1*=2 set %x%2=!%x%3! set %x%3=!%x%4! set %x%4=0 set /a "score+=!%x%1!" if !%x%1!==2048 (set won=1) if not !%x%1!==0 (set moved=1) ) if !%x%2!==!%x%3! ( set /a %x%2*=2 set %x%3=!%x%4! set %x%4=0 set /a "score+=!%x%2!" if !%x%2!==2048 (set won=1) if not !%x%2!==0 (set moved=1) ) else ( if !%x%3!==!%x%4! ( set /a %x%3*=2 set %x%4=0 set /a "score+=!%x%3!" if !%x%3!==2048 (set won=1) if not !%x%3!==0 (set moved=1) ) ) if !%x%1!==0 (set list=!list!%x%1&set /a blanktiles+=1) if !%x%2!==0 (set list=!list!%x%2&set /a blanktiles+=1) if !%x%3!==0 (set list=!list!%x%3&set /a blanktiles+=1) if !%x%4!==0 (set list=!list!%x%4&set /a blanktiles+=1) goto :EOF
- /PROCESSOR UP
- PROCESSOR LEFT
- proc3
set x1=%x:~1,1% set x=%x:~0,1% set cyc=1
- cyc3
if !%x%%cyc%%x1%!==0 ( set /a tm=%cyc%+1 for /l %%b in (!tm!,1,4) do ( if not !%x%%%b%x1%!==0 ( set "%x%%cyc%%x1%=!%x%%%b%x1%!" set "%x%%%b%x1%=0" set moved=1 goto :break3 ) ) )
- break3
if %cyc%==3 (goto :adder3) else (set /a cyc+=1&goto :cyc3)
- adder3
if !test!==1 ( set x1=%x:~1,1% set x=%x:~0,1% ) if !%x%1%x1%!==!%x%2%x1%! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%1%x1%*=2 set %x%2%x1%=!%x%3%x1%! set %x%3%x1%=!%x%4%x1%! set %x%4%x1%=0 set /a "score+=!%x%1%x1%!" if !%x%1%x1%!==2048 (set won=1) if not !%x%1%x1%!==0 (set moved=1) ) if !%x%2%x1%!==!%x%3%x1%! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%2%x1%*=2 set %x%3%x1%=!%x%4%x1%! set %x%4%x1%=0 set /a "score+=!%x%2%x1%!" if !%x%2%x1%!==2048 (set won=1) if not !%x%2%x1%!==0 (set moved=1) ) else ( if !%x%3%x1%!==!%x%4%x1%! ( if !test!==1 (set lose=0&goto :EOF) set /a %x%3%x1%*=2 set %x%4%x1%=0 set /a "score+=!%x%3%x1%!" if !%x%3%x1%!==2048 (set won=1) if not !%x%3%x1%!==0 (set moved=1) ) ) if !test!==0 ( if !%x%1%x1%!==0 (set list=!list!%x%1%x1%&set /a blanktiles+=1) if !%x%2%x1%!==0 (set list=!list!%x%2%x1%&set /a blanktiles+=1) if !%x%3%x1%!==0 (set list=!list!%x%3%x1%&set /a blanktiles+=1) if !%x%4%x1%!==0 (set list=!list!%x%4%x1%&set /a blanktiles+=1) ) goto :EOF
- /PROCESSOR LEFT
- PROCESSOR RIGHT
- proc4
set x1=%x:~1,1% set x=%x:~0,1% set cyc=4
- cyc4
if !%x%%cyc%%x1%!==0 ( set /a tm=%cyc%-1 for /l %%b in (!tm!,-1,1) do ( if not !%x%%%b%x1%!==0 ( set "%x%%cyc%%x1%=!%x%%%b%x1%!" set "%x%%%b%x1%=0" set moved=1 goto :break4 ) ) )
- break4
if %cyc%==2 (goto :adder4) else (set /a cyc-=1&goto :cyc4)
- adder4
if !%x%3%x1%!==!%x%4%x1%! ( set /a %x%4%x1%*=2 set %x%3%x1%=!%x%2%x1%! set %x%2%x1%=!%x%1%x1%! set %x%1%x1%=0 set /a "score+=!%x%4%x1%!" if !%x%4%x1%!==2048 (set won=1) if not !%x%4%x1%!==0 (set moved=1) ) if !%x%2%x1%!==!%x%3%x1%! ( set /a %x%3%x1%*=2 set %x%2%x1%=!%x%1%x1%! set %x%1%x1%=0 set /a "score+=!%x%3%x1%!" if !%x%3%x1%!==2048 (set won=1) if not !%x%3%x1%!==0 (set moved=1) ) else ( if !%x%1%x1%!==!%x%2%x1%! ( set /a %x%2%x1%*=2 set %x%1%x1%=0 set /a "score+=!%x%2%x1%!" if !%x%2%x1%!==2048 (set won=1) if not !%x%2%x1%!==0 (set moved=1) ) ) if !%x%1%x1%!==0 (set list=!list!%x%1%x1%&set /a blanktiles+=1) if !%x%2%x1%!==0 (set list=!list!%x%2%x1%&set /a blanktiles+=1) if !%x%3%x1%!==0 (set list=!list!%x%3%x1%&set /a blanktiles+=1) if !%x%4%x1%!==0 (set list=!list!%x%4%x1%&set /a blanktiles+=1) goto :EOF
- /PROCESSOR RIGHT
- FINAL RESULT
- res
call :display echo !msg! echo. echo Press N for NEW GAME, or P to EXIT... set "qwerty=" for /F "usebackq delims=" %%L in (`xcopy /L /w "%~f0" "%~f0" 2^>NUL`) do (
if not defined qwerty set "qwerty=%%L"
) set qwerty=%qwerty:~-1% if /i "!qwerty!"=="n" (goto :begin) if /i "!qwerty!"=="p" (exit) goto :res
- /FINAL RESULT
- DISPLAY
- display
for %%a in (1 2 3 4) do (for %%b in (1 2 3 4) do ( set da%%a%%b=!a%%a%%b! if !a%%a%%b! lss 1000 (set da%%a%%b= !da%%a%%b!) if !a%%a%%b! lss 100 (set da%%a%%b= !da%%a%%b!) if !a%%a%%b! lss 10 (set da%%a%%b= !da%%a%%b!) if !a%%a%%b!==0 (set da%%a%%b= !da%%a%%b:0=!) ) ) cls echo. echo 2048 Game echo Batch File Implementation echo. echo. echo. echo. +----+----+----+----+ echo. ^|%da11%^|%da21%^|%da31%^|%da41%^| W - Slide UP echo. +----+----+----+----+ S - Slide DOWN echo. ^|%da12%^|%da22%^|%da32%^|%da42%^| A - Slide LEFT echo. +----+----+----+----+ D - Slide RIGHT echo. ^|%da13%^|%da23%^|%da33%^|%da43%^| N - New GAME echo. +----+----+----+----+ P - EXIT echo. ^|%da14%^|%da24%^|%da34%^|%da44%^| echo. +----+----+----+----+ echo. echo.Score: !score! echo. goto :EOF
- /DISPLAY</lang>
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.
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>
Phix
Faithful desktop gui (windows only) reproduction of 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
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 msg = "" if game_won() then msg = "!!!YOU WON!!!\n\nAnother Go?" else add_rand(1) repaintWindow(main) if no_valid_moves() then msg = "You Lost.\n\nAnother Go?" end if end if if length(msg) then if messageBox("Game Over",msg,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>
Tcl
Text mode
<lang tcl>
- A minimal implementation of the game 2048 in Tcl.
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>