"""Ink painting style rendering primitives for Chinese aesthetic.""" import random import math import pygame from card_game.config import ( WINDOW_WIDTH, WINDOW_HEIGHT, INK_BLACK, PAPER_WHITE, ZHU_HONG, SONGHUA_GREEN, TENG_HUANG, GOLD, BG_COLOR, INK_WASH_1, INK_WASH_2, INK_WASH_3, INK_WASH_4, INK_WASH_5, ) # Cached surfaces _paper_texture = None _mountain_layers = None def init_cache(): """Pre-generate cached surfaces. Call once after pygame.init().""" global _paper_texture, _mountain_layers _paper_texture = _generate_paper_texture(WINDOW_WIDTH, WINDOW_HEIGHT) _mountain_layers = _generate_mountain_layers(WINDOW_WIDTH, WINDOW_HEIGHT) def get_paper_texture(): return _paper_texture def get_mountain_layers(): return _mountain_layers def _generate_paper_texture(w, h): """Generate a rice paper (宣纸) texture surface.""" surf = pygame.Surface((w, h)) surf.fill(BG_COLOR) # Add subtle noise rng = random.Random(42) for y in range(0, h, 2): for x in range(0, w, 2): noise = rng.gauss(0, 6) r = min(255, max(0, int(BG_COLOR[0] + noise))) g = min(255, max(0, int(BG_COLOR[1] + noise))) b = min(255, max(0, int(BG_COLOR[2] + noise))) surf.set_at((x, y), (r, g, b)) if x + 1 < w: surf.set_at((x + 1, y), (r, g, b)) if y + 1 < h: surf.set_at((x, y + 1), (r, g, b)) if x + 1 < w and y + 1 < h: surf.set_at((x + 1, y + 1), (r, g, b)) # Add fiber lines for _ in range(40): x1 = rng.randint(0, w) y1 = rng.randint(0, h) length = rng.randint(30, 150) angle = rng.uniform(0, math.pi) color = (rng.randint(200, 220), rng.randint(185, 205), rng.randint(160, 180)) points = [] for i in range(10): t = i / 9 px = int(x1 + length * t * math.cos(angle) + rng.gauss(0, 2)) py = int(y1 + length * t * math.sin(angle) + rng.gauss(0, 2)) points.append((px, py)) if len(points) >= 2: pygame.draw.lines(surf, color, False, points, 1) return surf def _generate_mountain_layers(w, h): """Generate 3 layers of misty mountain silhouettes.""" rng = random.Random(123) layers = [] layer_colors = [ (INK_WASH_1[0], INK_WASH_1[1], INK_WASH_1[2], 60), (INK_WASH_2[0], INK_WASH_2[1], INK_WASH_2[2], 45), (INK_WASH_3[0], INK_WASH_3[1], INK_WASH_3[2], 30), ] base_heights = [h * 0.55, h * 0.62, h * 0.70] for i, (color, base_y) in enumerate(zip(layer_colors, base_heights)): surf = pygame.Surface((w, h), pygame.SRCALPHA) points = [(0, h)] x = 0 while x <= w: freq1 = rng.uniform(0.003, 0.008) freq2 = rng.uniform(0.01, 0.02) amp1 = rng.uniform(30, 60) amp2 = rng.uniform(10, 25) y = base_y + amp1 * math.sin(x * freq1 + i) + amp2 * math.sin(x * freq2 + i * 2) points.append((x, int(y))) x += rng.randint(8, 20) points.append((w, h)) if len(points) >= 3: pygame.draw.polygon(surf, color, points) layers.append(surf) return layers def blit_paper_background(surface): """Blit the cached paper texture onto the surface.""" if _paper_texture: surface.blit(_paper_texture, (0, 0)) else: surface.fill(BG_COLOR) def blit_mountains(surface): """Blit mountain layers onto the surface.""" if _mountain_layers: for layer in _mountain_layers: surface.blit(layer, (0, 0)) def draw_ink_rect(surface, rect, color, alpha=255, border_radius=0): """Draw a rectangle with slightly wobbly brush-stroke edges.""" x, y, w, h = rect if isinstance(rect, (list, tuple)) else (rect.x, rect.y, rect.w, rect.h) s = pygame.Surface((w, h), pygame.SRCALPHA) base_color = (*color[:3], alpha) # Draw base rect with slight gradient for col in range(w): gradient = 1.0 - 0.1 * (col / max(w, 1)) r = min(255, max(0, int(color[0] * gradient))) g = min(255, max(0, int(color[1] * gradient))) b = min(255, max(0, int(color[2] * gradient))) pygame.draw.line(s, (r, g, b, alpha), (col, 0), (col, h - 1)) # Feather edges rng = random.Random(x * 1000 + y) for edge_x in range(min(3, w)): for edge_y in range(h): if rng.random() < 0.3: s.set_at((edge_x, edge_y), (0, 0, 0, 0)) if rng.random() < 0.3: s.set_at((w - 1 - edge_x, edge_y), (0, 0, 0, 0)) for edge_y in range(min(3, h)): for edge_x in range(w): if rng.random() < 0.3: s.set_at((edge_x, edge_y), (0, 0, 0, 0)) if rng.random() < 0.3: s.set_at((edge_x, h - 1 - edge_y), (0, 0, 0, 0)) surface.blit(s, (x, y)) def draw_ink_circle(surface, center, radius, color, alpha=200): """Draw a circle with irregular ink-wash edges.""" cx, cy = center size = radius * 2 + 4 s = pygame.Surface((size, size), pygame.SRCALPHA) scx, scy = size // 2, size // 2 rng = random.Random(cx * 100 + cy) n = max(12, radius) for i in range(n): angle = 2 * math.pi * i / n r = radius + rng.gauss(0, max(1, radius * 0.08)) px = int(scx + r * math.cos(angle)) py = int(scy + r * math.sin(angle)) pygame.draw.circle(s, (*color[:3], alpha), (px, py), max(2, int(radius * 0.4))) # Fill center pygame.draw.circle(s, (*color[:3], alpha), (scx, scy), max(1, radius - 2)) surface.blit(s, (cx - size // 2, cy - size // 2)) def draw_brush_stroke(surface, start, end, width, color, alpha=180): """Draw a thick-to-thin brush stroke line.""" x1, y1 = start x2, y2 = end dx, dy = x2 - x1, y2 - y1 length = math.sqrt(dx * dx + dy * dy) if length < 1: return steps = max(int(length / 2), 4) rng = random.Random(int(x1 * 100 + y1)) # Perpendicular direction for jitter nx, ny = -dy / length, dx / length for i in range(steps): t = i / (steps - 1) # Width tapers: starts thin, peaks middle, ends thin w = width * (1 - abs(2 * t - 1) * 0.6) * (0.8 + 0.2 * rng.random()) px = x1 + dx * t + nx * rng.gauss(0, 1.5) py = y1 + dy * t + ny * rng.gauss(0, 1.5) a = max(50, int(alpha * (0.7 + 0.3 * rng.random()))) circle_s = pygame.Surface((int(w * 2 + 4), int(w * 2 + 4)), pygame.SRCALPHA) pygame.draw.circle(circle_s, (*color[:3], a), (int(w + 2), int(w + 2)), max(1, int(w))) surface.blit(circle_s, (int(px - w - 2), int(py - w - 2))) def draw_seal_stamp(surface, rect, text, font, color=None): """Draw a traditional Chinese seal (印章): red square with white text.""" if color is None: color = ZHU_HONG x, y, w, h = rect if isinstance(rect, (list, tuple)) else (rect.x, rect.y, rect.w, rect.h) s = pygame.Surface((w, h), pygame.SRCALPHA) # Red background with slight irregularity rng = random.Random(x * 100 + y + w) pygame.draw.rect(s, color, (0, 0, w, h)) # Feather edges slightly for edge in range(2): for i in range(w): if rng.random() < 0.25: s.set_at((i, edge), (0, 0, 0, 0)) s.set_at((i, h - 1 - edge), (0, 0, 0, 0)) for i in range(h): if rng.random() < 0.25: s.set_at((edge, i), (0, 0, 0, 0)) s.set_at((w - 1 - edge, i), (0, 0, 0, 0)) # White border pygame.draw.rect(s, (255, 255, 255), (0, 0, w, h), 2) # Text in white text_surf = font.render(text, True, (255, 255, 255)) tx = (w - text_surf.get_width()) // 2 ty = (h - text_surf.get_height()) // 2 s.blit(text_surf, (tx, ty)) surface.blit(s, (x, y)) def draw_scroll(surface, rect, scroll_color=None): """Draw a scroll/卷轴 shape with wooden rollers.""" if scroll_color is None: scroll_color = (210, 195, 170) x, y, w, h = rect if isinstance(rect, (list, tuple)) else (rect.x, rect.y, rect.w, rect.h) roller_h = 6 roller_color = (140, 90, 50) # Top roller pygame.draw.rect(surface, roller_color, (x - 4, y, w + 8, roller_h), border_radius=3) pygame.draw.rect(surface, (100, 65, 35), (x - 4, y, w + 8, roller_h), 1, border_radius=3) # Paper body body_rect = pygame.Rect(x, y + roller_h, w, h - roller_h * 2) draw_ink_rect(surface, body_rect, scroll_color, alpha=240) # Bottom roller by = y + h - roller_h pygame.draw.rect(surface, roller_color, (x - 4, by, w + 8, roller_h), border_radius=3) pygame.draw.rect(surface, (100, 65, 35), (x - 4, by, w + 8, roller_h), 1, border_radius=3) def draw_ink_text(surface, text, pos, font, color, shadow=True): """Render text with a subtle ink bleed shadow.""" if shadow: shadow_color = (max(0, color[0] - 40), max(0, color[1] - 40), max(0, color[2] - 40)) shadow_surf = font.render(text, True, shadow_color) surface.blit(shadow_surf, (pos[0] + 1, pos[1] + 1)) text_surf = font.render(text, True, color) surface.blit(text_surf, pos) return text_surf def draw_cloud_pattern(surface, rect): """Draw traditional Chinese cloud motifs (祥云) as decoration.""" x, y, w, h = rect if isinstance(rect, (list, tuple)) else (rect.x, rect.y, rect.w, rect.h) color = (*INK_WASH_2[:3], 80) s = pygame.Surface((w, h), pygame.SRCALPHA) rng = random.Random(x * 7 + y) n_clouds = max(1, w // 80) for i in range(n_clouds): cx = rng.randint(10, w - 10) cy = rng.randint(5, h - 5) # Simple cloud: overlapping arcs for j in range(3): r = rng.randint(6, 12) ox = j * 8 - 8 oy = rng.randint(-3, 3) pygame.draw.circle(s, color, (cx + ox, cy + oy), r) surface.blit(s, (x, y)) def draw_zone_bg(surface, rect, base_color, accent_color=None): """Draw a battlefield zone background with ink wash effect.""" x, y, w, h = rect if isinstance(rect, (list, tuple)) else (rect.x, rect.y, rect.w, rect.h) s = pygame.Surface((w, h), pygame.SRCALPHA) # Base fill s.fill((*base_color[:3], 120)) # Subtle horizontal brush strokes rng = random.Random(y) for _ in range(5): by = rng.randint(0, h) bx = rng.randint(0, w // 4) bw = rng.randint(w // 3, w) bh = rng.randint(2, 6) bc = (*INK_WASH_2[:3], rng.randint(15, 35)) pygame.draw.rect(s, bc, (bx, by, bw, bh)) surface.blit(s, (x, y)) # Border line if accent_color: pygame.draw.line(surface, accent_color, (x, y + h - 1), (x + w, y + h - 1), 1) def draw_ink_hp_bar(surface, x, y, w, h, ratio, bg_color=None): """Draw an HP bar with ink brush style.""" if bg_color is None: bg_color = INK_WASH_4 pygame.draw.rect(surface, bg_color, (x, y, w, h)) if ratio > 0: bar_w = max(1, int(w * ratio)) if ratio > 0.5: bar_color = SONGHUA_GREEN elif ratio > 0.25: bar_color = TENG_HUANG else: bar_color = ZHU_HONG # Brush-stroke bar rng = random.Random(x * 100 + y) for px in range(bar_w): thickness = h - rng.randint(0, 1) pygame.draw.line(surface, bar_color, (x + px, y + (h - thickness) // 2), (x + px, y + (h + thickness) // 2))