Before running the GUI, install pygame once:
pip install pygame   or   pip3 install pygame
Then run: python3 hog_gui.py
"""
hog_gui.py  —  The Game of Hog  (pygame GUI)

Before running, install pygame once:
    pip install pygame

Run:      python3 hog_gui.py
vs CPU:   python3 hog_gui.py -f
Keys:     0-9 roll count  |  ` for 10  |  r restart
"""

import sys, threading, queue, random, time, math, argparse

try:
    import pygame
except ImportError:
    print()
    print("  pygame is required:  pip install pygame")
    print("                  or:  pip3 install pygame")
    print()
    sys.exit(1)

import hog, dice
from ucb import main

# ═══════════════════════════════════════════════════════
#  Window
# ═══════════════════════════════════════════════════════
W, H  = 860, 580
FPS   = 60
DELAY = 1500   # computer thinking ms

# ═══════════════════════════════════════════════════════
#  Layout — three column: [player 0] [dice arena] [player 1]
# ═══════════════════════════════════════════════════════
HDR_H   = 50
PANEL_W = 170
CTR_X   = PANEL_W
CTR_W   = W - 2 * PANEL_W     # 520

# Dice (2 rows × 5, each 80px)
DIE_SZ  = 80
DIE_GAP = 9
_DTW    = 5 * DIE_SZ + 4 * DIE_GAP   # 436
DICE_X0 = CTR_X + (CTR_W - _DTW) // 2
DICE_Y0 = HDR_H + 38
DICE_Y1 = DICE_Y0 + DIE_SZ + DIE_GAP

# Roll tiles — two rows  (0–5 on top, 6–10 below)
TW, TH, TGAP = 62, 42, 7
_TR0 = 6 * TW + 5 * TGAP       # 397
_TR1 = 5 * TW + 4 * TGAP       # 338
TILE_Y0  = H - 132
TILE_Y1  = TILE_Y0 + TH + 8
TILE_X0  = CTR_X + (CTR_W - _TR0) // 2
TILE_X1  = CTR_X + (CTR_W - _TR1) // 2

# ═══════════════════════════════════════════════════════
#  CS7 palette — warm parchment + mint + terracotta
# ═══════════════════════════════════════════════════════
C_BG     = (236, 230, 216)   # parchment
C_CTR    = (250, 246, 238)   # dice arena (lighter)
C_BDR    = (204, 196, 180)   # borders

C_P0_BG  = (207, 241, 231)   # mint-washed player 0 panel
C_P1_BG  = (252, 226, 206)   # peach player 1 panel
C_P0_DIM = (228, 238, 234)   # dimmed panel (not your turn)
C_P1_DIM = (248, 236, 230)

C_MINT   = ( 77, 187, 160)
C_MINTD  = ( 46, 138, 115)
C_TERRA  = (201, 104,  58)
C_TERRD  = (154,  78,  36)

C_INK    = ( 30,  24,  16)
C_INK2   = ( 70,  62,  52)
C_DIM    = (128, 118, 104)
C_BDIM   = (178, 170, 158)

C_AMBER  = (196, 134,  14)
C_RED    = (188,  44,  44)
C_GREEN  = ( 48, 148,  80)
C_GOLD   = (172, 128,   8)
C_WHITE  = (255, 255, 255)

P_COL    = [C_MINT, C_TERRA]
P_DARK   = [C_MINTD, C_TERRD]
P_ON     = [C_P0_BG,  C_P1_BG]
P_OFF    = [C_P0_DIM, C_P1_DIM]

# Die dots (0–100 coords, scaled to die size)
_DOTS = {
    1: [(50, 50)],
    2: [(28, 28), (72, 72)],
    3: [(28, 28), (50, 50), (72, 72)],
    4: [(28, 28), (72, 28), (28, 72), (72, 72)],
    5: [(28, 28), (72, 28), (50, 50), (28, 72), (72, 72)],
    6: [(28, 28), (72, 28), (28, 50), (72, 50), (28, 72), (72, 72)],
}

# ═══════════════════════════════════════════════════════
#  Drawing helpers
# ═══════════════════════════════════════════════════════
def rrect(surf, col, rect, r, border=None, bw=1):
    pygame.draw.rect(surf, col, rect, border_radius=r)
    if border:
        pygame.draw.rect(surf, border, rect, bw, border_radius=r)


def draw_die(surf, x, y, value, pig=False, size=DIE_SZ):
    bg  = (255, 234, 234) if pig else C_WHITE
    bdr = (212, 186, 186) if pig else C_BDR
    dot = C_RED if (pig and value == 1) else C_INK
    # Shadow
    pygame.draw.rect(surf, C_BDR, (x + 3, y + 4, size, size), border_radius=13)
    # Face
    pygame.draw.rect(surf, bg, (x, y, size, size), border_radius=13)
    pygame.draw.rect(surf, bdr, (x, y, size, size), 2, border_radius=13)
    # Dots
    dr = max(4, size * 7 // 100)
    for px, py in _DOTS.get(value, []):
        cx = x + px * size // 100
        cy = y + py * size // 100
        pygame.draw.circle(surf, dot, (cx, cy), dr)


def pbar(surf, x, y, w, h, pct, col, bg=C_BDR, r=4):
    pygame.draw.rect(surf, bg, (x, y, w, h), border_radius=r)
    fw = max(0, int(min(pct, 1.0) * w))
    if fw > r:
        pygame.draw.rect(surf, col, (x, y, fw, h), border_radius=r)


def blit_c(surf, t, cx, y):
    surf.blit(t, t.get_rect(centerx=cx, y=y))


# ═══════════════════════════════════════════════════════
#  Die sprite  (spring-physics scale-in + face cycling)
# ═══════════════════════════════════════════════════════
class DieSprite:
    NFRAMES = 8

    def __init__(self, slot):
        self.slot    = slot
        self.visible = False
        self.value   = 6
        self.disp    = 6
        self.pig     = False
        self.rolling = False
        self.frame   = 0
        self.timer   = 0.0
        self.sc      = 0.0    # spring scale 0→1
        self.vel     = 0.0

    def start(self, final):
        self.value   = final
        self.pig     = (final == 1)
        self.disp    = random.randint(1, 6)
        self.rolling = True
        self.frame   = 0
        self.timer   = 0.0
        self.sc      = 0.0
        self.vel     = 0.065
        self.visible = True

    def update(self, dt):
        if self.sc < 1.0 or abs(self.vel) > 0.002:
            self.vel  += (1.0 - self.sc) * 0.014
            self.vel  *= 0.81
            self.sc   += self.vel * dt / 16.0
            if self.sc > 1.06 and self.vel < 0.01:
                self.sc, self.vel = 1.0, 0.0
        if not self.rolling:
            return
        intv = 40 + self.frame * 18
        self.timer += dt
        if self.timer >= intv:
            self.timer = 0.0
            self.frame += 1
            if self.frame >= self.NFRAMES:
                self.rolling = False
                self.disp    = self.value
            else:
                self.disp = random.randint(1, 6)

    def draw(self, surf, x, y):
        if not self.visible:
            return
        s   = max(8, int(DIE_SZ * min(self.sc, 1.12)))
        off = (DIE_SZ - s) // 2
        pig = self.pig and not self.rolling
        draw_die(surf, x + off, y + off, self.disp, pig=pig, size=s)


# ═══════════════════════════════════════════════════════
#  Particle
# ═══════════════════════════════════════════════════════
class Particle:
    def __init__(self, x, y, col):
        self.x  = float(x)
        self.y  = float(y)
        self.vx = random.uniform(-5.5, 5.5)
        self.vy = random.uniform(-8.0, -2.0)
        self.life = random.randint(45, 100)
        self.max  = self.life
        self.r    = random.randint(3, 8)
        self.col  = col

    def update(self):
        self.x   += self.vx
        self.y   += self.vy
        self.vy  += 0.24
        self.life -= 1

    def draw(self, surf):
        frac = max(0.0, self.life / self.max)
        c = tuple(int(bg + (v - bg) * frac) for v, bg in zip(self.col, C_BG))
        pygame.draw.circle(surf, c, (int(self.x), int(self.y)), max(1, self.r))

    @property
    def alive(self): return self.life > 0


# ═══════════════════════════════════════════════════════
#  Kill signal
# ═══════════════════════════════════════════════════════
class _Kill(BaseException):
    pass


# ═══════════════════════════════════════════════════════
#  Game
# ═══════════════════════════════════════════════════════
class HogGame:

    def __init__(self, computer=False):
        pygame.init()
        self.screen   = pygame.display.set_mode((W, H), pygame.RESIZABLE)
        pygame.display.set_caption('The Game of Hog — CS7')
        self.clock    = pygame.time.Clock()
        self.computer = computer

        self._fonts()

        # State
        self.who         = 0
        self.scores      = [0, 0]
        self.state       = 'waiting'     # waiting | rolling | gameover
        self.prompt      = ''
        self.prompt_col  = C_INK
        self.sub         = ''
        self.sub_col     = C_DIM
        self.winner      = None
        self.particles   = []
        self.sprites     = [DieSprite(i) for i in range(10)]
        self.dice_count  = 0

        # Running turn score
        self.turn_rolls  = []
        self.turn_pig    = False
        self.turn_total  = 0

        # Flash effect
        self.flash_t     = 0.0
        self.flash_col   = C_RED

        # Win overlay alpha
        self.win_alpha   = 0.0

        # Queues
        self.roll_q  = queue.Queue()
        self.evt_q   = queue.Queue()
        self._kill   = threading.Event()
        self._gt     = None

        self._build_tiles()
        self._rst = self._btn(W - 126, 12, 112, 30, '↺  New Game', self._restart)

        self._new_game()

    # ── Fonts ──────────────────────────────────────────
    def _fonts(self):
        self.fT  = pygame.font.SysFont('Georgia',    19, bold=True)   # title
        self.fPN = pygame.font.SysFont('Helvetica',  11, bold=True)   # player name
        self.fSC = pygame.font.SysFont('Helvetica',  56, bold=True)   # score
        self.fSC2= pygame.font.SysFont('Helvetica',  22, bold=True)   # small score
        self.fPR = pygame.font.SysFont('Helvetica',  13, bold=True)   # prompt
        self.fST = pygame.font.SysFont('Helvetica',  12)              # status
        self.fTL = pygame.font.SysFont('Helvetica',  15, bold=True)   # tile number
        self.fHT = pygame.font.SysFont('Helvetica',  11)              # hint
        self.fBD = pygame.font.SysFont('Helvetica',  11, bold=True)   # badge
        self.fWN = pygame.font.SysFont('Georgia',    36, bold=True)   # win overlay

    # ── Button helpers ─────────────────────────────────
    def _btn(self, x, y, w, h, label, cb):
        return {'rect': pygame.Rect(x, y, w, h), 'label': label, 'cb': cb}

    def _build_tiles(self):
        self.tiles = []
        for n in range(11):
            row  = 0 if n <= 5 else 1
            col  = n if n <= 5 else n - 6
            tx   = (TILE_X0 if row == 0 else TILE_X1) + col * (TW + TGAP)
            ty   = TILE_Y0 if row == 0 else TILE_Y1
            self.tiles.append(self._btn(tx, ty, TW, TH, str(n),
                                        lambda v=n: self._pick(v)))

    # ── Game thread ────────────────────────────────────
    def _new_game(self):
        self._kill.clear()
        self.who, self.scores = 0, [0, 0]
        self.state     = 'waiting'
        self.winner    = None
        self.dice_count = 0
        self.particles  = []
        self.prompt = self.sub = ''
        self.turn_rolls = []
        self.turn_pig   = False
        self.turn_total = 0
        self.flash_t    = 0.0
        self.win_alpha  = 0.0
        for s in self.sprites:
            s.visible = False
        for q in (self.roll_q, self.evt_q):
            while not q.empty():
                try: q.get_nowait()
                except queue.Empty: break
        self._gt = threading.Thread(target=self._game_thread, daemon=True)
        self._gt.start()

    def _game_thread(self):
        hog.six_sided  = self._mkdice(6)
        hog.four_sided = self._mkdice(4)
        try:
            s0, s1 = hog.play(
                lambda s, o: self._strat(0, s, o),
                lambda s, o: self._strat(1, s, o),
            )
            self.evt_q.put(('gameover', s0, s1))
        except _Kill:
            self.evt_q.put(('killed',))
        except Exception as e:
            self.evt_q.put(('error', str(e)))

    def _mkdice(self, sides):
        fair = dice.make_fair_dice(sides)
        def gui_dice():
            if self._kill.is_set(): raise _Kill
            val = fair()
            self.evt_q.put(('die', val, sides))
            t0 = time.monotonic()
            while time.monotonic() - t0 < 0.43:
                if self._kill.is_set(): raise _Kill
                time.sleep(0.04)
            return val
        return gui_dice

    def _strat(self, player, score, opp):
        if self._kill.is_set(): raise _Kill
        self.evt_q.put(('turn', player, score, opp))
        if self.computer and player == 1:
            t0 = time.monotonic()
            while time.monotonic() - t0 < DELAY / 1000.0:
                if self._kill.is_set(): raise _Kill
                time.sleep(0.05)
            try:    n = hog.final_strategy(score, opp)
            except: n = 4
        else:
            n = self.roll_q.get()
            if n < 0 or self._kill.is_set(): raise _Kill
        self.evt_q.put(('rolling', n))
        return n

    # ── Events ─────────────────────────────────────────
    def _drain(self):
        try:
            while True:
                ev  = self.evt_q.get_nowait()
                tag = ev[0]

                if tag == 'killed':
                    self._new_game()

                elif tag == 'turn':
                    _, p, sc, opp = ev
                    self.who           = p
                    self.scores[p]     = sc
                    self.scores[1 - p] = opp
                    self.state         = 'waiting'
                    self.dice_count    = 0
                    self.turn_rolls    = []
                    self.turn_pig      = False
                    self.turn_total    = 0
                    for s in self.sprites: s.visible = False
                    try:    wild = hog.select_dice(sc, opp) == hog.four_sided
                    except: wild = False
                    if wild:
                        self.prompt     = 'HOG WILD!  Rolling 4-sided dice  —  pick count:'
                        self.prompt_col = C_AMBER
                    else:
                        self.prompt     = 'How many dice?'
                        self.prompt_col = P_COL[p]
                    self.sub = ''

                elif tag == 'rolling':
                    _, n = ev
                    self.state  = 'rolling'
                    self.prompt = f'Rolling {n} {"die" if n == 1 else "dice"}...'

                elif tag == 'die':
                    _, val, _ = ev
                    idx = self.dice_count
                    if idx < 10:
                        self.sprites[idx].start(val)
                        self.dice_count += 1
                    self.turn_rolls.append(val)
                    if val == 1:
                        self.turn_pig = True
                        self.flash_t  = 420.0
                        self.flash_col = C_RED
                        x, y = self._dxy(self.dice_count - 1)
                        for _ in range(28):
                            self.particles.append(
                                Particle(x + DIE_SZ // 2, y + DIE_SZ // 2, C_TERRA))
                    else:
                        self.turn_total += val

                elif tag == 'gameover':
                    _, s0, s1 = ev
                    self.scores  = [s0, s1]
                    self.winner  = 0 if s0 > s1 else 1
                    self.state   = 'gameover'
                    self.prompt  = ''
                    col = P_COL[self.winner]
                    self.flash_t   = 0.0
                    self.win_alpha = 0.0
                    for _ in range(100):
                        c = random.choice([col, P_DARK[self.winner],
                                           C_AMBER, (255, 210, 100), C_WHITE])
                        self.particles.append(
                            Particle(random.randint(60, W - 60), H // 3, c))

                elif tag == 'error':
                    self.sub, self.sub_col = f'Error: {ev[1]}', C_RED

        except queue.Empty:
            pass

    # ── Positions ──────────────────────────────────────
    def _dxy(self, idx):
        row = idx // 5
        col = idx % 5
        return DICE_X0 + col * (DIE_SZ + DIE_GAP), \
               (DICE_Y0 if row == 0 else DICE_Y1)

    # ══════════════════════════════════════════════════════
    #  Draw
    # ══════════════════════════════════════════════════════
    def _draw_player(self, p, active, t):
        x  = 0 if p == 0 else W - PANEL_W
        bg = P_ON[p] if active else P_OFF[p]
        sc  = self.scores[p]
        col = P_COL[p]

        # Panel background
        pygame.draw.rect(self.screen, bg, (x, HDR_H, PANEL_W, H - HDR_H))

        # Inner colored stripe (on the edge nearest the center)
        edge_x = PANEL_W - 4 if p == 0 else W - PANEL_W
        stripe_col = col if active else C_BDIM
        pygame.draw.rect(self.screen, stripe_col, (edge_x, HDR_H, 4, H - HDR_H))

        # Player label
        px = x + PANEL_W // 2
        nc = col if active else C_BDIM
        lbl = self.fPN.render(f'PLAYER {p}', True, nc)
        self.screen.blit(lbl, lbl.get_rect(centerx=px, y=HDR_H + 16))

        # Score
        sc_col = C_INK if active else C_BDIM
        t_sc = self.fSC.render(str(sc), True, sc_col)
        self.screen.blit(t_sc, t_sc.get_rect(centerx=px, y=HDR_H + 34))

        # "to 100" label
        rem = self.fHT.render(f'{max(0, 100-sc)} to go', True, C_DIM if active else C_BDIM)
        self.screen.blit(rem, rem.get_rect(centerx=px, y=HDR_H + 98))

        # Progress bar
        bar_w = PANEL_W - 28
        bar_x = x + 14
        bar_y = HDR_H + 115
        pbar(self.screen, bar_x, bar_y, bar_w, 9, sc / 100.0, col,
             bg=C_BDR if active else C_BDIM)

        # Milestones (25, 50, 75)
        for m in (25, 50, 75):
            mx = bar_x + int(m / 100.0 * bar_w)
            col_tick = C_DIM if active else C_BDIM
            pygame.draw.line(self.screen, col_tick, (mx, bar_y - 2), (mx, bar_y + 11), 1)

        # YOUR TURN badge (pulsing)
        if active and self.state in ('waiting', 'rolling'):
            pulse = 0.75 + 0.25 * math.sin(t * 3.0)
            bw  = int(PANEL_W - 24)
            bh  = 26
            by  = HDR_H + 140
            bx  = x + 12
            alpha_bg = int(220 * pulse)
            badge_surf = pygame.Surface((bw, bh), pygame.SRCALPHA)
            r_col = (*col, alpha_bg)
            pygame.draw.rect(badge_surf, r_col, (0, 0, bw, bh), border_radius=7)
            self.screen.blit(badge_surf, (bx, by))
            arrow = '▶' if p == 0 else '◀'
            label = f'{arrow} YOUR TURN'
            bt = self.fBD.render(label, True, C_WHITE)
            self.screen.blit(bt, bt.get_rect(centerx=x + PANEL_W // 2, y=by + 7))

    def _draw_center(self, t):
        # Center panel background
        pygame.draw.rect(self.screen, C_CTR,
                         (CTR_X, HDR_H, CTR_W, H - HDR_H))

        # Subtle inner shadow top
        for i in range(5):
            a = 30 - i * 6
            if a > 0:
                s = pygame.Surface((CTR_W, 1), pygame.SRCALPHA)
                s.fill((0, 0, 0, a))
                self.screen.blit(s, (CTR_X, HDR_H + i))

        # "DICE" label
        dl = self.fHT.render('D  I  C  E', True, C_DIM)
        blit_c(self.screen, dl, CTR_X + CTR_W // 2, HDR_H + 16)

        # Separator line under dice label
        pygame.draw.line(self.screen, C_BDR,
                         (CTR_X + 20, HDR_H + 30), (CTR_X + CTR_W - 20, HDR_H + 30), 1)

        # Dice sprites
        for i, sp in enumerate(self.sprites):
            sp.update(self.clock.get_time())
            if sp.visible:
                x, y = self._dxy(i)
                sp.draw(self.screen, x, y)

        # Turn total / pig-out display
        ty = DICE_Y1 + DIE_SZ + 14
        if self.turn_rolls:
            if self.turn_pig:
                msg = self.fPR.render('PIG OUT!   Turn score: 1', True, C_RED)
                blit_c(self.screen, msg, CTR_X + CTR_W // 2, ty)
            else:
                total = self.turn_total
                rolls = '+'.join(str(r) for r in self.turn_rolls)
                label = f'{rolls} = {total}' if len(self.turn_rolls) > 1 else f'= {total}'
                msg   = self.fPR.render(f'Turn so far:  {label}', True, C_GREEN)
                blit_c(self.screen, msg, CTR_X + CTR_W // 2, ty)

        # Prompt (e.g. "Player 0 — how many dice?")
        if self.prompt:
            # Colour band behind prompt
            band_y = ty + 22
            band = pygame.Surface((CTR_W, 24), pygame.SRCALPHA)
            c = (*(self.prompt_col), 22)
            band.fill(c)
            self.screen.blit(band, (CTR_X, band_y))
            pt = self.fPR.render(self.prompt, True, self.prompt_col)
            blit_c(self.screen, pt, CTR_X + CTR_W // 2, band_y + 4)

        # Separator above tiles
        pygame.draw.line(self.screen, C_BDR,
                         (CTR_X + 12, TILE_Y0 - 12), (CTR_X + CTR_W - 12, TILE_Y0 - 12), 1)

    def _draw_tiles(self):
        en  = (self.state == 'waiting')
        col = P_COL[self.who]
        mpos = pygame.mouse.get_pos()
        for btn in self.tiles:
            r   = btn['rect']
            hov = r.collidepoint(mpos) and en
            if hov:
                bg  = col
                tc  = C_WHITE
                bdr = col
            elif en:
                bg  = C_WHITE
                tc  = C_INK
                bdr = C_BDR
            else:
                bg  = C_CTR
                tc  = C_BDIM
                bdr = C_BDR
            rrect(self.screen, bg, r, 9, bdr, 2)
            t = self.fTL.render(btn['label'], True, tc)
            self.screen.blit(t, t.get_rect(center=r.center))

    def _draw_header(self):
        # Cream header bar
        pygame.draw.rect(self.screen, C_BG, (0, 0, W, HDR_H))
        pygame.draw.line(self.screen, C_BDR, (0, HDR_H), (W, HDR_H), 1)

        t  = self.fT.render('The Game of Hog', True, C_INK)
        cs = self.fHT.render('CS7', True, C_DIM)
        self.screen.blit(t, (20, 14))
        self.screen.blit(cs, (22 + t.get_width() + 8, 18))

        # Restart button
        r   = self._rst['rect']
        mpos = pygame.mouse.get_pos()
        hov = r.collidepoint(mpos)
        rrect(self.screen, C_TERRA if hov else C_WHITE,
              r, 8, C_TERRA, 2)
        tc  = C_WHITE if hov else C_TERRA
        bt  = self.fBD.render(self._rst['label'], True, tc)
        self.screen.blit(bt, bt.get_rect(center=r.center))

    def _draw_win_overlay(self, dt):
        if self.winner is None:
            return
        # Fade in
        self.win_alpha = min(self.win_alpha + dt * 0.18, 210)
        overlay = pygame.Surface((W, H), pygame.SRCALPHA)
        bg_col  = (*P_ON[self.winner], int(self.win_alpha))
        overlay.fill(bg_col)
        self.screen.blit(overlay, (0, 0))

        t0 = self.fWN.render(f'Player {self.winner} wins!', True, P_DARK[self.winner])
        blit_c(self.screen, t0, W // 2, H // 2 - 42)

        s0, s1 = self.scores
        t1 = self.fPR.render(f'Final score  —  Player 0: {s0}    Player 1: {s1}',
                             True, C_INK2)
        blit_c(self.screen, t1, W // 2, H // 2 + 4)

        t2 = self.fST.render('Press  r  or click New Game to play again', True, C_DIM)
        blit_c(self.screen, t2, W // 2, H // 2 + 36)

    def _draw_flash(self, dt):
        if self.flash_t <= 0:
            return
        frac = min(1.0, self.flash_t / 350.0)
        alpha = int(90 * frac)
        fs = pygame.Surface((W, H), pygame.SRCALPHA)
        fs.fill((*self.flash_col, alpha))
        self.screen.blit(fs, (0, 0))
        self.flash_t = max(0.0, self.flash_t - dt)

    def draw(self, dt):
        t = time.monotonic()
        self.screen.fill(C_BG)

        self._draw_player(0, self.who == 0, t)
        self._draw_player(1, self.who == 1, t)
        self._draw_center(t)
        self._draw_tiles()
        self._draw_header()

        # Particles (on top of everything except overlays)
        for p in self.particles:
            p.update(); p.draw(self.screen)
        self.particles = [p for p in self.particles if p.alive]

        self._draw_flash(dt)

        if self.state == 'gameover':
            self._draw_win_overlay(dt)

        # Bottom hint
        h = self.fHT.render('keys:  0-9 pick count  ·  ` for 10  ·  r restart',
                             True, C_DIM)
        blit_c(self.screen, h, W // 2, H - 16)

        pygame.display.flip()

    # ── Input ──────────────────────────────────────────
    def _pick(self, n):
        if self.state == 'waiting':
            self.roll_q.put(n)

    def _restart(self):
        if self._gt and self._gt.is_alive():
            self._kill.set()
            self.roll_q.put(-1)
        else:
            self._new_game()

    def _handle(self, ev):
        if ev.type == pygame.QUIT:
            return False
        if ev.type == pygame.KEYDOWN:
            k = ev.key
            if k == pygame.K_r:
                self._restart()
            elif self.state == 'waiting':
                if pygame.K_0 <= k <= pygame.K_9:
                    self._pick(k - pygame.K_0)
                elif k == pygame.K_BACKQUOTE:
                    self._pick(10)
        if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
            if self._rst['rect'].collidepoint(ev.pos):
                self._restart()
            elif self.state == 'waiting':
                for btn in self.tiles:
                    if btn['rect'].collidepoint(ev.pos):
                        btn['cb']()
        return True

    # ── Main loop ──────────────────────────────────────
    def run(self):
        while True:
            dt = self.clock.tick(FPS)
            for ev in pygame.event.get():
                if not self._handle(ev):
                    self._kill.set()
                    pygame.quit()
                    return
            self._drain()
            self.draw(dt)


# ═══════════════════════════════════════════════════════
#  Entry
# ═══════════════════════════════════════════════════════
def run_GUI(computer=False):
    HogGame(computer=computer).run()


@main
def run(*args):
    parser = argparse.ArgumentParser(description='Hog GUI — CS7')
    parser.add_argument('-f', '--final', action='store_true',
                        help='Play against your final_strategy.')
    parser.add_argument('-d', '--delay', type=int, default=2,
                        help='Computer thinking delay (seconds).')
    args = parser.parse_args()
    global DELAY
    DELAY = args.delay * 1000
    run_GUI(computer=args.final)