2048: Difference between revisions
(bugfix: moving just a 2 onto a 2* not a valid move) |
No edit summary |
||
Line 21: | Line 21: | ||
* Win condition. |
* Win condition. |
||
* Lose condition. |
* Lose condition. |
||
=={{header|C++}}== |
|||
<lang cpp> |
|||
#include <time.h> |
|||
#include <iostream> |
|||
#include <string> |
|||
#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> |
|||
{{out}} |
|||
<pre> |
|||
SCORE: 2024 |
|||
+------+------+------+------+ |
|||
| 2 | 8 | 32 | 256 | |
|||
+------+------+------+------+ |
|||
| | | 4 | 32 | |
|||
+------+------+------+------+ |
|||
| | | 2 | 8 | |
|||
+------+------+------+------+ |
|||
| | | | 2 | |
|||
+------+------+------+------+ |
|||
(W)Up (S)Down (A)Left (D)Right |
|||
</pre> |
|||
=={{header|Tcl}}== |
=={{header|Tcl}}== |
Revision as of 05:59, 7 November 2014
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]
- 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.
C++
<lang cpp>
- include <time.h>
- include <iostream>
- include <string>
- 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
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>