sub drawboard( B() as ubyte )
    dim as string outstr = ""
    for i as ubyte = 0 to 15
        if B(i) = 0 then
            outstr = outstr + " XX "
        elseif B(i) < 10 then
            outstr = outstr + "  "+str(B(i))+" "
        else
            outstr = outstr + " " +str(B(i))+" "
        end if
        if i mod 4 = 3 then
            print outstr
            print
            outstr = ""
        end if
    next i
    print
end sub

function move( B() as ubyte, byref gap as ubyte, direction as ubyte ) as ubyte
    dim as integer targ = gap
    select case direction
        case 1  'UP
            targ = gap - 4
        case 2  'RIGHT
            if gap mod 4 = 3 then return gap
            targ = gap + 1
        case 3  'DOWN
            targ = gap + 4
        case 4
            if gap mod 4 = 0 then return gap
            targ = gap - 1
        case else
            return gap
    end select
    if targ > 15 or targ < 0 then return gap
    swap B(targ), B(gap)
    return targ
end function

sub shuffle( B() as ubyte, byref gap as ubyte )
    for i as ubyte = 0 to 100
        gap = move(B(), gap, int(rnd*4) + 1)
    next i
end sub

function solved( B() as ubyte ) as boolean
    dim as integer i
    for i = 0 to 14
        if B(i)>B(i+1) then return false
    next i
    return true
end function

dim as ubyte i, B(15), direction, gap
for i = 0 to 15
    B(i) = i
next i
shuffle B(), gap

while not solved( B() )
    cls
    drawboard B()
    print gap
    print "1 = up, 2=right, 3=down, 4=left"
    input direction
    gap = move( B(), gap, direction )
wend

cls
print "Congratulations! You win!"
print
drawboard B()


'Charlie Ogier (C) 15PuzzleGame 24/04/2017 V0.1.0 Licenced under MIT
'Inspiration came from: -
''http://rosettacode.org/wiki/15_Puzzle_Game
''Bugs or comments to [email protected]
'Written in Gambas 3.9.2 - Updated on the Gambas Farm 01/05/2017
'Updated so that the message and the Title show the same amount of moves 01/06/2017
'Form now expandable. Font height automated. Form size and position saved 06/06/2107

'Simulate playing the 15 - game(puzzle)         Yes in GUI
'Generate a random start position               Yes
'Prompt the user for which piece To move        No
'Validate if the move is legal(possible)        Yes
'Display the game(puzzle) as pieces are moved   Yes in GUI
'Announce when the puzzle is solved             Yes
'Possibly keep track of the number of moves     Yes

byPos As New Byte[]                                             'Stores the position of the 'Tiles'
siMoves As Short                                                'Stores the amount of moves
hTimer As Timer                                                 'Timer
dTimerStart As Date                                             'Stores the start time
dTimerDiff As Date                                              'Stores the time from the start to now
bTimerOn As Boolean                                             'To know if the Timer is running

Public Sub Form_Open()                                          'Form opens

Settings.read(Me, "Window")                                     'Get details of the last window position and size
With Me                                                         'With the Form..
  .Padding = 5                                                  'Pad the edges
  .Arrangement = Arrange.Row                                    'Arrange the Form
  .Title = "15PuzzleGame v0.3.0"                                'Set the Form Title
End With

BuildForm                                                       'Go to the BuildForm routine

End

Public Sub BuildForm()                                          'To add items to the Form
Dim hButton As Button                                           'We need some Buttons
Dim byRand, byTest As Byte                                      'Various variables
Dim bOK As Boolean                                              'Used to stop duplicate numbers being added
Dim bSolvable As Boolean

Repeat                                                          'Repeat until the puzzle is solvable
  Do                                                            'Start of a Do loop to create 0 to 15 in random order
    byRand = Rand(0, 15)                                        'Get a random number between 0 and 15
    If byRand = 0 Then byRand = 99                              'Change 0 to 99 for the Blank space
    bOK = True                                                  'Set bOK to True
    For Each byTest In byPos                                    'For each number stored in the array byPos
      If byRand = byTest Then bOK = False                       'Check to see if it already exists, if it does set bOK to False
    Next
    If bOK Then byPos.Add(byRand)                               'If not a duplicate then add it to the array
    If byPos.max = 15 Then Break                                'Once the array has 16 numbers get out of here. 99 is used for the blank space
  Loop
  bSolvable = IsItSolvable()                                    'Go to the 'check if puzzle is solvable' routine
  If Not bSolvable Then byPos.clear                             'If it's not solvable then clear byPos
Until bSolvable = True                                          'Repeat until the puzzle is solvable

For byRand = 0 To 15                                            'Loop
  If byPos[byRand] = 99 Then                                    'Check if value is 99 as this is where the blank space will go
    AddPanel                                                    'Go to the AddPanel routine to add the blank space
    Continue                                                    'Skip to the end of the loop
  Endif
  hButton = New Button(Me) As "AllButtons"                      'Add a new button to the Form, all buttons grouped as 'AllButtons'
  With hButton                                                  'With the following properties
    .Text = Str(byPos[byRand])                                  'Add Button text
    .Tag = Str(byPos[byRand])                                   'Add a Tag
    .Height = (Me.Height - 10) / 4                              'Set the Button height
    .Width = (Me.Width - 10) / 4                                'Set the Button width
    .Font.Bold = True                                           'Set the font to Bold
    .Font.Size = 16                                             'Set Font size
  End With
Next

AddTimer                                                        'Go to the AddTimer routine

End

Public Sub AddPanel()                                           'Routine to add an invisible panel that is the blank area
Dim hPanel As Panel                                             'We need a Panel

HPanel = New Panel(Me)                                          'Add the Panel to the Form
With HPanel                                                     'With the following Properties
  .Tag = 99                                                     'Set a Tag to 99
  .Height = (Me.Height - 10) / 4                                'Set the height
  .Width = (Me.Width - 10) / 4                                  'Set the width
End With

End

Public Sub AddTimer()                                           'To add a Timer

hTimer = New Timer As "MyTimer"                                 'Add a Timer
hTimer.Delay = 1000                                             'Set the timer delay

End

Public Sub MyTimer_Timer()                                      'Timer

Me.Title = siMoves & " Moves "                                  'Set the Form Title to show the amount of moves taken

If dTimerStart Then                                             'If a start time has been set then
  dTimerDiff = Time(Now) - dTimerStart                          'Calculate the time difference between StartTime and Now
  Me.Title &= " - " & Str(dTimerDiff)                           'Add the time taken to the Form Title
End If

End

Public Sub AllButtons_Click()                                   'What to do when a Button is clicked
Dim byLast As Byte = Last.Tag                                   'Get the Tag of the Button clicked
Dim byTemp, byCount As Byte                                     'Various variables
Dim byCheck As Byte[] = [88, 88, 88, 88]                        'Used for checking for the blank space
Dim byWChgeck As New Byte[16, 4]
Dim oObj As Object                                              'We need to enumerate Objects

For Each oObj In Me.Children                                    'For each Object (Buttons in this case) that are Children of the Form..
  If oObj.Tag = byLast Then Break                               'If the Tag of the Button matches then we know the position of the Button on the form so get out of here
  Inc byCount                                                   'Increase the value of byCount
Next

Select Case byCount                                             'Depending on the position of the Button
  Case 0                                                        'e.g 0 then we need to check positions 1 & 4 for the blank
    byCheck[0] = 1
    byCheck[1] = 4
  Case 1
    byCheck[0] = 0
    byCheck[1] = 2
    byCheck[2] = 5
  Case 2
    byCheck[0] = 1
    byCheck[1] = 3
    byCheck[2] = 6
  Case 3
    byCheck[0] = 2
    byCheck[1] = 7
  Case 4
    byCheck[0] = 0
    byCheck[1] = 5
    byCheck[2] = 8
  Case 5                                                        'e.g 5 then we need to check positions 1, 4, 6 & 9 for the blank
    byCheck[0] = 1
    byCheck[1] = 4
    byCheck[2] = 6
    byCheck[3] = 9
  Case 6
    byCheck[0] = 2
    byCheck[1] = 5
    byCheck[2] = 7
    byCheck[3] = 10
  Case 7
    byCheck[0] = 3
    byCheck[1] = 6
    byCheck[2] = 11
  Case 8
    byCheck[0] = 4
    byCheck[1] = 9
    byCheck[2] = 12
  Case 9
    byCheck[0] = 5
    byCheck[1] = 8
    byCheck[2] = 10
    byCheck[3] = 13
  Case 10
    byCheck[0] = 6
    byCheck[1] = 9
    byCheck[2] = 11
    byCheck[3] = 14
  Case 11
    byCheck[0] = 7
    byCheck[1] = 10
    byCheck[2] = 15
  Case 12
    byCheck[0] = 8
    byCheck[1] = 13
  Case 13
    byCheck[0] = 9
    byCheck[1] = 12
    byCheck[2] = 14
  Case 14
    byCheck[0] = 10
    byCheck[1] = 13
    byCheck[2] = 15
  Case 15
    byCheck[0] = 11
    byCheck[1] = 14
End Select

For Each byTemp In byCheck                                      'For each value in byCheck
  If byTemp = 88 Then Break                                     'If byTemp = 88 then get out of here
  If byPos[byTemp] = 99 Then                                    'If the position checked is 99 (the blank) then..
    byPos[byTemp] = Last.Text                                   'Set the new position of the Tile in byPos
    byPos[byCount] = 99                                         'Set the existing Tile position to = 99 (blank)
    Inc siMoves                                                 'Inc the amount of moves made
    If Not bTimerOn Then                                        'If the Timer is now needed then
      dTimerStart = Time(Now)                                   'Set the Start time to NOW
      hTimer.start                                              'Start the Timer
      bTimerOn = True                                           'Set bTimerOn to True
    Endif
    Break                                                       'Get out of here
  End If
Next

RebuildForm                                                     'Go to the RebuilForm routine
CheckIfPuzzleCompleted                                          'Check to see if the puzzle has been solved

End

Public Sub CheckIfPuzzleCompleted()                             'Is the puzzle is complete
Dim byTest As Byte[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 99] 'byPos will equal this if it is completed
Dim siCount As Short                                            'Counter
Dim bCompleted As Boolean = True                                'Completed?
Dim sMessage As String                                          'String to store the display message

For siCount = 0 To 15                                           'Loop through the byPos
  If byPos[siCount] <> byTest[siCount] Then                     'If the position does not match the completed position then..
    bCompleted = False                                          'Set bCompleted to False
    Break                                                       'Get out of here
  Endif
Next

If bCompleted Then                                              'If the puzzle is completed then
  hTimer.Stop                                                   'Stop the timer
  Me.Title = siMoves & " Moves "                                'Set the Form Title to show the amount of moves taken
  sMessage = "Congratulations!!\n"                              'Build sMessage
  sMessage &= Str(siMoves) & " Moves\n"                         'Build sMessage
  sMessage &= "Time = " & Str(dTimerDiff)                       'Build sMessage
  Message(sMessage, "OK")                                       'Put up a congratulatory message
  Me.Close                                                      'Close the form
Endif

End

Public Sub RebuildForm()                                        'To clear the Form and rebuild with the Tiles in the new postion
Dim hButton As Button                                           'We need Buttons
Dim byCount, byTemp As Byte                                     'Various variables
Dim siFontH As Short

Me.Children.clear                                               'Clear the Form of all Objects

For Each byTemp In byPos                                        'For each 'Position'
  If byTemp = 99 Then                                           'If the Position's value is 99 then it's the space
    AddPanel                                                    'Go to the AddPanel routine
  Else                                                          'If the Position's value is NOT 99 then
    hButton = New Button(Me) As "AllButtons"                    'Create a new Button
  With hButton                                                  'With the following properties
    .Text = Str(byPos[byCount])                                 'Text as stored in byPos
    .Tag = Str(byPos[byCount])                                  'Tag as stored in byPos
    .Height = (Me.Height - 10) / 4                              'Set the Button height
    .Width = (Me.Width - 10) / 4                                'Set the Button width
    .Font.Bold = True                                           'Set the Font to Bold
      End With
    If Me.Width > Me.Height Then                                'If Form Width is greater than Form Width then..
      siFontH = Me.Height                                       'siFontH = Form Height
    Else                                                        'Else..
      siFontH = Me.Width                                        'siFontH = Form Width
    End If
  hButton.Font.size = siFontH / 16                              'Set Font height
  Endif

  Inc byCount                                                   'Increase counter
Next

End

Public Sub Form_Resize()                                        'If the form is resized

RebuildForm                                                     'Rebuild the Form

End

Public Sub IsItSolvable() As Boolean                            'To check if the puzzle is solvable
Dim bSolvable, bBlankOnEven As Boolean                          'Triggers
Dim siCount0, siCount1, siInversion As Short                    'Counters

For siCount0 = 0 To byPos.Max                                   'Loop through the positions
  If byPos[siCount0] = 99 Then                                  'The blank
    If InStr("0,1,2,3,8,9,10,11,", Str(siCount0 & ",")) Then    'Is the blank on an even row (counting from the bottom) if so..
      bBlankOnEven = True                                       'bBlankOnEven = True
    End If
    Continue                                                    'Go to the end of the loop
  End If
  For siCount1 = siCount0 + 1 To byPos.Max                      'Loop through the positions
    If byPos[siCount0] > byPos[siCount1] Then Inc siInversion   'Counts the inversions
  Next                                                          'See https://www.cs.bham.ac.uk/~mdr/teaching/modules04/java2/TilesSolvability.html
Next

If bBlankOnEven And Odd(siInversion) Then bSolvable = True      'Blank is on an even row (counting from the bottom) then the number of inversions in a solvable situation is odd
If Not bBlankOnEven And Even(siInversion) Then bSolvable = True 'Blank is on an odd row (counting from the bottom) then the number of inversions in a solvable situation is even

Return bSolvable                                                'Return the value

End

Public Sub Form_Close()

Settings.Write(Me, "Window")                                    'Store the window position and size

End


package main

import (
	"fmt"
	"math/rand"
	"strings"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())
	p := newPuzzle()
	p.play()
}

type board [16]cell
type cell uint8
type move uint8

const (
	up move = iota
	down
	right
	left
)

func randMove() move { return move(rand.Intn(4)) }

var solvedBoard = board{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}

func (b *board) String() string {
	var buf strings.Builder
	for i, c := range b {
		if c == 0 {
			buf.WriteString("  .")
		} else {
			_, _ = fmt.Fprintf(&buf, "%3d", c)
		}
		if i%4 == 3 {
			buf.WriteString("\n")
		}
	}
	return buf.String()
}

type puzzle struct {
	board board
	empty int // board[empty] == 0
	moves int
	quit  bool
}

func newPuzzle() *puzzle {
	p := &puzzle{
		board: solvedBoard,
		empty: 15,
	}
	// Could make this configurable, 10==easy, 50==normal, 100==hard
	p.shuffle(50)
	return p
}

func (p *puzzle) shuffle(moves int) {
	// As with other Rosetta solutions, we use some number
	// of random moves to "shuffle" the board.
	for i := 0; i < moves || p.board == solvedBoard; {
		if p.doMove(randMove()) {
			i++
		}
	}
}

func (p *puzzle) isValidMove(m move) (newIndex int, ok bool) {
	switch m {
	case up:
		return p.empty - 4, p.empty/4 > 0
	case down:
		return p.empty + 4, p.empty/4 < 3
	case right:
		return p.empty + 1, p.empty%4 < 3
	case left:
		return p.empty - 1, p.empty%4 > 0
	default:
		panic("not reached")
	}
}

func (p *puzzle) doMove(m move) bool {
	i := p.empty
	j, ok := p.isValidMove(m)
	if ok {
		p.board[i], p.board[j] = p.board[j], p.board[i]
		p.empty = j
		p.moves++
	}
	return ok
}

func (p *puzzle) play() {
	fmt.Printf("Starting board:")
	for p.board != solvedBoard && !p.quit {
		fmt.Printf("\n%v\n", &p.board)
		p.playOneMove()
	}
	if p.board == solvedBoard {
		fmt.Printf("You solved the puzzle in %d moves.\n", p.moves)
	}
}

func (p *puzzle) playOneMove() {
	for {
		fmt.Printf("Enter move #%d (U, D, L, R, or Q): ", p.moves+1)
		var s string
		if n, err := fmt.Scanln(&s); err != nil || n != 1 {
			continue
		}

		s = strings.TrimSpace(s)
		if s == "" {
			continue
		}

		var m move
		switch s[0] {
		case 'U', 'u':
			m = up
		case 'D', 'd':
			m = down
		case 'L', 'l':
			m = left
		case 'R', 'r':
			m = right
		case 'Q', 'q':
			fmt.Printf("Quiting after %d moves.\n", p.moves)
			p.quit = true
			return
		default:
			fmt.Println(`
Please enter "U", "D", "L", or "R" to move the empty cell
up, down, left, or right. You can also enter "Q" to quit.
Upper or lowercase is accepted and only the first non-blank
character is important (i.e. you may enter "up" if you like).
`)
			continue
		}

		if !p.doMove(m) {
			fmt.Println("That is not a valid move at the moment.")
			continue
		}

		return
	}
}


#include "inkey.ch"
#include "Box.ch"

procedure Main()
    // console init
    SET SCOREBOARD OFF
    SetMode(30,80)
    ret := 0

    // main loop
    yn := .F.
    DO WHILE yn == .F.
        // draw console
        cls
        @ 0, 0 TO MaxRow(), MaxCol() DOUBLE
        SetColor("BG+/B,W/N")
        @ 0, 4 SAY "  Slidng puzzle game  "
        SetColor()

        // input size of grid
        tam := 0
        @ MaxRow() - 2, 4 SAY "Size of grid: " GET tam PICTURE "9"
        READ

        // Initialize numbers
        lista := ARRAY (tam * tam)
        FOR z := 1 TO tam * tam
            lista[z] := z
        NEXT
        lista1 := lista
        grid := ARRAY (tam,tam)

        // populate grid with numbers
        FOR i := 1 TO tam
            FOR j := 1 TO tam
                grid[i,j] := lista1[ (i-1) * tam + j]
            NEXT
        NEXT
        Mostra(@grid)
        InKey(0)

        // initialize the game
        n := 0
        t := 0
        lista := lista1     // lista for scrambling, lista1 preserve numbers in order
        DO WHILE .T.
            // scrambling numbers
            FOR i := 1 TO tam*tam
                n := Int ( ft_Rand1(tam * tam - 1) + 1 )
                t := lista[n]
                lista[n] := lista[i]
                lista[i] := t
            NEXT
            // have solution?
            possp := 0
            invct := 0  // change counter
            FOR i := 1 TO tam * tam -1
                IF lista[i] != tam*tam
                    FOR j := i + 1 TO tam * tam
                        IF lista[j] != tam*tam
                            IF lista[i] > lista[j]
                                invct++
                            ENDIF
                        ENDIF
                    NEXT
                ELSE
                    possp := i
                ENDIF
            NEXT
            linv := If( ( (invct % 2) == 0 ), .T., .F.)
            lkin := If( ( (tam - Int( (possp -1) / tam )) % 2) == 0, .T., .F. )

            IF ( (tam % 2) != 0)    // if grid size is odd
                IF linv                 // if number of positions changes is even, solvable
                    EXIT
                ELSE
                    LOOP                // if is odd, not solvable, scramble more
                ENDIF               // if grid size is even
            ELSE
                                        // If changes is even and space position is in odd line
                                        // or changes is odd and space position is in even line
                                        // (XOR condition) is solvable
                IF (linv .AND. !lkin) .OR. (!linv .AND. lkin) // XOR !!!
                    EXIT
                ElSE                    // else scramble more
                    LOOP
                ENDIF
            ENDIF

        ENDDO

        // populate the grid with scrambled numbers
        FOR i := 1 TO tam
            FOR j := 1 TO tam
                grid[i,j] := lista[ (i-1) * tam + j]
            NEXT
        NEXT
        ret := Mostra(@grid)

    // play
        key := 0
        DO WHILE LastKey() != K_ESC
            key := 0
            // find the space coords
            xe := 0
            ye := 0
            lv := tam*tam
            FOR i := 1 TO tam
                FOR j := 1 TO tam
                    IF grid[i,j] == lv
                        xe :=i
                        ye :=j
                    ENDIF
                NEXT
            NEXT
            // the direction keys
            key := inkey(0)
            DO CASE
                CASE key == K_UP
                    IF xe > 1
                        grid[xe,ye] := grid[xe-1,ye]
                        grid[xe-1,ye] := lv
                    ENDIF
                    ret := Mostra(@grid)
                CASE key == K_DOWN
                    IF xe < tam
                        grid[xe,ye] := grid[xe+1,ye]
                        grid[xe+1,ye] := lv
                    ENDIF
                    ret := Mostra(@grid)
                CASE key == K_LEFT
                    IF ye > 1
                        grid[xe,ye] := grid[xe,ye-1]
                        grid[xe,ye-1] := lv
                    ENDIF
                    ret := Mostra(@grid)
                CASE key == K_RIGHT
                    IF ye < tam
                        grid[xe,ye] := grid[xe,ye+1]
                        grid[xe,ye+1] := lv
                    ENDIF
                    ret := Mostra(@grid)
            ENDCASE
            IF ret == tam*tam-1                             // ret is qtty numbers in position
                @ MaxRow() - 3, 4 SAY "Fim de jogo!"        // if ret == (size*size) -1
                key := K_ESC                                // all numbers in position
                EXIT                                        // game solved
            ENDIF
        ENDDO
        @ MaxRow() - 2, 4 SAY "Deseja sair? (yn): " GET yn PICTURE "Y"
        READ
        @ MaxRow() - 3, 4 SAY "              "
    ENDDO
return NIL

FUNCTION Mostra(grid)
    // Show the gris
    fim := 0                                                    // how many numbers in position?
    SetColor("BG+/B,W/N")
    @ 5,10 , 5 + tam * 2, 9 + tam * 4 BOX B_SINGLE + Space(1)
    i := 0
    FOR y := 1 TO tam
        FOR x := 1 TO tam
            IF grid[x,y] == tam * tam                           // show space
                SetColor(" B/GR+, W/N")
                @ x*2 + 4, i + 11 SAY "  "
                SetColor("BG+/B,W/N")
            ELSE
                IF ( (x-1) * tam + y ) == grid[x,y]             // show number in position
                    SetColor("W/G,W/N")
                    @ x*2 + 4, i + 11 SAY grid[x,y] PICTURE "99"
                    fim++
                ELSE                                            // show number out position
                    SetColor("BG+/B,W/N")
                    @ x*2 + 4, i + 11 SAY grid[x,y] PICTURE "99"
                ENDIF
            ENDIF
        NEXT
        i = i + 4
    NEXT
    SetColor(" W/N, BG+/B")
RETURN fim


import Data.Array
import System.Random

type Puzzle = Array (Int, Int) Int

main :: IO ()
main = do
    putStrLn "Please enter the difficulty level: 0, 1 or 2"
    userInput <- getLine
    let diffLevel = read userInput
    if userInput == "" || any (\c -> c < '0' || c > '9') userInput || diffLevel > 2 || diffLevel < 0
        then putStrLn "That is not a valid difficulty level." >> main
        else shufflePuzzle ([10, 50, 100] !! diffLevel) solvedPuzzle >>= gameLoop

gameLoop :: Puzzle -> IO ()
gameLoop puzzle
    | puzzle == solvedPuzzle = putStrLn "You solved the puzzle!" >> printPuzzle puzzle
    | otherwise = do
    printPuzzle puzzle
    putStrLn "Please enter number to move"
    userInput <- getLine
    if any (\c -> c < '0' || c > '9') userInput
        then putStrLn "That is not a valid number." >> gameLoop puzzle
        else let move = read userInput in
            if move `notElem` validMoves puzzle
                then putStrLn "This move is not available." >> gameLoop puzzle
                else gameLoop (applyMove move puzzle)

validMoves :: Puzzle -> [Int]
validMoves puzzle = [puzzle ! (row', column') |
                     row' <- [rowEmpty-1..rowEmpty+1], column' <- [columnEmpty-1..columnEmpty+1],
                     row' < 4, row' >= 0, column' < 4, column' >= 0,
                     (row' == rowEmpty) /= (column' == columnEmpty)]
    where (rowEmpty, columnEmpty) = findIndexOfNumber 16 puzzle

applyMove :: Int -> Puzzle -> Puzzle
applyMove numberToMove puzzle = puzzle // [(indexToMove, 16), (emptyIndex, numberToMove)]
    where indexToMove = findIndexOfNumber numberToMove puzzle
          emptyIndex = findIndexOfNumber 16 puzzle

findIndexOfNumber :: Int -> Puzzle -> (Int, Int)
findIndexOfNumber number puzzle = case filter (\idx -> number == puzzle ! idx)
                                              (indices puzzle) of
                                      [idx] -> idx
                                      _ -> error "BUG: number not in puzzle"

printPuzzle :: Puzzle -> IO ()
printPuzzle puzzle = do
    putStrLn "+--+--+--+--+"
    putStrLn $ "|" ++ formatCell (0, 0) ++ "|" ++ formatCell (0, 1) ++ "|" ++ formatCell (0, 2) ++ "|" ++ formatCell (0, 3) ++ "|"
    putStrLn "+--+--+--+--+"
    putStrLn $ "|" ++ formatCell (1, 0) ++ "|" ++ formatCell (1, 1) ++ "|" ++ formatCell (1, 2) ++ "|" ++ formatCell (1, 3) ++ "|"
    putStrLn "+--+--+--+--+"
    putStrLn $ "|" ++ formatCell (2, 0) ++ "|" ++ formatCell (2, 1) ++ "|" ++ formatCell (2, 2) ++ "|" ++ formatCell (2, 3) ++ "|"
    putStrLn "+--+--+--+--+"
    putStrLn $ "|" ++ formatCell (3, 0) ++ "|" ++ formatCell (3, 1) ++ "|" ++ formatCell (3, 2) ++ "|" ++ formatCell (3, 3) ++ "|"
    putStrLn "+--+--+--+--+"
    where formatCell idx
              | i == 16 = "  "
              | i > 9 = show i
              | otherwise = " " ++ show i
              where i = puzzle ! idx

solvedPuzzle :: Puzzle
solvedPuzzle = listArray ((0, 0), (3, 3)) [1..16]

shufflePuzzle :: Int -> Puzzle -> IO Puzzle
shufflePuzzle 0 puzzle = return puzzle
shufflePuzzle numOfShuffels puzzle = do
    let moves = validMoves puzzle
    randomIndex <- randomRIO (0, length moves - 1)
    let move = moves !! randomIndex
    shufflePuzzle (numOfShuffels - 1) (applyMove move puzzle)


require'general/misc/prompt'

genboard=:3 :0
  b=. ?~16
  if. 0<C.!.2 b do.
    b=. (<0 _1)C. b
  end.
  a: (b i.0)} <"0 b
)

done=: (<"0]1+i.15),a:

shift=: |.!._"0 2
taxi=: |:,/"2(_1 1 shift i.4 4),_1 1 shift"0 1/ i.4 4

showboard=:3 :0
  echo 'current board:'
  echo 4 4$y
)

help=:0 :0

  Slide a number block into the empty space
  until you get:
┌──┬──┬──┬──┐
│1 │2 │3 │4 │
├──┼──┼──┼──┤
│5 │6 │7 │8 │
├──┼──┼──┼──┤
│9 │10│11│12│
├──┼──┼──┼──┤
│13│14│15│  │
└──┴──┴──┴──┘
  Or type 'q' to quit.
)

getmove=:3 :0
  showboard y
  blank=. y i. a:
  options=. /:~ ;y {~ _ -.~ blank { taxi
  whilst. -. n e. options do.
    echo 'Valid moves: ',":options
    t=. prompt 'move which number? '
    if. 'q' e. t do.
      echo 'giving up'
      throw.
    elseif. 'h' e. t do.
      echo help
      showboard y
    end.
    n=. {._".t
  end.
  (<blank,y i.<n) C. y
)

game=: 3 :0
  echo '15 puzzle'
  echo 'h for help, q to quit'
  board=. genboard''
  whilst. -. done-:board do.
    board=. getmove board
  end.
  showboard board
  echo 'You win.'
)


package fifteenpuzzle;

import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;

class FifteenPuzzle extends JPanel {

    private final int side = 4;
    private final int numTiles = side * side - 1;

    private final Random rand = new Random();
    private final int[] tiles = new int[numTiles + 1];
    private final int tileSize;
    private int blankPos;
    private final int margin;
    private final int gridSize;
    private boolean gameOver;

    private FifteenPuzzle() {
        final int dim = 640;

        margin = 80;
        tileSize = (dim - 2 * margin) / side;
        gridSize = tileSize * side;

        setPreferredSize(new Dimension(dim, dim + margin));
        setBackground(Color.WHITE);
        setForeground(new Color(0x6495ED)); // cornflowerblue
        setFont(new Font("SansSerif", Font.BOLD, 60));

        gameOver = true;

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (gameOver) {
                    newGame();

                } else {

                    int ex = e.getX() - margin;
                    int ey = e.getY() - margin;

                    if (ex < 0 || ex > gridSize || ey < 0 || ey > gridSize) {
                        return;
                    }

                    int c1 = ex / tileSize;
                    int r1 = ey / tileSize;
                    int c2 = blankPos % side;
                    int r2 = blankPos / side;

                    int clickPos = r1 * side + c1;

                    int dir = 0;
                    if (c1 == c2 && Math.abs(r1 - r2) > 0) {
                        dir = (r1 - r2) > 0 ? 4 : -4;

                    } else if (r1 == r2 && Math.abs(c1 - c2) > 0) {
                        dir = (c1 - c2) > 0 ? 1 : -1;
                    }

                    if (dir != 0) {
                        do {
                            int newBlankPos = blankPos + dir;
                            tiles[blankPos] = tiles[newBlankPos];
                            blankPos = newBlankPos;
                        } while (blankPos != clickPos);
                        tiles[blankPos] = 0;
                    }

                    gameOver = isSolved();
                }
                repaint();
            }
        });

        newGame();
    }

    private void newGame() {
        do {
            reset();
            shuffle();
        } while (!isSolvable());
        gameOver = false;
    }

    private void reset() {
        for (int i = 0; i < tiles.length; i++) {
            tiles[i] = (i + 1) % tiles.length;
        }
        blankPos = tiles.length - 1;
    }

    private void shuffle() {
        // don't include the blank space in the shuffle, leave it
        // in the home position
        int n = numTiles;
        while (n > 1) {
            int r = rand.nextInt(n--);
            int tmp = tiles[r];
            tiles[r] = tiles[n];
            tiles[n] = tmp;
        }
    }

    /*  Only half the permutations of the puzzle are solvable.

        Whenever a tile is preceded by a tile with higher value it counts
        as an inversion. In our case, with the blank space in the home
        position, the number of inversions must be even for the puzzle
        to be solvable.

        See also:
        www.cs.bham.ac.uk/~mdr/teaching/modules04/java2/TilesSolvability.html
     */
    private boolean isSolvable() {
        int countInversions = 0;
        for (int i = 0; i < numTiles; i++) {
            for (int j = 0; j < i; j++) {
                if (tiles[j] > tiles[i]) {
                    countInversions++;
                }
            }
        }
        return countInversions % 2 == 0;
    }

    private boolean isSolved() {
        if (tiles[tiles.length - 1] != 0) {
            return false;
        }
        for (int i = numTiles - 1; i >= 0; i--) {
            if (tiles[i] != i + 1) {
                return false;
            }
        }
        return true;
    }

    private void drawGrid(Graphics2D g) {
        for (int i = 0; i < tiles.length; i++) {
            int r = i / side;
            int c = i % side;
            int x = margin + c * tileSize;
            int y = margin + r * tileSize;

            if (tiles[i] == 0) {
                if (gameOver) {
                    g.setColor(Color.GREEN);
                    drawCenteredString(g, "\u2713", x, y);
                }
                continue;
            }

            g.setColor(getForeground());
            g.fillRoundRect(x, y, tileSize, tileSize, 25, 25);
            g.setColor(Color.blue.darker());
            g.drawRoundRect(x, y, tileSize, tileSize, 25, 25);
            g.setColor(Color.WHITE);

            drawCenteredString(g, String.valueOf(tiles[i]), x, y);
        }
    }

    private void drawStartMessage(Graphics2D g) {
        if (gameOver) {
            g.setFont(getFont().deriveFont(Font.BOLD, 18));
            g.setColor(getForeground());
            String s = "click to start a new game";
            int x = (getWidth() - g.getFontMetrics().stringWidth(s)) / 2;
            int y = getHeight() - margin;
            g.drawString(s, x, y);
        }
    }

    private void drawCenteredString(Graphics2D g, String s, int x, int y) {
        FontMetrics fm = g.getFontMetrics();
        int asc = fm.getAscent();
        int des = fm.getDescent();

        x = x + (tileSize - fm.stringWidth(s)) / 2;
        y = y + (asc + (tileSize - (asc + des)) / 2);

        g.drawString(s, x, y);
    }

    @Override
    public void paintComponent(Graphics gg) {
        super.paintComponent(gg);
        Graphics2D g = (Graphics2D) gg;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        drawGrid(g);
        drawStartMessage(g);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setTitle("Fifteen Puzzle");
            f.setResizable(false);
            f.add(new FifteenPuzzle(), BorderLayout.CENTER);
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        });
    }
}


var board, zx, zy, clicks, possibles, clickCounter, oldzx = -1, oldzy = -1;
function getPossibles() {
    var ii, jj, cx = [-1, 0, 1, 0], cy = [0, -1, 0, 1];
    possibles = [];
    for( var i = 0; i < 4; i++ ) {
        ii = zx + cx[i]; jj = zy + cy[i];
        if( ii < 0 || ii > 3 || jj < 0 || jj > 3 ) continue;
        possibles.push( { x: ii, y: jj } );
    }
}
function updateBtns() {
    var b, v, id;
    for( var j = 0; j < 4; j++ ) {
        for( var i = 0; i < 4; i++ ) {
            id = "btn" + ( i + j * 4 );
            b = document.getElementById( id );
            v = board[i][j];
            if( v < 16 ) {
                b.innerHTML = ( "" + v );
                b.className = "button"
            }
            else {
                b.innerHTML = ( "" );
                b.className = "empty";
            }
        }
    }
    clickCounter.innerHTML = "Clicks: " + clicks;
}
function shuffle() {
    var v = 0, t;
    do {
        getPossibles();
        while( true ) {
            t = possibles[Math.floor( Math.random() * possibles.length )];
            console.log( t.x, oldzx, t.y, oldzy )
            if( t.x != oldzx || t.y != oldzy ) break;
        }
        oldzx = zx; oldzy = zy;
        board[zx][zy] = board[t.x][t.y];
        zx = t.x; zy = t.y;
        board[zx][zy] = 16;
    } while( ++v < 200 );
}
function restart() {
    shuffle();
    clicks = 0;
    updateBtns();
}
function checkFinished() {
    var a = 0;
    for( var j = 0; j < 4; j++ ) {
        for( var i = 0; i < 4; i++ ) {
            if( board[i][j] < a ) return false;
            a = board[i][j];
        }
    }
    return true;
}
function btnHandle( e ) {
    getPossibles();
    var c = e.target.i, r = e.target.j, p = -1;
    for( var i = 0; i < possibles.length; i++ ) {
        if( possibles[i].x == c && possibles[i].y == r ) {
            p = i;
            break;
        }
    }
    if( p > -1 ) {
        clicks++;
        var t = possibles[p];
        board[zx][zy] = board[t.x][t.y];
        zx = t.x; zy = t.y;
        board[zx][zy] = 16;
        updateBtns();
        if( checkFinished() ) {
            setTimeout(function(){
                alert( "WELL DONE!" );
                restart();
            }, 1);
        }
    }
}
function createBoard() {
    board = new Array( 4 );
    for( var i = 0; i < 4; i++ ) {
        board[i] = new Array( 4 );
    }
    for( var j = 0; j < 4; j++ ) {
        for( var i = 0; i < 4; i++ ) {
            board[i][j] = ( i + j * 4 ) + 1;
        }
    }
    zx = zy = 3; board[zx][zy] = 16;
}
function createBtns() {
    var b, d = document.createElement( "div" );
    d.className += "board";
    document.body.appendChild( d );
    for( var j = 0; j < 4; j++ ) {
        for( var i = 0; i < 4; i++ ) {
            b = document.createElement( "button" );
            b.id = "btn" + ( i + j * 4 );
            b.i = i; b.j = j;
            b.addEventListener( "click", btnHandle, false );
            b.appendChild( document.createTextNode( "" ) );
            d.appendChild( b );
        }
    }
    clickCounter = document.createElement( "p" );
    clickCounter.className += "txt";
    document.body.appendChild( clickCounter );
}
function start() {
    createBtns();
    createBoard();
    restart();
}


using Random

const size = 4
const puzzle = string.(reshape(1:16, size, size))
puzzle[16] = " "
rng = MersenneTwister(Int64(round(time())))
shufflepuzzle() = (puzzle .= shuffle(rng, puzzle))
findtile(t) = findfirst(x->x == t, puzzle)
findhole() = findtile(" ")

function issolvable()
    inversioncount = 1
    asint(x) = (puzzle[x] == " ") ? 0 : parse(Int64, puzzle[x])
    for i in 1:size^2-1, j in i:size^2
        if puzzle[i] == " " || puzzle[j] == " "
            continue
        end
        if parse(Int, puzzle[i]) < parse(Int, puzzle[j])
            inversioncount += 1
        end
    end
    if size % 2 == 1
        return inversioncount % 2 == 0
    end
    pos = findhole()
    inversioncount += pos[2]
    return inversioncount & 1 == 0
end

function nexttohole()
    holepos = findhole()
    row = holepos[1]
    col = holepos[2]
    if row == 1
        if col == 1
            return [[row, col + 1], [row + 1, col]]
        elseif col == size
            return [[row, col - 1], [row + 1, col]]
        else
            return [[row, col - 1], [row, col + 1], [row + 1, col]]
        end
    elseif row == size
        if col == 1
            return [[row - 1, col], [row, col + 1]]
        elseif col == size
            return [[row - 1, col], [row, col - 1]]
        else
            return [[row - 1, col], [row, col - 1], [row, col + 1]]
        end
    else
        if col == 1
            return [[row - 1, col], [row, col + 1], [row + 1, col]]
        elseif col == size
            return [[row - 1, col], [row, col - 1], [row + 1, col]]
        else
            return [[row - 1, col], [row, col - 1], [row, col + 1], [row + 1, col]]
        end
    end
end

possiblemoves() = map(pos->puzzle[pos[1], pos[2]], nexttohole())

function movehole(tiletofill)
    if tiletofill in possiblemoves()
        curpos = findtile(tiletofill)
        holepos = findhole()
        puzzle[holepos] = tiletofill
        puzzle[curpos] = " "
    else
        println("Bad tile move $tiletofill.\nPossible moves are $(possiblemoves()).")
    end
end

function printboard()
    ppuz(x,y) = print(lpad(rpad(puzzle[x,y], 3), 4), "|")
    print("+----+----+----+----+\n|")
    for j in 1:size, i in 1:size
        ppuz(i,j)
        if i == size
            print("\n")
            if j < size
                 print("|")
            end
        end
    end
    println("+----+----+----+----+")

end

function puzzle15play()
    solved() = (puzzle[1:15] == string.(1:15))
    shufflepuzzle()
    println("This puzzle is", issolvable() ? " " : " not ", "solvable.")
    while !solved()
        printboard()
        print("Possible moves are: $(possiblemoves()), 0 to exit. Your move? =>  ")
        s = readline()
        if s == "0"
            exit(0)
        end
        movehole(s)
    end
    printboard()
    println("Puzzle solved.")
end

puzzle15play()


// version 1.1.3

import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension
import java.awt.Font
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.util.Random
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.SwingUtilities

class FifteenPuzzle(dim: Int, val margin: Int) : JPanel() {

    private val rand = Random()
    private val tiles = IntArray(16)
    private val tileSize = (dim - 2 * margin) / 4
    private val gridSize = tileSize * 4
    private var blankPos = 0

    init {
        preferredSize = Dimension(dim, dim)
        background = Color.white
        val cornflowerBlue = 0x6495ED
        foreground = Color(cornflowerBlue)
        font = Font("SansSerif", Font.BOLD, 60)

        addMouseListener(object : MouseAdapter() {
            override fun mousePressed(e: MouseEvent) {
                val ex = e.x - margin
                val ey = e.y - margin
                if (ex !in 0..gridSize || ey !in 0..gridSize) return

                val c1 = ex / tileSize
                val r1 = ey / tileSize
                val c2 = blankPos % 4
                val r2 = blankPos / 4
                if ((c1 == c2 && Math.abs(r1 - r2) == 1) ||
                    (r1 == r2 && Math.abs(c1 - c2) == 1)) {
                    val clickPos = r1 * 4 + c1
                    tiles[blankPos] = tiles[clickPos]
                    tiles[clickPos] = 0
                    blankPos = clickPos
                }
                repaint()
            }
        })

        shuffle()
    }

    private fun shuffle() {
        do {
            reset()
            // don't include the blank space in the shuffle,
            // leave it in the home position
            var n = 15
            while (n > 1) {
                val r = rand.nextInt(n--)
                val tmp = tiles[r]
                tiles[r] = tiles[n]
                tiles[n] = tmp
            }
        } while (!isSolvable())
    }

    private fun reset() {
        for (i in 0 until tiles.size) {
            tiles[i] = (i + 1) % tiles.size
        }
        blankPos = 15
    }

    /*  Only half the permutations of the puzzle are solvable.

        Whenever a tile is preceded by a tile with higher value it counts
        as an inversion. In our case, with the blank space in the home
        position, the number of inversions must be even for the puzzle
        to be solvable.
    */

    private fun isSolvable(): Boolean {
        var countInversions = 0
        for (i in 0 until 15) {
            (0 until i)
                .filter { tiles[it] > tiles[i] }
                .forEach { countInversions++ }
        }
        return countInversions % 2 == 0
    }

    private fun drawGrid(g: Graphics2D) {
        for (i in 0 until tiles.size) {
            if (tiles[i] == 0) continue

            val r = i / 4
            val c = i % 4
            val x = margin + c * tileSize
            val y = margin + r * tileSize

            with(g) {
                color = foreground
                fillRoundRect(x, y, tileSize, tileSize, 25, 25)
                color = Color.black
                drawRoundRect(x, y, tileSize, tileSize, 25, 25)
                color = Color.white
            }
            drawCenteredString(g, tiles[i].toString(), x, y)
        }
    }

    private fun drawCenteredString(g: Graphics2D, s: String, x: Int, y: Int) {
        val fm = g.fontMetrics
        val asc = fm.ascent
        val des = fm.descent

        val xx = x + (tileSize - fm.stringWidth(s)) / 2
        val yy = y + (asc + (tileSize - (asc + des)) / 2)

        g.drawString(s, xx, yy)
    }

    override fun paintComponent(gg: Graphics) {
        super.paintComponent(gg)
        val g = gg as Graphics2D
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON)
        drawGrid(g)
    }
}

fun main(args: Array<String>) {
    SwingUtilities.invokeLater {
        val f = JFrame()
        with(f) {
            defaultCloseOperation = JFrame.EXIT_ON_CLOSE
            title = "Fifteen Puzzle"
            isResizable = false
            add(FifteenPuzzle(640, 80), BorderLayout.CENTER)
            pack()
            setLocationRelativeTo(null)
            isVisible = true
        }
    }
}


' 15-PUZZLE GAME
' ********************************
dim d(16)
dim ds$(16) ' Board pieces
dim sh(3)

call buildBoard introAndLevel()
call printPuzzle
do
  print "To move a piece, enter its number: "
  input x
  while isMoveValid(x, y, z) = 0
    print "Wrong move."
    call printPuzzle
    print "To move a piece, enter its number: "
    input x
  wend
  d(z) = x
  d(y) = 0
  call printPuzzle
loop until isPuzzleComplete()
print "YOU WON!"
end

sub printPuzzle
  for p = 1 to 16
    if d(p) = 0 then
      ds$(p) = "     "
    else
      ds$(p) = using("###", d(p)) + "  "
    end if
  next p
  print "+-----+-----+-----+-----+"
  print "|"; ds$(1); "|"; ds$(2); "|"; ds$(3); "|"; ds$(4); "|"
  print "+-----+-----+-----+-----+"
  print "|"; ds$(5); "|"; ds$(6); "|"; ds$(7); "|"; ds$(8); "|"
  print "+-----+-----+-----+-----+"
  print "|"; ds$(9); "|"; ds$(10); "|";ds$(11); "|"; ds$(12); "|"
  print "+-----+-----+-----+-----+"
  print "|"; ds$(13); "|"; ds$(14); "|"; ds$(15); "|"; ds$(16); "|"
  print "+-----+-----+-----+-----+"
end sub

function introAndLevel()
  cls
  sh(1) = 10
  sh(2) = 50
  sh(3) = 100
  print "15 PUZZLE GAME"
  print
  print
  print "Please enter level of difficulty,"
  do
    print "1 (easy), 2 (medium) or 3 (hard): ";
    input level
  loop while (level < 1) or (level > 3)
  introAndLevel = level
end function

sub buildBoard level
  ' Set pieces in correct order first
  for p = 1 to 15
    d(p) = p
  next p
  d(16) = 0 ' 0 = empty piece/slot
  z = 16 ' z = empty position
  print
  print "Shuffling pieces";
  for n = 1 to sh(level)
    print ".";
    do
      x = int(rnd(0) * 4) + 1
      select case x
        case 1
          r = z - 4
        case 2
          r = z + 4
        case 3
          if (z - 1) mod 4 <> 0 then
            r = z - 1
          end if
        case 4
          if z mod 4 <> 0 then
            r = z + 1
          end if
      end select
    loop while (r < 1) or (r > 16)
    d(z) = d(r)
    z = r
    d(z) = 0
  next n
  cls
end sub

function isMoveValid(piece, byref piecePos, byref emptyPos)
  mv = 0
  if (piece >= 1) and (piece <= 15) then
    piecePos = piecePosition(piece)
    emptyPos = piecePosition(0)
    ' Check if empty piece is above, below, left or right to piece
    if (piecePos - 4 = emptyPos) or _
      (piecePos + 4 = emptyPos) or _
      ((piecePos - 1 = emptyPos) and (emptyPos mod 4 <> 0)) or _
      ((piecePos + 1 = emptyPos) and (piecePos mod 4 <> 0)) then
      mv = 1
    end if
  end if
  isMoveValid = mv
end function

function piecePosition(piece)
  p = 1
  while d(p) <> piece
    p = p + 1
    if p > 16 then
      print "UH OH!"
      stop
    end if
  wend
  piecePosition = p
end function

function isPuzzleComplete()
  pc = 0
  p = 1
  while (p < 16) and (d(p) = p)
    p = p + 1
  wend
  if p = 16 then
    pc = 1
  end if
  isPuzzleComplete = pc
end function


#Please note that all this code can be performed in livecode with just few mouse clicks
#This is just a pure script exampe
on OpenStack
   show me  #Usually not necessary
   #tile creation
   set the width of the templateButton to 50
   set the height of the templateButton to 50
   repeat with i=1 to 16
      create button
      set the label of  button i to i
      if i =1 then
         set the top of button 1 to 0
         set the left of button 1 to 0
      end if
      if i > 1 and 1 <=4  then
         set the left of button i to the right of button (i-1)
         set the top of button i to the top of button 1
      end if
      if i >= 5 and i <= 8 then
         set the top of button i to the bottom of button 1
         if i = 5 then
            set the left of button i to the left of button 1
         else
            set the left of button i to the right of button (i - 1)
         end if
      end if
      if i >= 9 and i <= 12 then
         set the top of button i to the bottom of button 5
         if i = 9 then
            set the left of button i to the left of button 1
         else
            set the left of button i to the right of button (i - 1)
         end if
      end if
      if i >= 13 and i <= 16 then
         set the top of button i to the bottom of button 9
         if i = 13 then
            set the left of button i to the left of button 1
         else
            set the left of button i to the right of button (i - 1)
         end if
      end if
      #this is usally the script directly wirtten in the objects, it's really weird this way
      put "on MouseUp" &CR& "if checkDistance(the label of me) then" & CR &"put the loc of me into temp" into ts
      put CR& "set the loc of me to the loc of button 16" after ts
      put CR& "set the loc of button 16 to temp" & Cr & "end if " &CR &"End MouseUp" after ts
      set the script of button i to ts
   end repeat
   #graphic adjustements
   set the visible of button 16 to false
   set the width of this stack to the right of button 16
   set the height of this stack to the bottom of button 16
end openStack

function checkDistance i
   if (((the top of button i - the bottom of button 16) = 0 OR (the top of button 16 - the bottom of button i) = 0) AND the left of button i = the left of button 16) OR (((the left of button i - the right of button 16) = 0 OR (the right of button i - the left of button 16) = 0) AND the top of button i = the top of button 16) then
      return true
   else
      return false
   end if
end checkDistance


math.randomseed( os.time() )
local puz = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0 }
local dir = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }
local sx, sy = 4, 4

function isValid( tx, ty )
    return tx > 0 and tx < 5 and ty > 0 and ty < 5
end
function display()
    io.write( "\n\n" )
    for j = 1, 4 do
        io.write( "+----+----+----+----+\n" )
        for i = 1, 4 do
            local d = puz[i + j * 4 - 4]
            io.write( ": " )
            if d < 10 then io.write( " " ) end
            if d < 1  then
                io.write( "  " )
            else
                io.write( string.format( "%d ", d ) )
            end
        end
        io.write( ":\n" )
    end
    io.write( "+----+----+----+----+\n\n" )
end
function getUserNove()
    local moves, r, tx, ty = {}
    for d = 1, 4 do
        tx = sx; ty = sy
        tx = tx + dir[d][1]; ty = ty + dir[d][2]

        if isValid( tx, ty ) then
            table.insert( moves, puz[tx + ty * 4 - 4] )
        end
    end

    io.write( "Your possible moves are: " )
    for i = 1, #moves do
        io.write( string.format( "%d ", moves[i] ) )
    end

    io.write ( "\nYour move: " ); r = tonumber( io.read() )
    if r ~= nil then
        for i = 1, #moves do
            if moves[i] == r then return r end
        end
    end
    print( "Invalid move!" )
    return -1
end
function checked( r )
    for i = 1, #puz do
        if puz[i] == r then
            puz[i] = 0
            sx = 1 + ( i - 1 ) % 4; sy = math.floor( ( i + 3 ) / 4 )
        elseif puz[i] == 0 then
            puz[i] = r
        end
    end
    for i = 2, #puz - 1 do
        if puz[i - 1] + 1 ~= puz[i] then return false end
    end
    return true
end
function beginGame()
    local r, d, tx, ty
    while( true ) do
        for i = 1, 100 do
            while( true ) do
                tx = sx; ty = sy; d = math.random( 4 )
                tx = tx + dir[d][1]; ty = ty + dir[d][2]
                if isValid( tx, ty ) then break end
            end
            puz[sx + sy * 4 - 4] = puz[tx + ty * 4 - 4]
            puz[tx + ty * 4 - 4] = 0; sx = tx; sy = ty
        end
        while( true ) do
            display(); r = getUserNove()
            if r > 0 then
                if checked( r ) then
                    display()
                    io.write( "\nDone!\n\nPlay again (Y/N)?" )
                    r = io.read()
                    if r ~= "Y" and r ~= "y" then
                        return
                    else
                        break
                    end
                end
            end
        end
    end
end
-- [ entry point ] --
beginGame()


grid = MapThread[{#1,#2} &, {Range @ 16, Range @ 16}]

Move[x_] := (empty = Select[grid, #[[1]]==16 &][[1,2]];
             If[(empty == x+4) || (empty == x-4) ||
                (Mod[empty,4] != 0 && empty == x-1) ||
                (Mod[empty,4] != 1 && empty == x+1),
             oldEmpty = grid[[empty]][[1]];
             grid[[empty]][[1]] = grid[[x]][[1]];
             grid[[x]][[1]] = oldEmpty])

CButton[{x_,loc_}] := If[x==16, Null, Button[x,Move @ loc]]

Dynamic @ Grid @ Partition[CButton /@ grid,4]


:- module fifteen.
:- interface.
:- use_module random, io.

:- type board.
:- type invalid_board
    --->    invalid_board(board).
:- type move
    --->    up
    ;       down
    ;       left
    ;       right.

    % init(Board):
    % Board is fifteen game in its initial state
    %
:- pred init(board::out) is det.

    % print(Board, !IO)
:- pred print(board::in, io.io::di, io.io::uo) is det.

    % Shuffled(Board, !RS):
    % Board is a fifteen game in a random (but valid) state.
    %
:- pred shuffled(board::out, random.supply::mdi, random.supply::muo) is det.

    % space(Board) = I:
    % I is the index of the blank space in the board.
    % Throws invalid_board iff there is no blank.
    %
:- func space(board) = int.

    % move(Move, !Board):
    % Move the blank space in a board in the given direction.
    % Fails if this is an invalid move to make.
    %
:- pred move(move::in, board::in, board::out) is semidet.

:- implementation.
:- import_module bt_array, int, list, string.
:- use_module array, exception.

:- type board == bt_array(int).

init(B) :- from_list(0, (1 .. 15) ++ [0], B).

print(B, !IO) :-
    Tile = (func(N) = ( if N = 0 then s("  ") else s(string.format("%2d", [i(N)])) )),
    io.format("\
|----+----+----+----|
| %s | %s | %s | %s |
| %s | %s | %s | %s |
| %s | %s | %s | %s |
| %s | %s | %s | %s |
|----+----+----+----|
",
        map(Tile, to_list(B)), !IO).

shuffled(!:B, !RS) :-
    init(!:B),
    some [!A] (
        array.from_list(to_list(!.B), !:A),
        array.random_permutation(!A, !RS),
        from_list(0, array.to_list(!.A), !:B)
    ).

space(Board) = I :- space(0, Board, I).

:- pred space(int::in, board::in, int::out) is det.
space(N, Board, I) :-
    ( if semidet_lookup(Board, N, X) then
        ( if X = 0 then
            N = I
        else
            space(N + 1, Board, I)
        )
    else
        exception.throw(invalid_board(Board))
    ).

:- pred swap(int::in, int::in, board::in, board::out) is det.
swap(I, J, !B) :-
    X = !.B ^ elem(I),
    Y = !.B ^ elem(J),
    !B ^ elem(I) := Y,
    !B ^ elem(J) := X.

move(M, !B) :- move(space(!.B), M, !B).

:- pred move(int::in, move::in, board::in, board::out) is semidet.
move(I, up, !B) :-
    I >= 4,
    swap(I, I - 4, !B).
move(I, down, !B) :-
    I < 12,
    swap(I, I + 4, !B).
move(I, left, !B) :-
    not (I = 0 ; I = 4 ; I = 8 ; I = 12),
    swap(I, I - 1, !B).
move(I, right, !B) :-
    not (I = 3 ; I = 7 ; I = 11 ; I = 15),
    swap(I, I + 1, !B).


15Game ;
	; setting the layout
	f i=1:1:15 s box(i)=16-i
	; no number for box 16
	s box(16)=""
	f  {
	; initialise i for incrementation
		s i=0
	; write out the 4-by-4 box
		f row=1:1:4 {
			w !?20
			f column=1:1:4 {
				s i=$i(i)
				w $j(box(i),2)," "
			}
		}
		r !!,"Enter number to move (q to quit): ",number
		q:number="q"
		f i=1:1:16 q:box(i)=""
		if box(i)="" {
			if i>4,number=box(i-4) {
				s box(i)=number,box(i-4)=""
				w !!
			}
			elseif i>1,i'=5,i'=9,i'=13,number=box(i-1) {
				s box(i)=number,box(i-1)=""
				w !!
			}
			elseif i<16,i'=4,i'=8,i'=12,number=box(i+1) {
				s box(i)=number,box(i+1)=""
				w !!
			}
			elseif i<13,number=box(i+4) {
				s box(i)=number,box(i+4)=""
				w !!
			}
			else {
				w !,"You have to enter a number either above, below, left or right of the empty space."
			}
		}
	}
	q


import random, terminal

type
  Tile = uint8
  Board = array[16, Tile]

type
  Operation = enum
    opInvalid
    opUp
    opDown
    opLeft
    opRight
    opQuit
    opRestart

func charToOperation(c: char): Operation =
  case c
  of 'w', 'W': opUp
  of 'a', 'A': opLeft
  of 's', 'S': opDown
  of 'd', 'D': opRight
  of 'q', 'Q': opQuit
  of 'r', 'R': opRestart
  else: opInvalid

proc getKey(): Operation =
  var c = getch()
  if c == '\e':
    c = getch()
    if c == '[':
      case getch()
      of 'A': opUp
      of 'D': opLeft
      of 'B': opDown
      of 'C': opRight
      else: opInvalid
    else: charToOperation c
  else: charToOperation c

func isSolved(board: Board): bool =
  for i in 0..<board.high:
    if i != board[i].int - 1:
      return false
  true

func findTile(b: Board, n: Tile): int =
  for i in 0..b.high:
    if b[i] == n:
      return i

func canSwap(a, b: int): bool =
  let dist = a - b
  dist == 4 or dist == -4 or
    (dist == 1 and a mod 4 != 0) or
    (dist == -1 and b mod 4 != 0)

func pad(i: Tile): string =
  if i == 0:
    "│  "
  elif i < 10:
    "│ " & $i
  else:
    "│" & $i

proc draw(b: Board) =
  echo "┌──┬──┬──┬──┐\n",
     b[0].pad, b[1].pad, b[2].pad, b[3].pad,
     "│\n├──┼──┼──┼──┤\n",
     b[4].pad, b[5].pad, b[6].pad, b[7].pad,
     "│\n├──┼──┼──┼──┤\n",
     b[8].pad, b[9].pad, b[10].pad, b[11].pad,
     "│\n├──┼──┼──┼──┤\n",
     b[12].pad, b[13].pad, b[14].pad, b[15].pad,
     "│\n└──┴──┴──┴──┘"

proc clearScreen ()=
  for i in 1..10:
    eraseLine()
    cursorUp()

func calcPosMove(b: var Board; o: Operation; ): int =
  var posEmpty = b.findTile 0
  case o
  of opUp:    return posEmpty + 4
  of opDown:  return posEmpty - 4
  of opLeft:  return posEmpty + 1
  of opRight: return posEmpty - 1
  else: return -1

proc moveTile (b : var Board, op : Operation) : bool =
  let posMove = b.calcPosMove op
  let posEmpty = b.findTile 0
  if posMove < 16 and posMove >= 0 and canSwap(posEmpty, posMove):
    swap b[posEmpty], b[posMove]
    return true
  return false

proc shuffleBoard ( b: var Board, nSteps : int = 2000 ) =
  var opMove = @[opUp, opLeft, opDown, opRight]
  for i in 0 ..< nSteps:
    let op = opMove[rand(3)]
    discard b.moveTile op

proc generateBoard: Board =
  for i in 0..15:
    if i == 15 :
      result[i] = 0
    else:
      result[i] = (i + 1).Tile
  shuffleBoard result

when isMainModule:
  randomize()
  var
    board = generateBoard()
    empty = board.findTile 0

  block gameLoop:
    while not isSolved board:
      # draw
      draw board
      echo "Press arrow keys or WASD to move, R to Restart, Q to Quit"

      # handle input
      while true:
        let op = getKey()
        case op
        of opRestart:
          board = generateBoard()
          empty = board.findTile 0
          break
        of opQuit: break gameLoop
        of opInvalid: continue
        else:
          if board.moveTile op:
            empty = board.findTile 0
            break

      clearScreen()

    draw board
    echo "You win!"


module Puzzle =
struct
  type t = int array
  let make () =
    [| 15; (* 0: the empty space *)
        0;  1;  2;  3;
        4;  5;  6;  7;
        8;  9; 10; 11;
       12; 13; 14;  |]

  let move p n =
    let hole, i = p.(0), p.(n) in
    p.(0) <- i;
    p.(n) <- hole

  let print p =
    let out = Array.make 16 "   " in
    for i = 1 to 15 do
      out.(p.(i)) <- Printf.sprintf " %2d" i
    done;
    for i = 0 to 15 do
      if (i mod 4) = 0 then print_newline ();
      print_string out.(i);
    done

  let shuffle p n =
    for i = 1 to n do
      move p (1 + Random.int 15)
    done
end

let play () =
  let p = Puzzle.make () in
  Puzzle.shuffle p 20;
  while true do
    Puzzle.print p;
    print_string " > ";
    Puzzle.move p (read_line () |> int_of_string)
  done


program fifteen;
{$mode objfpc}
{$modeswitch advancedrecords}
{$coperators on}
uses
  SysUtils, crt;
type
  TPuzzle = record
  private
  const
    ROW_COUNT  = 4;
    COL_COUNT  = 4;
    CELL_COUNT = ROW_COUNT * COL_COUNT;
    RAND_RANGE = 101;
  type
    TTile          = 0..Pred(CELL_COUNT);
    TAdjacentCell  = (acLeft, acTop, acRight, acBottom);
    TPossibleMoves = set of TTile;
    TCellAdjacency = set of TAdjacentCell;
    TBoard         = array[0..Pred(CELL_COUNT)] of TTile;
  class var
    HBar: string;
  var
    FBoard: TBoard;
    FZeroPos,
    FMoveCount: Integer;
    FZeroAdjacency: TCellAdjacency;
    FPossibleMoves: TPossibleMoves;
    FSolved: Boolean;
    procedure DoMove(aTile: TTile);
    procedure CheckPossibleMoves;
    procedure PrintBoard;
    procedure PrintPossibleMoves;
    procedure TestSolved;
    procedure GenerateBoard;
    class constructor Init;
  public
    procedure New;
    function  UserMoved: Boolean;
    property  MoveCount: Integer read FMoveCount;
    property  Solved: Boolean read FSolved;
  end;

procedure TPuzzle.DoMove(aTile: TTile);
var
  Pos: Integer = -1;
  Adj: TAdjacentCell;
begin
  for Adj in FZeroAdjacency do
    begin
      case Adj of
        acLeft:   Pos := Pred(FZeroPos);
        acTop:    Pos := FZeroPos - COL_COUNT;
        acRight:  Pos := Succ(FZeroPos);
        acBottom: Pos := FZeroPos + COL_COUNT;
      end;
      if FBoard[Pos] = aTile then
        break;
    end;
  FBoard[FZeroPos] := aTile;
  FZeroPos := Pos;
  FBoard[Pos] := 0;
end;

procedure TPuzzle.CheckPossibleMoves;
var
  Row, Col: Integer;
begin
  Row := FZeroPos div COL_COUNT;
  Col := FZeroPos mod COL_COUNT;
  FPossibleMoves := [];
  FZeroAdjacency := [];
  if Row > 0 then
    begin
      FPossibleMoves += [FBoard[FZeroPos - COL_COUNT]];
      FZeroAdjacency += [acTop];
    end;
  if Row < Pred(ROW_COUNT) then
    begin
      FPossibleMoves += [FBoard[FZeroPos + COL_COUNT]];
      FZeroAdjacency += [acBottom];
    end;
  if Col > 0 then
    begin
      FPossibleMoves += [FBoard[Pred(FZeroPos)]];
      FZeroAdjacency += [acLeft];
    end;
  if Col < Pred(COL_COUNT) then
    begin
      FPossibleMoves += [FBoard[Succ(FZeroPos)]];
      FZeroAdjacency += [acRight];
    end;
end;

procedure TPuzzle.PrintBoard;
const
  Space = ' ';
  VBar  = '|';
  VBar1 = '| ';
  VBar2 = '|  ';
  VBar3 = '|    ';
var
  I, J, Pos, Tile: Integer;
  Row: string;
begin
  ClrScr;
  Pos := 0;
  WriteLn(HBar);
  for I := 1 to ROW_COUNT do
    begin
      Row := '';
      for J := 1 to COL_COUNT do
        begin
          Tile := Integer(FBoard[Pos]);
          case Tile of
            0:    Row += VBar3;
            1..9: Row += VBar2 + Tile.ToString + Space;
          else
            Row += VBar1 + Tile.ToString + Space;
          end;
          Inc(Pos);
        end;
      WriteLn(Row + VBar);
      WriteLn(HBar);
    end;
  if not Solved then
    PrintPossibleMoves;
end;

procedure TPuzzle.PrintPossibleMoves;
var
  pm: TTile;
  spm: string = '';
begin
  for pm in FPossibleMoves do
    spm += Integer(pm).ToString + ' ';
  WriteLn('possible moves: ', spm);
end;

procedure TPuzzle.TestSolved;
  function IsSolved: Boolean;
  var
    I: Integer;
  begin
    for I := 0 to CELL_COUNT - 3 do
      if FBoard[I] <> Pred(FBoard[Succ(I)]) then
        exit(False);
    Result := True;
  end;
begin
  FSolved := IsSolved;
  if not Solved then
    CheckPossibleMoves;
end;

procedure TPuzzle.GenerateBoard;
var
  I, CurrMove, SelMove: Integer;
  Tile: TTile;
begin
  FZeroPos := Pred(CELL_COUNT);
  FBoard[FZeroPos] := 0;
  for I := 0 to CELL_COUNT - 2 do
    FBoard[I] := Succ(I);
  for I := 1 to Random(RAND_RANGE) do
    begin
      CheckPossibleMoves;
      SelMove := 0;
      for Tile in FPossibleMoves do
        Inc(SelMove);
      SelMove := Random(SelMove);
      CurrMove := 0;
      for Tile in FPossibleMoves do
        begin
          if CurrMove = SelMove then
            begin
              DoMove(Tile);
              break;
            end;
          Inc(CurrMove);
        end;
    end;
end;

class constructor TPuzzle.Init;
var
  I: Integer;
begin
  HBar := '';
  for I := 1 to COL_COUNT do
    HBar += '+----';
  HBar += '+';
end;

procedure TPuzzle.New;
begin
  FSolved := False;
  FMoveCount := 0;
  GenerateBoard;
  CheckPossibleMoves;
  PrintBoard;
end;

function TPuzzle.UserMoved: Boolean;
const
  Sorry          = 'sorry, ';
  InvalidInput   = ' is invalid input';
  ImpossibleMove = ' is impossible move';
var
  UserInput: string;
  Tile: Integer = 0;
begin
  ReadLn(UserInput);
  case LowerCase(UserInput) of
    'c', 'cancel': exit(False);
  end;
  Result := True;
  if not Tile.TryParse(UserInput, Tile) then
    begin
      WriteLn(Sorry, UserInput, InvalidInput);
      exit;
    end;
  if not (Tile in [1..Pred(CELL_COUNT)]) then
    begin
      WriteLn(Sorry, Tile, InvalidInput);
      exit;
    end;
  if not (Tile in FPossibleMoves) then
    begin
      WriteLn(Sorry, Tile, ImpossibleMove);
      PrintPossibleMoves;
      exit;
    end;
  DoMove(Tile);
  Inc(FMoveCount);
  TestSolved;
  PrintBoard;
end;

procedure PrintStart;
begin
  ClrScr;
  WriteLn('Fifteen puzzle start:');
  WriteLn('  enter a tile number and press <enter> to move' );
  WriteLn('  enter Cancel(C) and press <enter> to exit' );
  Window(10, 4, 58, 21);
end;

procedure Terminate;
begin
  ClrScr;
  Window(1, 1, 80, 25);
  ClrScr;
  WriteLn('Fifteen puzzle exit.');
  Halt;
end;

function UserWantContinue(aMoveCount: Integer): Boolean;
var
  UserInput: string;
begin
  WriteLn('Congratulations! Puzzle solved in ', aMoveCount, ' moves.');
  WriteLn('Play again(Yes(Y)/<any button>)?');
  ReadLn(UserInput);
  case LowerCase(UserInput) of
    'y', 'yes': exit(True);
  end;
  Result := False;
end;

procedure Run;
var
  Puzzle: TPuzzle;
begin
  Randomize;
  PrintStart;
  repeat
    Puzzle.New;
    while not Puzzle.Solved do
      if not Puzzle.UserMoved then
        Terminate;
    if not UserWantContinue(Puzzle.MoveCount) then
      Terminate;
  until False;
end;

begin
  Run;
end.


use strict;
use warnings;

use Getopt::Long;
use List::Util 1.29 qw(shuffle pairmap first all);
use Tk;
    # 5 options                                 1 label text
my ($verbose,@fixed,$nocolor,$charsize,$extreme,$solvability);

unless (GetOptions (
                     'verbose!' => \$verbose,
                     'tiles|positions=i{16}' => \@fixed,
                     'nocolor' => \$nocolor,
                     'charsize|size|c|s=i' => \$charsize,
                     'extreme|x|perl' => \$extreme,
                    )
        ) { die "invalid arguments!";}

@fixed = &check_req_pos(@fixed) if @fixed;

my $mw = Tk::MainWindow->new(-bg=>'black',-title=>'Giuoco del 15');

if ($nocolor){ $mw->optionAdd( '*Button.background',   'ivory' );}

$mw->optionAdd('*Button.font', 'Courier '.($charsize or 16).' bold' );
$mw->bind('<Control-s>', sub{#&init_board;
                             &shuffle_board});

my $top_frame = $mw->Frame( -borderwidth => 2, -relief => 'groove',
                           )->pack(-expand => 1, -fill => 'both');

$top_frame->Label( -textvariable=>\$solvability,
                  )->pack(-expand => 1, -fill => 'both');

my $game_frame = $mw->Frame(  -background=>'saddlebrown',
                              -borderwidth => 10, -relief => 'groove',
                            )->pack(-expand => 1, -fill => 'both');

# set victory conditions in pairs of coordinates
my @vic_cond =  pairmap {
       [$a,$b]
    } qw(0 0 0 1 0 2 0 3
         1 0 1 1 1 2 1 3
         2 0 2 1 2 2 2 3
         3 0 3 1 3 2 3 3);

my $board = [];

my $victorious = 0;

&init_board;

if ( $extreme ){ &extreme_perl}

&shuffle_board;

MainLoop;

################################################################################
sub init_board{
  # tiles from 1 to 15
  for (0..14){
     $$board[$_]={
          btn=>$game_frame->Button(
                            -text => $_+1,
                            -relief => 'raised',
                            -borderwidth => 3,
                            -height => 2,
                            -width =>  4,
                                  -background=>$nocolor?'ivory':'gold1',
                                  -activebackground => $nocolor?'ivory':'gold1',
                                  -foreground=> $nocolor?'black':'DarkRed',
                                  -activeforeground=>$nocolor?'black':'DarkRed'
          ),
          name => $_+1,     # x and y set by shuffle_board
     };
     if (($_+1) =~ /^(2|4|5|7|10|12|13|15)$/ and !$nocolor){
         $$board[$_]{btn}->configure(
                                  -background=>'DarkRed',
                                  -activebackground => 'DarkRed',
                                  -foreground=> 'gold1',
                                  -activeforeground=>'gold1'
         );
     }
   }
   # empty tile
   $$board[15]={
          btn=>$game_frame->Button(
                            -relief => 'sunken',
                            -borderwidth => 3,
                            -background => 'lavender',
                            -height => 2,
                            -width =>  4,
          ),
          name => 16,      # x and y set by shuffle_board
     };
}
################################################################################
sub shuffle_board{
    if ($victorious){
        $victorious=0;
        &init_board;
    }
    if (@fixed){
          my $index = 0;

          foreach my $tile(@$board[@fixed]){
                  my $xy = $vic_cond[$index];
                  ($$tile{x},$$tile{y}) = @$xy;
                  $$tile{btn}->grid(-row=>$$xy[0], -column=> $$xy[1]);
                  $$tile{btn}->configure(-command =>[\&move,$$xy[0],$$xy[1]]);
                  $index++;
          }
          undef @fixed;
    }
    else{
        my @valid = shuffle (0..15);
        foreach my $tile ( @$board ){
            my $xy = $vic_cond[shift @valid];
            ($$tile{x},$$tile{y}) = @$xy;
            $$tile{btn}->grid(-row=>$$xy[0], -column=> $$xy[1]);
            $$tile{btn}->configure(-command => [ \&move, $$xy[0], $$xy[1] ]);
        }
    }
    my @appear =  map {$_->{name}==16?'X':$_->{name}}
                  sort{$$a{x}<=>$$b{x}||$$a{y}<=>$$b{y}}@$board;
    print "\n".('-' x 57)."\n".
          "Appearence of the board:\n[@appear]\n".
          ('-' x 57)."\n".
          "current\tfollowers\t               less than current\n".
          ('-' x 57)."\n" if $verbose;
    # remove the, from now on inutile, 'X' for the empty space
    @appear = grep{$_ ne 'X'} @appear;
    my $permutation;
    foreach my $num (0..$#appear){
        last if $num == $#appear;
         my $perm;
          $perm += grep {$_ < $appear[$num]} @appear[$num+1..$#appear];
          if ($verbose){
            print "[$appear[$num]]\t@appear[$num+1..$#appear]".
            (" " x (37 - length "@appear[$num+1..$#appear]")).
            "\t   $perm ".($num == $#appear  - 1 ? '=' : '+')."\n";
          }
          $permutation+=$perm;
    }
    print +(' ' x 50)."----\n" if $verbose;
    if ($permutation % 2){
        print "Impossible game with odd permutations!".(' ' x 13).
              "$permutation\n"if $verbose;
        $solvability = "Impossible game with odd permutations [$permutation]\n".
                        "(ctrl-s to shuffle)".
                        (($verbose or $extreme) ? '' :
                           " run with --verbose to see more info");
        return;
    }
    # 105 is the max permutation
    my $diff =  $permutation == 0 ? 'SOLVED' :
                $permutation < 35 ? 'EASY  ' :
                $permutation < 70 ? 'MEDIUM' : 'HARD  ';
    print "$diff game with even permutations".(' ' x 17).
          "$permutation\n" if $verbose;
    $solvability = "$diff game with permutation parity of [$permutation]\n".
                    "(ctrl-s to shuffle)";
}
################################################################################
sub move{
    # original x and y
    my ($ox, $oy) = @_;
    my $self = first{$_->{x} == $ox and $_->{y} == $oy} @$board;
    return if $$self{name}==16;
    # check if one in n,s,e,o is the empty one
    my $empty = first {$_->{name} == 16 and
                          ( ($_->{x}==$ox-1 and $_->{y}==$oy) or
                            ($_->{x}==$ox+1 and $_->{y}==$oy) or
                            ($_->{x}==$ox and $_->{y}==$oy-1) or
                            ($_->{x}==$ox and $_->{y}==$oy+1)
                           )
                      } @$board;
    return unless $empty;
    # empty x and y
    my ($ex,$ey) = ($$empty{x},$$empty{y});
    # reconfigure emtpy tile
    $$empty{btn}->grid(-row => $ox, -column => $oy);
    $$empty{x}=$ox;    $$empty{y}=$oy;
    # reconfigure pressed tile
    $$self{btn}->grid(-row => $ex, -column => $ey);
    $$self{btn}->configure(-command => [ \&move, $ex, $ey ]);
    $$self{x}=$ex;    $$self{y}=$ey;
    # check for victory if the empty one is at the bottom rigth tile (3,3)
    &check_win if $$empty{x} == 3 and $$empty{y} == 3;
}
################################################################################
sub check_win{
     foreach my $pos (0..$#$board){
        return unless ( $$board[$pos]->{'x'} == $vic_cond[$pos]->[0] and
                        $$board[$pos]->{'y'} == $vic_cond[$pos]->[1]);
     }
     # victory!
     $victorious = 1;
     my @text =  ('Dis','ci','pu','lus','15th','','','at',
                  'P','e','r','l','M','o','n','ks*');
     foreach my $tile(@$board){
            $$tile{btn}->configure( -text=> shift @text,
                                    -command=>sub{return});
            $mw->update;
            sleep 1;
     }
}
################################################################################
sub check_req_pos{
    my @wanted = @_;
    # fix @wanted: seems GetOptions does not die if more elements are passed
    @wanted = @wanted[0..15];
    my @check = (1..16);
    unless ( all {$_ == shift @check} sort {$a<=>$b} @wanted ){
        die "tiles must be from 1 to 16 (empty tile)\nyou passed [@wanted]\n";
    }
    return map {$_-1} @wanted;
}
################################################################################
sub extreme_perl {
  $verbose = 0;
  $mw->optionAdd('*font', 'Courier 20 bold');
  my @extreme = (
    'if $0',                               #1
    "\$_=\n()=\n\"foo\"=~/o/g",            #2
    "use warnings;\n\$^W ?\nint((length\n'Discipulus')/3)\n:'15'",   #3
    "length \$1\nif \$^X=~\n\/(?:\\W)(\\w*)\n(?:\\.exe)\$\/", #4
    "use Config;\n\$Config{baserev}",                   #5.
    "(split '',\nvec('JAPH'\n,1,8))[0]",       #6
    "scalar map\n{ord(\$_)=~/1/g}\nqw(p e r l)", #7
    "\$_ = () =\n'J A P H'\n=~\/\\b\/g",   # 8
    "eval join '+',\nsplit '',\n(substr\n'12345',3,2)",  #9
    'printf \'%b\',2',                     #10
    "int(((1+sqrt(5))\n/ 2)** 7 /\nsqrt(5)+0.5)-2",    #11
    "split '',\nunpack('V',\n01234567))\n[6,4]",  # 12
    'J','A','P','H'                               # 13..16
  );
  foreach (0..15){
      $$board[$_]{btn}->configure(-text=> $extreme[$_],
                                 -height => 8,
                                  -width =>  16, ) if $extreme[$_];

  }
  @fixed = qw(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15);
  $mw->after(5000,\&shuffle_board);#
}


constant ESC=27, UP=328, LEFT=331, RIGHT=333, DOWN=336
sequence board = tagset(15)&0, solve = board
integer pos = 16

procedure print_board()
    for i=1 to length(board) do
        puts(1,iff(i=pos?"   ":sprintf("%3d",{board[i]})))
        if mod(i,4)=0 then puts(1,"\n") end if
    end for
    puts(1,"\n")
end procedure

procedure move(integer d)
    integer new_pos = pos+{+4,+1,-1,-4}[d]
    if new_pos>=1 and new_pos<=16
    and (mod(pos,4)=mod(new_pos,4) -- same col, or row:
     or floor((pos-1)/4)=floor((new_pos-1)/4)) then
        {board[pos],board[new_pos]} = {board[new_pos],0}
        pos = new_pos
    end if
end procedure

for i=1 to 5 do move(rand(4)) end for
while 1 do
    print_board()
    if board=solve then exit end if
    integer c = find(wait_key(),{ESC,UP,LEFT,RIGHT,DOWN})-1
    if c=0 then exit end if
    move(c)
end while
puts(1,"solved!\n")


<?php
// Puzzle 15 Game - Rosseta Code - PHP 7 as the server-side script language.

// This program DOES NOT use cookies.

session_start([
  "use_only_cookies" => 0,
  "use_cookies" => 0,
  "use_trans_sid" => 1,
]);

class Location
{
  protected $column, $row;

  function __construct($column, $row){
    $this->column = $column;
    $this->row = $row;
  }
  function create_neighbor($direction){
    $dx = 0; $dy = 0;
    switch ($direction){
      case 0: case 'left':  $dx = -1; break;
      case 1: case 'right': $dx = +1; break;
      case 2: case 'up':    $dy = -1; break;
      case 3: case 'down':  $dy = +1; break;
    }
    return new Location($this->column + $dx, $this->row + $dy);
  }
  function equals($that){
    return $this->column == $that->column && $this->row == $that->row;
  }
  function is_inside_rectangle($left, $top, $right, $bottom){
    return $left <= $this->column && $this->column <= $right
        && $top <= $this->row && $this->row <= $bottom;
  }
  function is_nearest_neighbor($that){
    $s = abs($this->column - $that->column) + abs($this->row - $that->row);
    return $s == 1;
  }
}

class Tile
{
  protected $index;
  protected $content;
  protected $target_location;
  protected $current_location;

  function __construct($index, $content, $row, $column){
    $this->index = $index;
    $this->content = $content;
    $this->target_location = new Location($row, $column);
    $this->current_location = $this->target_location;
  }
  function get_content(){
    return $this->content;
  }
  function get_index(){
    return $this->index;
  }
  function get_location(){
    return $this->current_location;
  }
  function is_completed(){
    return $this->current_location->equals($this->target_location);
  }
  function is_empty(){
    return $this->content == NULL;
  }
  function is_nearest_neighbor($that){
    $a = $this->current_location;
    $b = $that->current_location;
    return $a->is_nearest_neighbor($b);
  }
  function swap_locations($that){
    $a = $this->current_location;
    $b = $that->current_location;
    $this->current_location = $b;
    $that->current_location = $a;
  }
}

class Model
{
  protected $N;
  protected $M;
  protected $tiles;

  function __construct($N, $M){
    $this->N = $N;
    $this->M = $M;
    $this->tiles[0] = new Tile(0, NULL, $N, $M);
    for ($k = 1; $k < $N * $M; $k++ ){
      $i = 1 + intdiv($k - 1, $M);
      $j = 1 + ($k - 1) % $M;
      $this->tiles[$k] = new Tile($k, (string)$k, $i, $j);
    }
    $number_of_shuffles = 1000;
    $i = 0;
    while ($i < $number_of_shuffles)
      if ($this->move_in_direction(random_int(0, 3)))
        $i++;
  }
  function get_N(){
    return $this->N;
  }
  function get_M(){
    return $this->M;
  }
  function get_tile_by_index($index){
    return $this->tiles[$index];
  }
  function get_tile_at_location($location){
    foreach($this->tiles as $tile)
      if ($location->equals($tile->get_location()))
        return $tile;
    return NULL;
  }
  function is_completed(){
    foreach($this->tiles as $tile)
      if (!$tile->is_completed())
        return FALSE;
    return TRUE;
  }
  function move($tile){
    if ($tile != NULL)
      foreach($this->tiles as $target){
        if ($target->is_empty() && $target->is_nearest_neighbor($tile)){
          $tile->swap_locations($target);
          break;
        }
      }
  }
  function move_in_direction($direction){
    foreach($this->tiles as $tile)
      if ($tile->is_empty())
        break;
    $location = $tile->get_location()->create_neighbor($direction);
    if ($location->is_inside_rectangle(0, 0, $this->M, $this->N)){
      $tile = $this->get_tile_at_location($location);
      $this->move($tile);
      return TRUE;
    }
    return FALSE;
  }
}

class View
{
  protected $model;

  function __construct($model){
      $this->model = $model;
  }
  function show(){
    $N = $this->model->get_N();
    $M = $this->model->get_M();
    echo "<form>";
    for ($i = 1; $i <= $N; $i++){
      for ($j = 1; $j <= $M; $j++){
        $tile = $this->model->get_tile_at_location(new Location($i, $j));
        $content = $tile->get_content();
        if ($content != NULL)
          echo ""
          .    "<input type='submit' class='puzzle' name='index'"
          .    "value='$content'>"
          .    "";
        else
          echo "";
      }
      echo "
";
    }
    echo "</form>";
    if ($this->model->is_completed()){
      echo "
";
      echo "You win!";
      echo "
";
    }
  }
}

class Controller
{
  protected $model;
  protected $view;

  function __construct($model, $view){
    $this->model = $model;
    $this->view = $view;
  }
  function run(){
    if (isset($_GET['index'])){
      $index = $_GET['index'];
      $this->model->move($this->model->get_tile_by_index($index));
    }
    $this->view->show();
  }
}
?>

<!DOCTYPE html>
<meta charset="UTF-8">

  <title>15 puzzle game</title>

<?php
    if (!isset($_SESSION['model'])){
      $width = 4; $height = 4;
      $model = new Model($width, $height);
    }
    else
      $model = unserialize($_SESSION['model']);
    $view = new View($model);
    $controller = new Controller($model, $view);
    $controller->run();
    $_SESSION['model'] = serialize($model);
  ?>


 import util.

main =>
    Board = {{1,2,3,4},
             {5,6,7,8},
             {9,10,11,12},
             {13,14,15,0}},
    Goal = copy_term(Board),
    shuffle(Board),
    play(Board,Goal).

shuffle(Board) =>
    foreach (_ in 1..1000)
        R1 = random() mod 4 + 1,
        C1 = random() mod 4 + 1,
        R2 = random() mod 4 + 1,
        C2 = random() mod 4 + 1,
        T := Board[R1,C1],
        Board[R1,C1] := Board[R2,C2],
        Board[R2,C2] := T
    end.

play(Board,Goal) =>
    while (Board != Goal)
        print_board(Board),
        possible_moves(Board,R0,C0,Moves),
        printf("Possible moves are: %w, 0 to exit. Your move? =>  ", Moves),
        S = read_line().strip(),
        if S == "0" || S == "" then
            halt
        else
            move_hole(Board,R0,C0,to_int(S))
        end
    end,
    print_board(Board),
    println("Puzzle solved.").

print_board(Board) =>
    N = len(Board),
    print("+----+----+----+----+\n"),
    foreach (R in 1..N)
        print("|"),
        foreach (C in 1..N)
            printf("%4d|", Board[R,C])
        end,
        nl
    end,
    println("+----+----+----+----+").

possible_moves(Board,R0,C0,Moves) =>
    N = len(Board),
    between(1,N,R0),
    between(1,N,C0),
    Board[R0,C0] == 0, !,
    NeibsOfHole = [(R1,C1) : (R1,C1) in [(R0-1,C0), (R0+1,C0), (R0,C0-1), (R0,C0+1)], R1 >= 1, R1 =< N, C1 >= 1, C1 =< N],
    Moves = sort([Board[R,C] : (R,C) in NeibsOfHole]).

move_hole(Board,R0,C0,S) =>
    N = len(Board),
    between(1,N,R),
    between(1,N,C),
    Board[R,C] == S, !,
    Board[R0,C0] := S,
    Board[R,C] := 0.


#15 Puzzle Game
$Script:Neighbours = @{
    "1" = @("2","5")
    "2" = @("1","3","6")
    "3" = @("2","4","7")
    "4" = @("3","8")
    "5" = @("1","6","9")
    "6" = @("2","5","7","10")
    "7" = @("3","6","8","11")
    "8" = @("4","7","12")
    "9" = @("5","10","13")
    "10" = @("6","9","11","14")
    "11" = @("7","10","12","15")
    "12" = @("8","11","0")
    "13" = @("9","14")
    "14" = @("10","13","15")
    "15" = @("11","14","0")
    "0" = @("12","15")
}
$script:blank = ''
#region XAML window definition
$xaml = @'
<Window
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
   MinWidth="200"
   Width ="333.333"
   Title="15 Game"
   Topmost="True" Height="398.001" VerticalAlignment="Center" HorizontalAlignment="Center">
    <Grid HorizontalAlignment="Center" Height="285" Margin="0" VerticalAlignment="Center" Width="300">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button x:Name="B_1" Content="01" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24"/>
        <Button x:Name="B_2" Content="02" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="1"/>
        <Button x:Name="B_3" Content="03" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="2"/>
        <Button x:Name="B_4" Content="04" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="3"/>
        <Button x:Name="B_5" Content="05" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Row="1"/>
        <Button x:Name="B_6" Content="06" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="1" Grid.Row="1"/>
        <Button x:Name="B_7" Content="07" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="2" Grid.Row="1"/>
        <Button x:Name="B_8" Content="08" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="3" Grid.Row="1"/>
        <Button x:Name="B_9" Content="09" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Margin="0,71,0,0" Grid.Row="1" Grid.RowSpan="2"/>
        <Button x:Name="B_10" Content="10" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="1" Grid.Row="1" Margin="0,71,0,0" Grid.RowSpan="2"/>
        <Button x:Name="B_11" Content="11" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="2" Grid.Row="1" Margin="0,71,0,0" Grid.RowSpan="2"/>
        <Button x:Name="B_12" Content="12" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="3" Grid.Row="1" Margin="0,71,0,0" Grid.RowSpan="2"/>
        <Button x:Name="B_13" Content="13" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Margin="0,71,0,0" Grid.Row="2" Grid.RowSpan="2"/>
        <Button x:Name="B_14" Content="14" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="1" Grid.Row="2" Margin="0,71,0,0" Grid.RowSpan="2"/>
        <Button x:Name="B_15" Content="15" HorizontalAlignment="Center" VerticalAlignment="Center" Width="75" Height="72" FontSize="24" Grid.Column="2" Grid.Row="2" Margin="0,71,0,0" Grid.RowSpan="2"/>
        <Button x:Name="B_0" Content="00" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Height="72" FontSize="24" Grid.Column="3" Grid.Row="2" Margin="0,71,0,0" Grid.RowSpan="2"/>
        <Button x:Name="B_Jumble" Grid.ColumnSpan="2" Content="Jumble Tiles" Grid.Column="1" HorizontalAlignment="Left" Height="23" Margin="0,81,0,-33" Grid.Row="3" VerticalAlignment="Top" Width="150"/>
    </Grid>
</Window>
'@
#endregion

#region Code Behind
function Convert-XAMLtoWindow
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $XAML
    )

    Add-Type -AssemblyName PresentationFramework

    $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
    $result = [Windows.Markup.XAMLReader]::Load($reader)
    $reader.Close()
    $reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
    while ($reader.Read())
    {
        $name=$reader.GetAttribute('Name')
        if (!$name) { $name=$reader.GetAttribute('x:Name') }
        if($name)
        {$result | Add-Member NoteProperty -Name $name -Value $result.FindName($name) -Force}
    }
    $reader.Close()
    $result
}
function Show-WPFWindow
{
    param
    (
        [Parameter(Mandatory=$true)]
        [Windows.Window]
        $Window
    )

    $result = $null
    $null = $window.Dispatcher.InvokeAsync{
        $result = $window.ShowDialog()
        Set-Variable -Name result -Value $result -Scope 1
    }.Wait()
    $result
}
#endregion Code Behind

#region Convert XAML to Window
$window = Convert-XAMLtoWindow -XAML $xaml

#endregion

#region Define Event Handlers
function Test-Victory{
    #Evaluate if all the labels are in the correct position
    $victory = $true
    foreach($num in 1..15){
        if([int]$window."b_$num".Content -ne $num){$victory = $false;break}
    }
    return($victory)
}
function Test-Move($Number){
    #Number is a string of the pressed button number.
    if($Script:Neighbours[$Number] -contains $script:blank){
        return($true)
    } else {
        return($false)
    }
}
Function Move-Tile($Number,$Bypass){
    if((!(Test-Victory)) -or $Bypass){
        if(Test-Move $Number){
            #Set the new window label
            $window."B_$script:blank".content = $window."B_$Number".content
            $window."B_$script:blank".background = $window."B_$Number".background
            $window."B_$Number".background = "#FFDDDDDD"
            #Set the new blank window label
            $window."B_$Number".content = ''
            #Enable the old blank tile
            $window."B_$script:blank".isenabled = $true
            #disable the new blank tile
            $window."B_$Number".isenabled = $false
            #set the new blank
            $script:blank = $Number
        }
    }
}
function Move-TileRandom{
    $lastmove = "1"
    for($i=0;$i -lt 500;$i++){
        $move = $Script:Neighbours[$script:blank] | Where-Object {$_ -ne $lastmove} | Get-Random
        $lastmove = $move
        Move-Tile $move $true
    }
}
function Set-TileColour($Tile){
    #I was curious about setting tiles to a checkerboard pattern at this stage. It's probably far better to just define it in the xaml
    #Ignore the blank tile
    if($Tile -ne 0){
        #check if the row of the tile is odd or even
        if((([math]::floor(($Tile - 1)/4)) % 2 -eq 0)){
            #check if the tile is odd or even
            if($Tile % 2 -eq 0){
                $window."B_$Tile".Background = "#FFFF7878"
            } else {
                $window."B_$Tile".Background = "#FF9696FF"
            }
        }else{
            if($Tile % 2 -eq 0){
                $window."B_$Tile".Background = "#FF9696FF"
            } else {
                $window."B_$Tile".Background = "#FFFF7878"
            }
        }
    } else {
        $window.B_0.Background = "#FFDDDDDD"
    }
}
$window.B_1.add_Click{
    $n = "1"
    move-tile $n
}
$window.B_2.add_Click{
    $n = "2"
    move-tile $n
}
$window.B_3.add_Click{
    $n = "3"
    move-tile $n
}
$window.B_4.add_Click{
    $n = "4"
    move-tile $n
}
$window.B_5.add_Click{
    $n = "5"
    move-tile $n
}
$window.B_6.add_Click{
    $n = "6"
    move-tile $n
}
$window.B_7.add_Click{
    $n = "7"
    move-tile $n
}
$window.B_8.add_Click{
    $n = "8"
    move-tile $n
}
$window.B_9.add_Click{
    $n = "9"
    move-tile $n
}
$window.B_10.add_Click{
    $n = "10"
    move-tile $n
}
$window.B_11.add_Click{
    $n = "11"
    move-tile $n
}
$window.B_12.add_Click{
    $n = "12"
    move-tile $n
}
$window.B_13.add_Click{
    $n = "13"
    move-tile $n
}
$window.B_14.add_Click{
    $n = "14"
    move-tile $n
}
$window.B_15.add_Click{
    $n = "15"
    move-tile $n
}
$window.B_0.add_Click{
    $n = "0"
    move-tile $n
}

$window.B_Jumble.add_Click{
    Move-TileRandom
}
#endregion Event Handlers
(([math]::floor(("9" - 1)/4)) % 2 -eq 0)
#region Manipulate Window Content
#initial processing of tiles
$array = 0..15 | ForEach-Object {"{0:00}" -f $_}
0..15 | ForEach-Object {if($array[$_] -ne '00'){$window."B_$_".content = $array[$_]} else {$window."B_$_".content = '';$script:blank = "$_";$window."B_$_".isenabled = $false};Set-TileColour $_}
#Shove them around a bit
Move-TileRandom
#endregion
# Show Window
$result = Show-WPFWindow -Window $window