2048: Difference between revisions

From Rosetta Code
Content added Content deleted
Line 37: Line 37:
=={{header|Batch File}}==
=={{header|Batch File}}==
This code uses XCOPY.EXE as key input. Check [http://stackoverflow.com/questions/18942356/batch-file-choice-syntax/18942798#18942798 HERE] for details.
This code uses XCOPY.EXE as key input. Check [http://stackoverflow.com/questions/18942356/batch-file-choice-syntax/18942798#18942798 HERE] for details.
<lang dos>
<lang dos>::
::
::2048 Task from Rosetta Code Wiki
::2048 Task from Rosetta Code Wiki
::Batch File Implementation
::Batch File Implementation
Line 453: Line 452:
set da%a%%b%=!a%a%%b%!
set da%a%%b%=!a%a%%b%!
goto :EOF
goto :EOF
::/"BEAUTIFY" DISPLAY
::/"BEAUTIFY" DISPLAY</lang>
</lang>


=={{header|C++}}==
=={{header|C++}}==

Revision as of 10:43, 26 May 2015

2048 is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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 ( goto :yes ) 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 (goto :no) ) else ( set test=1&set lose=1 for %%a in (a1 a2 a3 a4) do (set x=%%a&call :adder1) if !lose!==1 (goto :no) ) ) 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 ) else ( 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 ) else ( 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 ) else ( 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 ) else ( 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 ) else ( 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 ) else ( 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
WIN!!!
yes

call :display echo Congrats^^! You SOLVED the 2048 puzzle^^! 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 :yes

/WIN!!!
LOSE :(
no

call :display echo Game Over due to Out of Moves... Sorry :^( 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 :no

/LOSE :(
DISPLAY
display

for %%a in (1 2 3 4) do ( for %%b in (1 2 3 4) do ( set a=%%a set b=%%b call :beautify ) ) 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
"BEAUTIFY" DISPLAY
beautify

if !a%a%%b%!==0 (set da%a%%b%= &goto :EOF) if !a%a%%b%! lss 10 (set da%a%%b%= !a%a%%b%!&goto :EOF) if !a%a%%b%! lss 100 (set da%a%%b%= !a%a%%b%!&goto :EOF) if !a%a%%b%! lss 1000 (set da%a%%b%= !a%a%%b%!&goto :EOF) set da%a%%b%=!a%a%%b%! goto :EOF

/"BEAUTIFY" DISPLAY</lang>

C++

<lang cpp>

  1. include <time.h>
  2. include <iostream>
  3. include <string>
  4. include <iomanip>

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

D

Translation of: C++

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


Python

<lang python>

  1. !/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>

  1. A minimal implementation of the game 2048 in Tcl.

package require Tcl 8.5 package require struct::matrix package require struct::list

  1. Board size.

set size 4

  1. Iterate over all cells of the game board and run script for each.
  2. The game board is a 2D matrix of a fixed size that consists of elements
  3. called "cells" that each can contain a game tile (corresponds to numerical
  4. values of 2, 4, 8, ..., 2048) or nothing (zero).
  5. - cellList is a list of cell indexes (coordinates), which are
  6. themselves lists of two numbers each. They each represent the location
  7. of a given cell on the board.
  8. - varName1 are varName2 are names of the variables the will be assigned
  9. the index values.
  10. - cellVarName is the name of the variable that at each step of iteration
  11. will contain the numerical value of the present cell. Assigning to it will
  12. change the cell's value.
  13. - 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
   }

}

  1. Generate a list of cell indexes for all cells on the board, i.e.,
  2. {{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

}

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

}

  1. Return 1 if the predicate pred is true when applied to all items on the list
  2. 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

}

  1. Check if list represents valid cell coordinates.

proc valid-cell? cell {

   map-and $cell valid-index

}

  1. Get the value of a game board cell.

proc cell-get cell {

   board get cell {*}$cell

}

  1. Set the value of a game board cell.

proc cell-set {cell value} {

   board set cell {*}$cell $value

}

  1. Filter a list of board cell indexes cellList to only have those indexes
  2. that correspond to empty board cells.

proc empty {cellList} {

   ::struct::list filterfor x $cellList {[cell-get $x] == 0}

}

  1. Pick a random item from the given list.

proc pick list {

   lindex $list [expr {int(rand() * [llength $list])}]

}

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

}

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

}

  1. If checkOnly is false try to shift all cells one step in the direction of
  2. 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

}

  1. Is it possible to move any tiles in the direction of directionVect?

proc can-move? {directionVect} {

   move-all $directionVect 1

}

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

}

  1. Check lose condition. The player loses when the win condition isn't met and
  2. 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
   }

}

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

See http://wiki.tcl.tk/39566.