"""Dessin du plateau avec pygame (vue de dessus, ou inclinée quand un tir part en 3D).""" from __future__ import annotations import math import numpy as np import pygame from . import config as C PAPER = (244, 246, 241) GRID_MINOR = (226, 232, 233) GRID_MAJOR = (196, 208, 214) AXIS = (128, 140, 150) INK = (34, 38, 50) INK_SIDE = (70, 76, 92) DEAD = (150, 150, 150) SHADOW = (175, 180, 188) MAX_TILT = math.radians(52) def font(size, bold=False): return pygame.font.SysFont("cambriamath,cambria,segoeuisymbol,dejavusans,arial", size, bold=bold) class Camera: def __init__(self, rect: pygame.Rect): self.rect = pygame.Rect(rect) self.s = min(self.rect.w / (2 * C.BOARD_W), self.rect.h / (2 * C.BOARD_H)) self.tilt = 0.0 # 0 = vue de dessus, 1 = inclinée au maximum def p(self, x, y, z=0.0): th = self.tilt * MAX_TILT cx, cy = self.rect.centerx, self.rect.centery + self.tilt * 0.12 * self.rect.h return (cx + x * self.s, cy - (y * math.cos(th) + z * math.sin(th)) * self.s) def pts(self, xs, ys, zs): th = self.tilt * MAX_TILT cx, cy = self.rect.centerx, self.rect.centery + self.tilt * 0.12 * self.rect.h X = cx + xs * self.s Y = cy - (ys * math.cos(th) + zs * math.sin(th)) * self.s return np.column_stack([X, Y]) def _cyl(surf, cam, x, y, r, h, top, side, off=(0, 0)): """Un cylindre vu en biais : flanc puis dessus (off = décalage du calque).""" th = cam.tilt * MAX_TILT rw, rh = r * cam.s, max(1.0, r * cam.s * math.cos(th)) bx, by = cam.p(x, y, 0) tx, ty = cam.p(x, y, h) bx, by, tx, ty = bx - off[0], by - off[1], tx - off[0], ty - off[1] pygame.draw.polygon(surf, side, [(bx - rw, by), (bx + rw, by), (tx + rw, ty), (tx - rw, ty)]) pygame.draw.ellipse(surf, side, (bx - rw, by - rh, 2 * rw, 2 * rh)) pygame.draw.ellipse(surf, top, (tx - rw, ty - rh, 2 * rw, 2 * rh)) def _obstacle(surf, cam, ob, craters): """Dessine un obstacle avec ses cratères (chaque trou ne creuse que son propre obstacle).""" x, y, r, h = ob tilted = cam.tilt > 0.01 th = cam.tilt * MAX_TILT rw = r * cam.s rh = max(1.0, rw * math.cos(th)) bx, by = cam.p(x, y, 0) ty = cam.p(x, y, h)[1] if tilted else by box = pygame.Rect(int(bx - rw) - 2, int(ty - rh) - 2, int(2 * rw) + 5, int(by - ty + 2 * rh) + 5) spr = pygame.Surface(box.size, pygame.SRCALPHA) off = box.topleft if tilted: _cyl(spr, cam, x, y, r, h, INK, INK_SIDE, off) else: c = cam.p(x, y) pygame.draw.circle(spr, INK, (c[0] - off[0], c[1] - off[1]), rw) clear = (0, 0, 0, 0) for cx, cy, cr in craters: if math.hypot(cx - x, cy - y) > r + cr: continue if tilted: _cyl(spr, cam, cx, cy, cr, h + 0.05, clear, clear, off) else: c = cam.p(cx, cy) pygame.draw.circle(spr, clear, (c[0] - off[0], c[1] - off[1]), cr * cam.s) surf.blit(spr, off) def draw_board(surf, cam, world, colors=((60, 90, 200), (200, 70, 60)), highlight=None): """Papier millimétré, obstacles, cratères, soldats.""" r = cam.rect pygame.draw.rect(surf, PAPER, r.inflate(4, 4)) # quadrillage for x in range(-25, 26): col = AXIS if x == 0 else GRID_MAJOR if x % 5 == 0 else GRID_MINOR pygame.draw.line(surf, col, cam.p(x, -C.BOARD_H), cam.p(x, C.BOARD_H), 2 if x == 0 else 1) for y in range(-15, 16): col = AXIS if y == 0 else GRID_MAJOR if y % 5 == 0 else GRID_MINOR pygame.draw.line(surf, col, cam.p(-C.BOARD_W, y), cam.p(C.BOARD_W, y), 2 if y == 0 else 1) corners = [cam.p(-25, -15), cam.p(25, -15), cam.p(25, 15), cam.p(-25, 15)] pygame.draw.polygon(surf, AXIS, corners, 2) # obstacles (on ne dessine pas hors du plateau en vue de dessus) clip = surf.get_clip() if cam.tilt <= 0.01: surf.set_clip(r) order = np.argsort(-world.obstacles[:, 1]) if cam.tilt > 0.01 else range(len(world.obstacles)) for i in order: _obstacle(surf, cam, world.obstacles[i], world.craters) surf.set_clip(clip) # soldats soldiers = sorted(enumerate(world.soldiers), key=lambda t: -t[1].y) for i, s in soldiers: col = colors[s.team] if s.alive else DEAD rad = C.SOLDIER_R * cam.s if cam.tilt > 0.01: b, t = cam.p(s.x, s.y, 0), cam.p(s.x, s.y, C.SOLDIER_H) pygame.draw.line(surf, col, b, t, max(3, int(rad * 0.8))) pygame.draw.circle(surf, col, t, rad * 0.7) pygame.draw.circle(surf, INK, t, rad * 0.7, 1) else: pygame.draw.circle(surf, col, cam.p(s.x, s.y), rad) pygame.draw.circle(surf, INK, cam.p(s.x, s.y), rad, 2 if i == highlight else 1) if not s.alive: c = cam.p(s.x, s.y, C.SOLDIER_H if cam.tilt > 0.01 else 0) d = rad * 0.7 pygame.draw.line(surf, INK, (c[0] - d, c[1] - d), (c[0] + d, c[1] + d), 2) pygame.draw.line(surf, INK, (c[0] - d, c[1] + d), (c[0] + d, c[1] - d), 2) if i == highlight and s.alive: pygame.draw.circle(surf, INK, cam.p(s.x, s.y), rad + 5, 1) def draw_shot(surf, cam, res, color, frac=1.0, width=3, faded=False): n = len(res.xs) if n < 2: return None k = max(2, int(n * frac)) step = max(1, k // 900) xs, ys, zs = res.xs[:k:step], res.ys[:k:step], res.zs[:k:step] xs, ys, zs = np.append(xs, res.xs[k - 1]), np.append(ys, res.ys[k - 1]), np.append(zs, res.zs[k - 1]) col = tuple(int(c + (255 - c) * 0.6) for c in color) if faded else color if cam.tilt > 0.01 and (zs > C.SHOT_Z + 0.05).any(): # ombre au sol sh = cam.pts(xs, ys, np.zeros_like(zs)) pygame.draw.lines(surf, SHADOW, False, sh.tolist(), 1) pts = cam.pts(xs, ys, zs if cam.tilt > 0.01 else np.zeros_like(zs)) pygame.draw.lines(surf, col, False, pts.tolist(), width) head = pts[-1] if not faded: pygame.draw.circle(surf, col, head, width + 3) pygame.draw.circle(surf, INK, head, width + 3, 1) return head def draw_boom(surf, center, t, color): """Petite explosion qui grossit puis s'efface (t de 0 à 1).""" r = 6 + 34 * t a = int(255 * (1 - t)) s = pygame.Surface((int(2 * r + 4), int(2 * r + 4)), pygame.SRCALPHA) pygame.draw.circle(s, (*color, a), (r + 2, r + 2), r, 4) pygame.draw.circle(s, (*INK, a // 2), (r + 2, r + 2), r * 0.55, 2) surf.blit(s, (center[0] - r - 2, center[1] - r - 2))