Spaces:
Sleeping
Sleeping
| """参数化 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, | |
| } | |