"""参数化 SVG 渲染: traits + age + stage + sick + mood → 蛐蛐形象. 设计原则: 零依赖、零成本、演化连续可见。 映射(可随时调,但保持单调直觉): 勇 brave → 触须张角/前肢姿态(越勇越张扬) 萌 cute → 眼睛大小/身体圆润度 怨 grudge → 体色向紫黑偏移/眉角下压 智 wit → 额头花纹复杂度(条纹数) 馋 glutton → 肚子大小 stage → 整体尺寸 + 翅膀层数(蜕皮=大变样) sick → 整体降饱和 + 绿色病气 + 眼睛变 X """ from __future__ import annotations from traits import TRAIT_KEYS, molt_stage def _lerp(a: float, b: float, t: float) -> float: return a + (b - a) * t def _color(traits: dict, sick: bool) -> tuple[str, str]: """主体色: 健康=暖棕→怨气高偏紫黑; 生病=灰绿.""" g = traits.get("grudge", 0.5) if sick: return "#7a8a6a", "#5a6a4a" r = int(_lerp(150, 90, g)) gr = int(_lerp(95, 60, g)) b = int(_lerp(60, 110, g)) return f"rgb({r},{gr},{b})", f"rgb({max(r-40,0)},{max(gr-30,0)},{max(b-20,0)})" def render_svg(traits: dict, feed_count: int = 0, sick: bool = False, mood: str = "calm", width: int = 360, height: int = 280) -> str: """返回完整 字符串.""" t = {k: float(traits.get(k, 0.5)) for k in TRAIT_KEYS} stage = molt_stage(feed_count) scale = _lerp(0.7, 1.15, min(stage, 3) / 3) body_fill, body_dark = _color(t, sick) cx, cy = width / 2, height / 2 + 20 belly = _lerp(34, 52, t["glutton"]) * scale # 馋→肚子 body_ry = _lerp(belly * 0.78, belly * 0.95, t["cute"]) # 萌→圆润 eye_r = _lerp(4.5, 9.5, t["cute"]) * scale # 萌→大眼 ant_spread = _lerp(8, 42, t["brave"]) # 勇→触须张角(度) ant_len = _lerp(55, 85, t["brave"]) * scale stripes = 1 + int(round(t["wit"] * 4)) # 智→花纹条数 brow_drop = _lerp(-3, 5, t["grudge"]) # 怨→眉角下压 head_cx = cx - belly * 0.95 head_cy = cy - body_ry * 0.45 head_r = _lerp(16, 22, t["cute"]) * scale parts: list[str] = [] parts.append( f'' ) # 地面 parts.append(f'') # 后腿(蛐蛐标志性大跳腿) leg = belly * 1.25 parts.append( f'' ) # 身体 parts.append(f'') # 翅膀层数 = stage+1(蜕皮可见) for i in range(stage + 1): wy = cy - body_ry * 0.55 - i * 4 * scale parts.append( f'' ) # 智力花纹 for i in range(stripes): sx = cx - belly * 0.4 + i * (belly * 0.8 / max(stripes - 1, 1)) parts.append( f'' ) # 头 parts.append(f'') # 触须(勇→张角) for sign in (-1, 1): ang = (-90 + sign * ant_spread) * 3.14159 / 180 ex = head_cx + ant_len * 1.0 * __import__("math").cos(ang) * (0.6 if sign < 0 else 0.8) ey = head_cy + ant_len * __import__("math").sin(ang) mx = head_cx + sign * 10 my = head_cy - ant_len * 0.6 parts.append( f'' ) # 眼睛 / 病眼 eye_x, eye_y = head_cx - head_r * 0.25, head_cy - head_r * 0.15 if sick: s = eye_r * 0.8 parts.append( f'' f'' f'' ) # 病气泡 parts.append(f'') else: parts.append(f'') parts.append(f'') # 怨气眉 parts.append( f'' ) # 嘴(mood 简单弯曲) smile = {"happy": 6, "excited": 7, "loved": 6, "content": 4, "calm": 1, "sad": -5, "angry": -6, "hurt": -6, "disgusted": -4}.get(mood, 1) parts.append( f'' ) parts.append("") return "".join(parts) def svg_params(traits: dict, feed_count: int, sick: bool, mood: str) -> dict: """快照里存的渲染参数(回放史料).""" return { "traits": {k: round(float(traits.get(k, 0.5)), 4) for k in TRAIT_KEYS}, "stage": molt_stage(feed_count), "sick": bool(sick), "mood": mood, "feed_count": feed_count, }