#!/usr/bin/python3
import gi, random
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Pango

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='15 Puzzle')
        self.set_default_size(340, 400)
        self.moves = 0
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        vbox.set_margin_top(10); vbox.set_margin_bottom(10)
        vbox.set_margin_start(10); vbox.set_margin_end(10)
        self.add(vbox)
        hb = Gtk.Box(spacing=10)
        self.move_lbl = Gtk.Label(label='Moves: 0')
        hb.pack_start(self.move_lbl, True, True, 0)
        sh_btn = Gtk.Button(label='Shuffle')
        sh_btn.connect('clicked', self.shuffle)
        hb.pack_start(sh_btn, False, False, 0)
        vbox.pack_start(hb, False, False, 0)
        self.grid = Gtk.Grid(column_spacing=4, row_spacing=4)
        vbox.pack_start(self.grid, True, True, 0)
        self.btns = {}
        self.board = list(range(1, 16)) + [0]
        for pos in range(16):
            r, c = divmod(pos, 4)
            btn = Gtk.Button()
            btn.set_size_request(72, 72)
            btn.override_font(Pango.FontDescription('Bold 18'))
            btn.connect('clicked', self.tile_clicked, pos)
            self.grid.attach(btn, c, r, 1, 1)
            self.btns[pos] = btn
        self.status = Gtk.Label(label='')
        vbox.pack_start(self.status, False, False, 0)
        self.render()
    def render(self):
        for pos, val in enumerate(self.board):
            btn = self.btns[pos]
            if val == 0:
                btn.set_label('')
                btn.set_sensitive(False)
                btn.get_style_context().add_class('suggested-action')
            else:
                btn.set_label(str(val))
                btn.set_sensitive(True)
                btn.get_style_context().remove_class('suggested-action')
    def tile_clicked(self, btn, pos):
        blank = self.board.index(0)
        br, bc = divmod(blank, 4)
        pr, pc = divmod(pos, 4)
        if abs(br-pr) + abs(bc-pc) == 1:
            self.board[blank], self.board[pos] = self.board[pos], self.board[blank]
            self.moves += 1
            self.move_lbl.set_text(f'Moves: {self.moves}')
            self.render()
            if self.board == list(range(1,16)) + [0]:
                self.status.set_markup(f'<span foreground="green" weight="bold">Solved in {self.moves} moves!</span>')
    def shuffle(self, *_):
        self.moves = 0
        self.move_lbl.set_text('Moves: 0')
        self.status.set_text('')
        for _ in range(1000):
            blank = self.board.index(0)
            br, bc = divmod(blank, 4)
            neighbors = []
            for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                nr, nc = br+dr, bc+dc
                if 0<=nr<4 and 0<=nc<4: neighbors.append(nr*4+nc)
            nb = random.choice(neighbors)
            self.board[blank], self.board[nb] = self.board[nb], self.board[blank]
        self.render()

win = App()
win.connect('destroy', Gtk.main_quit)
win.show_all()
Gtk.main()
