16 puzzle game
16 numbered pieces of the puzzle are placed out of order on a 4 X 4 grid. The correct order to win is to order the pieces as 1 through 16, read left to right, top to bottom:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
How to Play: The aim is to get the pieces back in order by clicking on the yellow arrows (choosing a location to rotate a row or column) to slide the pieces left or right, up or down.
1 14 3 4 1 2 3 4 5 2 7 8 --> 5 6 7 8 9 6 11 12 9 10 11 12 13 10 15 16 13 14 15 16 ^
The Easy puzzle target is 3 moves, for the Hard puzzle it is 12 moves (or less!). Can it be that simple?
- Task
Create 16 Puzzle Game.
See details: [1]
Video: 16 Puzzle Game
- Related task
AutoHotkey
With Solver, See 16_puzzle_game/autohotkey
FreeBASIC
Const easy = 1, hard = 4
Dim Shared As Byte n(1 To 16)
Sub initGrid ()
For i As Byte = 0 To 15
n(i) = i + 1
Next
End Sub
Sub rotate (ix() As Byte)
Dim As Byte last = n(ix(3))
For i As Byte = 3 To 1 Step -1
n(ix(i)) = n(ix(i-1))
Next
n(ix(0)) = last
End Sub
Function hasWon () As Boolean
For i As Byte = 0 To 15
If n(i) <> i+1 Then Return False
Next
Return True
End Function
Sub setDiff (level As Byte)
Dim As Byte moves = Iif(level = easy, 3, 12)
Dim As Byte rc(), j, i
For i = 0 To moves
Redim As Byte rc(20)
Dim As Byte r = Int(Rnd * 2)
Dim As Byte s = Int(Rnd * 4)
If r = 0 Then ' rotate random row
For j = s*4 To (s+1)*4
rc(j) = j
Next
Else ' rotate random column
For j = s To s+16 Step 4
rc(j) = j
Next
End If
rotate(rc())
If hasWon() Then ' do it again
i = -1
End If
i += 1
Next
Print Using !"\nTarget is ## moves."; moves
End Sub
Sub drawGrid ()
Print !"\n D1 D2 D3 D4"
Print " +-------------------+"
Print Using "R1 | ## | ## | ## | ## | L1"; n(0); n(1); n(2); n(3)
Print " |----+----+----+----|"
Print Using "R2 | ## | ## | ## | ## | L2"; n(4); n(5); n(6); n(7)
Print " |----+----+----+----|"
Print Using "R3 | ## | ## | ## | ## | L3"; n(8); n(9); n(10); n(11)
Print " |----+----+----+----|"
Print Using "R4 | ## | ## | ## | ## | L4"; n(12); n(13); n(14); n(15)
Print " +-------------------+"
Print !" U1 U2 U3 U4\n"
End Sub
'--- Programa Principal ---
Randomize Timer
Dim As Byte level = easy
Dim As String diff
Print "Enter difficulty level easy or hard E/H: ";
Do
Input ; "", diff
Loop Until Instr("eEhH", diff)
If Ucase(diff) = "H" Then level = hard
Cls
Print "Sixteen Puzzle"
initGrid()
setDiff(level)
Dim As Byte ix(0 To 3)
Print "When entering moves, you can also enter Q to quit or S to start again."
Dim As Byte c, moves = 0
Dim As String*2 move
Do
drawGrid()
If hasWon() Then
Print "Congratulations, you have won the game in"; moves; " moves!!"
While Inkey = "": Wend
Exit Do
End If
Do
Print "Moves so far = "; moves
Input "Enter move : ", move
Select Case Trim(Ucase(move))
Case "D1", "D2", "D3", "D4"
c = Cint(Right(move,1)) - 49
ix(0) = 0 + c
ix(1) = 4 + c
ix(2) = 8 + c
ix(3) = 12 + c
rotate(ix())
moves += 1
Exit Do
Case "L1", "L2", "L3", "L4"
c = Cint(Right(move,1)) - 49
ix(0) = 3 + 4*c
ix(1) = 2 + 4*c
ix(2) = 1 + 4*c
ix(3) = 0 + 4*c
rotate(ix())
moves += 1
Exit Do
Case "U1", "U2", "U3", "U4"
c = Cint(Right(move,1)) - 49
ix(0) = 12 + c
ix(1) = 8 + c
ix(2) = 4 + c
ix(3) = 0 + c
rotate(ix())
moves += 1
Exit Do
Case "R1", "R2", "R3", "R4"
c = Cint(Right(move,1)) - 49
ix(0) = 0 + 4*c
ix(1) = 1 + 4*c
ix(2) = 2 + 4*c
ix(3) = 3 + 4*c
rotate(ix())
moves += 1
Exit Do
Case "Q"
Exit Do, Do
Case "S"
Cls
initGrid()
setDiff(level)
moves = 0
Exit Do
Case Else
Print "Invalid move, try again."
End Select
Loop
Loop
'--------------------------
Sleep
FutureBasic
begin enum
_down = 1
_right
_up
_left
_new = 100
_restrt
_help
_end
end enum
str63 board, startPos, winBoard
void local fn init
window 1,,(0,0,340,340)
int x
for x = 1 to 4
button 10+x,,,@"⬇️",(x*50+20,270,50,50),,NSBezelStyleTexturedSquare
ControlSetFontWithName( 10+x, @"Menlo", 32 )
button 30+x,,,@"⬆️",(x*50+20, 20,50,50),,NSBezelStyleTexturedSquare
ControlSetFontWithName( 30+x, @"Menlo", 32 )
button 25-x,,,@"➡️",( 20,x*50+20,50,50),,NSBezelStyleTexturedSquare
ControlSetFontWithName( 25-x, @"Menlo", 32 )
button 45-x,,,@"⬅️",(270,x*50+20,50,50),,NSBezelStyleTexturedSquare
ControlSetFontWithName( 45-x, @"Menlo", 32 )
next
button _new ,,,@"New", ( 20,270,50,50),,NSBezelStyleTexturedSquare
button _end ,,,@"Quit", ( 20, 20,50,50),,NSBezelStyleTexturedSquare
button _restrt,,,@"Redo", (270,270,50,50),,NSBezelStyleTexturedSquare
button _help ,,,@"Help", (270, 20,50,50),,NSBezelStyleTexturedSquare
for x = 1 to 16
winBoard += chr$(x)
next
board = winBoard
end fn
void local fn drawBoard
int c = 1, x, y, z
cls
for y = 70 to 220 step 50
for x = 70 to 220 step 50
rect fill (x,y,48,48), fn coloryellow
if board[c] > 9 then z = x-3 else z = x+6
print %(z,y+6) str(board[c]);
c++
next
next
end fn
void local fn move( tag as int )
int r, d, rc = (tag mod 10)
select tag / 10
case _up : d = +4
case _right : d = -1 : rc *= 4
case _down : d = -4 : rc += 12
case else : d = +1 : rc = rc * 4 - 3
end select
for r = rc to rc + 2 * d step d
swap board[r], board[r+d]
next
if board == winBoard then window 1, @"!!! YOU WON !!!" : text,,fn colorRed
fn drawBoard
end fn
void local fn newGame
window 1, @"16 PUZZLE GAME"
int r
for r = 1 to 16
swap board[r], board[rnd(16)]
next
startPos = board
text @"Arial bold", 32, fn colorblue
fn drawBoard
end fn
void local fn ask( tag as long )
CFStringRef s1, s2 : int btn
select tag
case _help
s1 = @"Use the arrow buttons to move numbers in rows and columns.\n¬
Win by arranging the numbers in order, left to right, top to bottom:"
s2 = @" 1 2 3 4 \n 5 6 7 8 \n 9 10 11 12\n 13 14 15 16"
btn = alert 1, NSAlertStyleInformational, s1, s2, @"Okay"
case _new : if board == winBoard then fn newGame : exit fn
s1 = @"Leave this puzzle\nand start a new one?"
btn = alert 1, NSAlertStyleInformational, s1,,@"New puzzle;Cancel"
if btn == NSAlertFirstButtonReturn then fn newGame
case _restrt
s1 = @"Restart this puzzle\nfrom the beginning?"
btn = alert 1, NSAlertStyleInformational,s1,,@"Restart;Cancel"
if btn == NSAlertFirstButtonReturn then board = startPos : fn drawBoard
case _end
btn = alert 1, NSAlertStyleInformational,@"Quit 16 GAME?",,@"Quit;Cancel"
if btn == NSAlertFirstButtonReturn then end
end select
end fn
void local fn doDialog(ev as long, tag as long)
select ev
case _btnClick : if tag < _new then fn move( tag ) else fn ask( tag )
case _windowWillClose : end
end select
end fn
on dialog fn doDialog
fn init
fn newGame
handleevents
- Output:
Go
package main
import (
"bufio"
"fmt"
"log"
"math/rand"
"os"
"strings"
"time"
)
const (
easy = 1
hard = 4
)
var n [16]int
func initGrid() {
for i := 0; i < 16; i++ {
n[i] = i + 1
}
}
func setDiff(level int) {
moves := 3
if level == hard {
moves = 12
}
rc := make([]int, 0, 4)
for i := 0; i < moves; i++ {
rc = rc[:0]
r := rand.Intn(2)
s := rand.Intn(4)
if r == 0 { // rotate random row
for j := s * 4; j < (s+1)*4; j++ {
rc = append(rc, j)
}
} else { // rotate random column
for j := s; j < s+16; j += 4 {
rc = append(rc, j)
}
}
var rca [4]int
copy(rca[:], rc)
rotate(rca)
if hasWon() { // do it again
i = -1
}
}
fmt.Println("Target is", moves, "moves.")
}
func drawGrid() {
fmt.Println()
fmt.Println(" D1 D2 D3 D4")
fmt.Println(" ╔════╦════╦════╦════╗")
fmt.Printf("R1 ║ %2d ║ %2d ║ %2d ║ %2d ║ L1\n", n[0], n[1], n[2], n[3])
fmt.Println(" ╠════╬════╬════╬════╣")
fmt.Printf("R2 ║ %2d ║ %2d ║ %2d ║ %2d ║ L2\n", n[4], n[5], n[6], n[7])
fmt.Println(" ╠════╬════╬════╬════╣")
fmt.Printf("R3 ║ %2d ║ %2d ║ %2d ║ %2d ║ L3\n", n[8], n[9], n[10], n[11])
fmt.Println(" ╠════╬════╬════╬════╣")
fmt.Printf("R4 ║ %2d ║ %2d ║ %2d ║ %2d ║ L4\n", n[12], n[13], n[14], n[15])
fmt.Println(" ╚════╩════╩════╩════╝")
fmt.Println(" U1 U2 U3 U4\n")
}
func rotate(ix [4]int) {
last := n[ix[3]]
for i := 3; i >= 1; i-- {
n[ix[i]] = n[ix[i-1]]
}
n[ix[0]] = last
}
func hasWon() bool {
for i := 0; i < 16; i++ {
if n[i] != i+1 {
return false
}
}
return true
}
func check(err error) {
if err != nil {
log.Fatal(err)
}
}
func main() {
initGrid()
rand.Seed(time.Now().UnixNano())
level := easy
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("Enter difficulty level easy or hard E/H : ")
scanner.Scan()
diff := strings.ToUpper(strings.TrimSpace(scanner.Text()))
if diff != "E" && diff != "H" {
fmt.Println("Invalid response, try again.")
} else {
if diff == "H" {
level = hard
}
break
}
}
check(scanner.Err())
setDiff(level)
var ix [4]int
fmt.Println("When entering moves, you can also enter Q to quit or S to start again.")
moves := 0
outer:
for {
drawGrid()
if hasWon() {
fmt.Println("Congratulations, you have won the game in", moves, "moves!!")
return
}
for {
fmt.Println("Moves so far =", moves, "\n")
fmt.Print("Enter move : ")
scanner.Scan()
move := strings.ToUpper(strings.TrimSpace(scanner.Text()))
check(scanner.Err())
switch move {
case "D1", "D2", "D3", "D4":
c := int(move[1] - 49)
ix[0] = 0 + c
ix[1] = 4 + c
ix[2] = 8 + c
ix[3] = 12 + c
rotate(ix)
moves++
continue outer
case "L1", "L2", "L3", "L4":
c := int(move[1] - 49)
ix[0] = 3 + 4*c
ix[1] = 2 + 4*c
ix[2] = 1 + 4*c
ix[3] = 0 + 4*c
rotate(ix)
moves++
continue outer
case "U1", "U2", "U3", "U4":
c := int(move[1] - 49)
ix[0] = 12 + c
ix[1] = 8 + c
ix[2] = 4 + c
ix[3] = 0 + c
rotate(ix)
moves++
continue outer
case "R1", "R2", "R3", "R4":
c := int(move[1] - 49)
ix[0] = 0 + 4*c
ix[1] = 1 + 4*c
ix[2] = 2 + 4*c
ix[3] = 3 + 4*c
rotate(ix)
moves++
continue outer
case "Q":
return
case "S":
initGrid()
setDiff(level)
moves = 0
continue outer
default:
fmt.Println("Invalid move, try again.")
}
}
}
}
- Output:
Sample game:
Enter difficulty level easy or hard E/H : e Target is 3 moves. When entering moves, you can also enter Q to quit or S to start again. D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 7 ║ 8 ║ 5 ║ 6 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 16 ║ 13 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Moves so far = 0 Enter move : l4 D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 7 ║ 8 ║ 5 ║ 6 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 13 ║ 14 ║ 15 ║ 16 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Moves so far = 1 Enter move : l2 D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 8 ║ 5 ║ 6 ║ 7 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 13 ║ 14 ║ 15 ║ 16 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Moves so far = 2 Enter move : l2 D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 1 ║ 2 ║ 3 ║ 4 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 5 ║ 6 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 9 ║ 10 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 13 ║ 14 ║ 15 ║ 16 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Congratulations, you have won the game in 3 moves!!
J
Assumes a recent release of jqt:
require'ide/qt/gl2'
coinsert'jgl2'
NB. event handlers
game_reset_button=: {{ draw COUNT=: #SETUP=: EMPTY [BOARD=: WIN }}
game_restart_button=: {{ draw COUNT=: 0 [rotate"1 SETUP [BOARD=: WIN }}
game_easy_button=: {{ setup 3 }}
game_hard_button=: {{ setup 15 }}
game_board_mbldown=: {{
loc=. (20+40*i.5)I.2{._".sysdata
if. 1=#ndx=. loc -. 0 5 do.
side=. 2#.<:>.loc%4 NB. _2: left, _1 top, 1 bottom, 2 right
draw rotate side, ndx
end.
}}
NB. game logic
BOARD=: WIN=: 1+i.4 4
message=: {{
color=. (COUNT>#SETUP){::;:'black red'
A=. '<span style="color: ',color,'">' [Z=. '</span>'
if. BOARD-:WIN do. A,'You win',Z return. end.
A,(":COUNT),Z,' of ',":#SETUP
}}
setup=: {{ game_restart_button SETUP=: (_2 _1 1 2{~?y#4),.1+?y#4 }}
rotate=: {{
COUNT=: COUNT+1
'side ndx'=. y-0 1
flip=. |: if. 2=|side do. flip=. ] end.
BOARD=: flip ((*side)|.ndx{flip BOARD) ndx} flip BOARD
}}
NB. rendering
wd {{)n
pc game closeok;
cc board isidraw;
set board wh 200 200;
cc msg static center;
bin h;
cc easy button;
set easy tooltip start game which can be completed in 3 moves;
cc hard button;
set hard tooltip start game which can be completed in 15 moves;
bin z; bin h;
cc restart button;
cc reset button;
set reset tooltip set board to initial position, ending any current game;
pshow;
}}
draw=: {{
glclear''
glbrush glrgb 3#224 NB. silver
glrect 0 0 200 200
glbrush glrgb 0 0 255 NB. blue
glrect T=:20 20 40 40+"1]40*4 4 1 1#:i.4 4
glbrush glrgb 255 255 0 NB. yellow
glpolygon (,200-])(,;"1@(_2<@|.\"1]))0 30 0 50 10 40+"1(i.4)*/6$0 40
gltextcolor glrgb 3#255 NB. white
glfont '"lucidia console" 16'
BOARD {{ gltext ":x [ gltextxy y+5 0*_1^1<#":x }}"_2(30+40*4 4#:|:i.4 4)
if. EMPTY-:SETUP do.
wd {{)n
set msg text <b>easy</b> or <b>hard</b> to start;
set restart enable 0;
set restart caption;
}} else. wd {{)n
set msg text MESSAGE;
set restart enable 1;
set restart caption restart;
}} rplc 'MESSAGE';message''
end.
glpaint''
}}
NB. start:
game_reset_button''
JavaScript
Try it here.
You'll also need a html file:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono&display=swap" rel="stylesheet"> <link rel="stylesheet" type="text/css" media="screen" href="./css/main.css"> <title>16 Puzzle</title> </head> <body> <div id="done">WELL DONE!</div> <div id="board"></div> <div id="moves"></div> <button id="shuffle">SHUFFLE</button> <script src="./src/index.js" type="module"></script> </body> </html>
And css file:
* { margin: 0; border: 0; text-align: center; font-family: 'Ubuntu Mono', monospace; user-select: none; } button { border-radius: 5px; width: 300px; height: 80px; font-size: 40px; margin-top: 60px; } #board { width: 410px; height: 410px; margin: 120px auto 30px auto; } #done { font-size: 140px; padding: 20px; color: #fff; background-color: rgba(0, 23, 56, .5); border: 1px solid rgb(0, 90, 220); width: 700px; position: absolute; top: 250px; left: calc(50% - 380px); } #moves { font-size: 40px; line-height: 80px; height: 80px; width: 300px; margin: auto; border: 1px solid #000; border-radius: 5px; } .btn, .numbers, .hide { float: left; width: 64px; height: 64px; line-height: 65px; font-size: 40px; border: 1px solid black; color: black; background-color: white; cursor: none; margin: 1px; transition: all .3s; } .btn:hover { background-color: rgba(71, 231, 71, 0.5); cursor: pointer; } .hide { border: 1px solid white; cursor: none; }
class Puzzle {
constructor() {
this.moves;
this.started;
this.board = document.getElementById("board");
document.getElementById("shuffle").addEventListener("click", () => {
this.shuffle();
});
this.reset();
}
reset() {
while (this.board.hasChildNodes()) {
this.board.removeChild(this.board.firstChild);
}
this.moves = 0;
this.started = false;
document.getElementById("moves").innerText = this.moves;
document.getElementById("done").style.visibility = "hidden";
let t = 1;
for (let y = 0; y < 6; y++) {
for (let x = 0; x < 6; x++) {
const d = document.createElement("div");
d.id = `${x}_${y}`;
if (y === 0 || x === 0 || y === 5 || x === 5) {
if (((y === 0 || y === 5) && (x === 0 || x === 5))) {
d.className = "hide";
} else {
d.className = "btn";
if (y === 0) {
d.innerText = "🡇";
d.func = () => {
this.rollDownRight(x, true);
};
} else if (y === 5) {
d.innerText = "🡅";
d.func = () => {
this.rollUpLeft(x, true);
};
}
if (x === 0) {
d.innerText = "🡆";
d.func = () => {
this.rollDownRight(y, false);
};
} else if (x === 5) {
d.innerText = "🡄";
d.func = () => {
this.rollUpLeft(y, false);
};
}
d.addEventListener("click", (ev) => {
ev.srcElement.func();
})
}
} else {
d.className = "numbers";
d.innerText = `${t}`;
t++;
}
this.board.appendChild(d);
}
}
document.getElementById("shuffle").innerText = "SHUFFLE";
}
shuffle() {
if (this.started) {
this.reset();
} else {
this.started = true;
const e = Math.floor(Math.random() * 30) + 30;
for (let z = 0; z < e; z++) {
switch (Math.floor(Math.random() * 4)) {
case 0:
this.rollDownRight(Math.floor(Math.random() * 4) + 1, false);
break;
case 1:
this.rollUpLeft(Math.floor(Math.random() * 4) + 1, true);
break;
case 2:
this.rollUpLeft(Math.floor(Math.random() * 4) + 1, false);
break;
case 3:
this.rollDownRight(Math.floor(Math.random() * 4) + 1, true);
break;
}
}
this.moves = 0;
document.getElementById("moves").innerText = this.moves;
document.getElementById("shuffle").innerText = "RESTART";
}
}
getElements(l, col) {
const z = Array.from(document.querySelectorAll(".numbers"));
for (let e = 15; e > -1; e--) {
if (z[e].id[(col ? 0 : 2)] != l) {
z.splice(e, 1)
}
}
return z;
}
rollDownRight(x, col) {
if (!this.started) return;
const z = this.getElements(x, col),
a = z[3].innerText;
for (let r = 3; r > 0; r--) {
z[r].innerText = z[r - 1].innerText;
}
z[0].innerText = a;
this.updateMoves();
this.checkSolved();
}
rollUpLeft(x, col) {
if (!this.started) return;
const z = this.getElements(x, col),
a = z[0].innerText;
for (let r = 0; r < 3; r++) {
z[r].innerText = z[r + 1].innerText;
}
z[3].innerText = a;
this.updateMoves();
this.checkSolved();
}
updateMoves() {
this.moves++;
document.getElementById("moves").innerText = this.moves;
}
checkSolved() {
function check() {
const z = document.querySelectorAll(".numbers");
let u = 1;
for (let r of z) {
if (r.innerText != u) return false;
u++;
}
return true;
}
if (this.started && check()) {
document.getElementById("done").style.visibility = "visible";
}
}
}
new Puzzle();
jq
Adapted from Wren
Works with jq, the C implementation of jq
Works with gojq, the Go implementation of jq (*)
The following implementation illustrates the use of "stderr" to simplify interaction with the user.
The program also illustrates the use of the MRG32k3a pseudo-random number generator, a jq implementation of which which is available from RosettaCode.org. An alternative that would probably be acceptable here would be simply to use the built-in `now`.
include "MRG32k3a" {search: "."}; # see comment above
### Generic functions
def lpad($len): tostring | ($len - length) as $l | (" " * $l) + .;
def inform(msg):
. as $in
| msg | stderr | $in;
### The 16-Puzzle Game
def easy: 1;
def hard: 4;
def drawGrid:
def row($left; $array; $right):
([$left, ($array[] | lpad(2) ), $right] | join(" ║ ")) + "\n";
inform(
" D1 D2 D3 D4\n"
+ " ╔════╦════╦════╦════╗\n"
+ row("R1"; .n[0:4]; "L1")
+ " ╠════╬════╬════╬════╣\n"
+ row("R2"; .n[4:8]; "L2")
+ " ╠════╬════╬════╬════╣\n"
+ row("R3"; .n[8:12];" L3")
+ " ╠════╬════╬════╬════╣\n"
+ row("R4"; .n[12:16];"L4")
+ " ╚════╩════╩════╩════╝\n"
+ " U1 U2 U3 U4\n"
);
# If $check is a string, interpret it as a regex to be checked.
# Otherwise, allow any non-blank input
def prompt($prompt; $check):
def p:
inform($prompt)
| try input catch halt
| if ($check|type) == "string"
then if (try test($check) catch false) then .
else p
end
elif trim != "" then .
else p
end;
p;
def initGrid:
{n: [range ( 0; 16) | . + 1]};
# Input: {n}
# $ix should be an array of length 4
def rotate($ix):
.n[$ix[3]] as $last
| reduce range (3; 0; -1) as $i (.; .n[$ix[$i]] = .n[$ix[$i-1]])
| .n[$ix[0]] = $last;
def hasWon:
.n as $n
| all( range(0;15); . as $i | $n[$i] == $i + 1);
# Input: {prng} as per MRG32k3a
# Output: {prng, prn} where .prn is randomly selected from range(0;$n)
def prn($n):
.prng |= nextFloat
| .prn = (.prng|.nextFloat * $n | trunc) ;
# Set difficulty level
# Input: {n}
def setDiff($level):
(if $level == easy then 3 else 12 end) as $moves
| .rc = []
| .i = 0
| .prng = (seed(now | tostring | sub("^.*[.]";"") | tonumber))
| until(.i >= $moves;
.rc = []
| prn(2) | .prn as $r
| prn(4) | .prn as $s
| if ($r == 0)
then reduce range($s*4; ($s+1)*4) as $j (.; .rc += [$j] )
else # rotate random column
reduce range($s; $s+16; 4) as $j (.; .rc += [$j] )
end
| rotate(.rc)
| if hasWon
then # start again
.i = -1
end
| .i += 1
)
| inform("Target is \($moves) moves.\n");
def play:
def trim: gsub(" ";"");
def difficulty:
prompt("Enter difficulty level (E for easy, H for hard): "; "^[eEhH]")
| .[:1]
| ascii_upcase;
initGrid
| .level = (if difficulty == "H" then hard else easy end)
| setDiff(.level)
| inform("When entering moves, you can also enter Q to quit or S to start again.\n")
| .moves = 0
| label $out
| recurse(
drawGrid
| if hasWon
then inform( "Congratulations, you have won the game in \(.moves) moves!\n")
| break $out
end
| inform("Moves so far = \(.moves)\n")
| (prompt("Enter move : "; "^ *([qsQS]|[drluDRLU] *[1234])")
| ascii_upcase | trim) as $move
| $move[:1] as $a
| (if $a | test("[SQ]")
then null
else (($move[1:]|explode[0]) - 49)
end ) as $c
| if $a == "D"
then rotate([0,4,8,12] | map(. + $c))
| .moves += 1
elif $a == "L"
then rotate([3,2,1,0] | map(. + 4*$c))
| .moves += 1
elif $a == "U"
then rotate([12,8,4,0] | map(. + $c))
| .moves += 1
elif $a == "R"
then rotate([0,1,2,3] | map(. + 4*$c))
| .moves += 1
elif $move == "Q"
then break $out
elif $move == "S"
then inform("Restarting...\n")
| initGrid
| setDiff(.level)
| .moves = 0
else inform("Invalid move, try again.\n")
end )
| empty ;
play
- Output:
Invocation: jq -nRr -f 16-puzzle-game.jq
Enter difficulty level (E for easy, H for hard): e Target is 3 moves. When entering moves, you can also enter Q to quit or S to start again. D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 4 ║ 1 ║ 13 ║ 3 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 5 ║ 2 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 9 ║ 6 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 16 ║ 10 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Moves so far = 0 Enter move : d1 D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 16 ║ 1 ║ 13 ║ 3 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 4 ║ 2 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 5 ║ 6 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 9 ║ 10 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Moves so far = 1 Enter move : u1 D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 4 ║ 1 ║ 13 ║ 3 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 5 ║ 2 ║ 7 ║ 8 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 9 ║ 6 ║ 11 ║ 12 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 16 ║ 10 ║ 14 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Moves so far = 2 Enter move : s Restarting... Target is 12 moves. D1 D2 D3 D4 ╔════╦════╦════╦════╗ R1 ║ 1 ║ 9 ║ 6 ║ 4 ║ L1 ╠════╬════╬════╬════╣ R2 ║ 8 ║ 5 ║ 10 ║ 14 ║ L2 ╠════╬════╬════╬════╣ R3 ║ 3 ║ 7 ║ 2 ║ 13 ║ L3 ╠════╬════╬════╬════╣ R4 ║ 11 ║ 16 ║ 12 ║ 15 ║ L4 ╚════╩════╩════╩════╝ U1 U2 U3 U4 Moves so far = 0 Enter move : q
Julia
using Gtk, Random
function puzzle16app(bsize)
aclock, clock = "\u27f2", "\u27f3"
win = GtkWindow("16 Game", 300, 300) |> (GtkFrame() |> (box = GtkBox(:v)))
toolbar = GtkToolbar()
newgame = GtkToolButton("New Game")
set_gtk_property!(newgame, :label, "New Game")
set_gtk_property!(newgame, :is_important, true)
push!(toolbar, newgame)
grid = GtkGrid()
map(w -> push!(box, w),[toolbar, grid])
buttons = Array{Gtk.GtkButtonLeaf,2}(undef, bsize + 2, bsize + 2)
for i in 1:bsize+2, j in 1:bsize+2
grid[i,j] = buttons[i,j] = GtkButton()
set_gtk_property!(buttons[i,j], :expand, true)
end
inorder = string.(reshape(1:bsize*bsize, bsize, bsize))
puzzle = shuffle(inorder)
rotatecol(puzzle, col, n) = puzzle[:, col] .= circshift(puzzle[:, col], n)
rotaterow(puzzle, row, n) = puzzle[row, :] .= circshift(puzzle[row, :], n)
iswon() = puzzle == inorder
won = false
function findrowcol(button)
for i in 1:bsize+2, j in 1:bsize+2
if buttons[i, j] == button
return i, j
end
end
return 0, 0
end
function playerclicked(button)
if !won
i, j = findrowcol(button)
if i == 1
rotatecol(puzzle, j - 1, 1)
elseif i == bsize + 2
rotatecol(puzzle, j - 1, -1)
elseif j == 1
rotaterow(puzzle, i - 1, 1)
elseif j == bsize + 2
rotaterow(puzzle, i - 1, -1)
end
end
update!()
end
function setup!()
for i in 1:bsize+2, j in 1:bsize+2
if 1 < j < bsize + 2
if i == 1
signal_connect(playerclicked, buttons[i, j], "clicked")
elseif i == bsize + 2
signal_connect(playerclicked, buttons[i, j], "clicked")
end
elseif 1 < i < bsize + 2
if j == 1
signal_connect(playerclicked, buttons[i, j], "clicked")
elseif j == bsize + 2
signal_connect(playerclicked, buttons[i, j], "clicked")
end
end
end
end
function update!()
for i in 1:bsize+2, j in 1:bsize+2
if 1 < j < bsize + 2
if i == 1
set_gtk_property!(buttons[i, j], :label, clock)
elseif i == bsize + 2
set_gtk_property!(buttons[i, j], :label, aclock)
else
set_gtk_property!(buttons[i, j], :label, puzzle[i-1, j-1])
end
elseif 1 < i < bsize + 2
if j == 1
set_gtk_property!(buttons[i, j], :label, clock)
elseif j == bsize + 2
set_gtk_property!(buttons[i, j], :label, aclock)
end
end
end
if iswon()
won = true
info_dialog("Game over.\nScore: $score", win)
end
showall(win)
end
function initialize!(w)
puzzle = shuffle(inorder)
won = false
update!()
end
setup!()
condition = Condition()
endit(w) = notify(condition)
signal_connect(initialize!, newgame, :clicked)
signal_connect(endit, win, :destroy)
initialize!(win)
showall(win)
wait(condition)
end
puzzle16app(4)
Nim
Not a direct translation as there are a lot of differences, but, at least, the graphical interface is similar and the logic is the same.
import random, sequtils, strutils
import gintro/[glib, gobject, gtk, gio]
const
BoardSize = 4
GridSize = BoardSize + 2
Clock = "\u27f2"
AClock = "\u27f3"
type
Value = 1..16
Puzzle = array[BoardSize, array[BoardSize, Value]]
PuzzleApp = ref object of Application
inOrder: Puzzle # Target grid.
puzzle: Puzzle # Current grid.
buttons: array[GridSize, array[GridSize, Button]] # Button grid.
won: bool # True if game won.
moves: Natural # Count of moves.
#---------------------------------------------------------------------------------------------------
proc initPuzzle(puzzle: var Puzzle; data: openArray[Value]) =
## Initialize the puzzle with a list of values.
var i = 0
for row in puzzle.mitems:
for cell in row.mitems:
cell = data[i]
inc i
#---------------------------------------------------------------------------------------------------
proc showMessage(app: PuzzleApp) =
## As "gintro" doesn't provide "MessageDialog" yet, we will use a simple dialog.
let dialog = newDialog()
dialog.setModal(true)
let label = newLabel("You won in $# move(s)".format(app.moves))
dialog.contentArea.add(label)
discard dialog.addButton("Ok", ord(ResponseType.ok))
dialog.showAll()
discard dialog.run()
dialog.destroy()
#---------------------------------------------------------------------------------------------------
proc onQuit(button: ToolButton; window: ApplicationWindow) =
## Procedure called when clicking quit button.
window.destroy()
#---------------------------------------------------------------------------------------------------
proc rotateRow(puzzle: var Puzzle; row: Natural; left: bool) =
## Rotate a row left or right.
if left:
let first = puzzle[row][0]
for i in 1..puzzle.high: puzzle[row][i-1] = puzzle[row][i]
puzzle[row][^1] = first
else:
let last = puzzle[row][^1]
for i in countdown(puzzle.high, 1): puzzle[row][i] = puzzle[row][i-1]
puzzle[row][0] = last
#---------------------------------------------------------------------------------------------------
proc rotateCol(puzzle: var Puzzle; col: Natural; up: bool) =
## Rotate a column up or down.
if up:
let first = puzzle[0][col]
for i in 1..puzzle.high: puzzle[i-1][col] = puzzle[i][col]
puzzle[^1][col] = first
else:
let last = puzzle[^1][col]
for i in countdown(puzzle[0].high, 1): puzzle[i][col] =puzzle[i-1][col]
puzzle[0][col] = last
#---------------------------------------------------------------------------------------------------
proc findRowCol(app: PuzzleApp; button: Button): (int, int) =
## Find the row and column of a button.
for i in [0, BoardSize+1]:
for j in 1..Boardsize:
if app.buttons[i][j] == button: return (i, j)
for j in [0, BoardSize+1]:
for i in 1..Boardsize:
if app.buttons[i][j] == button: return (i, j)
#---------------------------------------------------------------------------------------------------
proc update(app: PuzzleApp) =
## Update the grid.
for i in 0..BoardSize+1:
for j in 0..BoardSize+1:
if j in 1..BoardSize:
if i == 0:
app.buttons[i][j].setLabel(Clock)
elif i == BoardSize + 1:
app.buttons[i][j].setLabel(AClock)
else:
app.buttons[i][j].setLabel($app.puzzle[i-1][j-1])
elif i in 1..BoardSize:
if j == 0:
app.buttons[i][j].setLabel(Clock)
elif j == BoardSize + 1:
app.buttons[i][j].setLabel(AClock)
if app.puzzle == app.inOrder:
app.won = true
app.showMessage()
#---------------------------------------------------------------------------------------------------
proc onClick(button: Button; app: PuzzleApp) =
## Procedure called when the user cliked a grid button.
if not app.won:
inc app.moves
let (i, j) = app.findRowCol(button)
if i == 0:
app.puzzle.rotateCol(j - 1, true)
elif i == BoardSize + 1:
app.puzzle.rotateCol(j - 1, false)
elif j == 0:
app.puzzle.rotateRow(i - 1, true)
elif j == BoardSize + 1:
app.puzzle.rotateRow(i - 1, false)
app.update()
#---------------------------------------------------------------------------------------------------
proc newGame(button: ToolButton; app: PuzzleApp) =
## Prepare a new game.
var values = toSeq(Value.low..Value.high)
values.shuffle()
app.puzzle.initPuzzle(values)
app.won = false
app.update()
#---------------------------------------------------------------------------------------------------
proc activate(app: PuzzleApp) =
## Activate the application.
let window = app.newApplicationWindow()
window.setTitle("16 puzzle game")
window.setSizeRequest(300, 340)
let box = newBox(Orientation.vertical, 0)
window.add box
let toolbar = newToolbar()
let newGameButton = newToolButton(label = "New game")
toolbar.insert(newGameButton, 0)
let quitButton = newToolButton(label = "Quit")
toolbar.insert(quitButton, 1)
box.add toolbar
let grid = newGrid()
box.add grid
for i in 0..BoardSize+1:
for j in 0..BoardSize+1:
let button = newButton()
button.setHexpand(true)
button.setVexpand(true)
app.buttons[i][j] = button
grid.attach(button, j, i, 1, 1)
var values = toSeq(Value.low..Value.high)
app.inOrder.initPuzzle(values)
values.shuffle()
app.puzzle.initPuzzle(values)
for i in [0, BoardSize + 1]:
for j in 1..BoardSize:
app.buttons[i][j].connect("clicked", onClick, app)
for j in [0, BoardSize + 1]:
for i in 1..BoardSize:
app.buttons[i][j].connect("clicked", onClick, app)
newGameButton.connect("clicked", newGame, app)
quitButton.connect("clicked", onQuit, window)
app.won = false
app.update()
window.showAll()
#———————————————————————————————————————————————————————————————————————————————————————————————————
randomize()
let app = newApplication(PuzzleApp, "Rosetta.Puzzle16Game")
discard app.connect("activate", activate)
discard app.run()
Perl
#!/usr/bin/perl
use strict; # http://www.rosettacode.org/wiki/16_Puzzle_Game
use warnings;
use List::Util qw( any );
use Tk;
my $size = $ARGV[0] // 4;
my $width = length $size ** 2;
my $message = '';
my $steps;
my @board;
my @again;
my $easy = 3;
my $mw = MainWindow->new( -title => '16 Puzzle in Tk' );
$mw->geometry( '+470+300' );
$mw->optionAdd('*font' => 'sans 14');
my $frame = $mw->Frame(-bg => 'gray', -relief => 'ridge',
-borderwidth => 5)->pack;
my $label = $mw->Label( -textvariable => \$message, -font => 'times-bold 30',
)->pack;
$mw->Button( -text => "Exit", -command => sub {$mw->destroy},
)->pack(-side => 'right');
$mw->Button( -text => "Reset", -command => sub {
@board = @again;
show();
$message = $steps = 0;
$label->configure(-fg => 'black');
},)->pack(-side => 'right');
$mw->Button( -text => "Easy", -command => sub {$easy = 3; generate()},
)->pack(-side => 'left');
$mw->Button( -text => "Hard", -command => sub {$easy = 12; generate()},
)->pack(-side => 'left');
my @cells = map {
$frame->Label(-text => $_, -relief => 'sunken', -borderwidth => 2,
-fg => 'white', -bg => 'blue', -font => 'sans 24',
-padx => 7, -pady => 7, -width => $width,
)->grid(-row => int( $_ / $size + 2 ), -column => $_ % $size + 2,
-sticky => "nsew",
) } 0 .. $size ** 2 - 1;
for my $i (1 .. $size)
{
$frame->Button(-text => ">", -command => sub {move("R$i") },
)->grid(-row => $i + 1, -column => 1, -sticky => "nsew");
$frame->Button(-text => "<", -command => sub {move("L$i") },
)->grid(-row => $i + 1, -column => $size + 2, -sticky => "nsew");
$frame->Button(-text => "v", -command => sub {move("D$i") },
)->grid(-row => 1, -column => $i + 1, -sticky => "nsew");
$frame->Button(-text => "^", -command => sub {move("U$i") },
)->grid(-row => $size + 2, -column => $i + 1, -sticky => "nsew");
}
generate();
MainLoop;
-M $0 < 0 and exec $0, @ARGV; # restart if source file modified since start
sub randommove { move( qw(U D L R)[rand 4] . int 1 + rand $size ) }
sub show { $cells[$_]->configure(-text => $board[$_]) for 0 .. $size ** 2 - 1 }
sub success { not any { $_ + 1 != $board[$_] } 0 .. $size ** 2 - 1 }
sub move
{
my ($dir, $index) = split //, shift;
$index--;
my @from = map {
$dir =~ /L|R/i ? $_ + $size * $index : $_ * $size + $index
} 0 .. $size - 1;
@board[@from] = (@board[@from])[ ($dir =~ /L|U/i || -1) .. $size - 1, 0 ];
show();
$message = ++$steps;
$label->configure(-fg => success() ? 'red' : 'black');
success() and $message = "Solved in $steps";
}
sub generate
{
@board = 1 .. $size ** 2;
randommove() for 1 .. 1 + int rand $easy;
success() and randommove();
@again = @board;
$message = $steps = 0;
}
Phix
NB arrow keys not tested on linux, but "UDLR" should work...
constant level = 5, ESC=27, UP=328, DOWN=336, LEFT=331, RIGHT=333 sequence board = tagset(16), solve = board procedure print_board() printf(1," 1 2 3 4\n") for r=1 to 4 do printf(1,"%d: %2d %2d %2d %2d\n",r&board[r*4-3..r*4]) end for puts(1,"\n") end procedure procedure move(integer d,rc) -- d is 1..4 for up/down/left/right -- rc is 1..4 for row(d>=3)/column(d<=2) sequence idx = repeat(0,4), tiles = repeat(0,4) for i=1 to 4 do idx[i] = iff(d<=2?rc+(i-1)*4:(rc-1)*4+i) tiles[i] = board[idx[i]] end for -- ?{d,rc,idx} idx = iff(mod(d,2)?idx[4]&idx[1..3]:idx[2..4]&idx[1]) for i=1 to 4 do board[idx[i]] = tiles[i] end for end procedure for i=1 to level do move(rand(4),rand(4)) end for while 1 do print_board() if board=solve then puts(1,"Solved!\n") exit end if puts(1,"Your move (escape|Up/Down|Left/Right & 1/2/3/4):") integer d, rc while true do while true do d = find(upper(wait_key()),{ESC,UP,DOWN,LEFT,RIGHT}&"UDLR")-1 if d!=-1 then exit end if end while if d=0 then puts(1,"\n\nYou gave up!\n") exit end if if d>4 then d-=4 end if puts(1,"UDLR"[d]) while true do rc = find(upper(wait_key()),ESC&"1234UDLR"&{UP,DOWN,LEFT,RIGHT}