16 puzzle game

From Rosetta Code
Revision as of 16:02, 26 July 2022 by Rdm (talk | contribs) (J)
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.

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

Go

<lang 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.")
           }
       }
   }

}</lang>

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:<lang J>require'ide/qt/gl2' coinsert'jgl2'

NB. event handlers game_reset_button=: Template:Draw COUNT=: game_restart_button=: {{ draw COUNT=: 0 [rotate"1 SETUP [BOARD=: WIN }} game_easy_button=: Template:Setup 3 game_hard_button=: Template: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=. '' [Z=. ''
 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 250
 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 easy or hard 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</lang>

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;
}

<lang javascript> 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(); </lang>

Julia

<lang 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) </lang>

Nim

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. <lang Nim>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.
  1. ---------------------------------------------------------------------------------------------------

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
  1. ---------------------------------------------------------------------------------------------------

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()
  1. ---------------------------------------------------------------------------------------------------

proc onQuit(button: ToolButton; window: ApplicationWindow) =

 ## Procedure called when clicking quit button.
 window.destroy()
  1. ---------------------------------------------------------------------------------------------------

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
  1. ---------------------------------------------------------------------------------------------------

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
  1. ---------------------------------------------------------------------------------------------------

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)
  1. ---------------------------------------------------------------------------------------------------

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()
  1. ---------------------------------------------------------------------------------------------------

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()
  1. ---------------------------------------------------------------------------------------------------

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()
  1. ---------------------------------------------------------------------------------------------------

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()
  1. ———————————————————————————————————————————————————————————————————————————————————————————————————

randomize() let app = newApplication(PuzzleApp, "Rosetta.Puzzle16Game") discard app.connect("activate", activate) discard app.run()</lang>

Perl

<lang 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;
 }</lang>

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})-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

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

Translation of: Go

<lang perl6># 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-- }
  }

}</lang>

REXX

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. <lang rexx>/*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

  1. =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</lang>
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

<lang ring>

  1. 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

</lang> Output image:

16 Puzzle Game

Wren

Translation of: Go
Library: Wren-trait
Library: Wren-fmt
Library: Wren-ioutil
Library: Wren-str

<lang ecmascript>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.")
       }
   }

}</lang>

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