cricket-commons / render.py
lerp666's picture
Upload render.py with huggingface_hub
25d2d3e verified
Raw
History Blame Contribute Delete
6.01 kB
"""参数化 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:
"""返回完整 <svg> 字符串."""
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'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" '
f'viewBox="0 0 {width} {height}">'
)
# 地面
parts.append(f'<ellipse cx="{cx}" cy="{cy + body_ry + 14}" rx="{belly * 1.6}" ry="10" fill="#00000014"/>')
# 后腿(蛐蛐标志性大跳腿)
leg = belly * 1.25
parts.append(
f'<path d="M {cx + belly * 0.5} {cy} q {leg * 0.55} {-leg * 0.7} {leg * 0.32} {leg * 0.05} '
f'l {-leg * 0.12} {leg * 0.62}" stroke="{body_dark}" stroke-width="{5 * scale}" fill="none" stroke-linecap="round"/>'
)
# 身体
parts.append(f'<ellipse cx="{cx}" cy="{cy}" rx="{belly}" ry="{body_ry}" fill="{body_fill}"/>')
# 翅膀层数 = stage+1(蜕皮可见)
for i in range(stage + 1):
wy = cy - body_ry * 0.55 - i * 4 * scale
parts.append(
f'<ellipse cx="{cx + 6}" cy="{wy}" rx="{belly * 0.85}" ry="{body_ry * 0.45}" '
f'fill="{body_dark}" opacity="{0.55 - i * 0.12}"/>'
)
# 智力花纹
for i in range(stripes):
sx = cx - belly * 0.4 + i * (belly * 0.8 / max(stripes - 1, 1))
parts.append(
f'<line x1="{sx}" y1="{cy - body_ry * 0.25}" x2="{sx}" y2="{cy + body_ry * 0.35}" '
f'stroke="{body_dark}" stroke-width="{2 * scale}" opacity="0.6"/>'
)
# 头
parts.append(f'<circle cx="{head_cx}" cy="{head_cy}" r="{head_r}" fill="{body_fill}"/>')
# 触须(勇→张角)
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'<path d="M {head_cx} {head_cy - head_r * 0.7} Q {mx} {my} {ex} {ey}" '
f'stroke="{body_dark}" stroke-width="{2.2 * scale}" fill="none" stroke-linecap="round"/>'
)
# 眼睛 / 病眼
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'<g stroke="#3a3a3a" stroke-width="2.4" stroke-linecap="round">'
f'<line x1="{eye_x - s}" y1="{eye_y - s}" x2="{eye_x + s}" y2="{eye_y + s}"/>'
f'<line x1="{eye_x - s}" y1="{eye_y + s}" x2="{eye_x + s}" y2="{eye_y - s}"/></g>'
)
# 病气泡
parts.append(f'<circle cx="{head_cx - head_r * 1.4}" cy="{head_cy - head_r}" r="5" fill="#9ab87a" opacity="0.8"/>')
else:
parts.append(f'<circle cx="{eye_x}" cy="{eye_y}" r="{eye_r}" fill="#241a12"/>')
parts.append(f'<circle cx="{eye_x + eye_r * 0.3}" cy="{eye_y - eye_r * 0.3}" r="{eye_r * 0.3}" fill="#fff"/>')
# 怨气眉
parts.append(
f'<line x1="{eye_x - eye_r * 1.4}" y1="{eye_y - eye_r * 1.6 + brow_drop}" '
f'x2="{eye_x + eye_r * 1.2}" y2="{eye_y - eye_r * 1.6 - brow_drop}" '
f'stroke="#241a12" stroke-width="2.6" stroke-linecap="round"/>'
)
# 嘴(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'<path d="M {head_cx - 7} {head_cy + head_r * 0.45} q 7 {smile} 14 0" '
f'stroke="#241a12" stroke-width="2.2" fill="none" stroke-linecap="round"/>'
)
parts.append("</svg>")
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,
}