游戏可以运行
This commit is contained in:
340
card_game/ink_style.py
Normal file
340
card_game/ink_style.py
Normal file
@@ -0,0 +1,340 @@
|
||||
"""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))
|
||||
Reference in New Issue
Block a user