graphwar-arena / graphwar /render.py
Foxgrad's picture
Add game engine and bots
3304d77 verified
Raw History Blame Contribute Delete
6.55 kB
"""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))