15 puzzle game: Difference between revisions
m (→{{header|Tcl}}) |
m (→{{header|REXX}}: added comments to the REXX section header.) |
||
Line 360: | Line 360: | ||
=={{header|REXX}}== |
=={{header|REXX}}== |
||
This REXX version allows the user to specify the size of the puzzle. |
This REXX version allows the user to specify the size of the puzzle ('''N''', where '''NxN''' is the size of the puzzle). |
||
<lang rexx>/*REXX pgm implements the 15-puzzle (Gem Puzzle, Boss Puzzle, Mystic Square).*/ |
<lang rexx>/*REXX pgm implements the 15-puzzle (Gem Puzzle, Boss Puzzle, Mystic Square).*/ |
||
parse arg N seed . /*obtain optional arguments from the CL*/ |
parse arg N seed . /*obtain optional arguments from the CL*/ |
Revision as of 21:25, 21 February 2016
Implement the Fifteen Puzzle Game.
C++
<lang cpp>
- include <time.h>
- include <vector>
- include <string>
- include <iostream>
class p15 { public :
void play() { bool p = true; std::string a; while( p ) { createBrd(); while( !isDone() ) { drawBrd();getMove(); } drawBrd(); std::cout << "\n\nCongratulations!\nPlay again (Y/N)?"; std::cin >> a; if( a != "Y" && a != "y" ) break; } }
private:
void createBrd() { int i = 1; std::vector<int> v; for( ; i < 16; i++ ) { brd[i - 1] = i; } brd[15] = 0; x = y = 3; for( i = 0; i < 1000; i++ ) { getCandidates( v ); move( v[rand() % v.size()] ); v.clear(); } } void move( int d ) { int t = x + y * 4; switch( d ) { case 1: y--; break; case 2: x++; break; case 4: y++; break; case 8: x--; } brd[t] = brd[x + y * 4]; brd[x + y * 4] = 0; } void getCandidates( std::vector<int>& v ) { if( x < 3 ) v.push_back( 2 ); if( x > 0 ) v.push_back( 8 ); if( y < 3 ) v.push_back( 4 ); if( y > 0 ) v.push_back( 1 ); } void drawBrd() { int r; std::cout << "\n\n"; for( int y = 0; y < 4; y++ ) { std::cout << "+----+----+----+----+\n"; for( int x = 0; x < 4; x++ ) { r = brd[x + y * 4]; std::cout << "| "; if( r < 10 ) std::cout << " "; if( !r ) std::cout << " "; else std::cout << r << " "; } std::cout << "|\n"; } std::cout << "+----+----+----+----+\n"; } void getMove() { std::vector<int> v; getCandidates( v ); std::vector<int> p; getTiles( p, v ); unsigned int i; while( true ) { std::cout << "\nPossible moves: "; for( i = 0; i < p.size(); i++ ) std::cout << p[i] << " "; int z; std::cin >> z; for( i = 0; i < p.size(); i++ ) if( z == p[i] ) { move( v[i] ); return; } } } void getTiles( std::vector<int>& p, std::vector<int>& v ) { for( unsigned int t = 0; t < v.size(); t++ ) { int xx = x, yy = y; switch( v[t] ) { case 1: yy--; break; case 2: xx++; break; case 4: yy++; break; case 8: xx--; } p.push_back( brd[xx + yy * 4] ); } } bool isDone() { for( int i = 0; i < 15; i++ ) { if( brd[i] != i + 1 ) return false; } return true; } int brd[16], x, y;
}; int main( int argc, char* argv[] ) {
srand( ( unsigned )time( 0 ) ); p15 p; p.play(); return 0;
} </lang>
+----+----+----+----+ | 11 | 5 | 12 | 3 | +----+----+----+----+ | 10 | 7 | 6 | 4 | +----+----+----+----+ | 13 | | 2 | 1 | +----+----+----+----+ | 15 | 14 | 8 | 9 | +----+----+----+----+ Possible moves: 2 13 14 7
J
Implementation:
<lang J>require'general/misc/prompt'
genboard=:3 :0
b=. ?~16 if. 0<C.!.2 b do. b=. (<0 _1)C. b end. a: (b i.0)} <"0 b
)
done=: (<"0]1+i.15),a:
shift=: |.!._"0 2 taxi=: |:,/"2(_1 1 shift i.4 4),_1 1 shift"0 1/ i.4 4
showboard=:3 :0
echo 'current board:' echo 4 4$y
)
help=:0 :0
Slide a number block into the empty space until you get:
┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│11│12│ ├──┼──┼──┼──┤ │13│14│15│ │ └──┴──┴──┴──┘
Or type 'q' to quit.
)
getmove=:3 :0
showboard y blank=. y i. a: options=. /:~ ;y {~ _ -.~ blank { taxi whilst. -. n e. options do. echo 'Valid moves: ',":options t=. prompt 'move which number? ' if. 'q' e. t do. echo 'giving up' throw. elseif. 'h' e. t do. echo help showboard y end. n=. {._".t end. (<blank,y i.<n) C. y
)
game=: 3 :0
echo '15 puzzle' echo 'h for help, q to quit' board=. genboard whilst. -. done-:board do. board=. getmove board end. showboard board echo 'You win.'
)</lang>
Most of this is user interface code. We initially shuffle the numbers randomly, then check their parity and swap the first and last squares if needed. Then, for each move, we allow the user to pick one of the taxicab neighbors of the empty square.
A full game would be too big to be worth showing here, so for the purpose of giving a glimpse of what this looks like in action we replace the random number generator with a constant:
<lang J> game 15 puzzle h for help, q to quit current board: ┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│ │11│ ├──┼──┼──┼──┤ │13│14│15│12│ └──┴──┴──┴──┘ Valid moves: 7 10 11 15 move which number? 11 current board: ┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│11│ │ ├──┼──┼──┼──┤ │13│14│15│12│ └──┴──┴──┴──┘ Valid moves: 8 11 12 move which number? 12 current board: ┌──┬──┬──┬──┐ │1 │2 │3 │4 │ ├──┼──┼──┼──┤ │5 │6 │7 │8 │ ├──┼──┼──┼──┤ │9 │10│11│12│ ├──┼──┼──┼──┤ │13│14│15│ │ └──┴──┴──┴──┘ You win.</lang>
Perl 6
Most of this is interface code. Reused substantial portions from the 2048 task. Use the arrow keys to slide tiles, press 'q' to quit or 'n' for a new puzzle. Requires a POSIX termios aware terminal. Ensures that the puzzle is solvable by shuffling the board with an even number of swaps, then checking for even taxicab parity for the empty space. <lang perl6>use Term::termios;
constant $saved = Term::termios.new(fd => 1).getattr; constant $termios = Term::termios.new(fd => 1).getattr;
- raw mode interferes with carriage returns, so
- set flags needed to emulate it manually
$termios.unset_iflags(<BRKINT ICRNL ISTRIP IXON>); $termios.unset_lflags(< ECHO ICANON IEXTEN ISIG>); $termios.setattr(:DRAIN);
- reset terminal to original setting on exit
END { $saved.setattr(:NOW) }
constant n = 4; # board size constant cell = 6; # cell width
constant $top = join '─' x cell, '┌', '┬' xx n-1, '┐'; constant $mid = join '─' x cell, '├', '┼' xx n-1, '┤'; constant $bot = join '─' x cell, '└', '┴' xx n-1, '┘';
my %dir = (
"\e[A" => 'up', "\e[B" => 'down', "\e[C" => 'right', "\e[D" => 'left',
);
my @solved = [1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,' ']; my @board; new();
sub new () {
loop { @board = shuffle(); last if parity-ok(@board); }
}
sub shuffle () {
my @c = [1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,' ']; for (^16).pick(*) -> $y, $x { my ($yd, $ym, $xd, $xm) = ($y div n, $y mod n, $x div n, $x mod n); my $temp = @c[$ym;$yd]; @c[$ym;$yd] = @c[$xm;$xd]; @c[$xm;$xd] = $temp; } @c;
}
sub parity-ok (@b) {
so (sum @b».grep(/' '/,:k).grep(/\d/, :kv)) %% 2;
}
sub row (@row) { '│' ~ (join '│', @row».¢er) ~ '│' }
sub center ($s){
my $c = cell - $s.chars; my $pad = ' ' x ceiling($c/2); sprintf "%{cell}s", "$s$pad";
}
sub draw-board {
run('clear'); print qq:to/END/;
Press direction arrows to move.
Press q to quit. Press n for a new puzzle.
$top { join "\n\t$mid\n\t", map { .&row }, @board } $bot
{ (so @board ~~ @solved) ?? 'Solved!!' !! } END }
sub slide (@c is copy) {
my $t = (grep { /' '/ }, :k, @c)[0]; return @c unless $t and $t > 0; @c[$t,$t-1] = @c[$t-1,$t]; @c;
}
multi sub move('up') {
map { @board[*;$_] = reverse slide reverse @board[*;$_] }, ^n;
}
multi sub move('down') {
map { @board[*;$_] = slide @board[*;$_] }, ^n;
}
multi sub move('left') {
map { @board[$_] = reverse slide reverse @board[$_] }, ^n;
}
multi sub move('right') {
map { @board[$_] = slide @board[$_] }, ^n;
}
loop {
draw-board;
# Read up to 4 bytes from keyboard buffer. # Page navigation keys are 3-4 bytes each. # Specifically, arrow keys are 3. my $key = $*IN.read(4).decode;
move %dir{$key} if so %dir{$key}; last if $key eq 'q'; # (q)uit new() if $key eq 'n';
}</lang> Sample screen shot:
Press direction arrows to move. Press q to quit. Press n for a new puzzle. ┌──────┬──────┬──────┬──────┐ │ 2 │ 1 │ 10 │ 14 │ ├──────┼──────┼──────┼──────┤ │ 15 │ 11 │ 12 │ │ ├──────┼──────┼──────┼──────┤ │ 13 │ 3 │ 6 │ 7 │ ├──────┼──────┼──────┼──────┤ │ 9 │ 4 │ 5 │ 8 │ └──────┴──────┴──────┴──────┘
REXX
This REXX version allows the user to specify the size of the puzzle (N, where NxN is the size of the puzzle). <lang rexx>/*REXX pgm implements the 15-puzzle (Gem Puzzle, Boss Puzzle, Mystic Square).*/ parse arg N seed . /*obtain optional arguments from the CL*/ if N== | N==',' then N=4 /*Not specified? Then use the default.*/ if datatype(seed,'W') then call random ,,seed /*if given, use a RANDOM seed.*/ sep='────────'; prompt=sep 'Please enter a tile number ' sep " (or Quit)." nn=N*N-1; nh=N*N; w=length(nn); $=; @.=
do i=1 for NN; $=$ i; end /* [◄] build a solution for testing.*/
done=$ /* [↓] scramble the tiles in puzzle.*/
do j=1 for NN; #=words($); a=random(1,#) @.j=word($,a) ; $=delword($,a,1) end /*j*/
do until puzz=done & @.nh== /*perform moves (slides) until solved*/ x=0; call showgrid 1; say; say prompt; pull x _ . if abbrev('QUIT',x,1) then do; say; say; say sep 'quiting.'; exit; end select when x == then em="nothing entered" when _\== then em="too many items entered" when \datatype(x,'N') then em="number isn't numeric" when \datatype(x,'W') then em="number isn't an integer" when x=0 then em="number can't be zero" when x<0 then em="number can't be negative" when x>nn then em="number can't be greater then" nn otherwise em= end /*select*/ /* [↑] verify the human entered data*/
if em\== then do; say sep em'.'; iterate; end /*possible error message?*/ x=x/1 /*normalize the integer. */ call showgrid 0; g= /*validate if slide is OK*/ ?=holeRow-1; if ?>0 & ?<=N then g=g !.?.holeCol /*is the tile OK to move?*/ ?=holeRow+1; if ?>0 & ?<=N then g=g !.?.holeCol /* " " " " " " */ ?=holeCol-1; if ?>0 & ?<=N then g=g !.holeRow.? /* " " " " " " */ ?=holeCol+1; if ?>0 & ?<=N then g=g !.holeRow.? /* " " " " " " */ if wordpos(x,g)==0 then do; say sep 'tile' x "can't be moved."; iterate; end @.hole=x; @.what= call showgrid 0 /*slide tile to the hole.*/ end /*until*/
call showgrid 1; say; say sep 'Congratulations! The' nn"-puzzle is solved." exit /*stick a fork in it, we're all done. */ /*────────────────────────────────────────────────────────────────────────────*/ shorten: return left( arg(1), length(arg(1)) - 1) /*────────────────────────────────────────────────────────────────────────────*/ showgrid: arg show; !.=; #=0; puzz=
top='╔'copies( copies("═", w+2)'╦', N); top=shorten(top)"╗" bar='╠'copies( copies("═", w+2)'╬', N); bar=shorten(bar)"╣" bot='╚'copies( copies("═", w+2)'╩', N); bot=shorten(bot)"╝" if show then say sep ' ' top do row=1 for N; z='║' do col=1 for N; #=#+1; y=@.#; puzz=puzz y; !.row.col=y _=' 'right(@.#,w)" ║"; z=z || _ if @.#== then do; hole=#; holeRow=row; holeCol=col; end if @.#==x then do; what=#; whatRow=row; whatCol=col; end end /*col*/ if show then say sep ' ' z if row\==N & show then say sep ' ' bar end /*row*/ if show then say sep ' ' bot return</lang>
output when using the default inputs:
──────── ╔═══╦═══╦═══╗ ──────── ║ 6 ║ 3 ║ 4 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 8 ║ 7 ║ 5 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 1 ║ 2 ║ ║ ──────── ╚═══╩═══╩═══╝ ──────── Please enter a tile number ──────── (or Quit). 5 ──────── ╔═══╦═══╦═══╗ ──────── ║ 6 ║ 3 ║ 4 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 8 ║ 7 ║ ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 1 ║ 2 ║ 5 ║ ──────── ╚═══╩═══╩═══╝ ──────── Please enter a tile number ──────── (or Quit). 7 ──────── ╔═══╦═══╦═══╗ ──────── ║ 6 ║ 3 ║ 4 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 8 ║ ║ 7 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 1 ║ 2 ║ 5 ║ ──────── ╚═══╩═══╩═══╝ ──────── Please enter a tile number ──────── (or Quit). 8 ──────── ╔═══╦═══╦═══╗ ──────── ║ 6 ║ 3 ║ 4 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ ║ 8 ║ 7 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 1 ║ 2 ║ 5 ║ ──────── ╚═══╩═══╩═══╝ ──────── Please enter a tile number ──────── (or Quit). 1 ──────── ╔═══╦═══╦═══╗ ──────── ║ 6 ║ 3 ║ 4 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ 1 ║ 8 ║ 7 ║ ──────── ╠═══╬═══╬═══╣ ──────── ║ ║ 2 ║ 5 ║ ──────── ╚═══╩═══╩═══╝ ──────── Please enter a tile number ──────── (or Quit). quit ──────── quiting.
Ring
CalmoSoft Fifteen Puzzle Game written in Ring Programming Language (http://ring-lang.net)
Output: [video]
The code: <lang ring> load "guilib.ring"
App1 = new qApp {
rnd = [] empty = 16
win1 = new qWidget() { move(0,0) resize(350,400) setWindowTitle("CalmoSoft Fifteen Puzzle Game") new qPushButton(win1) { setgeometry(100,220,120,30) settext("Scramble") setclickevent("scramble()") }
btn1 = new qPushButton(win1) { setgeometry(100,100,30,30) setclickevent("moveTile(1)") }
btn2 = new qPushButton(win1) { setgeometry(130,100,30,30) setclickevent("moveTile(2)") }
btn3 = new qPushButton(win1) { setgeometry(160,100,30,30) setclickevent("moveTile(3)") } btn4 = new qPushButton(win1) { setgeometry(190,100,30,30) setclickevent("moveTile(4)") }
btn5 = new qPushButton(win1) { setgeometry(100,130,30,30) setclickevent("moveTile(5)") } btn6 = new qPushButton(win1) { setgeometry(130,130,30,30) setclickevent("moveTile(6)") }
btn7 = new qPushButton(win1) { setgeometry(160,130,30,30) setclickevent("moveTile(7)") }
btn8 = new qPushButton(win1) { setgeometry(190,130,30,30) setclickevent("moveTile(8)") }
btn9 = new qPushButton(win1) { setgeometry(100,160,30,30) setclickevent("moveTile(9)") }
btn10 = new qPushButton(win1) { setgeometry(130,160,30,30) setclickevent("moveTile(10)") }
btn11 = new qPushButton(win1) { setgeometry(160,160,30,30) setclickevent("moveTile(11)") }
btn12 = new qPushButton(win1) { setgeometry(190,160,30,30) setclickevent("moveTile(12)") }
btn13 = new qPushButton(win1) { setgeometry(100,190,30,30) setclickevent("moveTile(13)") }
btn14 = new qPushButton(win1) { setgeometry(130,190,30,30) setclickevent("moveTile(14)") }
btn15 = new qPushButton(win1) { setgeometry(160,190,30,30) setclickevent("moveTile(15)") }
btn16 = new qPushButton(win1) { setgeometry(190,190,30,30) settext("") setclickevent("moveTile(16)") }
resetbtn = new qPushButton(win1) { setgeometry(100,250,120,30) settext("Reset") setclickevent("resetTiles()") }
button = [btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8, btn9, btn10, btn11, btn12, btn13, btn14, btn15, btn16] for i = 1 to 15 button[i] {settext(string(i))} next show() } exec()
}
func scramble
for n= 1 to 300 nr=random(16) up = (empty = (nr - 4)) down = (empty = (nr + 4)) left = ((empty = (nr - 1)) and ((nr % 4) != 1)) right = ((empty = (nr + 1)) and ((nr % 4) != 0)) move = up or down or left or right if move = 1 and (nr != 0) button[nr] { temp = text() } button[empty] {settext(temp)} button[nr] {settext("")} empty = nr ok next
func moveTile nr2
up = (empty = (nr2 - 4)) down = (empty = (nr2 + 4)) left = ((empty = (nr2- 1)) and ((nr2 % 4) != 1)) right = ((empty = (nr2 + 1)) and ((nr2 % 4) != 0)) move = up or down or left or right if move = 1 button[nr2] { temp2 = text() } button[empty] {settext(temp2)} button[nr2] {settext("")} empty = nr2 ok
func resetTiles
empty = 16 for i = 1 to 15 button[i] {settext(string(i))} next button[16] {settext("")}
</lang>
Tcl
see: [_ttp://wiki.tcl.tk/15067 Classic 15 Puzzle] and [_ttp://wiki.tcl.tk/15085 N-Puzzle]