codepet 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/codepet.js +16 -3
- package/package.json +9 -2
- package/scripts/animate.py +0 -207
- package/scripts/crop_all.py +0 -46
- package/scripts/crop_expressions.py +0 -73
- package/scripts/gen_expressions.py +0 -95
- package/scripts/gen_variants.py +0 -107
- package/scripts/render_sprite.py +0 -558
package/bin/codepet.js
CHANGED
|
@@ -13,6 +13,18 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const { execSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
// 跨平台 Python 命令检测
|
|
18
|
+
function getPython() {
|
|
19
|
+
for (const cmd of ['python3', 'python']) {
|
|
20
|
+
try {
|
|
21
|
+
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
22
|
+
return cmd;
|
|
23
|
+
} catch {}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const PYTHON = getPython();
|
|
16
28
|
const core = require('../core');
|
|
17
29
|
|
|
18
30
|
const SPRITES_DIR = path.join(__dirname, '..', 'sprites');
|
|
@@ -181,12 +193,13 @@ function renderSprite(character, variant) {
|
|
|
181
193
|
const renderImgScript = path.join(__dirname, '..', 'scripts', 'render_to_image.py');
|
|
182
194
|
if (fs.existsSync(renderImgScript)) {
|
|
183
195
|
try {
|
|
184
|
-
const imgPath = execSync(
|
|
196
|
+
const imgPath = execSync(`${PYTHON} "${renderImgScript}" "${character}" "${variant}"`, { encoding: 'utf-8' }).trim();
|
|
185
197
|
console.log(`[图片:${imgPath}]`);
|
|
186
198
|
} catch (e) {}
|
|
187
199
|
}
|
|
188
200
|
} else {
|
|
189
201
|
// 终端模式:彩色像素画
|
|
202
|
+
if (!PYTHON) return; // Python 未安装,静默跳过
|
|
190
203
|
const renderScript = path.join(__dirname, '..', 'scripts', 'img2terminal.py');
|
|
191
204
|
let spritePath = path.join(SPRITES_DIR, character, `${variant}.png`);
|
|
192
205
|
if (!fs.existsSync(spritePath)) {
|
|
@@ -194,7 +207,7 @@ function renderSprite(character, variant) {
|
|
|
194
207
|
}
|
|
195
208
|
if (fs.existsSync(spritePath) && fs.existsSync(renderScript)) {
|
|
196
209
|
try {
|
|
197
|
-
const output = execSync(
|
|
210
|
+
const output = execSync(`${PYTHON} "${renderScript}" "${spritePath}" ${RENDER_WIDTH}`, { encoding: 'utf-8' });
|
|
198
211
|
console.log(output);
|
|
199
212
|
} catch (e) {}
|
|
200
213
|
}
|
|
@@ -336,7 +349,7 @@ function doCard() {
|
|
|
336
349
|
const cardScript = path.join(__dirname, '..', 'scripts', 'pet_card.py');
|
|
337
350
|
if (fs.existsSync(cardScript)) {
|
|
338
351
|
try {
|
|
339
|
-
execSync(
|
|
352
|
+
execSync(`${PYTHON} "${cardScript}"`, { encoding: 'utf-8', stdio: 'inherit' });
|
|
340
353
|
// 确保弹出 Preview
|
|
341
354
|
const cardPath = path.join(os.homedir(), '.codepet', 'card.png');
|
|
342
355
|
execSync(`open "${cardPath}"`, { stdio: 'ignore' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codepet",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "在编程软件里养电子宠物 — 支持 Claude Code / Codex / Cursor / VS Code 等 10+ 平台",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,14 @@
|
|
|
33
33
|
"bin/",
|
|
34
34
|
"core/",
|
|
35
35
|
"scripts/*.js",
|
|
36
|
-
"scripts
|
|
36
|
+
"scripts/img2terminal.py",
|
|
37
|
+
"scripts/img2ascii.py",
|
|
38
|
+
"scripts/img2emoji.py",
|
|
39
|
+
"scripts/polaroid.py",
|
|
40
|
+
"scripts/pet_card.py",
|
|
41
|
+
"scripts/render_to_image.py",
|
|
42
|
+
"scripts/show_all.py",
|
|
43
|
+
"scripts/show_all_expressions.py",
|
|
37
44
|
"scripts/*.sh",
|
|
38
45
|
"sprites/*.png",
|
|
39
46
|
"sprites/*/",
|
package/scripts/animate.py
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""CodePet 终端动画 — 眨眼、动嘴、摆手"""
|
|
3
|
-
|
|
4
|
-
import os, sys, time, copy
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
7
|
-
sys.path.insert(0, SCRIPT_DIR)
|
|
8
|
-
|
|
9
|
-
from img2terminal import render_image
|
|
10
|
-
from PIL import Image, ImageDraw
|
|
11
|
-
|
|
12
|
-
SPRITE_DIR = os.path.join(SCRIPT_DIR, "..", "sprites")
|
|
13
|
-
RESET = "\033[0m"
|
|
14
|
-
BOLD = "\033[1m"
|
|
15
|
-
HIDE_CURSOR = "\033[?25l"
|
|
16
|
-
SHOW_CURSOR = "\033[?25h"
|
|
17
|
-
|
|
18
|
-
# ── 每个角色的关键区域(基于裁切后的图片坐标)──
|
|
19
|
-
# 格式: { 'eyes': (x1,y1,x2,y2), 'mouth': (x1,y1,x2,y2), 'arm': (x1,y1,x2,y2) }
|
|
20
|
-
# 用 None 表示没有该部位动画
|
|
21
|
-
|
|
22
|
-
REGIONS = {
|
|
23
|
-
'bibilabu': {
|
|
24
|
-
'eyes': (105, 90, 160, 110), # 猫眼睛区域
|
|
25
|
-
'mouth': (115, 110, 150, 130), # 猫嘴区域
|
|
26
|
-
},
|
|
27
|
-
'bagayalu': {
|
|
28
|
-
'eyes': (100, 100, 200, 130), # 水豚眼
|
|
29
|
-
'mouth': (120, 140, 200, 175), # 水豚嘴
|
|
30
|
-
},
|
|
31
|
-
'wodedaodun': {
|
|
32
|
-
'eyes': (60, 80, 140, 110), # 柴犬眼
|
|
33
|
-
'mouth': (80, 115, 150, 145), # 柴犬嘴
|
|
34
|
-
},
|
|
35
|
-
'bababoyi': {
|
|
36
|
-
'eyes': (60, 80, 220, 130), # 猫球大眼
|
|
37
|
-
'mouth': (110, 130, 170, 155), # 猫球嘴
|
|
38
|
-
},
|
|
39
|
-
'waibibabu': {
|
|
40
|
-
'eyes': (100, 75, 190, 100), # 壮汉眼
|
|
41
|
-
'mouth': (95, 115, 195, 155), # 壮汉嘴/胡子
|
|
42
|
-
'arm': (50, 170, 250, 250), # 壮汉手臂
|
|
43
|
-
},
|
|
44
|
-
'gugugaga': {
|
|
45
|
-
'eyes': (80, 120, 210, 155), # 刘海猫眼
|
|
46
|
-
'mouth': (120, 160, 185, 185), # 刘海猫嘴
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
def make_blink_frame(img, eye_region):
|
|
51
|
-
"""眨眼:用眼睛区域的平均肤色画一条横线盖住眼睛"""
|
|
52
|
-
frame = img.copy()
|
|
53
|
-
x1, y1, x2, y2 = eye_region
|
|
54
|
-
# 取眼睛周围的肤色(眼睛上方几像素)
|
|
55
|
-
sample_y = max(0, y1 - 5)
|
|
56
|
-
colors = []
|
|
57
|
-
for x in range(x1, min(x2, frame.width)):
|
|
58
|
-
px = frame.getpixel((x, sample_y))
|
|
59
|
-
if len(px) >= 3:
|
|
60
|
-
colors.append(px[:3])
|
|
61
|
-
if not colors:
|
|
62
|
-
return frame
|
|
63
|
-
avg = tuple(sum(c[i] for c in colors) // len(colors) for i in range(3))
|
|
64
|
-
|
|
65
|
-
draw = ImageDraw.Draw(frame)
|
|
66
|
-
# 画一条略粗的线代表闭眼
|
|
67
|
-
mid_y = (y1 + y2) // 2
|
|
68
|
-
for dy in range(-1, 2):
|
|
69
|
-
draw.line([(x1 + 3, mid_y + dy), (x2 - 3, mid_y + dy)], fill=avg + (255,), width=1)
|
|
70
|
-
return frame
|
|
71
|
-
|
|
72
|
-
def make_mouth_open(img, mouth_region):
|
|
73
|
-
"""张嘴:把嘴巴区域往下移 2px,露出深色缝隙"""
|
|
74
|
-
frame = img.copy()
|
|
75
|
-
x1, y1, x2, y2 = mouth_region
|
|
76
|
-
# 拷贝嘴巴区域
|
|
77
|
-
mouth = frame.crop((x1, y1, x2, y2))
|
|
78
|
-
# 用嘴巴上方的颜色填充原位置(模拟张开)
|
|
79
|
-
sample_y = max(0, y1 - 2)
|
|
80
|
-
for x in range(x1, min(x2, frame.width)):
|
|
81
|
-
px = frame.getpixel((x, sample_y))
|
|
82
|
-
for y in range(y1, min(y1 + 3, y2)):
|
|
83
|
-
frame.putpixel((x, y), px)
|
|
84
|
-
# 嘴巴下移
|
|
85
|
-
frame.paste(mouth, (x1, y1 + 2))
|
|
86
|
-
return frame
|
|
87
|
-
|
|
88
|
-
def make_arm_move(img, arm_region):
|
|
89
|
-
"""摆手:把手臂区域往左/右移 3px"""
|
|
90
|
-
frame = img.copy()
|
|
91
|
-
x1, y1, x2, y2 = arm_region
|
|
92
|
-
arm = frame.crop((x1, y1, x2, y2))
|
|
93
|
-
# 用背景色填原位
|
|
94
|
-
bg_color = (255, 255, 255, 0)
|
|
95
|
-
draw = ImageDraw.Draw(frame)
|
|
96
|
-
draw.rectangle([x1, y1, x2, y2], fill=bg_color)
|
|
97
|
-
# 手臂左移
|
|
98
|
-
frame.paste(arm, (x1 - 3, y1))
|
|
99
|
-
return frame
|
|
100
|
-
|
|
101
|
-
def generate_character_frames(name, img):
|
|
102
|
-
"""为每个角色生成动画序列"""
|
|
103
|
-
regions = REGIONS.get(name, {})
|
|
104
|
-
frames = [img] # 帧0: 原图
|
|
105
|
-
|
|
106
|
-
eye_r = regions.get('eyes')
|
|
107
|
-
mouth_r = regions.get('mouth')
|
|
108
|
-
arm_r = regions.get('arm')
|
|
109
|
-
|
|
110
|
-
if name == 'waibibabu':
|
|
111
|
-
# 歪比巴卜: 原图 → 摆手 → 原图 → 眨眼张嘴
|
|
112
|
-
if arm_r:
|
|
113
|
-
frames.append(make_arm_move(img, arm_r))
|
|
114
|
-
frames.append(img.copy())
|
|
115
|
-
if eye_r and mouth_r:
|
|
116
|
-
f = make_blink_frame(img, eye_r)
|
|
117
|
-
f = make_mouth_open(f, mouth_r)
|
|
118
|
-
frames.append(f)
|
|
119
|
-
elif name == 'bababoyi':
|
|
120
|
-
# 巴巴博一: 原图 → 原图 → 原图 → 眨眼(猫头鹰眨眼慢)
|
|
121
|
-
frames.append(img.copy())
|
|
122
|
-
frames.append(img.copy())
|
|
123
|
-
if eye_r:
|
|
124
|
-
frames.append(make_blink_frame(img, eye_r))
|
|
125
|
-
elif name == 'wodedaodun':
|
|
126
|
-
# 我的刀盾: 基本不动,偶尔张嘴(打哈欠)
|
|
127
|
-
frames.append(img.copy())
|
|
128
|
-
frames.append(img.copy())
|
|
129
|
-
if mouth_r:
|
|
130
|
-
frames.append(make_mouth_open(img, mouth_r))
|
|
131
|
-
else:
|
|
132
|
-
# 其他角色: 原图 → 原图 → 眨眼 → 张嘴
|
|
133
|
-
frames.append(img.copy())
|
|
134
|
-
if eye_r:
|
|
135
|
-
frames.append(make_blink_frame(img, eye_r))
|
|
136
|
-
if mouth_r:
|
|
137
|
-
frames.append(make_mouth_open(img, mouth_r))
|
|
138
|
-
|
|
139
|
-
return frames
|
|
140
|
-
|
|
141
|
-
def animate(name, width=45, fps=2, duration=0):
|
|
142
|
-
img_path = os.path.join(SPRITE_DIR, f"{name}.png")
|
|
143
|
-
if not os.path.exists(img_path):
|
|
144
|
-
print(f"图片不存在: {img_path}")
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
names_zh = {
|
|
148
|
-
'bibilabu': '比比拉布', 'bagayalu': '八嘎呀路',
|
|
149
|
-
'wodedaodun': '我的刀盾', 'bababoyi': '巴巴博一',
|
|
150
|
-
'waibibabu': '歪比巴卜', 'gugugaga': '咕咕嘎嘎',
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
img = Image.open(img_path).convert("RGBA")
|
|
154
|
-
|
|
155
|
-
# 生成动画帧(图片级别)
|
|
156
|
-
img_frames = generate_character_frames(name, img)
|
|
157
|
-
|
|
158
|
-
# 预渲染所有帧为终端字符串
|
|
159
|
-
rendered = []
|
|
160
|
-
for f in img_frames:
|
|
161
|
-
rendered.append(render_image(f, width))
|
|
162
|
-
|
|
163
|
-
delay = 1.0 / fps
|
|
164
|
-
frame_count = len(rendered)
|
|
165
|
-
zh = names_zh.get(name, name)
|
|
166
|
-
line_count = len(rendered[0].splitlines())
|
|
167
|
-
|
|
168
|
-
print(HIDE_CURSOR, end="")
|
|
169
|
-
print(f"\n {BOLD}{zh}{RESET}(Ctrl+C 退出)\n")
|
|
170
|
-
|
|
171
|
-
try:
|
|
172
|
-
i = 0
|
|
173
|
-
while True:
|
|
174
|
-
frame = rendered[i % frame_count]
|
|
175
|
-
for line in frame.splitlines():
|
|
176
|
-
print(f" {line}")
|
|
177
|
-
sys.stdout.flush()
|
|
178
|
-
time.sleep(delay)
|
|
179
|
-
print(f"\033[{line_count}A", end="")
|
|
180
|
-
i += 1
|
|
181
|
-
if duration > 0 and i > duration * fps:
|
|
182
|
-
break
|
|
183
|
-
except KeyboardInterrupt:
|
|
184
|
-
pass
|
|
185
|
-
finally:
|
|
186
|
-
for line in rendered[0].splitlines():
|
|
187
|
-
print(f" {line}")
|
|
188
|
-
print(SHOW_CURSOR)
|
|
189
|
-
print()
|
|
190
|
-
|
|
191
|
-
if __name__ == "__main__":
|
|
192
|
-
name_map = {
|
|
193
|
-
'比比拉布': 'bibilabu', '八嘎呀路': 'bagayalu',
|
|
194
|
-
'我的刀盾': 'wodedaodun', '巴巴博一': 'bababoyi',
|
|
195
|
-
'歪比巴卜': 'waibibabu', '咕咕嘎嘎': 'gugugaga',
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if len(sys.argv) < 2:
|
|
199
|
-
print("用法: python3 animate.py <角色名> [宽度] [fps]")
|
|
200
|
-
print("例: python3 animate.py 歪比巴卜 45 2")
|
|
201
|
-
sys.exit(1)
|
|
202
|
-
|
|
203
|
-
name = name_map.get(sys.argv[1], sys.argv[1])
|
|
204
|
-
width = int(sys.argv[2]) if len(sys.argv) > 2 else 45
|
|
205
|
-
fps = float(sys.argv[3]) if len(sys.argv) > 3 else 1.5
|
|
206
|
-
|
|
207
|
-
animate(name, width, fps)
|
package/scripts/crop_all.py
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""从原图裁切6个角色并转为终端像素画,同时保存单独的PNG"""
|
|
3
|
-
|
|
4
|
-
import os
|
|
5
|
-
from PIL import Image
|
|
6
|
-
|
|
7
|
-
SRC = "/Users/akane/Downloads/20260401160756.jpg"
|
|
8
|
-
OUT_DIR = "/Users/akane/Projects/codepet/sprites"
|
|
9
|
-
os.makedirs(OUT_DIR, exist_ok=True)
|
|
10
|
-
|
|
11
|
-
img = Image.open(SRC)
|
|
12
|
-
W, H = img.size # 1056 x 840
|
|
13
|
-
|
|
14
|
-
# 原图布局: 2行3列 + 底部有文字
|
|
15
|
-
# 第一行: 比比拉布, 八嘎呀路, 我的刀盾 (y: 0~400)
|
|
16
|
-
# 第二行: 巴巴博一, 歪比巴卜, 咕咕嘎嘎 (y: 400~800)
|
|
17
|
-
# 每列约 352px 宽
|
|
18
|
-
|
|
19
|
-
crops = {
|
|
20
|
-
'bibilabu': (30, 10, 290, 310), # 比比拉布 — 去掉底部文字
|
|
21
|
-
'bagayalu': (350, 10, 680, 320), # 八嘎呀路
|
|
22
|
-
'wodedaodun': (720, 50, 1040, 300), # 我的刀盾
|
|
23
|
-
'bababoyi': (20, 410, 300, 670), # 巴巴博一
|
|
24
|
-
'waibibabu': (360, 400, 660, 700), # 歪比巴卜
|
|
25
|
-
'gugugaga': (720, 410, 1040, 690), # 咕咕嘎嘎
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
names_zh = {
|
|
29
|
-
'bibilabu': '比比拉布',
|
|
30
|
-
'bagayalu': '八嘎呀路',
|
|
31
|
-
'wodedaodun': '我的刀盾',
|
|
32
|
-
'bababoyi': '巴巴博一',
|
|
33
|
-
'waibibabu': '歪比巴卜',
|
|
34
|
-
'gugugaga': '咕咕嘎嘎',
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
for name, (x1, y1, x2, y2) in crops.items():
|
|
38
|
-
cropped = img.crop((x1, y1, x2, y2))
|
|
39
|
-
# 保存 PNG
|
|
40
|
-
out_path = os.path.join(OUT_DIR, f"{name}.png")
|
|
41
|
-
cropped.save(out_path)
|
|
42
|
-
print(f"[OK] {names_zh[name]} -> {out_path} ({cropped.size[0]}x{cropped.size[1]})")
|
|
43
|
-
|
|
44
|
-
print(f"\n全部保存到 {OUT_DIR}/")
|
|
45
|
-
print("接下来运行:")
|
|
46
|
-
print(" python3 scripts/img2terminal.py sprites/bibilabu.png 25")
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""从 6 张表情表裁切出 36 张单图,避开方格线和文字"""
|
|
3
|
-
|
|
4
|
-
import os
|
|
5
|
-
from PIL import Image
|
|
6
|
-
|
|
7
|
-
SRC_DIR = "/Users/akane/Projects/codepet/sprites/6"
|
|
8
|
-
OUT_DIR = "/Users/akane/Projects/codepet/sprites"
|
|
9
|
-
|
|
10
|
-
# 文件 → 角色映射
|
|
11
|
-
FILES = {
|
|
12
|
-
"1.png": "bibilabu",
|
|
13
|
-
"2.png": "bagayalu",
|
|
14
|
-
"3.png": "wodedaodun",
|
|
15
|
-
"4.png": "bababoyi",
|
|
16
|
-
"5.png": "waibibabu",
|
|
17
|
-
"6.png": "gugugaga",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
# 表情顺序:2行3列
|
|
21
|
-
EXPRESSIONS = [
|
|
22
|
-
["normal", "happy", "sleep"],
|
|
23
|
-
["eat", "pet", "worry"],
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
def crop_sheet(src_path, char_id):
|
|
27
|
-
"""从一张 2x3 表情表裁切出 6 张单图"""
|
|
28
|
-
img = Image.open(src_path)
|
|
29
|
-
W, H = img.size
|
|
30
|
-
|
|
31
|
-
# 每个格子的大小
|
|
32
|
-
col_w = W // 3
|
|
33
|
-
row_h = H // 2
|
|
34
|
-
|
|
35
|
-
# 大幅内缩:避开边框线和底部英文文字
|
|
36
|
-
# 顶部缩 5%,底部缩 28%(文字+边框),左右各缩 6%
|
|
37
|
-
pad_top = int(row_h * 0.05)
|
|
38
|
-
pad_bottom = int(row_h * 0.28)
|
|
39
|
-
pad_left = int(col_w * 0.06)
|
|
40
|
-
pad_right = int(col_w * 0.06)
|
|
41
|
-
|
|
42
|
-
char_dir = os.path.join(OUT_DIR, char_id)
|
|
43
|
-
os.makedirs(char_dir, exist_ok=True)
|
|
44
|
-
|
|
45
|
-
for row_idx, row_exprs in enumerate(EXPRESSIONS):
|
|
46
|
-
for col_idx, expr in enumerate(row_exprs):
|
|
47
|
-
x1 = col_idx * col_w + pad_left
|
|
48
|
-
y1 = row_idx * row_h + pad_top
|
|
49
|
-
x2 = (col_idx + 1) * col_w - pad_right
|
|
50
|
-
y2 = (row_idx + 1) * row_h - pad_bottom
|
|
51
|
-
|
|
52
|
-
cropped = img.crop((x1, y1, x2, y2))
|
|
53
|
-
out_path = os.path.join(char_dir, f"{expr}.png")
|
|
54
|
-
cropped.save(out_path)
|
|
55
|
-
print(f" ✓ {char_id}/{expr}.png ({cropped.size[0]}x{cropped.size[1]})")
|
|
56
|
-
|
|
57
|
-
print()
|
|
58
|
-
|
|
59
|
-
def main():
|
|
60
|
-
print("\n 🔪 裁切表情表...\n")
|
|
61
|
-
for filename, char_id in FILES.items():
|
|
62
|
-
src = os.path.join(SRC_DIR, filename)
|
|
63
|
-
if not os.path.exists(src):
|
|
64
|
-
print(f" ✗ {filename} 不存在,跳过")
|
|
65
|
-
continue
|
|
66
|
-
print(f" [{char_id}] ← {filename}")
|
|
67
|
-
crop_sheet(src, char_id)
|
|
68
|
-
|
|
69
|
-
print(" ✅ 36 张裁切完成!")
|
|
70
|
-
print(" 查看: ! open ~/Projects/codepet/sprites/\n")
|
|
71
|
-
|
|
72
|
-
if __name__ == "__main__":
|
|
73
|
-
main()
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""用豆包 Seedream API 批量生成 6 角色 × 6 表情 = 36 张图"""
|
|
3
|
-
|
|
4
|
-
import os, json, time, urllib.request, urllib.error
|
|
5
|
-
|
|
6
|
-
ARK_API_KEY = os.environ.get("ARK_API_KEY", "YOUR_API_KEY_HERE")
|
|
7
|
-
API_URL = "https://ark.cn-beijing.volces.com/api/v3/images/generations"
|
|
8
|
-
MODEL = "doubao-seedream-5-0-260128"
|
|
9
|
-
OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "sprites")
|
|
10
|
-
|
|
11
|
-
CHARACTERS = {
|
|
12
|
-
"bibilabu": "比比拉布, a cute cat wearing a yellow banana costume, cat face peeking out from banana peel, small grey paws, meme style",
|
|
13
|
-
"bagayalu": "八嘎呀路, a shiba inu dog sitting upright, golden brown fur, melancholic zen expression, meme painting style",
|
|
14
|
-
"wodedaodun": "我的刀盾, a shiba inu dog lying completely flat on belly like a loaf, brown fur, side view, very flat, meme painting style",
|
|
15
|
-
"bababoyi": "巴巴博一, an extremely round fat cat shaped like a ball, huge dark owl-like eyes, pink nose, grey and cream colored, meme style",
|
|
16
|
-
"waibibabu": "歪比巴卜, a cartoon muscular man with grey baseball cap, thick brown beard, white polo shirt, cartoon illustration style",
|
|
17
|
-
"gugugaga": "咕咕嘎嘎, a round chubby cat with thick dark grey bangs hair covering forehead, white face below bangs, small pink mouth, meme style",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
EXPRESSIONS = {
|
|
21
|
-
"normal": "calm default expression, looking forward",
|
|
22
|
-
"happy": "very happy, big smile, eyes squinting with joy, sparkles around",
|
|
23
|
-
"sleep": "eyes peacefully closed, sleeping, zzZ floating above head, peaceful",
|
|
24
|
-
"eat": "mouth wide open eating food, cheeks puffed, holding food, enjoying",
|
|
25
|
-
"pet": "being gently petted on head, eyes half-closed in pleasure, blushing cheeks, content",
|
|
26
|
-
"worry": "anxious worried expression, sweat drop on forehead, nervous wide eyes, stressed",
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
def generate_image(prompt, output_path):
|
|
30
|
-
"""调用豆包 API 生成图片"""
|
|
31
|
-
data = json.dumps({
|
|
32
|
-
"model": MODEL,
|
|
33
|
-
"prompt": prompt + ", white background, high quality, detailed",
|
|
34
|
-
"response_format": "url",
|
|
35
|
-
"size": "2K",
|
|
36
|
-
"stream": False,
|
|
37
|
-
}).encode('utf-8')
|
|
38
|
-
|
|
39
|
-
req = urllib.request.Request(API_URL, data=data, headers={
|
|
40
|
-
"Content-Type": "application/json",
|
|
41
|
-
"Authorization": f"Bearer {ARK_API_KEY}",
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
try:
|
|
45
|
-
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
46
|
-
result = json.loads(resp.read())
|
|
47
|
-
url = result["data"][0]["url"]
|
|
48
|
-
# 下载图片
|
|
49
|
-
urllib.request.urlretrieve(url, output_path)
|
|
50
|
-
return True
|
|
51
|
-
except Exception as e:
|
|
52
|
-
print(f" ✗ 失败: {e}")
|
|
53
|
-
return False
|
|
54
|
-
|
|
55
|
-
def main():
|
|
56
|
-
total = 0
|
|
57
|
-
success = 0
|
|
58
|
-
failed = []
|
|
59
|
-
|
|
60
|
-
print("\n 🎨 开始生成 36 张角色表情图...\n")
|
|
61
|
-
|
|
62
|
-
for char_id, char_desc in CHARACTERS.items():
|
|
63
|
-
char_dir = os.path.join(OUT_DIR, char_id)
|
|
64
|
-
os.makedirs(char_dir, exist_ok=True)
|
|
65
|
-
|
|
66
|
-
print(f" [{char_id}]")
|
|
67
|
-
for expr_id, expr_desc in EXPRESSIONS.items():
|
|
68
|
-
total += 1
|
|
69
|
-
output_path = os.path.join(char_dir, f"{expr_id}.png")
|
|
70
|
-
|
|
71
|
-
prompt = f"{char_desc}, {expr_desc}"
|
|
72
|
-
print(f" {expr_id}...", end=" ", flush=True)
|
|
73
|
-
|
|
74
|
-
if generate_image(prompt, output_path):
|
|
75
|
-
size_kb = os.path.getsize(output_path) // 1024
|
|
76
|
-
print(f"✓ ({size_kb}KB)")
|
|
77
|
-
success += 1
|
|
78
|
-
else:
|
|
79
|
-
failed.append(f"{char_id}/{expr_id}")
|
|
80
|
-
|
|
81
|
-
# 间隔避免限流
|
|
82
|
-
time.sleep(2)
|
|
83
|
-
|
|
84
|
-
print()
|
|
85
|
-
|
|
86
|
-
print(f" ──────────────────────")
|
|
87
|
-
print(f" 完成: {success}/{total} 成功")
|
|
88
|
-
if failed:
|
|
89
|
-
print(f" 失败: {', '.join(failed)}")
|
|
90
|
-
print(f"\n 查看结果:")
|
|
91
|
-
print(f" ! open {OUT_DIR}")
|
|
92
|
-
print()
|
|
93
|
-
|
|
94
|
-
if __name__ == "__main__":
|
|
95
|
-
main()
|
package/scripts/gen_variants.py
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""从基础图片自动生成 6 个场景变体"""
|
|
3
|
-
|
|
4
|
-
import os, sys
|
|
5
|
-
from PIL import Image, ImageEnhance, ImageFilter, ImageDraw, ImageFont
|
|
6
|
-
|
|
7
|
-
SPRITE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "sprites")
|
|
8
|
-
|
|
9
|
-
CHARACTERS = ['bibilabu', 'bagayalu', 'wodedaodun', 'bababoyi', 'waibibabu', 'gugugaga']
|
|
10
|
-
|
|
11
|
-
# 6 个场景
|
|
12
|
-
VARIANTS = {
|
|
13
|
-
'normal': '普通',
|
|
14
|
-
'happy': '开心',
|
|
15
|
-
'sleep': '睡觉',
|
|
16
|
-
'eat': '吃东西',
|
|
17
|
-
'pet': '被撸',
|
|
18
|
-
'worry': '焦虑',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
def make_happy(img):
|
|
22
|
-
"""开心:提亮 + 暖色调"""
|
|
23
|
-
enhancer = ImageEnhance.Brightness(img)
|
|
24
|
-
bright = enhancer.enhance(1.15)
|
|
25
|
-
# 暖色:增加红黄
|
|
26
|
-
r, g, b, a = bright.split()
|
|
27
|
-
r = r.point(lambda x: min(255, int(x * 1.05)))
|
|
28
|
-
g = g.point(lambda x: min(255, int(x * 1.02)))
|
|
29
|
-
return Image.merge('RGBA', (r, g, b, a))
|
|
30
|
-
|
|
31
|
-
def make_sleep(img):
|
|
32
|
-
"""睡觉:变暗 + 轻微模糊"""
|
|
33
|
-
enhancer = ImageEnhance.Brightness(img)
|
|
34
|
-
dark = enhancer.enhance(0.8)
|
|
35
|
-
enhancer2 = ImageEnhance.Color(dark)
|
|
36
|
-
desat = enhancer2.enhance(0.7)
|
|
37
|
-
return desat.filter(ImageFilter.GaussianBlur(radius=0.5))
|
|
38
|
-
|
|
39
|
-
def make_eat(img):
|
|
40
|
-
"""吃东西:微微歪头(旋转 3 度)"""
|
|
41
|
-
rotated = img.rotate(-3, expand=False, fillcolor=(255, 255, 255, 0))
|
|
42
|
-
# 稍微提亮(吃东西开心)
|
|
43
|
-
enhancer = ImageEnhance.Brightness(rotated)
|
|
44
|
-
return enhancer.enhance(1.08)
|
|
45
|
-
|
|
46
|
-
def make_pet(img):
|
|
47
|
-
"""被撸:温暖色调 + 轻微放大"""
|
|
48
|
-
w, h = img.size
|
|
49
|
-
# 轻微放大 5%
|
|
50
|
-
new_w, new_h = int(w * 1.05), int(h * 1.05)
|
|
51
|
-
big = img.resize((new_w, new_h), Image.LANCZOS)
|
|
52
|
-
# 裁回原尺寸(居中)
|
|
53
|
-
left = (new_w - w) // 2
|
|
54
|
-
top = (new_h - h) // 2
|
|
55
|
-
cropped = big.crop((left, top, left + w, top + h))
|
|
56
|
-
# 暖色
|
|
57
|
-
enhancer = ImageEnhance.Color(cropped)
|
|
58
|
-
warm = enhancer.enhance(1.1)
|
|
59
|
-
enhancer2 = ImageEnhance.Brightness(warm)
|
|
60
|
-
return enhancer2.enhance(1.1)
|
|
61
|
-
|
|
62
|
-
def make_worry(img):
|
|
63
|
-
"""焦虑:冷色调 + 轻微缩小"""
|
|
64
|
-
# 冷色:增蓝减红
|
|
65
|
-
r, g, b, a = img.split()
|
|
66
|
-
r = r.point(lambda x: int(x * 0.92))
|
|
67
|
-
b = b.point(lambda x: min(255, int(x * 1.08)))
|
|
68
|
-
cold = Image.merge('RGBA', (r, g, b, a))
|
|
69
|
-
# 轻微降低饱和
|
|
70
|
-
enhancer = ImageEnhance.Color(cold)
|
|
71
|
-
return enhancer.enhance(0.85)
|
|
72
|
-
|
|
73
|
-
GENERATORS = {
|
|
74
|
-
'normal': lambda img: img.copy(),
|
|
75
|
-
'happy': make_happy,
|
|
76
|
-
'sleep': make_sleep,
|
|
77
|
-
'eat': make_eat,
|
|
78
|
-
'pet': make_pet,
|
|
79
|
-
'worry': make_worry,
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
def generate_all():
|
|
83
|
-
total = 0
|
|
84
|
-
for char in CHARACTERS:
|
|
85
|
-
base_path = os.path.join(SPRITE_DIR, f"{char}.png")
|
|
86
|
-
if not os.path.exists(base_path):
|
|
87
|
-
print(f" [!] 跳过 {char}:基础图片不存在")
|
|
88
|
-
continue
|
|
89
|
-
|
|
90
|
-
base = Image.open(base_path).convert("RGBA")
|
|
91
|
-
var_dir = os.path.join(SPRITE_DIR, char)
|
|
92
|
-
os.makedirs(var_dir, exist_ok=True)
|
|
93
|
-
|
|
94
|
-
for variant, zh_name in VARIANTS.items():
|
|
95
|
-
gen = GENERATORS[variant]
|
|
96
|
-
result = gen(base)
|
|
97
|
-
out_path = os.path.join(var_dir, f"{variant}.png")
|
|
98
|
-
result.save(out_path)
|
|
99
|
-
total += 1
|
|
100
|
-
|
|
101
|
-
print(f" ✓ {char} — {len(VARIANTS)} 个变体")
|
|
102
|
-
|
|
103
|
-
print(f"\n 共生成 {total} 张图片,保存到 {SPRITE_DIR}/[角色名]/")
|
|
104
|
-
|
|
105
|
-
if __name__ == '__main__':
|
|
106
|
-
print("\n 🎨 生成角色场景变体...\n")
|
|
107
|
-
generate_all()
|
package/scripts/render_sprite.py
DELETED
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""CodePet 像素精灵渲染器 v2 — 基于真实形象"""
|
|
3
|
-
|
|
4
|
-
import sys
|
|
5
|
-
|
|
6
|
-
# ── 调色板(从真实图片取色)──
|
|
7
|
-
C = {
|
|
8
|
-
'.': None,
|
|
9
|
-
# 香蕉/植物
|
|
10
|
-
'Y': (215, 195, 55), # 香蕉黄
|
|
11
|
-
'y': (185, 165, 45), # 香蕉暗黄
|
|
12
|
-
'G': (95, 120, 45), # 蒂绿
|
|
13
|
-
# 灰猫(比比拉布/巴巴博一/咕咕嘎嘎)
|
|
14
|
-
'S': (185, 180, 175), # 银灰猫毛
|
|
15
|
-
's': (155, 150, 145), # 深银灰
|
|
16
|
-
'A': (205, 200, 195), # 亮灰
|
|
17
|
-
'a': (130, 125, 120), # 暗灰
|
|
18
|
-
# 水豚(八嘎呀路/我的刀盾)
|
|
19
|
-
'B': (195, 145, 75), # 金棕
|
|
20
|
-
'b': (165, 120, 60), # 深棕
|
|
21
|
-
'L': (225, 200, 155), # 浅棕/肚
|
|
22
|
-
'l': (240, 220, 180), # 亮浅棕
|
|
23
|
-
'M': (210, 170, 110), # 中棕
|
|
24
|
-
# 巴巴博一(猫球)
|
|
25
|
-
'C': (215, 200, 180), # 奶油色身体
|
|
26
|
-
'c': (195, 175, 155), # 深奶油
|
|
27
|
-
# 歪比巴卜(卡通人)
|
|
28
|
-
'F': (225, 185, 145), # 肤色
|
|
29
|
-
'f': (200, 160, 120), # 深肤色
|
|
30
|
-
'H': (165, 105, 55), # 胡子棕
|
|
31
|
-
'h': (135, 80, 40), # 深胡子
|
|
32
|
-
'K': (165, 170, 175), # 帽灰
|
|
33
|
-
'k': (140, 145, 150), # 帽深灰
|
|
34
|
-
'T': (242, 242, 238), # 白衣
|
|
35
|
-
't': (220, 220, 216), # 衣影
|
|
36
|
-
# 咕咕嘎嘎(黑发)
|
|
37
|
-
'D': (65, 65, 70), # 黑发
|
|
38
|
-
'd': (90, 90, 95), # 深灰发
|
|
39
|
-
# 通用
|
|
40
|
-
'W': (240, 235, 225), # 白
|
|
41
|
-
'w': (250, 248, 242), # 亮白
|
|
42
|
-
'X': (30, 30, 30), # 黑(眼)
|
|
43
|
-
'x': (60, 55, 50), # 深灰(眼)
|
|
44
|
-
'P': (205, 155, 145), # 粉鼻
|
|
45
|
-
'p': (185, 130, 120), # 深粉
|
|
46
|
-
'R': (195, 105, 100), # 红嘴
|
|
47
|
-
'J': (80, 95, 130), # 牛仔蓝
|
|
48
|
-
'j': (60, 75, 105), # 深蓝
|
|
49
|
-
'n': (170, 130, 90), # 鼻棕
|
|
50
|
-
'E': (50, 80, 50), # 深绿
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
SPRITES = {}
|
|
54
|
-
|
|
55
|
-
# ═══════════════════════════════════════
|
|
56
|
-
# 1. 比比拉布(香蕉猫)
|
|
57
|
-
# 灰猫脸从香蕉里探出,绿蒂,灰爪
|
|
58
|
-
# 20w × 26h
|
|
59
|
-
# ═══════════════════════════════════════
|
|
60
|
-
SPRITES['bibilabu'] = {
|
|
61
|
-
'normal': [
|
|
62
|
-
".........EE.........",
|
|
63
|
-
"........EGGE........",
|
|
64
|
-
".......EGGGE........",
|
|
65
|
-
"........YYY.........",
|
|
66
|
-
".......YYYYY........",
|
|
67
|
-
"......YYYYYY........",
|
|
68
|
-
".....YYsSSsYY.......",
|
|
69
|
-
".....YsSSSSsY.......",
|
|
70
|
-
"....YYSXSSXSyY......",
|
|
71
|
-
"....YYSSPSSsyY......",
|
|
72
|
-
"....YYsSSSSYYY......",
|
|
73
|
-
".....YYYYYYYY.......",
|
|
74
|
-
".....YYYYYYYY.......",
|
|
75
|
-
".....yYYYYYYy.......",
|
|
76
|
-
".....yyyYYYYy.......",
|
|
77
|
-
".....yyyYYYy........",
|
|
78
|
-
"......yyYYy.........",
|
|
79
|
-
"......yyyYy.........",
|
|
80
|
-
".......yyyy.........",
|
|
81
|
-
"......yyy...........",
|
|
82
|
-
".......SS.SS........",
|
|
83
|
-
"......SS...SS.......",
|
|
84
|
-
],
|
|
85
|
-
'happy': [
|
|
86
|
-
".........EE.........",
|
|
87
|
-
"........EGGE........",
|
|
88
|
-
".......EGGGE........",
|
|
89
|
-
"........YYY.........",
|
|
90
|
-
".......YYYYY........",
|
|
91
|
-
"......YYYYYY........",
|
|
92
|
-
".....YYsSSsYY.......",
|
|
93
|
-
".....YsSSSSsY.......",
|
|
94
|
-
"....YYSaSSaSyY......",
|
|
95
|
-
"....YYSSPSSsyY......",
|
|
96
|
-
"....YYsSSSSYYY......",
|
|
97
|
-
".....YYYYYYYY.......",
|
|
98
|
-
".....YYYYYYYY.......",
|
|
99
|
-
".....yYYYYYYy.......",
|
|
100
|
-
".....yyyYYYYy.......",
|
|
101
|
-
".....yyyYYYy........",
|
|
102
|
-
"......yyYYy.........",
|
|
103
|
-
"......yyyYy.........",
|
|
104
|
-
".......yyyy.........",
|
|
105
|
-
"......yyy...........",
|
|
106
|
-
".......SS.SS........",
|
|
107
|
-
"......SS...SS.......",
|
|
108
|
-
],
|
|
109
|
-
'sleep': [
|
|
110
|
-
".........EE.........",
|
|
111
|
-
"........EGGE........",
|
|
112
|
-
".......EGGGE........",
|
|
113
|
-
"........YYY.........",
|
|
114
|
-
".......YYYYY........",
|
|
115
|
-
"......YYYYYY........",
|
|
116
|
-
".....YYsSSsYY.......",
|
|
117
|
-
".....YsSSSSsY.......",
|
|
118
|
-
"....YYSsSssSyY......",
|
|
119
|
-
"....YYSSPSSsyY......",
|
|
120
|
-
"....YYsSSSSYYY......",
|
|
121
|
-
".....YYYYYYYY.......",
|
|
122
|
-
".....YYYYYYYY.......",
|
|
123
|
-
".....yYYYYYYy.......",
|
|
124
|
-
".....yyyYYYYy.......",
|
|
125
|
-
".....yyyYYYy........",
|
|
126
|
-
"......yyYYy.........",
|
|
127
|
-
"......yyyYy.........",
|
|
128
|
-
".......yyyy.........",
|
|
129
|
-
"......yyy...........",
|
|
130
|
-
".......SS.SS........",
|
|
131
|
-
"......SS...SS.......",
|
|
132
|
-
],
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
# ═══════════════════════════════════════
|
|
136
|
-
# 2. 八嘎呀路(坐姿水豚)
|
|
137
|
-
# 金棕色,3/4侧面坐,闭眼忧郁,浅肚
|
|
138
|
-
# 20w × 22h
|
|
139
|
-
# ═══════════════════════════════════════
|
|
140
|
-
SPRITES['bagayalu'] = {
|
|
141
|
-
'normal': [
|
|
142
|
-
"..........bb........",
|
|
143
|
-
".........bBBb.......",
|
|
144
|
-
"........bBBBBb......",
|
|
145
|
-
".......bBBBBBBb.....",
|
|
146
|
-
"......bBBBBBBBBb....",
|
|
147
|
-
".....bBbBBbBBBBBb...",
|
|
148
|
-
".....bBBnnBBBBBBb...",
|
|
149
|
-
".....bBBBBBBBBBBb...",
|
|
150
|
-
"....bBBBBBBBBBBBb...",
|
|
151
|
-
"...bBBBBBBBBBBBBb...",
|
|
152
|
-
"..bBBBBBBBBBBBBBb...",
|
|
153
|
-
"..bBBBLLLLLBBBBb....",
|
|
154
|
-
"..bBBLLLLLLLBBBb....",
|
|
155
|
-
"..bBBLLLLLLLBBb.....",
|
|
156
|
-
"...bBBLLLLLBBb......",
|
|
157
|
-
"...bBBBBBBBBBb......",
|
|
158
|
-
"....bbBBBBBbb.......",
|
|
159
|
-
"....bBb..bBBb.......",
|
|
160
|
-
"....bb....bbb.......",
|
|
161
|
-
],
|
|
162
|
-
'happy': [
|
|
163
|
-
"..........bb........",
|
|
164
|
-
".........bBBb.......",
|
|
165
|
-
"........bBBBBb......",
|
|
166
|
-
".......bBBBBBBb.....",
|
|
167
|
-
"......bBBBBBBBBb....",
|
|
168
|
-
".....bBMBBMBBBBBb...",
|
|
169
|
-
".....bBBnnBBBBBBb...",
|
|
170
|
-
".....bBBLLBBBBBBb...",
|
|
171
|
-
"....bBBBBBBBBBBBb...",
|
|
172
|
-
"...bBBBBBBBBBBBBb...",
|
|
173
|
-
"..bBBBBBBBBBBBBBb...",
|
|
174
|
-
"..bBBBLLLLLBBBBb....",
|
|
175
|
-
"..bBBLLLLLLLBBBb....",
|
|
176
|
-
"..bBBLLLLLLLBBb.....",
|
|
177
|
-
"...bBBLLLLLBBb......",
|
|
178
|
-
"...bBBBBBBBBBb......",
|
|
179
|
-
"....bbBBBBBbb.......",
|
|
180
|
-
"....bBb..bBBb.......",
|
|
181
|
-
"....bb....bbb.......",
|
|
182
|
-
],
|
|
183
|
-
'sleep': [
|
|
184
|
-
"..........bb........",
|
|
185
|
-
".........bBBb.......",
|
|
186
|
-
"........bBBBBb......",
|
|
187
|
-
".......bBBBBBBb.....",
|
|
188
|
-
"......bBBBBBBBBb....",
|
|
189
|
-
".....bBbBBbBBBBBb...",
|
|
190
|
-
".....bBBnnBBBBBBb...",
|
|
191
|
-
".....bBBBBBBBBBBb...",
|
|
192
|
-
"....bBBBBBBBBBBBb...",
|
|
193
|
-
"...bBBBBBBBBBBBBb...",
|
|
194
|
-
"..bBBBBBBBBBBBBBb...",
|
|
195
|
-
"..bBBBLLLLLBBBBb....",
|
|
196
|
-
"..bBBLLLLLLLBBBb....",
|
|
197
|
-
"..bBBLLLLLLLBBb.....",
|
|
198
|
-
"...bBBLLLLLBBb......",
|
|
199
|
-
"...bBBBBBBBBBb......",
|
|
200
|
-
"....bbBBBBBbb.......",
|
|
201
|
-
"....bBb..bBBb.......",
|
|
202
|
-
"....bb....bbb.......",
|
|
203
|
-
],
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
# ═══════════════════════════════════════
|
|
207
|
-
# 3. 我的刀盾(趴姿水豚)
|
|
208
|
-
# 完全趴平像面包,侧面,微笑
|
|
209
|
-
# 22w × 12h
|
|
210
|
-
# ═══════════════════════════════════════
|
|
211
|
-
SPRITES['wodedaodun'] = {
|
|
212
|
-
'normal': [
|
|
213
|
-
".......bbbbb........",
|
|
214
|
-
"....bbbBBBBBbbb.....",
|
|
215
|
-
"...bBBBBBBBBBBBbb...",
|
|
216
|
-
"..bBbBbBBBBBBBBBBb..",
|
|
217
|
-
"..bBBnBBBBBBBBBBBBb.",
|
|
218
|
-
"..bBBBLLBBBBBBBBBBb.",
|
|
219
|
-
"...bBBBBBBBBBBBBBb..",
|
|
220
|
-
"...bbLBBBBBBBBLbb...",
|
|
221
|
-
"....bb.........bb...",
|
|
222
|
-
],
|
|
223
|
-
'happy': [
|
|
224
|
-
".......bbbbb........",
|
|
225
|
-
"....bbbBBBBBbbb.....",
|
|
226
|
-
"...bBBBBBBBBBBBbb...",
|
|
227
|
-
"..bBMBMBBBBBBBBBBb..",
|
|
228
|
-
"..bBBnBBBBBBBBBBBBb.",
|
|
229
|
-
"..bBBBLLBBBBBBBBBBb.",
|
|
230
|
-
"...bBBBBBBBBBBBBBb..",
|
|
231
|
-
"...bbLBBBBBBBBLbb...",
|
|
232
|
-
"....bb.........bb...",
|
|
233
|
-
],
|
|
234
|
-
'sleep': [
|
|
235
|
-
".......bbbbb........",
|
|
236
|
-
"....bbbBBBBBbbb.....",
|
|
237
|
-
"...bBBBBBBBBBBBbb...",
|
|
238
|
-
"..bBbBbBBBBBBBBBBb..",
|
|
239
|
-
"..bBBnBBBBBBBBBBBBb.",
|
|
240
|
-
"..bBBBLLBBBBBBBBBBb.",
|
|
241
|
-
"...bBBBBBBBBBBBBBb..",
|
|
242
|
-
"...bbLBBBBBBBBLbb...",
|
|
243
|
-
"....bb.........bb...",
|
|
244
|
-
],
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
# ═══════════════════════════════════════
|
|
248
|
-
# 4. 巴巴博一(圆猫球)
|
|
249
|
-
# 极度圆,巨大黑眼,粉鼻,灰+奶油色
|
|
250
|
-
# 20w × 20h
|
|
251
|
-
# ═══════════════════════════════════════
|
|
252
|
-
SPRITES['bababoyi'] = {
|
|
253
|
-
'normal': [
|
|
254
|
-
".......ssss.........",
|
|
255
|
-
"......sSSSSs........",
|
|
256
|
-
".....sSSSSSSSs......",
|
|
257
|
-
"....sSSSSSSSSSs.....",
|
|
258
|
-
"...sSSSSSSSSSSSs....",
|
|
259
|
-
"..sSxXxSSSSxXxSSs...",
|
|
260
|
-
"..sSXXxSSSSxXXSSs...",
|
|
261
|
-
"..sSSSSSSSSSSSSSSs..",
|
|
262
|
-
"..sSSSSSPPSSSSSSSs..",
|
|
263
|
-
"..sCCCCCCCCCCCCCs...",
|
|
264
|
-
"..CCCCCCCCCCCCCCCC..",
|
|
265
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
266
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
267
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
268
|
-
"..CCCCCCCCCCCCCCCC..",
|
|
269
|
-
"..cCCCCCCCCCCCCCc...",
|
|
270
|
-
"...ccCCCCCCCCcc.....",
|
|
271
|
-
"....cccccccccc......",
|
|
272
|
-
".....ss....ss.......",
|
|
273
|
-
],
|
|
274
|
-
'happy': [
|
|
275
|
-
".......ssss.........",
|
|
276
|
-
"......sSSSSs........",
|
|
277
|
-
".....sSSSSSSSs......",
|
|
278
|
-
"....sSSSSSSSSSs.....",
|
|
279
|
-
"...sSSSSSSSSSSSs....",
|
|
280
|
-
"..sSaxaSSSSaxaSSs...",
|
|
281
|
-
"..sSaaaSSSsaaaSs....",
|
|
282
|
-
"..sSSSSSSSSSSSSSSs..",
|
|
283
|
-
"..sSSSSSPPSSSSSSSs..",
|
|
284
|
-
"..sCCCCCCCCCCCCCs...",
|
|
285
|
-
"..CCCCCCCCCCCCCCCC..",
|
|
286
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
287
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
288
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
289
|
-
"..CCCCCCCCCCCCCCCC..",
|
|
290
|
-
"..cCCCCCCCCCCCCCc...",
|
|
291
|
-
"...ccCCCCCCCCcc.....",
|
|
292
|
-
"....cccccccccc......",
|
|
293
|
-
".....ss....ss.......",
|
|
294
|
-
],
|
|
295
|
-
'sleep': [
|
|
296
|
-
".......ssss.........",
|
|
297
|
-
"......sSSSSs........",
|
|
298
|
-
".....sSSSSSSSs......",
|
|
299
|
-
"....sSSSSSSSSSs.....",
|
|
300
|
-
"...sSSSSSSSSSSSs....",
|
|
301
|
-
"..sSSsSSSSSsSSSSSs..",
|
|
302
|
-
"..sSssSSSSssSSSSSs..",
|
|
303
|
-
"..sSSSSSSSSSSSSSSs..",
|
|
304
|
-
"..sSSSSSPPSSSSSSSs..",
|
|
305
|
-
"..sCCCCCCCCCCCCCs...",
|
|
306
|
-
"..CCCCCCCCCCCCCCCC..",
|
|
307
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
308
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
309
|
-
".CCCCCCCCCCCCCCCCCC.",
|
|
310
|
-
"..CCCCCCCCCCCCCCCC..",
|
|
311
|
-
"..cCCCCCCCCCCCCCc...",
|
|
312
|
-
"...ccCCCCCCCCcc.....",
|
|
313
|
-
"....cccccccccc......",
|
|
314
|
-
".....ss....ss.......",
|
|
315
|
-
],
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
# ═══════════════════════════════════════
|
|
319
|
-
# 5. 歪比巴卜(大胡子壮汉)
|
|
320
|
-
# 卡通风,灰帽,肤色脸,棕胡子,白Polo衫
|
|
321
|
-
# 18w × 26h
|
|
322
|
-
# ═══════════════════════════════════════
|
|
323
|
-
SPRITES['waibibabu'] = {
|
|
324
|
-
'normal': [
|
|
325
|
-
".......kkkk.........",
|
|
326
|
-
"......kKKKKk........",
|
|
327
|
-
".....kKKKKKKk.......",
|
|
328
|
-
".....KKKKKKKKK......",
|
|
329
|
-
"....FFFFFFFFFFf.....",
|
|
330
|
-
"....FxFFFFxFFFF.....",
|
|
331
|
-
"....FFFFFFFFFFFf....",
|
|
332
|
-
"....FFFnnFFFFFf.....",
|
|
333
|
-
"....fHHHHHHHHf......",
|
|
334
|
-
".....fHHHHHHf.......",
|
|
335
|
-
"......ffffff........",
|
|
336
|
-
".....TTTTTTTTT......",
|
|
337
|
-
"....TTTTtTTTTTT.....",
|
|
338
|
-
"...FTTTTtTTTTTF.....",
|
|
339
|
-
"...FFTTTtTTTTFF.....",
|
|
340
|
-
"...FFFTTtTTFFF......",
|
|
341
|
-
"....FFTTtTTFF.......",
|
|
342
|
-
".....TTTTTTT........",
|
|
343
|
-
".....TTTTTTT........",
|
|
344
|
-
"......TTTTT.........",
|
|
345
|
-
"......JJ.JJ.........",
|
|
346
|
-
".....JJJ.JJJ........",
|
|
347
|
-
".....JJ...JJ........",
|
|
348
|
-
"....jjj...jjj.......",
|
|
349
|
-
],
|
|
350
|
-
'happy': [
|
|
351
|
-
".......kkkk.........",
|
|
352
|
-
"......kKKKKk........",
|
|
353
|
-
".....kKKKKKKk.......",
|
|
354
|
-
".....KKKKKKKKK......",
|
|
355
|
-
"....FFFFFFFFFFf.....",
|
|
356
|
-
"....FaFFFFaFFFF.....",
|
|
357
|
-
"....FFFFFFFFFFFf....",
|
|
358
|
-
"....FFFnnFFFFFf.....",
|
|
359
|
-
"....fHHHLHHHHf......",
|
|
360
|
-
".....fHHHHHHf.......",
|
|
361
|
-
"......ffffff........",
|
|
362
|
-
".....TTTTTTTTT......",
|
|
363
|
-
"....TTTTtTTTTTT.....",
|
|
364
|
-
"...FTTTTtTTTTTF.....",
|
|
365
|
-
"...FFTTTtTTTTFF.....",
|
|
366
|
-
"...FFFTTtTTFFF......",
|
|
367
|
-
"....FFTTtTTFF.......",
|
|
368
|
-
".....TTTTTTT........",
|
|
369
|
-
".....TTTTTTT........",
|
|
370
|
-
"......TTTTT.........",
|
|
371
|
-
"......JJ.JJ.........",
|
|
372
|
-
".....JJJ.JJJ........",
|
|
373
|
-
".....JJ...JJ........",
|
|
374
|
-
"....jjj...jjj.......",
|
|
375
|
-
],
|
|
376
|
-
'sleep': [
|
|
377
|
-
".......kkkk.........",
|
|
378
|
-
"......kKKKKk........",
|
|
379
|
-
".....kKKKKKKk.......",
|
|
380
|
-
".....KKKKKKKKK......",
|
|
381
|
-
"....FFFFFFFFFFf.....",
|
|
382
|
-
"....FfFFFFfFFFF.....",
|
|
383
|
-
"....FFFFFFFFFFFf....",
|
|
384
|
-
"....FFFnnFFFFFf.....",
|
|
385
|
-
"....fHHHHHHHHf......",
|
|
386
|
-
".....fHHHHHHf.......",
|
|
387
|
-
"......ffffff........",
|
|
388
|
-
".....TTTTTTTTT......",
|
|
389
|
-
"....TTTTtTTTTTT.....",
|
|
390
|
-
"...FTTTTtTTTTTF.....",
|
|
391
|
-
"...FFTTTtTTTTFF.....",
|
|
392
|
-
"...FFFTTtTTFFF......",
|
|
393
|
-
"....FFTTtTTFF.......",
|
|
394
|
-
".....TTTTTTT........",
|
|
395
|
-
".....TTTTTTT........",
|
|
396
|
-
"......TTTTT.........",
|
|
397
|
-
"......JJ.JJ.........",
|
|
398
|
-
".....JJJ.JJJ........",
|
|
399
|
-
".....JJ...JJ........",
|
|
400
|
-
"....jjj...jjj.......",
|
|
401
|
-
],
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
# ═══════════════════════════════════════
|
|
405
|
-
# 6. 咕咕嘎嘎(黑刘海圆脸猫)
|
|
406
|
-
# 厚黑灰色刘海,白/灰圆脸,小嘴
|
|
407
|
-
# 20w × 18h
|
|
408
|
-
# ═══════════════════════════════════════
|
|
409
|
-
SPRITES['gugugaga'] = {
|
|
410
|
-
'normal': [
|
|
411
|
-
"......ddddddd.......",
|
|
412
|
-
".....dDDDDDDDd......",
|
|
413
|
-
"....dDDDDDDDDDd.....",
|
|
414
|
-
"...dDDDDDDDDDDDd....",
|
|
415
|
-
"..dDDDDDDDDDDDDDd...",
|
|
416
|
-
"..dDDDDDDDDDDDDDd...",
|
|
417
|
-
"..dDxDDDDDDxDDDDd...",
|
|
418
|
-
"..SSSSSSSSSSSSSSss..",
|
|
419
|
-
"..SSSSSSSSSSSSSSSs..",
|
|
420
|
-
"..SSSSSSSSSSSSSSss..",
|
|
421
|
-
"..sSSSSSRSSSSSSSs...",
|
|
422
|
-
"...sSSSSSSSSSSSs....",
|
|
423
|
-
"...sSSSSSSSSSSs.....",
|
|
424
|
-
"....sSWWWWWWSs......",
|
|
425
|
-
"....sWWWWWWWWs......",
|
|
426
|
-
".....sWWWWWWs.......",
|
|
427
|
-
"......ssssss........",
|
|
428
|
-
".......ss.ss........",
|
|
429
|
-
],
|
|
430
|
-
'happy': [
|
|
431
|
-
"......ddddddd.......",
|
|
432
|
-
".....dDDDDDDDd......",
|
|
433
|
-
"....dDDDDDDDDDd.....",
|
|
434
|
-
"...dDDDDDDDDDDDd....",
|
|
435
|
-
"..dDDDDDDDDDDDDDd...",
|
|
436
|
-
"..dDDDDDDDDDDDDDd...",
|
|
437
|
-
"..dDaDDDDDDaDDDDd...",
|
|
438
|
-
"..SSSSSSSSSSSSSSss..",
|
|
439
|
-
"..SSSSSSSSSSSSSSSs..",
|
|
440
|
-
"..SSSSSSSSSSSSSSss..",
|
|
441
|
-
"..sSSSSSLSSSSSSSs...",
|
|
442
|
-
"...sSSSSSSSSSSSs....",
|
|
443
|
-
"...sSSSSSSSSSSs.....",
|
|
444
|
-
"....sSWWWWWWSs......",
|
|
445
|
-
"....sWWWWWWWWs......",
|
|
446
|
-
".....sWWWWWWs.......",
|
|
447
|
-
"......ssssss........",
|
|
448
|
-
".......ss.ss........",
|
|
449
|
-
],
|
|
450
|
-
'sleep': [
|
|
451
|
-
"......ddddddd.......",
|
|
452
|
-
".....dDDDDDDDd......",
|
|
453
|
-
"....dDDDDDDDDDd.....",
|
|
454
|
-
"...dDDDDDDDDDDDd....",
|
|
455
|
-
"..dDDDDDDDDDDDDDd...",
|
|
456
|
-
"..dDDDDDDDDDDDDDd...",
|
|
457
|
-
"..dDsDDDDDDsDDDDd...",
|
|
458
|
-
"..SSSSSSSSSSSSSSss..",
|
|
459
|
-
"..SSSSSSSSSSSSSSSs..",
|
|
460
|
-
"..SSSSSSSSSSSSSSss..",
|
|
461
|
-
"..sSSSSSSSSSSSSSs...",
|
|
462
|
-
"...sSSSSSSSSSSSs....",
|
|
463
|
-
"...sSSSSSSSSSSs.....",
|
|
464
|
-
"....sSWWWWWWSs......",
|
|
465
|
-
"....sWWWWWWWWs......",
|
|
466
|
-
".....sWWWWWWs.......",
|
|
467
|
-
"......ssssss........",
|
|
468
|
-
".......ss.ss........",
|
|
469
|
-
],
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
# ── 渲染引擎 ──
|
|
473
|
-
|
|
474
|
-
def fg(r, g, b):
|
|
475
|
-
return f"\033[38;2;{r};{g};{b}m"
|
|
476
|
-
|
|
477
|
-
def bg(r, g, b):
|
|
478
|
-
return f"\033[48;2;{r};{g};{b}m"
|
|
479
|
-
|
|
480
|
-
RESET = "\033[0m"
|
|
481
|
-
BOLD = "\033[1m"
|
|
482
|
-
|
|
483
|
-
def render_pixel_pair(top_char, bot_char):
|
|
484
|
-
top_color = C.get(top_char)
|
|
485
|
-
bot_color = C.get(bot_char)
|
|
486
|
-
if top_color and bot_color:
|
|
487
|
-
if top_color == bot_color:
|
|
488
|
-
return f"{fg(*top_color)}█{RESET}"
|
|
489
|
-
else:
|
|
490
|
-
return f"{fg(*top_color)}{bg(*bot_color)}▀{RESET}"
|
|
491
|
-
elif top_color:
|
|
492
|
-
return f"{fg(*top_color)}▀{RESET}"
|
|
493
|
-
elif bot_color:
|
|
494
|
-
return f"{fg(*bot_color)}▄{RESET}"
|
|
495
|
-
else:
|
|
496
|
-
return " "
|
|
497
|
-
|
|
498
|
-
def render_sprite(name, state='normal', indent=4):
|
|
499
|
-
data = SPRITES.get(name, {}).get(state)
|
|
500
|
-
if not data:
|
|
501
|
-
print(f"未找到: {name}/{state}")
|
|
502
|
-
return
|
|
503
|
-
rows = list(data)
|
|
504
|
-
if len(rows) % 2 == 1:
|
|
505
|
-
max_len = max(len(r) for r in rows)
|
|
506
|
-
rows.append('.' * max_len)
|
|
507
|
-
max_len = max(len(r) for r in rows)
|
|
508
|
-
rows = [r.ljust(max_len, '.') for r in rows]
|
|
509
|
-
for i in range(0, len(rows), 2):
|
|
510
|
-
top_row = rows[i]
|
|
511
|
-
bot_row = rows[i + 1]
|
|
512
|
-
line = ""
|
|
513
|
-
for j in range(max_len):
|
|
514
|
-
tc = top_row[j] if j < len(top_row) else '.'
|
|
515
|
-
bc = bot_row[j] if j < len(bot_row) else '.'
|
|
516
|
-
line += render_pixel_pair(tc, bc)
|
|
517
|
-
print(f"{' ' * indent}{line}")
|
|
518
|
-
|
|
519
|
-
def render_card(name, state='normal'):
|
|
520
|
-
"""渲染单个角色带标题"""
|
|
521
|
-
names_zh = {
|
|
522
|
-
'bibilabu': '比比拉布(香蕉猫)',
|
|
523
|
-
'bagayalu': '八嘎呀路(坐姿水豚)',
|
|
524
|
-
'wodedaodun': '我的刀盾(趴姿水豚)',
|
|
525
|
-
'bababoyi': '巴巴博一(圆猫球)',
|
|
526
|
-
'waibibabu': '歪比巴卜(大胡子壮汉)',
|
|
527
|
-
'gugugaga': '咕咕嘎嘎(黑刘海猫)',
|
|
528
|
-
}
|
|
529
|
-
states_zh = {'normal': '普通', 'happy': '开心', 'sleep': '睡觉'}
|
|
530
|
-
title = names_zh.get(name, name)
|
|
531
|
-
st = states_zh.get(state, state)
|
|
532
|
-
print(f"\n {BOLD}{title}{RESET} · {st}")
|
|
533
|
-
render_sprite(name, state)
|
|
534
|
-
|
|
535
|
-
def render_all():
|
|
536
|
-
print()
|
|
537
|
-
print(" ═══════════════════════════════════")
|
|
538
|
-
print(f" 🐾 {BOLD}CodePet 像素角色画廊{RESET}")
|
|
539
|
-
print(" ═══════════════════════════════════")
|
|
540
|
-
for name in ['bibilabu', 'bagayalu', 'wodedaodun', 'bababoyi', 'waibibabu', 'gugugaga']:
|
|
541
|
-
for state in ['normal', 'happy', 'sleep']:
|
|
542
|
-
render_card(name, state)
|
|
543
|
-
print(" ───────────────────────────────────")
|
|
544
|
-
print()
|
|
545
|
-
|
|
546
|
-
# ── 入口 ──
|
|
547
|
-
if __name__ == '__main__':
|
|
548
|
-
name_map = {
|
|
549
|
-
'比比拉布': 'bibilabu', '八嘎呀路': 'bagayalu',
|
|
550
|
-
'我的刀盾': 'wodedaodun', '巴巴博一': 'bababoyi',
|
|
551
|
-
'歪比巴卜': 'waibibabu', '咕咕嘎嘎': 'gugugaga',
|
|
552
|
-
}
|
|
553
|
-
if len(sys.argv) < 2 or sys.argv[1] == 'all':
|
|
554
|
-
render_all()
|
|
555
|
-
else:
|
|
556
|
-
name = name_map.get(sys.argv[1], sys.argv[1])
|
|
557
|
-
state = sys.argv[2] if len(sys.argv) > 2 else 'normal'
|
|
558
|
-
render_card(name, state)
|