15 puzzle game: Difference between revisions
No edit summary |
|||
Line 1,062: | Line 1,062: | ||
impl P15 { |
impl P15 { |
||
fn new() -> P15 { |
fn new() -> P15 { |
||
let mut board = |
let mut board = (1..16).map(Cell::Card).collect::<Vec<_>>(); |
||
for i in 1..16 { |
|||
board.push(Cell::Card(i)); |
|||
} |
|||
board.push(Cell::Empty); |
board.push(Cell::Empty); |
||
Revision as of 17:00, 11 July 2016
You are encouraged to solve this task according to the task description, using any language you may know.
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
COBOL
Tested with GnuCOBOL
<lang cobol> >>SOURCE FORMAT FREE
- > This code is dedicated to the public domain
- > This is GNUCOBOL 2.0
identification division. program-id. fifteen. environment division. configuration section. repository. function all intrinsic. data division. working-storage section.
01 r pic 9. 01 r-empty pic 9. 01 r-to pic 9. 01 r-from pic 9.
01 c pic 9. 01 c-empty pic 9. 01 c-to pic 9. 01 c-from pic 9.
01 display-table.
03 display-row occurs 4. 05 display-cell occurs 4 pic 99.
01 tile-number pic 99. 01 tile-flags pic x(16).
01 display-move value spaces.
03 tile-id pic 99.
01 row-separator pic x(21) value all '.'. 01 column-separator pic x(3) value ' . '.
01 inversions pic 99. 01 current-tile pic 99.
01 winning-display pic x(32) value
'01020304' & '05060708' & '09101112' & '13141500'.
procedure division. start-fifteen.
display 'start fifteen puzzle' display ' enter a two-digit tile number and press <enter> to move' display ' press <enter> only to exit'
*> tables with an odd number of inversions are not solvable perform initialize-table with test after until inversions = 0 perform show-table accept display-move perform until display-move = spaces perform move-tile perform show-table move spaces to display-move accept display-move end-perform stop run .
initialize-table.
compute tile-number = random(seconds-past-midnight) *> seed only move spaces to tile-flags move 0 to current-tile inversions perform varying r from 1 by 1 until r > 4 after c from 1 by 1 until c > 4 perform with test after until tile-flags(tile-number + 1:1) = space compute tile-number = random() * 100 compute tile-number = mod(tile-number, 16) end-perform move 'x' to tile-flags(tile-number + 1:1) if tile-number > 0 and < current-tile add 1 to inversions end-if move tile-number to display-cell(r,c) current-tile end-perform compute inversions = mod(inversions,2) .
show-table.
if display-table = winning-display display 'winning' end-if display space row-separator perform varying r from 1 by 1 until r > 4 perform varying c from 1 by 1 until c > 4 display column-separator with no advancing if display-cell(r,c) = 00 display ' ' with no advancing move r to r-empty move c to c-empty else display display-cell(r,c) with no advancing end-if end-perform display column-separator end-perform display space row-separator .
move-tile.
if not (tile-id numeric and tile-id >= 01 and <= 15) display 'invalid tile number' exit paragraph end-if
*> find the entered tile-id row and column (r,c) perform varying r from 1 by 1 until r > 4 after c from 1 by 1 until c > 4 if display-cell(r,c) = tile-id exit perform end-if end-perform
*> show-table filled (r-empty,c-empty) evaluate true when r = r-empty if c-empty < c *> shift left perform varying c-to from c-empty by 1 until c-to > c compute c-from = c-to + 1 move display-cell(r-empty,c-from) to display-cell(r-empty,c-to) end-perform else *> shift right perform varying c-to from c-empty by -1 until c-to < c compute c-from = c-to - 1 move display-cell(r-empty,c-from) to display-cell(r-empty,c-to) end-perform end-if move 00 to display-cell(r,c) when c = c-empty if r-empty < r *>shift up perform varying r-to from r-empty by 1 until r-to > r compute r-from = r-to + 1 move display-cell(r-from,c-empty) to display-cell(r-to,c-empty) end-perform else *> shift down perform varying r-to from r-empty by -1 until r-to < r compute r-from = r-to - 1 move display-cell(r-from,c-empty) to display-cell(r-to,c-empty) end-perform end-if move 00 to display-cell(r,c) when other display 'invalid move' end-evaluate .
end program fifteen.</lang>
- Output:
prompt$ cobc -xj fifteen.cbl start fifteen puzzle enter a two-digit tile number and press <enter> to move press <enter> only to exit ..................... . 05 . 14 . 08 . 12 . . 01 . 10 . 03 . 09 . . 02 . 15 . 13 . 11 . . 06 . . 07 . 04 . .....................
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>
Java
<lang java>import java.awt.*; import java.awt.event.*; import java.util.Random; import javax.swing.*;
public class FifteenPuzzle extends JPanel {
final static int numTiles = 15; final static int side = 4;
Random rand = new Random(); int[] tiles = new int[numTiles + 1]; int tileSize, blankPos, margin, gridSize;
public FifteenPuzzle() { final int dim = 640;
margin = 80; tileSize = (dim - 2 * margin) / side; gridSize = tileSize * side;
setPreferredSize(new Dimension(dim, dim)); setBackground(Color.white); setForeground(new Color(0x6495ED)); // cornflowerblue setFont(new Font("SansSerif", Font.BOLD, 60));
addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { int ex = e.getX() - margin; int ey = e.getY() - margin;
if (ex < 0 || ex > gridSize || ey < 0 || ey > gridSize) return;
int c1 = ex / tileSize; int r1 = ey / tileSize; int c2 = blankPos % side; int r2 = blankPos / side;
if ((c1 == c2 && Math.abs(r1 - r2) == 1) || (r1 == r2 && Math.abs(c1 - c2) == 1)) {
int clickPos = r1 * side + c1; tiles[blankPos] = tiles[clickPos]; tiles[clickPos] = 0; blankPos = clickPos; } repaint(); } });
shuffle(); }
final void shuffle() { do { reset(); // don't include the blank space in the shuffle, leave it // in the home position int n = numTiles; while (n > 1) { int r = rand.nextInt(n--); int tmp = tiles[r]; tiles[r] = tiles[n]; tiles[n] = tmp; } } while (!isSolvable()); }
void reset() { for (int i = 0; i < tiles.length; i++) tiles[i] = (i + 1) % tiles.length; blankPos = numTiles; }
/* Only half the permutations of the puzzle are solvable.
Whenever a tile is preceded by a tile with higher value it counts as an inversion. In our case, with the blank space in the home position, the number of inversions must be even for the puzzle to be solvable.
See also: www.cs.bham.ac.uk/~mdr/teaching/modules04/java2/TilesSolvability.html */ boolean isSolvable() { int countInversions = 0; for (int i = 0; i < numTiles; i++) { for (int j = 0; j < i; j++) { if (tiles[j] > tiles[i]) countInversions++; } } return countInversions % 2 == 0; }
void drawGrid(Graphics2D g) { for (int i = 0; i < tiles.length; i++) { if (tiles[i] == 0) continue;
int r = i / side; int c = i % side; int x = margin + c * tileSize; int y = margin + r * tileSize;
g.setColor(getForeground()); g.fillRoundRect(x, y, tileSize, tileSize, 25, 25); g.setColor(Color.black); g.drawRoundRect(x, y, tileSize, tileSize, 25, 25); g.setColor(Color.white);
drawCenteredString(g, String.valueOf(tiles[i]), x, y); } }
void drawCenteredString(Graphics2D g, String s, int x, int y) { FontMetrics fm = g.getFontMetrics(); int asc = fm.getAscent(); int dec = fm.getDescent();
x = x + (tileSize - fm.stringWidth(s)) / 2; y = y + (asc + (tileSize - (asc + dec)) / 2);
g.drawString(s, x, y); }
@Override public void paintComponent(Graphics gg) { super.paintComponent(gg); Graphics2D g = (Graphics2D) gg; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawGrid(g); }
public static void main(String[] args) { SwingUtilities.invokeLater(() -> { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setTitle("Fifteen Puzzle"); f.setResizable(false); f.add(new FifteenPuzzle(), BorderLayout.CENTER); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); }); }
}</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 │ └──────┴──────┴──────┴──────┘
Racket
This is a GUI game; and there are difficulties getting screen shots onto RC. Use the arrow keys to slide the blank square.
It uses the 2htdp/universe
package.
<lang racket>#lang racket/base (require 2htdp/universe 2htdp/image racket/list racket/match)
(define ((fifteen->pict (finished? #f)) fifteen)
(for/fold ((i (empty-scene 0 0))) ((r 4)) (define row (for/fold ((i (empty-scene 0 0))) ((c 4)) (define v (list-ref fifteen (+ (* r 4) c))) (define cell (if v (overlay/align "center" "center" (rectangle 50 50 'outline (if finished? "white" "blue")) (text (number->string v) 30 "black")) (rectangle 50 50 'solid (if finished? "white" "powderblue")))) (beside i cell))) (above i row)))
(define (move-space fifteen direction)
(define idx (for/first ((i (in-naturals)) (x fifteen) #:unless x) i)) (define-values (row col) (quotient/remainder idx 4)) (define dest (+ idx (match direction ['l #:when (> col 0) -1] ['r #:when (< col 3) 1] ['u #:when (> row 0) -4] ['d #:when (< row 3) 4] [else 0]))) (list-set (list-set fifteen idx (list-ref fifteen dest)) dest #f))
(define (key-move-space fifteen a-key)
(cond [(key=? a-key "left") (move-space fifteen 'l)] [(key=? a-key "right") (move-space fifteen 'r)] [(key=? a-key "up") (move-space fifteen 'u)] [(key=? a-key "down") (move-space fifteen 'd)] [else fifteen]))
(define (shuffle-15 fifteen shuffles)
(for/fold ((rv fifteen)) ((_ shuffles)) (move-space rv (list-ref '(u d l r) (random 4)))))
(define fifteen0 '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #f))
(define (solved-world? w) (equal? w fifteen0))
(big-bang (shuffle-15 fifteen0 200)
(name "Fifteen") (to-draw (fifteen->pict)) (stop-when solved-world? (fifteen->pict #t)) (on-key key-move-space))</lang>
REXX
This REXX version allows the user to specify the size of the puzzle (N, where NxN is the size of the puzzle).
With some more complexity, the REXX computer program could be changed to allow multiple-tile moves (so that, for instance, three tiles could be slide to the right). <lang rexx>/*REXX program implements the 15-puzzle (AKA: 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 /*use repeatability seed for RANDOM BIF*/ sep='────────'; prompt=sep 'Please enter a tile number ' sep " (or Quit)." pad=translate(sep,,'─')" "; nn=N*N-1; nh=N*N; w=length(nn); $=; @.=
do i=1 for nn; $=$ i; end /*i*/ /* [◄] build a solution for testing. */
done=$ /* [↓] scramble the tiles in puzzle. */
do j=1 for nn; a=random(1,words($)); @.j=word($,a); $=delword($,a,1) end /*j*/
do until puzz=done & @.nh== /*perform moves until puzzle is solved.*/ x=0; call showGrid 1; say; say prompt; pull x _ . if abbrev('QUIT',x,1) then do; say; say; say sep "quitting."; exit; end select when x == then errMsg= "nothing entered" when _\== then errMsg= "too many items entered" when \datatype(x,'N') then errMsg= "tile number isn't numeric" when \datatype(x,'W') then errMsg= "tile number isn't an integer" when x=0 then errMsg= "tile number can't be zero" when x<0 then errMsg= "tile number can't be negative" when x>nn then errMsg= "tile number can't be greater then" nn otherwise errMsg= end /*select*/ /* [↑] verify the human entered data. */
if errMsg\== then do; say sep errMsg"."; iterate; end /*possible error message? */ call showGrid 0; g= /*validate, is move is OK?*/ rm=holeRow-1; rp=holeRow+1; cm=holeCol-1; cp=holeCol+1 /*these are possible moves*/ g=!.rm.holeCol !.rp.holeCol !.holeRow.cm !.holeRow.cp /* " " legal " */ if wordpos(x,g)==0 then do; say sep 'tile' x "can't be moved."; iterate; end @.hole=x; @.tile=; call showGrid 0 /*move specified tile ───► puzzle 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. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ showGrid: parse arg show; !.=; #=0; x=x/1; puzz=
top= '╔'copies(copies("═", w)'╦', N); top=left(top, length(top) -1)"╗" bar= '╠'copies(copies("═", w)'╬', N); bar=left(bar, length(bar) -1)"╣" bot= '╚'copies(copies("═", w)'╩', N); bot=left(bot, length(bot) -1)"╝" if show then say pad top do r=1 for N; z='║' do c=1 for N; #=#+1; y=@.#; puzz=puzz y; !.r.c=y _=right(@.#,w)"║"; z=z || _ /* [↓] find hole*/ if @.#== then do; hole=#; holeRow=r; holeCol=c; end if @.#==x then do; tile=#; tileRow=r; tileCol=c; end end /*c*/ /* [↑] find X. */ if show then do; say pad z; if r\==N then say pad bar; end end /*r*/ if show then say pad bot return</lang>
output when using the default input:
╔══╦══╦══╦══╗ ║10║ 7║ 8║11║ ╠══╬══╬══╬══╣ ║ 4║ 3║15║ 1║ ╠══╬══╬══╬══╣ ║ 9║12║ 2║13║ ╠══╬══╬══╬══╣ ║14║ 5║ 6║ ║ ╚══╩══╩══╩══╝ ──────── Please enter a tile number ──────── (or Quit). 13 ╔══╦══╦══╦══╗ ║10║ 7║ 8║11║ ╠══╬══╬══╬══╣ ║ 4║ 3║15║ 1║ ╠══╬══╬══╬══╣ ║ 9║12║ 2║ ║ ╠══╬══╬══╬══╣ ║14║ 5║ 6║13║ ╚══╩══╩══╩══╝ ──────── Please enter a tile number ──────── (or Quit). 1 ╔══╦══╦══╦══╗ ║10║ 7║ 8║11║ ╠══╬══╬══╬══╣ ║ 4║ 3║15║ ║ ╠══╬══╬══╬══╣ ║ 9║12║ 2║ 1║ ╠══╬══╬══╬══╣ ║14║ 5║ 6║13║ ╚══╩══╩══╩══╝ ──────── Please enter a tile number ──────── (or Quit). 15 ╔══╦══╦══╦══╗ ║10║ 7║ 8║11║ ╠══╬══╬══╬══╣ ║ 4║ 3║ ║15║ ╠══╬══╬══╬══╣ ║ 9║12║ 2║ 1║ ╠══╬══╬══╬══╣ ║14║ 5║ 6║13║ ╚══╩══╩══╩══╝ ──────── Please enter a tile number ──────── (or Quit). quit ──────── quitting.
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>
Output:
Rust
<lang rust> extern crate rand; use rand::{thread_rng, Rng}; use std::collections::HashMap; use std::fmt;
- [derive(Copy, Clone)]
enum Cell {
Card(usize), Empty,
}
- [derive(Eq, PartialEq, Hash)]
enum Direction {
Up, Down, Left, Right,
}
enum Action {
Move(Direction), Quit,
}
struct P15 {
board: Vec<Cell>,
}
impl P15 {
fn new() -> P15 { let mut board = (1..16).map(Cell::Card).collect::<Vec<_>>(); board.push(Cell::Empty);
let mut rng = thread_rng();
rng.shuffle(board.as_mut_slice()); while !P15::is_valid(board.clone()) { rng.shuffle(board.as_mut_slice()); }
P15 { board: board } }
fn is_valid(mut board: Vec<Cell>) -> bool { let mut permutations = 0;
let pos = board.iter().position(|&cell| match cell { Cell::Empty => true, _ => false }).unwrap();
if pos != 15 { board.swap(pos, 15); permutations += 1; }
for i in 1..16 { let pos = board.iter().position(|&cell| match cell { Cell::Card(val) if val == i => true, _ => false }).unwrap();
if pos + 1 != i { board.swap(pos, i - 1); permutations += 1; } }
permutations % 2 == 0 }
fn get_empty_position(&self) -> usize { self.board.iter().position(|&cell| match cell { Cell::Empty => true, _ => false }).unwrap() }
fn get_moves(&self) -> HashMap<Direction, Cell> { let mut moves = HashMap::with_capacity(4); let i = self.get_empty_position();
if i > 3 { moves.insert(Direction::Up, self.board[i - 4]); } if i % 4 != 0 { moves.insert(Direction::Left, self.board[i - 1]); } if i < 12 { moves.insert(Direction::Down, self.board[i + 4]); } if i % 4 != 3 { moves.insert(Direction::Right, self.board[i + 1]); } moves }
fn play(&mut self, direction: Direction) { use Direction::*; let i = self.get_empty_position(); match direction { Up => self.board.swap(i, i - 4), Left => self.board.swap(i, i - 1), Right => self.board.swap(i, i + 1), Down => self.board.swap(i, i + 4), }; }
fn is_complete(&self) -> bool { for (i, cell) in self.board.iter().enumerate() { match cell { &Cell::Card(value) if value == i+1 => (), &Cell::Empty if i == 15 => (), _ => return false, }; } true }
}
impl fmt::Display for P15 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Cell::*; try!(write!(f, "+----+----+----+----+\n")); for (i, cell) in self.board.iter().enumerate() { match cell { &Card(value) => { try!(write!(f, "| {:2} ", value)); }, &Empty => { try!(write!(f, "| ")); } };
if i % 4 == 3 { try!(write!(f, "|\n")); try!(write!(f, "+----+----+----+----+\n")); } } Ok(()) }
}
fn main() {
let mut p15 = P15::new(); let mut turns = 0; loop { println!("{}", p15); match ask_action(&p15.get_moves()) { Action::Move(direction) => { p15.play(direction); }, Action::Quit => { println!("Bye !"); break; } }
turns += 1;
if p15.is_complete() { println!("Well done ! You win in {} turns", turns); break; }
}
}
fn ask_action(moves: &HashMap<Direction, Cell>) -> Action {
use std::io::{self, Write}; use Action::*; use Direction::*; println!("Possible moves :");
match moves.get(&Up) { Some(&Cell::Card(value)) => { println!("\tU) {}", value); }, _ => () } match moves.get(&Left) { Some(&Cell::Card(value)) => { println!("\tL) {}", value); }, _ => () } match moves.get(&Right) { Some(&Cell::Card(value)) => { println!("\tR) {}", value); }, _ => () } match moves.get(&Down) { Some(&Cell::Card(value)) => { println!("\tD) {}", value); }, _ => () } println!("\tQ) Quit"); print!("Choose your move : "); io::stdout().flush().unwrap();
let mut action = String::new(); io::stdin().read_line(&mut action).ok().expect("read error"); match action.trim() { "U" if moves.contains_key(&Up) => Move(Up), "L" if moves.contains_key(&Left) => Move(Left), "R" if moves.contains_key(&Right) => Move(Right), "D" if moves.contains_key(&Down) => Move(Down), "Q" => Quit, _ => { println!("Unknown action : {}", action); ask_action(moves) } }
} </lang>
Tcl
Works with Tcl/Tk 8.5
This program uses Tk, the graphical counterpart to Tcl. The tiles are made of a grid of buttons, and the text on the buttons is moved around.
The button "New game" selects one of the canned puzzles. The window-title is used to show messages.
<lang tcl> # 15puzzle_21.tcl - HaJo Gurt - 2016-02-16
# http://wiki.tcl.tk/14403
#: 15-Puzzle - with grid, buttons and colors
package require Tk
set progVersion "15-Puzzle v0.21"; # 2016-02-20
global Msg Moves PuzzNr GoalNr set Msg " " set Moves -1 set PuzzNr 0 set GoalNr 0
set Keys { 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 }
set Puzz_T { T h e F i f t e e n P u z z l e }; # Title set Goal_T { x x x F i f t e e n x x x x x x }; # Title-highlight
set Puzz_0 { E G P N C A F B D L H I O K M _ }; # - / 116 set Puzz_1 { C A F B E G P N D L H I O K M _ }; # E / 156 from Tk-demo set Puzz_2 { E O N K M I _ G B H L P C F A D }; # L / 139 set Puzz_3 { P G M _ E L N D O K H I B C F A }; # EK / 146
set Goal_0 { A B C D E F G H I K L M N O P _ }; # Rows LTR / 1:E : 108 set Goal_1 { A E I N B F K O C G L P D H M _ }; # Cols forw. / 1:M : 114
set Puzz $Puzz_T set Goal $Goal_T
- ---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+---
proc Move {k} { # find the key with the empty tile: set e -1 foreach p $::Keys { set t [.key$p cget -text] if { $t eq "_" } { set e $p } } if {$e < 0} {return 0}; # no key with empty tile found if {$k == $e} {return 0}; # click was on the empty tile
set t [.key$k cget -text] .key$e config -text $t .key$k config -text "_"; return 1 }
proc Check {} { set ok 0 set i 0 foreach k $::Keys { set t [.key$k cget -text] set g [lindex $::Goal $i] incr i
.key$k config -background white if { $t eq $g } { .key$k config -background lightgreen; incr ok } if { $t eq "_" } { .key$k config -background gray } }
# Solved: update if { $ok > 15 && $::Moves > 0} { foreach k $::Keys { .key$k flash; bell; } } }
proc Click {k} { set ::Msg "" set val [.key$k cget -text] set ok [Move $k]
incr ::Moves $ok wm title . "$::Moves moves" Check }
proc ShowKeys {} { set i 0 foreach k $::Keys { set t [lindex $::Puzz $i] incr i .key$k config -text $t -background white; } Check }
proc NewGame {N} { global Msg Moves PuzzNr GoalNr
incr PuzzNr $N if { $PuzzNr > 3} { set PuzzNr 0 }
set ::Goal $::Goal_0; if { $GoalNr == 1} { set ::Goal $::Goal_1; }
if { $PuzzNr == 0} { set ::Puzz $::Puzz_0; } if { $PuzzNr == 1} { set ::Puzz $::Puzz_1; } if { $PuzzNr == 2} { set ::Puzz $::Puzz_2; } if { $PuzzNr == 3} { set ::Puzz $::Puzz_3; }
set Msg "Try again" if { $N>0 } { set Msg "New game" }
set Moves 0 ShowKeys wm title . "$Msg " }
- ---+----1----+----2----+----3----+----4----+----5----+----6----+----7----+---
button .reset -text "Restart" -fg blue -command {NewGame 0} button .newGame -text "New Game" -fg red -command {NewGame +1}
foreach k $::Keys { button .key$k -text "$k" -width 4 -command "Click $k" }
grid .newGame x .reset x -sticky nsew
grid .key11 .key12 .key13 .key14 -sticky nsew -padx 2 -pady 2 grid .key21 .key22 .key23 .key24 -sticky nsew -padx 2 -pady 2 grid .key31 .key32 .key33 .key34 -sticky nsew -padx 2 -pady 2 grid .key41 .key42 .key43 .key44 -sticky nsew -padx 2 -pady 2
grid configure .newGame .reset -columnspan 2 -padx 4
ShowKeys; Check wm title . $progVersion focus -force . wm resizable . 0 0
- For some more versions, see: http://wiki.tcl.tk/15067 : Classic 15 Puzzle and http://wiki.tcl.tk/15085 : N-Puzzle
</lang>