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

ROWS, COLS, MINES = 9, 9, 10

class App(Gtk.Window):
    def __init__(self):
        super().__init__(title='Minesweeper')
        self.set_default_size(340, 420)
        self.set_resizable(False)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
        vbox.set_margin_top(6); vbox.set_margin_bottom(6)
        vbox.set_margin_start(6); vbox.set_margin_end(6)
        self.add(vbox)
        hb = Gtk.Box(spacing=10)
        self.mine_lbl = Gtk.Label(label=f'💣 {MINES}')
        hb.pack_start(self.mine_lbl, True, True, 0)
        new_btn = Gtk.Button(label='New Game')
        new_btn.connect('clicked', lambda *_: self.new_game())
        hb.pack_start(new_btn, False, False, 0)
        self.time_lbl = Gtk.Label(label='⏱ 0')
        hb.pack_start(self.time_lbl, True, True, 0)
        vbox.pack_start(hb, False, False, 0)
        self.btns = [[None]*COLS for _ in range(ROWS)]
        grid = Gtk.Grid(column_spacing=2, row_spacing=2)
        for r in range(ROWS):
            for c in range(COLS):
                btn = Gtk.Button()
                btn.set_size_request(32, 32)
                btn.override_font(Pango.FontDescription('Bold 10'))
                btn.connect('clicked', self.left_click, r, c)
                btn.connect('button-press-event', self.right_click, r, c)
                grid.attach(btn, c, r, 1, 1)
                self.btns[r][c] = btn
        vbox.pack_start(grid, True, True, 0)
        self.status = Gtk.Label(label='Left click: reveal | Right click: flag')
        vbox.pack_start(self.status, False, False, 0)
        self.new_game()
    def new_game(self):
        self.board = [[0]*COLS for _ in range(ROWS)]
        self.revealed = [[False]*COLS for _ in range(ROWS)]
        self.flagged = [[False]*COLS for _ in range(ROWS)]
        self.game_over = False
        self.first_click = True
        self.start_time = None
        self.timer_id = None
        self.flags = 0
        self.mine_lbl.set_text(f'💣 {MINES}')
        self.time_lbl.set_text('⏱ 0')
        self.status.set_text('Left click: reveal | Right click: flag')
        for r in range(ROWS):
            for c in range(COLS):
                b = self.btns[r][c]
                b.set_label('')
                b.set_sensitive(True)
                ctx = b.get_style_context()
                ctx.remove_class('destructive-action')
                ctx.remove_class('suggested-action')
    def place_mines(self, safe_r, safe_c):
        positions = [(r,c) for r in range(ROWS) for c in range(COLS)
                     if abs(r-safe_r)>1 or abs(c-safe_c)>1]
        mines = random.sample(positions, min(MINES, len(positions)))
        for r,c in mines: self.board[r][c] = -1
        for r in range(ROWS):
            for c in range(COLS):
                if self.board[r][c] == -1: continue
                cnt = sum(1 for dr in [-1,0,1] for dc in [-1,0,1]
                          if 0<=r+dr<ROWS and 0<=c+dc<COLS and self.board[r+dr][c+dc]==-1)
                self.board[r][c] = cnt
    def left_click(self, _, r, c):
        if self.game_over or self.flagged[r][c] or self.revealed[r][c]: return
        if self.first_click:
            self.first_click = False
            self.place_mines(r, c)
            self.start_time = time.time()
            self.timer_id = GLib.timeout_add(1000, self.update_timer)
        if self.board[r][c] == -1:
            self.reveal_mines(r, c)
            return
        self.flood_reveal(r, c)
        self.check_win()
    def right_click(self, btn, e, r, c):
        if e.button != 3 or self.game_over or self.revealed[r][c]: return True
        self.flagged[r][c] = not self.flagged[r][c]
        if self.flagged[r][c]:
            self.btns[r][c].set_label('🚩')
            self.flags += 1
        else:
            self.btns[r][c].set_label('')
            self.flags -= 1
        self.mine_lbl.set_text(f'💣 {MINES - self.flags}')
        return True
    def flood_reveal(self, r, c):
        if r<0 or r>=ROWS or c<0 or c>=COLS: return
        if self.revealed[r][c] or self.flagged[r][c]: return
        self.revealed[r][c] = True
        n = self.board[r][c]
        btn = self.btns[r][c]
        if n == 0:
            btn.set_label(''); btn.set_sensitive(False)
            for dr in [-1,0,1]:
                for dc in [-1,0,1]:
                    if dr or dc: self.flood_reveal(r+dr, c+dc)
        else:
            colors = {1:'#0000cc',2:'#006600',3:'#cc0000',4:'#000099',
                      5:'#990000',6:'#009999',7:'#000000',8:'#666666'}
            btn.set_label(str(n))
            btn.set_sensitive(False)
    def reveal_mines(self, hit_r, hit_c):
        self.game_over = True
        if self.timer_id: GLib.source_remove(self.timer_id)
        for r in range(ROWS):
            for c in range(COLS):
                if self.board[r][c] == -1:
                    self.btns[r][c].set_label('💣' if (r!=hit_r or c!=hit_c) else '💥')
                    self.btns[r][c].get_style_context().add_class('destructive-action')
        self.status.set_markup('<span foreground="red" weight="bold">BOOM! Game Over!</span>')
    def check_win(self):
        for r in range(ROWS):
            for c in range(COLS):
                if self.board[r][c] != -1 and not self.revealed[r][c]: return
        self.game_over = True
        if self.timer_id: GLib.source_remove(self.timer_id)
        self.status.set_markup('<span foreground="green" weight="bold">You Win!</span>')
    def update_timer(self):
        if self.start_time:
            self.time_lbl.set_text(f'⏱ {int(time.time()-self.start_time)}')
        return not self.game_over

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