AutoHotkey

16 Puzzle Game Gui with Solver

WW:= 44
Gui, font, S16
oHome := [], oLocation := [], oGrid := [], W := WW/2
IniRead, n, %A_ScriptFullPath%:Stream:$DATA, gridSize, gridSize, 4
loop % n**2
    R := ((A_Index-1)//n) + 1
    , C := Mod(A_Index-1, n) + 1
    , oHome[A_Index] :=  {"R":R, "C":C}

; draw down arrows
x := WW
loop % n {
    Gui, add, button, x%x% y%W% w%WW% h%W% -TabStop garwBtn v%A_Index%_D, ?
    x := "+0"
}
; draw left and right arrows
cntr := 1
x := (n+1)*WW
Loop % n {
    R := A_Index
    Gui, add, button, xs-%W% y+0 w%W% h%WW% -TabStop garwBtn v%R%_R, ?
    loop % n    ; draw oGrid
        Gui, add, button, x+0 yp w%WW% h%WW% -TabStop ggrdBtn v%R%_%A_Index%, % oGrid[R, A_Index]:=cntr++
    Gui, add, button, x%x%   yp  w%W% h%WW% -TabStop garwBtn v%R%_L, ?
}
; darw up arrows
y := (n+1)*WW, x := "s"
loop % n {
    Gui, add, button, x%x% y%Y% w%WW% h%W% -TabStop garwBtn v%A_Index%_U, ?
    x := "+0"
}
y := WW*(n+1) + W + 10
Gui, add, Button, % "xs y" Y " w" n * WW " h" W+10 " gShuffle", Shuffle
Gui, add, Button, % "xs y+0    w" n * WW " h" W+10 " gReset", Reset
Gui, add, Button, % "xs y+0    w" n * WW " h" W+10 " gSolve", Solve
Gui, add, text,, Grid Size:
Gui, add, Edit, x+5 w40 vN Center, % n
Gui, add, button, x+0 w0 h0 hidden default gSubmit
;~ Gui, +AlwaysOnTop
Gui, Show, % "w" (WW*(n+2)), 16 Puzzle
SysGet, SM_CYCAPTION, 4
return

;------------------------------
solve(lRow:=0){
    global
    num := 0
    slp := 500

    ; top row
    loop % n
    {
        num++
        ToolTip, % "solving # " num, 0, 0
        if (oHome[num, "R"] = oLocation[num, "R"]) && (oHome[num, "C"] = oLocation[num, "C"])
            continue
        if (oHome[num, "R"] = oLocation[num, "R"]) && (oHome[num, "C"] <> oLocation[num, "C"]) && (num > 1) ; same row, different col
            Move(oLocation[num, "C"], "D")
        
        rowRev := false
        if (abs(oLocation[num, "C"] - oHome[num, "C"]) > n/2)
            rowRev := true
        
        if (oLocation[num, "C"] > oHome[num, "C"]) && !rowRev
            dir := "L"
        if (oLocation[num, "C"] > oHome[num, "C"]) && rowRev
            dir := "R"
        if (oLocation[num, "C"] < oHome[num, "C"]) && !rowRev
            dir := "R"
        if (oLocation[num, "C"] < oHome[num, "C"]) && rowRev
            dir := "L"            
        while (oLocation[num, "C"] <> oHome[num, "C"])
            Move(oLocation[num, "R"], dir)
        
        colRev := false
        if (abs(oLocation[num, "R"] - oHome[num, "R"]) > n/2)
            colRev := true

        if (oLocation[num, "R"] > oHome[num, "R"]) && !colRev
            dir := "U"
        if (oLocation[num, "R"] > oHome[num, "R"]) && colRev
            dir := "D"
        if (oLocation[num, "R"] < oHome[num, "R"]) && !colRev
            dir := "D"
        if (oLocation[num, "R"] < oHome[num, "R"]) && colRev
            dir := "U"
            
        while (oLocation[num, "R"] <> oHome[num, "R"])
            Move(oLocation[num, "C"], dir)
        update_buttons(oGrid)
    }

    ; general row
    loop % n**2 - 2*n
    {
        ;~ ToolTip % num
        num++
        ToolTip, % "solving # " num, 0, 0
        if (oLocation[num, "R"] = oHome[num, "R"]) && (oLocation[num, "C"] = oHome[num, "C"])
            continue
        if (oLocation[num, "R"] = oHome[num, "R"]) && (oLocation[num, "C"] <> oHome[num, "C"]) ; same row, different col
        {
            c := oLocation[num, "C"]
            Move(oLocation[num, "C"], "D")
            Move(oLocation[num, "R"], "R")
            Move(c, "U")                            ; restore previous column
        }
            
        if (oLocation[num, "C"] = oHome[num, "C"])
            Move(oLocation[num, "R"], "L")
        loop % oLocation[num, "R"] - oHome[num, "r"]
            Move(oHome[num, "C"], "D")
        while (oLocation[num, "C"] <> oHome[num, "C"])
            Move(oLocation[num, "R"], "L")
        while (oLocation[num, "R"] <> oHome[num, "R"])
            Move(oLocation[num, "C"], "U")
        update_buttons(oGrid)
    }
    update_buttons(oGrid)
    if lRow = 1
        return
    lastRow()
    ToolTip
}

lastRow(){
    global
    num++    
    loop % n-2
    {
        num++
        ToolTip, % "solving # " num, 0, 0
        if (oLocation[num, "R"] <> n)
        {
            while (oLocation[num-1, "C"] <> n-1)
                Move(n, "R")
            loop % n - oLocation[num, "R"]
                Move(n, "D")
            Move(n, "L")
            Move(n, "U")
            continue
        }
        while (oLocation[num, "C"] <> n) && (oLocation[num, "R"] = n)
            Move(n, "R")
        Move(n, "D")
        while (oLocation[num-1, "C"] <> n-1)
            Move(n, "R")
        Move(n, "U")
        update_buttons(oGrid)
    }
    celln1 := n**2 - n + 1
    while (oLocation[celln1, "C"] <> 1)
        Move(n, "L")
    update_buttons(oGrid)
    if !CheckSolved()
        solve()
}
;=====================================================================================
Submit:
Gui, Submit, NoHide
IniWrite, % n, %A_ScriptFullPath%:Stream:$DATA, gridSize, gridSize
Reload
return
;=====================================================================================
^Esc::
ExitApp
;------------------------------
!1::
obj_map(oGrid)
return
;=====================================================================================
; Solve By Using Arrow Buttons
;=====================================================================================
arwBtn:
m := StrSplit(A_GuiControl, "_")
Move(m[1], m[2])
CheckSolved()
return
;=====================================================================================
; Solve By Using Mouse Drag
;=====================================================================================
;------------------------------
#If ((mText := getButtonText()) ~= "^\d+$")
~LButton::

RR := oLocation[mText].R 
CC := oLocation[mText].C
DragLR := DragUD := false
CCF := RRF := 0

GuiControlGet, Pos, Pos, % RR "_" CC
MouseGetPos, mx1, my1

while % GetKeyState("Lbutton", "P")
{
    CCF := CCF ? CCF : CC
    RRF := RRF ? RRF : RR
    MouseGetPos, mx2, my2
    ; Drag Left / Right
    if (mx2<>mx1 && !DragUD) {
        loop % n {
            GuiControlGet, Pos%A_Index%, Pos, % RR "_" A_Index
            GuiControl, move, % RR "_" A_Index, % "x" Pos%A_Index%X +mx2-mx1 " y" PosY
            GuiControlGet, ButtonText%A_Index%,, % RR "_" A_Index
        }
        MouseGetPos, mx1, my1
        DragLR := true
    }
    ; Drag Up / Down
    if (my2<>my1 && !DragLR) {
        loop % n {
            GuiControlGet, Pos%A_Index%, Pos, % A_Index "_" CC
            GuiControl, move, % A_Index "_" CC, % "x" PosX " y" Pos%A_Index%Y +my2-my1
            GuiControlGet, ButtonText%A_Index%,, % A_Index "_" CC
        }
        MouseGetPos, mx1, my1
        DragUD := true
    }
    ; Roll off Right Edge
    if DragLR
        GuiControlGet, Pos, Pos, % RR "_" n
    if (PosX > WW*n + W) && DragLR {
        loop % n {
            GuiControlGet, Pos%A_Index%, Pos, % RR "_" A_Index
            GuiControl, move, % RR "_" A_Index, % "x" Pos%A_Index%X - WW " y" PosY
            i := A_Index-1
            GuiControl,, % RR "_" A_Index, % ButtonText%i%
        }
        GuiControl,, % RR "_" 1, % ButtonText%n%
        CCF++
        ;~ GuiControl, focus, % RR "_" CCF
        MouseGetPos, mx1, my1
        move(RR, "R")
    }
    ; Roll off Left Edge
    if DragLR
        GuiControlGet, Pos, Pos, % RR "_" 1
    if (PosX < W) && DragLR {
        loop % n {
            GuiControlGet, Pos%A_Index%, Pos, % RR "_" A_Index
            GuiControl, move, % RR "_" A_Index, % "x" Pos%A_Index%X + WW " y" PosY
            i := A_Index+1
            GuiControl,, % RR "_" A_Index, % ButtonText%i% 
        }
        GuiControl,, % RR "_" n, % ButtonText1 
        CCF--
        ;~ GuiControl, focus, % RR "_" CCF
        MouseGetPos, mx1, my1
        move(RR, "L")
    }
    ; Roll off Bottom Edge
    if DragUD
        GuiControlGet, Pos, Pos, % n "_" CC
    if (PosY > WW*n + W) && DragUD {
        loop % n {
            GuiControlGet, Pos%A_Index%, Pos, % A_Index "_" CC
            GuiControl, move, % A_Index "_" CC, % "x" PosX " y" Pos%A_Index%Y - WW
            i := A_Index-1
            GuiControl,, % A_Index "_" CC, % ButtonText%i%
        }
        GuiControl,, % 1 "_" CC, % ButtonText%n%
        RRF++
        ;~ GuiControl, focus, % RRF "_" CC
        MouseGetPos, mx1, my1
        move(CC, "D")
    }
    ; Roll off Top Edge
    if DragUD
        GuiControlGet, Pos, Pos, % 1 "_" CC
    if (PosY < W) && DragUD {
        loop % n {
            GuiControlGet, Pos%A_Index%, Pos, % A_Index "_" CC
            GuiControl, move, % A_Index "_" CC, % "x" PosX " y" Pos%A_Index%Y + WW
            i := A_Index+1
            GuiControl,, % A_Index "_" CC, % ButtonText%i%
        }
        GuiControl,, % n "_" CC, % ButtonText1
        RRF--
        ;~ GuiControl, focus, % RRF "_" CC
        MouseGetPos, mx1, my1
        move(CC, "U")
    }
}    ; End Drag
WinSet, Redraw,, A

if DragLR        ; Place row in proper position after dragging
    loop % n
        GuiControl, move, % RR "_" A_Index, % "x" WW*A_Index " y" PosY

if DragUD        ; Place column in proper position after dragging
    loop % n
        GuiControl, move, % A_Index "_" CC, % "x" PosX " y" WW*A_Index

Loop % n**2 {    ; Update oGrid
    R := ((A_Index-1)//n) + 1,    C := Mod(A_Index-1, n) + 1
    GuiControlGet, ButtonText,, % R "_" C
    oGrid[R, C] := ButtonText ; update oGrid based on button locations
}
;~ Gui, show
CheckSolved()
return
#If
;=====================================================================================
; Solve By Using Arrow Keys
;=====================================================================================
;------------------------------
#If WinActive("16 Puzzle") && oGridR && oGridC
;------------------------------
Right::
Move(oGridR, "R")
GuiControl, focus, % oGridR "_" ++oGridC
CheckSolved()
return
;------------------------------
Left::
Move(oGridR, "L")
GuiControl, focus, % oGridR "_" --oGridC
CheckSolved()
return
;------------------------------
Down::
Move(oGridC, "D")
GuiControl, focus, % ++oGridR "_" oGridC
CheckSolved()
return
;------------------------------
Up::
Move(oGridC, "U")
GuiControl, focus, % --oGridR "_" oGridC
CheckSolved()
return
#If
;=====================================================================================
; SUBROUTINES
;=====================================================================================
;------------------------------
GuiEscape:
GuiClose:
ExitApp
return
;=====================================================================================
; FUNCTIONS
;=====================================================================================
;------------------------------
Move(num, Dir, update:=1){
    global
    local r, c, v
    if (Dir="R"){                                            ; move row # num right
        x := oGrid[num, n] 
        loop, % n-1
            oGrid[num, n+1-A_Index] := oGrid[num, n-A_Index]
        oGrid[num, 1] := x
    }
    if (Dir="L"){                                            ; move row # num left
        x := oGrid[num, 1] 
        loop, % n-1
            oGrid[num, A_Index] := oGrid[num, A_Index+1]
        oGrid[num, n] := x
    }
    if (Dir="U"){                                            ; move column # num up
        x := oGrid[1, Num]
        loop, % n-1
            oGrid[A_Index, num] := oGrid[A_Index+1, num]
        oGrid[n, Num] := x
    }
    if (Dir="D"){                                            ; move column # num down
        x := oGrid[n, Num]
        loop, % n-1
            oGrid[n+1-A_Index, num] := oGrid[n-A_Index, num]
        oGrid[1, Num] := x
    }
    
    for r, obj in oGrid
        for c, v in obj
            oLocation[v] := {"R" : r, "C" : c}
    
    if update
        update_buttons(oGrid)
    if !shuffled
        return
    Sleep % slp
    MovesCount++
    ;~ WinSet, Redraw,, A
    return 
}
;------------------------------
CheckSolved(){
    global
    local r, c, v
    if !Shuffled
        return
    cntr := 0
    for R, obj in oGrid
        for C, v in obj
            if (oGrid[R, C]<>++cntr)
                return false
    Shuffled := 0
    MsgBox Solved in %MovesCount% moves
    return true
}
;------------------------------
update_buttons(oGrid){
    for R, obj in oGrid
        for C, v in obj
            GuiControl,, % R "_" C, % oGrid[R, C]    ; update buttons
}
;------------------------------
getButtonText(){
    MouseGetPos, mx, my, mw, mc
    if InStr(mc, "Button")
        GuiControlGet, ButtonText,, % mc
    return ButtonText
}
;------------------------------
Reset(){
    global
    local R, C, v
    for R, obj in oGrid
        for C, v in obj
            GuiControl,, % R "_" C, % oGrid[R,C] := C + (R-1)*n
    MovesCount := 0
}
;------------------------------
Shuffle(){
    global
    rows := ["R","L"], cols := ["U","D"], Shuffled := false
    loop, 200 {
        Random, num, 1, %n%
        Random, dir, 1,2
        Move(num, rows[dir], 0)
        Move(num, cols[dir], 0)
        if !Mod(A_Index, n )
            update_buttons(oGrid)
    }
    update_buttons(oGrid)
    Shuffled := true, MovesCount := 0
}
;------------------------------
grdBtn(){
    global
    m := StrSplit(A_GuiControl, "_")
    oGridR:= m[1], oGridC:= m[2]
    GuiControl, focus, % oGridR "_" oGridC
}
;------------------------------