I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

16 puzzle game

From Rosetta Code
16 puzzle game is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.


Task

Create 16 Puzzle Game.


See details: 16 Puzzle Game


Video: 16 Puzzle Game


Related task



Go[edit]

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

JavaScript[edit]

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();
 

Julia[edit]

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[edit]

Translation of: Julia
Library: gintro

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[edit]

#!/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[edit]

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})-1
            if rc>4 then
                if rc>8 then rc -= 4 end if
                d = rc-4
                puts(1,"\b \b"&"UDLR"[d])
            else
                if rc!=-1 then exit end if
            end if
        end while
        if rc=0 then
            puts(1,"\b \b")
        else
            printf(1,"%d\n\n",rc)
            move(d,rc)
            exit
        end if
    end while
    if d=0 then exit end if
end while
Output:

(a level 2 game)

    1  2  3  4
1:  1  2  7  4
2:  5  6 11  8
3:  9 10 16 12
4: 14 15  3 13

Your move (escape|Up/Down|Left/Right & 1/2/3/4):D3

    1  2  3  4
1:  1  2  3  4
2:  5  6  7  8
3:  9 10 11 12
4: 14 15 16 13

Your move (escape|Up/Down|Left/Right & 1/2/3/4):R4

    1  2  3  4
1:  1  2  3  4
2:  5  6  7  8
3:  9 10 11 12
4: 13 14 15 16

Solved!

Raku[edit]

For brevity, changed to zero-based and skipped some error handling.

Translation of: Go
# 20210914 Raku programming solution 
 
my (\easy,\hard) = 1,4 ; my @n = ^16; my \level = $ = easy ; my \moves = $ = 0;
 
sub hasWon { @n eq ^16 }
 
sub setDiff($level) {
say "\nTarget is ", ( moves = $level == hard ?? 12 !! 3 ).Str, " moves.";
for ^moves {
my \s = (^4).roll;
@n[ ( [ s, s+4 ... s+12 ] , [ s*4 .. s*4+3 ] ).roll ] .= rotate ;
redo if hasWon
}
}
 
sub drawGrid {
say "\n U1 U2 U3 U4";
say " ╔════╦════╦════╦════╗";
printf "L1 ║ %2d ║ %2d ║ %2d ║ %2d ║ R1\n", @n[0..3];
say " ╠════╬════╬════╬════╣";
printf "L2 ║ %2d ║ %2d ║ %2d ║ %2d ║ R2\n", @n[4..7];
say " ╠════╬════╬════╬════╣";
printf "L3 ║ %2d ║ %2d ║ %2d ║ %2d ║ R3\n", @n[8..11];
say " ╠════╬════╬════╬════╣";
printf "L4 ║ %2d ║ %2d ║ %2d ║ %2d ║ R4\n", @n[12..15];
say " ╚════╩════╩════╩════╝";
say " D1 D2 D3 D4\n"
}
 
sub init {
loop {
print "Enter difficulty level easy or hard E/H : ";
given $*IN.get.uc {
when 'E'|'H' { level = $_ eq 'H' ?? hard !! easy ; last }
default { say "Invalid response, try again." }
}
}
setDiff(level);
moves = 0;
}
 
init;
 
loop {
drawGrid;
if hasWon() {
say "Congratulations, you have won the game in {moves} moves.\n" and exit
}
say "When entering moves, you can also enter Q to quit or S to start again.";
say "\nMoves so far = {moves}\n";
print "Enter move : " ;
given $*IN.get.uc {
my \c = .substr(*-1).ord - 49 ; moves++ ;
when 'D1'|'D2'|'D3'|'D4' { @n[ (12,8,4,0) >>+>> c ] .= rotate }
when 'L1'|'L2'|'L3'|'L4' { @n[ (0,1,2,3) >>+>> 4*c ] .= rotate }
when 'U1'|'U2'|'U3'|'U4' { @n[ (0,4,8,12) >>+>> c ] .= rotate }
when 'R1'|'R2'|'R3'|'R4' { @n[ (3,2,1,0) >>+>> 4*c ] .= rotate }
when 'Q' { exit }
when 'S' { init }
default { say "\nInvalid move, try again." and moves-- }
}
}

REXX[edit]

This REXX version allows the user to choose the grid size for the   16   game (puzzle),   as well as the difficulty of the puzzle.
The user's responses may have optional whitespace in the answer, and the answer can be in any case (lower or uppercase).
Not all errors are checked so as to keep the program simpler.

/*REXX pgm implements the  16  game;  displays game grid, prompts for a move, game won? */
sep= copies("─",8); pad=left('',1+length(sep) ) /*pad=9 blanks. SEP is used for msgs.*/
parse arg N hard seed . /*obtain optional arguments from the CL*/
er= '***error***' /*literal used to indicate an error msg*/
if N=='' | N=="," then N= 4 /*Not specified? Then use the default.*/
if hard=='' | hard=="," then hard= 2 /* " " " " " " */
if \isInt(N) then do; say sep er "grid size isn't an integer: " N; exit 1; end
if N<2 | N>9 then do; say sep er "grid size is out of range: " N; exit 1; end
if isInt(seed) then call random , , seed /*use repeatability seed for RANDOM BIF*/
say sep 'Playing a ' N*N " game with a difficulty level of: " hard
#=0
do r=1 for N /* [◄] build a solution for testing. */
do c=1 for N; #= #+1; @.r.c= # /*bump number (count), define a cell. */
end /*c*/
end /*r*/
/* [↓] HARD is the puzzle difficulty.*/
do hard; row= random(1) /*scramble the grid HARD # of times.*/
if row then call move random(1,N)substr('LR', random(1, 2), 1) /* ◄── move row. */
else call move substr('abcdefghi',random(1,N), 1)substr("+-",random(1,2),1)
end /*hard*/ /* [↓] move col.*/
/*play 16─game until solved or quit.*/
do until done() /*perform moves until puzzle is solved.*/
call move /*get user's move(s) and validate it.*/
if errMsg\=='' then do; say sep er errMsg"."; iterate; end /*possible error msg?*/
end /*until*/
 
call show; say sep 'Congratulations! The' N**2"─puzzle is solved."
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
done: #=0; do r=1 to N; do c=1 to N; #=#+1; if @.r.c\==# then return 0; end; end; return 1
isInt: return datatype( arg(1), 'W') /*return 1 if arg is a whole number. */
ghost: do r=1 for n; do c=1 for n;  !.r.c= @.r.c; end /*r*/; end /*c*/; return
/*──────────────────────────────────────────────────────────────────────────────────────*/
move: arg x /*obtain optional move from ARG or ask.*/
ask1= sep 'Please enter a row number followed by a L or R, or'
ask2= sep ' enter a column letter followed by a + or -' @quit
@quit= ' (or Quit):'
if x=='' then do
if queued()==0 then do; say; call show; say ask1; say ask2
end
pull x; x= space(x, 0) /*obtain a response; elide whitespace.*/
end
y= left(x, 1); d= right(x, 1) /*get a number or letter, and also a ± */
num= isInt(d); let= datatype(y,'U') /*get direction to shift, based on type*/
if abbrev('QUIT', x, 1) then do; say; say; say sep "quitting."; exit; end
select
when x == '' then errMsg= "nothing entered"
when length(x)>2 then errMsg= "improper response: " x
when num & (y <1 | y >N ) then errMsg= "row isn't in range: " y
when num & (d\="L" & d\='R') then errMsg= "row shift isn't L or R: " d
when let & (y <"A" | y >HI ) then errMsg= "col isn't in range: " y
when let & (d\="+" & d\='-') then errMsg= "col shift isn't + or -: " d
otherwise errMsg=
end /*select*/ /* [↑] verify the human entered data. */
call ghost; yn= pos(y, 'ABCDEFGHI') /*create a ghost grid for easy moving. */
if isInt(y) then if d=='R' then do c=1 for N; cm= c-1; if c==1 then cm= c+N-1
@.y.c= !.y.cm
end
else do c=1 for N; cp= c+1; if c==N then cp= 1
@.y.c= !.y.cp
end
else if d=='-' then do r=1 for N; rm= r-1; if r==1 then rm= r+N-1
@.r.yn= !.rm.yn
end
else do r=1 for N; rp= r+1; if r==N then rp= 1
@.r.yn= !.rp.yn
end
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
show: top= '╔'copies( copies("═", 2)'╦', N); top= left( top, length(top) - 1)"╗"
bar= '╠'copies( copies("═", 2)'╬', N); bar= left( bar, length(bar) - 1)"╣"
bot= '╚'copies( copies("═", 2)'╩', N); bot= left( bot, length(bot) - 1)"╝"
ind= left('', 3 + length(N) ) /*compute indentation.*/
col= ind ind ind' ' subword("a- b- c- d- e- f- g- h- i-", 1, n)
HI= substr('abcdefghi', N, 1); upper HI
say col ind ind ind '- means shift a column down'; say pad ind top
do r=1 for N; z= r'R' " ║" /*build NxN game grid*/
do c=1 for N; z= z || right(@.r.c, 2)'║' /*build row by row. */
end /*c*/
z= z ' ' r"L" /*add right-side info*/
if r==1 then z= z pad'L means shift a row left' /* " 1st help info.*/
if r==2 then z= z pad'R means shift a row right' /* " 2nd " " */
say pad z; if r\==N then say pad ind bar
end /*r*/
say pad ind bot; say;
say translate(col, '+', "-") ind ind ind "+ means shift a column up"; say
return
output   when using the default inputs:
──────── Playing a  16  game with a difficulty level of:  2

                a- b- c- d-                -  means shift a column down
               ╔══╦══╦══╦══╗
          1R   ║ 1║ 2║15║ 4║   1L          L  means shift a row left
               ╠══╬══╬══╬══╣
          2R   ║ 5║ 6║ 3║ 8║   2L          R  means shift a row right
               ╠══╬══╬══╬══╣
          3R   ║12║ 9║ 7║11║   3L
               ╠══╬══╬══╬══╣
          4R   ║13║14║10║16║   4L
               ╚══╩══╩══╩══╝

                a+ b+ c+ d+                +  means shift a column up

──────── Please enter a     row  number    followed by a   L   or   R,       or
────────        enter a   column letter    followed by a   +   or   -       (or Quit):
c +                             ◄■■■■■■■■ user input                                                                                

                a- b- c- d-                -  means shift a column down
               ╔══╦══╦══╦══╗
          1R   ║ 1║ 2║ 3║ 4║   1L          L  means shift a row left
               ╠══╬══╬══╬══╣
          2R   ║ 5║ 6║ 7║ 8║   2L          R  means shift a row right
               ╠══╬══╬══╬══╣
          3R   ║12║ 9║10║11║   3L
               ╠══╬══╬══╬══╣
          4R   ║13║14║15║16║   4L
               ╚══╩══╩══╩══╝

                a+ b+ c+ d+                +  means shift a column up

──────── Please enter a     row  number    followed by a   L   or   R,       or
────────        enter a   column letter    followed by a   +   or   -       (or Quit):
3L                              ◄■■■■■■■■ user input
                a- b- c- d-                -  means shift a column down
               ╔══╦══╦══╦══╗
          1R   ║ 1║ 2║ 3║ 4║   1L          L  means shift a row left
               ╠══╬══╬══╬══╣
          2R   ║ 5║ 6║ 7║ 8║   2L          R  means shift a row right
               ╠══╬══╬══╬══╣
          3R   ║ 9║10║11║12║   3L
               ╠══╬══╬══╬══╣
          4R   ║13║14║15║16║   4L
               ╚══╩══╩══╩══╝

                a+ b+ c+ d+                +  means shift a column up

──────── Congratulations!   The 16─puzzle is solved.

Ring[edit]

 
# Project : Sixteen Puzzle Game
 
load "guilib.ring"
load "stdlib.ring"
 
app1 = new qapp {
 
t1 = 0
temp = ""
table = [][]
movesnr = 0
button = list(16)
begintiles = list(16)
pReturn = list(4)
CounterMan = 0
saveflag = 0
 
stylefusionblack()
 
win1 = new qwidget() {
move(0,0)
resize(360, 600)
setwindowtitle("Calmosoft Sixteen Puzzle Game")
 
for n = 1 to 16
col = n%4
if col = 0 col = 4 ok
row = ceil(n/4)
button[n] = new qpushbutton(win1)
{
setgeometry(60+col*40,60+row*40,40,40)
setstylesheet("color:white")
setstylesheet("background-color:blue")
settext(string(n))
}
next
 
buttonup1 = new qpushbutton(win1)
{
setgeometry(100, 60, 40, 40)
settext("up")
setclickevent("up1()")
}
 
buttonup2 = new qpushbutton(win1)
{
setgeometry(140, 60, 40, 40)
settext("up")
setclickevent("up2()")
}
 
buttonup3 = new qpushbutton(win1)
{
setgeometry(180, 60, 40, 40)
settext("up")
setclickevent("up3()")
}
 
buttonup4 = new qpushbutton(win1)
{
setgeometry(220, 60, 40, 40)
settext("up")
setclickevent("up4()")
}
 
buttondown1 = new qpushbutton(win1)
{
setgeometry(100, 260, 40, 40)
settext("down")
setclickevent("down1()")
}
 
buttondown2 = new qpushbutton(win1)
{
setgeometry(140, 260, 40, 40)
settext("down")
setclickevent("down2()")
}
 
buttondown3 = new qpushbutton(win1)
{
setgeometry(180, 260, 40, 40)
settext("down")
setclickevent("down3()")
}
 
buttondown4 = new qpushbutton(win1)
{
setgeometry(220, 260, 40, 40)
settext("down")
setclickevent("down4()")
}
 
buttonleft1 = new qpushbutton(win1)
{
setgeometry(60, 100, 40, 40)
settext("<<<")
setclickevent("left1()")
}
 
buttonleft2 = new qpushbutton(win1)
{
setgeometry(60, 140, 40, 40)
settext("<<<")
setclickevent("left2()")
}
 
buttonleft3 = new qpushbutton(win1)
{
setgeometry(60, 180, 40, 40)
settext("<<<")
setclickevent("left3()")
}
 
buttonleft4 = new qpushbutton(win1)
{
setgeometry(60, 220, 40, 40)
settext("<<<")
setclickevent("left4()")
}
 
buttonright1 = new qpushbutton(win1)
{
setgeometry(260, 100, 40, 40)
settext(">>>")
setclickevent("right1()")
}
 
buttonright2 = new qpushbutton(win1)
{
setgeometry(260, 140, 40, 40)
settext(">>>")
setclickevent("right2()")
}
 
buttonright3 = new qpushbutton(win1)
{
setgeometry(260, 180, 40, 40)
settext(">>>")
setclickevent("right3()")
}
 
buttonright4 = new qpushbutton(win1)
{
setgeometry(260, 220, 40, 40)
settext(">>>")
setclickevent("right4()")
}
 
buttonscramble = new qpushbutton(win1)
{
setgeometry(100, 300, 160, 40)
settext("Scarmble")
setclickevent("scramble()")
}
 
buttonreset = new qpushbutton(win1)
{
setgeometry(100, 340, 160, 40)
settext("Reset")
setclickevent("reset()")
}
 
buttonsave = new qpushbutton(win1)
{
setgeometry(100, 380, 160, 40)
settext("Save Game")
setclickevent("psaveEmpty()")
}
 
buttonplay = new qpushbutton(win1)
{
setgeometry(100,420,160,40)
settext("Replay Game")
setclickevent("pPlay()")
}
 
buttonnr = new qpushbutton(win1)
{
setgeometry(100, 460, 160, 40)
settext("Moves : ")
}
 
timebtn = new qpushbutton(win1)
{
setgeometry(100,500,160,40)
settext("Elapsed Time : ")
}
t1 = clock()
 
for i = 1 to 16
begintiles[i] = string(i)
next
 
TimerMan = new qtimer(win1)
{
setinterval(0.5)
settimeoutevent("pTime()")
stop()
}
show()
}
exec()
}
 
func scramble
reset()
empty = 16
movesnr = 0
buttonnr.settext("Moves : " + movesnr)
 
for n= 1 to 1000
nr=random(15)+1
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
temp1 = button[nr].text()
temp2 = button[empty].text()
button[empty].settext(temp1)
button[nr].settext(temp2)
empty = nr
ok
next
timebtn.settext("Elapsed Time : ")
t1 = clock()
table = []
saveflag = 0
for n= 1 to 16
if isstring(button[n].text())
begintiles[n] = button[n].text()
else
begintiles[n] = string(button[n].text())
ok
next
 
func reset
movesnr = 0
buttonnr.settext("Moves : " + movesnr)
for i = 1 to 16
button[i] {settext(string(i))}
button[i].setstylesheet("background-color:blue")
begintiles[i] = string(i)
next
timebtn.settext("Elapsed Time : ")
t1 = clock()
table = []
saveflag = 0
return
 
func pClock
t2 = (clock() - t1)/1000
timebtn.settext("Elapsed Time : " + t2 + " s")
 
func psaveEmpty
timebtn.settext("Elapsed Time : ")
t1 = clock()
return
 
func psave
if saveflag = 1
for n = 1 to 4
add(table, [pReturn[n], button[pReturn[n]].text()])
next
ok
 
func pPlay
if saveflag = 1
for n=1 to 16
button[n]{settext(begintiles[n])}
button[n].setstylesheet("background-color:blue")
next
timebtn.settext("Elapsed Time : ")
movesnr = 0
buttonnr.settext("Moves : " + movesnr)
t1 = clock()
CounterMan = 0
TimerMan.start()
ok
 
func pTime()
if saveflag = 1
CounterMan = CounterMan + 1
if CounterMan > 1
temp.setstylesheet("background-color:blue")
ok
pPlaySleep()
sleep(1)
if CounterMan = len(table)
TimerMan.stop()
ok
ok
 
func pPlaySleep
pClock()
button[table[CounterMan][1]].setstylesheet("background-color:orange")
temp = button[table[CounterMan][1]]
button[table[CounterMan][1]].settext(table[CounterMan][2])
movesnr = movesnr + 1
buttonnr.settext("Moves : " + movesnr)
return
 
func up1
temp = button[1].text()
button[1].settext(button[5].text())
button[5].settext(button[9].text())
button[9].settext(button[13].text())
button[13].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [1,5,9,13]
saveflag = 1
psave()
return
 
func up2
temp = button[2].text()
button[2].settext(button[6].text())
button[6].settext(button[10].text())
button[10].settext(button[14].text())
button[14].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [2,6,10,14]
saveflag = 1
psave()
return
 
func up3
temp = button[3].text()
button[3].settext(button[7].text())
button[7].settext(button[11].text())
button[11].settext(button[15].text())
button[15].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [3,7,11,15]
saveflag = 1
psave()
return
 
func up4
temp = button[4].text()
button[4].settext(button[8].text())
button[8].settext(button[12].text())
button[12].settext(button[16].text())
button[16].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [4,8,12,16]
saveflag = 1
psave()
return
 
func down1
temp = button[13].text()
button[13].settext(button[9].text())
button[9].settext(button[5].text())
button[5].settext(button[1].text())
button[1].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [13,9,5,1]
saveflag = 1
psave()
return
 
func down2
temp = button[14].text()
button[14].settext(button[10].text())
button[10].settext(button[6].text())
button[6].settext(button[2].text())
button[2].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [14,10,6,2]
saveflag = 1
psave()
return
 
func down3
temp = button[15].text()
button[15].settext(button[11].text())
button[11].settext(button[7].text())
button[7].settext(button[3].text())
button[3].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [15,11,7,3]
saveflag = 1
psave()
return
 
func down4
temp = button[16].text()
button[16].settext(button[12].text())
button[12].settext(button[8].text())
button[8].settext(button[4].text())
button[4].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [16,12,8,4]
saveflag = 1
psave()
return
 
func left1
temp = button[1].text()
button[1].settext(button[2].text())
button[2].settext(button[3].text())
button[3].settext(button[4].text())
button[4].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [1,2,3,4]
saveflag = 1
psave()
return
 
func left2
temp = button[5].text()
button[5].settext(button[6].text())
button[6].settext(button[7].text())
button[7].settext(button[8].text())
button[8].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [5,6,7,8]
saveflag = 1
psave()
return
 
func left3
temp = button[9].text()
button[9].settext(button[10].text())
button[10].settext(button[11].text())
button[11].settext(button[12].text())
button[12].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [9,10,11,12]
saveflag = 1
psave()
return
 
func left4
temp = button[13].text()
button[13].settext(button[14].text())
button[14].settext(button[15].text())
button[15].settext(button[16].text())
button[16].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [13,14,15,16]
saveflag = 1
psave()
return
 
func right1
temp = button[4].text()
button[4].settext(button[3].text())
button[3].settext(button[2].text())
button[2].settext(button[1].text())
button[1].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [4,3,2,1]
saveflag = 1
psave()
return
 
func right2
temp = button[8].text()
button[8].settext(button[7].text())
button[7].settext(button[6].text())
button[6].settext(button[5].text())
button[5].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [8,7,6,5]
saveflag = 1
psave()
return
 
func right3
temp = button[12].text()
button[12].settext(button[11].text())
button[11].settext(button[10].text())
button[10].settext(button[9].text())
button[9].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [12,11,10,9]
saveflag = 1
psave()
return
 
func right4
temp = button[16].text()
button[16].settext(button[15].text())
button[15].settext(button[14].text())
button[14].settext(button[13].text())
button[13].settext(temp)
movesnr =movesnr + 1
buttonnr.settext("Moves : " + string(movesnr))
pClock()
pReturn = [16,15,14,13]
saveflag = 1
psave()
return
 

Output image:

16 Puzzle Game

Wren[edit]

Translation of: Go
Library: Wren-trait
Library: Wren-fmt
Library: Wren-ioutil
Library: Wren-str
import "random" for Random
import "/trait" for Stepped
import "/fmt" for Fmt
import "/ioutil" for Input
import "/str" for Str
 
var rand = Random.new()
 
var easy = 1
var hard = 4
 
var n = List.filled(16, 0)
 
var initGrid = Fn.new {
for (i in 0..15) n[i] = i + 1
}
 
var rotate = Fn.new { |ix|
var last = n[ix[3]]
for (i in 3..1) n[ix[i]] = n[ix[i-1]]
n[ix[0]] = last
}
 
var hasWon = Fn.new {
for (i in 0...15) {
if (n[i] != i+1) return false
}
return true
}
 
var setDiff = Fn.new { |level|
var moves = (level == easy) ? 3 : 12
var rc = []
var i = 0
while (i < moves) {
rc.clear()
var r = rand.int(2)
var s = rand.int(4)
if (r == 0) { // rotate random row
for (j in s*4...(s+1)*4) rc.add(j)
} else { // rotate random column
for (j in Stepped.new(s...s+16, 4)) rc.add(j)
}
var rca = rc.toList
rotate.call(rca)
if (hasWon.call()) { // do it again
i = -1
}
i = i + 1
}
System.print("Target is %(moves) moves.")
}
 
var drawGrid = Fn.new {
System.print()
System.print(" D1 D2 D3 D4")
System.print(" ╔════╦════╦════╦════╗")
Fmt.print ("R1 ║ $2d ║ $2d ║ $2d ║ $2d ║ L1", n[0], n[1], n[2], n[3])
System.print(" ╠════╬════╬════╬════╣")
Fmt.print ("R2 ║ $2d ║ $2d ║ $2d ║ $2d ║ L2", n[4], n[5], n[6], n[7])
System.print(" ╠════╬════╬════╬════╣")
Fmt.print ("R3 ║ $2d ║ $2d ║ $2d ║ $2d ║ L3", n[8], n[9], n[10], n[11])
System.print(" ╠════╬════╬════╬════╣")
Fmt.print ("R4 ║ $2d ║ $2d ║ $2d ║ $2d ║ L4", n[12], n[13], n[14], n[15])
System.print(" ╚════╩════╩════╩════╝")
System.print(" U1 U2 U3 U4\n")
}
 
initGrid.call()
var level = easy
var diff = Input.option("Enter difficulty level easy or hard E/H : ", "eEhH")
if (diff == "h" || diff == "H") level = hard
setDiff.call(level)
var ix = List.filled(4, 0)
System.print("When entering moves, you can also enter Q to quit or S to start again.")
var moves = 0
while (true) {
drawGrid.call()
if (hasWon.call()) {
System.print("Congratulations, you have won the game in %(moves) moves!!")
return
}
while (true) {
System.print("Moves so far = %(moves)\n")
var move = Str.upper(Input.text("Enter move : ", 1).trim())
if (move == "D1" || move == "D2" || move == "D3" || move == "D4") {
var c = move[1].bytes[0] - 49
ix[0] = 0 + c
ix[1] = 4 + c
ix[2] = 8 + c
ix[3] = 12 + c
rotate.call(ix)
moves = moves + 1
break
} else if (move == "L1" || move == "L2" || move == "L3" || move == "L4") {
var c = move[1].bytes[0] - 49
ix[0] = 3 + 4*c
ix[1] = 2 + 4*c
ix[2] = 1 + 4*c
ix[3] = 0 + 4*c
rotate.call(ix)
moves = moves + 1
break
} else if (move == "U1" || move == "U2" || move == "U3" || move == "U4") {
var c = move[1].bytes[0] - 49
ix[0] = 12 + c
ix[1] = 8 + c
ix[2] = 4 + c
ix[3] = 0 + c
rotate.call(ix)
moves = moves + 1
break
} else if (move == "R1" || move == "R2" || move == "R3" || move == "R4") {
var c = move[1].bytes[0] - 49
ix[0] = 0 + 4*c
ix[1] = 1 + 4*c
ix[2] = 2 + 4*c
ix[3] = 3 + 4*c
rotate.call(ix)
moves = moves + 1
break
} else if (move == "Q") {
return
} else if (move == "S") {
initGrid.call()
setDiff.call(level)
moves = 0
break
} else {
System.print("Invalid move, try again.")
}
}
}
Output:

Sample (very easy!) 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 ║  3 ║  4 ║  1 ║  2 ║ L1
   ╠════╬════╬════╬════╣
R2 ║  5 ║  6 ║  7 ║  8 ║ L2
   ╠════╬════╬════╬════╣
R3 ║  9 ║ 10 ║ 11 ║ 12 ║ L3
   ╠════╬════╬════╬════╣
R4 ║ 16 ║ 13 ║ 14 ║ 15 ║ L4
   ╚════╩════╩════╩════╝
     U1   U2   U3   U4

Moves so far = 0

Enter move : l1

     D1   D2   D3   D4
   ╔════╦════╦════╦════╗
R1 ║  4 ║  1 ║  2 ║  3 ║ L1
   ╠════╬════╬════╬════╣
R2 ║  5 ║  6 ║  7 ║  8 ║ L2
   ╠════╬════╬════╬════╣
R3 ║  9 ║ 10 ║ 11 ║ 12 ║ L3
   ╠════╬════╬════╬════╣
R4 ║ 16 ║ 13 ║ 14 ║ 15 ║ L4
   ╚════╩════╩════╩════╝
     U1   U2   U3   U4

Moves so far = 1

Enter move : l1

     D1   D2   D3   D4
   ╔════╦════╦════╦════╗
R1 ║  1 ║  2 ║  3 ║  4 ║ L1
   ╠════╬════╬════╬════╣
R2 ║  5 ║  6 ║  7 ║  8 ║ L2
   ╠════╬════╬════╬════╣
R3 ║  9 ║ 10 ║ 11 ║ 12 ║ L3
   ╠════╬════╬════╬════╣
R4 ║ 16 ║ 13 ║ 14 ║ 15 ║ L4
   ╚════╩════╩════╩════╝
     U1   U2   U3   U4

Moves so far = 2

Enter move : l4

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