XMWAI 0.4.7__py3-none-any.whl → 0.4.9__py3-none-any.whl

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.

Potentially problematic release.


This version of XMWAI might be problematic. Click here for more details.

XMWAI/__init__.py CHANGED
@@ -4,10 +4,11 @@ from .bomb_core import bomb # 从子模块导入函数到顶层
4
4
  from .idiom_core import idiom, searchIdiom, get_json_path # 从子模块导入函数到顶层
5
5
  from .trial_class import make, get_file_content_as_base64, save_pic, detect_scale, open_image, get_file_content_as_base64_2, cartoon # 从子模块导入函数到顶层
6
6
  from .web_core import burger, cookbook # 从子模块导入函数到顶层
7
- from .snake_core import SnakeGame
8
-
7
+ from .effects_core import effects # 从子模块导入函数到顶层
8
+ from .fortune_core import fate, extract_number, extract_text, extract_fortune_description, get_default_data, download_image, web, fate_old # 从子模块导入函数到顶层
9
9
 
10
10
  __all__ = ["story", "photo", "reply", "poem", 'birthday', 'bomb',
11
11
  "idiom", "searchIdiom", "get_json_path", "crack", "show",
12
12
  "make", "get_file_content_as_base64", "save_pic", "detect_scale", "open_image",
13
- "get_file_content_as_base64_2", "cartoon", "burger", "cookbook", "SnakeGame"] # 可选:明确导出的内容
13
+ "get_file_content_as_base64_2", "cartoon", "burger", "cookbook",
14
+ "effects", "fate", "extract_number", "extract_text", "extract_fortune_description", "get_default_data", "download_image", "web", "fate_old"] # 可选:明确导出的内容
File without changes
XMWAI/assets/like.png ADDED
Binary file
XMWAI/effects_core.py ADDED
@@ -0,0 +1,205 @@
1
+ import turtle
2
+ import random
3
+ import time
4
+ import math
5
+ import tkinter as tk
6
+ from importlib.resources import files
7
+ from pathlib import Path
8
+
9
+ # ---------------------------
10
+ # 高亮色彩库
11
+ # ---------------------------
12
+ _ALL_VIBRANT_COLORS = [
13
+ "#FF0000", "#FF4500", "#FF8C00", "#FFD700", "#ADFF2F", "#00FF7F",
14
+ "#00FA9A", "#00FFFF", "#1E90FF", "#7B68EE", "#9932CC", "#FF69B4",
15
+ "#FF1493", "#FF00FF", "#FF7F50", "#FFA500", "#40E0D0", "#7FFF00",
16
+ "#FF5733", "#FFC300", "#DAF7A6", "#C70039", "#900C3F", "#581845"
17
+ ]
18
+
19
+ # ---------------------------
20
+ # 资源文件获取函数
21
+ # ---------------------------
22
+
23
+
24
+ def get_resource_path(filename: str) -> str:
25
+ """返回包内资源的绝对路径"""
26
+ return str(files("XMWAI.assets").joinpath(filename))
27
+
28
+ # ---------------------------
29
+ # 特效函数
30
+ # ---------------------------
31
+
32
+
33
+ def _effect_stars(screen):
34
+ """炫彩星星闪烁"""
35
+ t = turtle.Turtle(visible=False)
36
+ t.speed(0)
37
+ t.hideturtle()
38
+ screen.tracer(False)
39
+
40
+ stars = [(random.randint(-250, 250), random.randint(-180, 200),
41
+ random.randint(10, 30)) for _ in range(30)]
42
+
43
+ for _ in range(12): # 闪烁次数
44
+ t.clear()
45
+ for x, y, size in stars:
46
+ t.penup()
47
+ t.goto(x, y)
48
+ t.pendown()
49
+ t.color(random.choice(_ALL_VIBRANT_COLORS))
50
+ t.begin_fill()
51
+ for _ in range(5):
52
+ t.forward(size)
53
+ t.right(144)
54
+ t.end_fill()
55
+ screen.update()
56
+ time.sleep(0.07)
57
+
58
+ t.clear()
59
+ screen.update()
60
+
61
+
62
+ def _effect_like(screen, img_path=None, flash_times=1, flash_interval=0.2):
63
+ """点赞动画"""
64
+ screen.tracer(False)
65
+ canvas = screen.getcanvas()
66
+ img_path = img_path or get_resource_path("like.png")
67
+
68
+ tk_img = tk.PhotoImage(file=img_path)
69
+ screen._tk_img_ref = tk_img # 保持引用
70
+
71
+ w = screen.window_width()
72
+ h = screen.window_height()
73
+ img_id = canvas.create_image(w//4, h//4, image=tk_img)
74
+
75
+ for _ in range(flash_times * 2):
76
+ canvas.itemconfigure(img_id, state='normal')
77
+ screen.update()
78
+ time.sleep(flash_interval)
79
+ canvas.itemconfigure(img_id, state='hidden')
80
+ screen.update()
81
+ time.sleep(flash_interval)
82
+
83
+ canvas.delete(img_id)
84
+ screen.update()
85
+
86
+
87
+ def _effect_fireworks(screen):
88
+ """极速瞬爆烟花"""
89
+ t = turtle.Turtle(visible=False)
90
+ t.speed(0)
91
+ t.hideturtle()
92
+ screen.tracer(False)
93
+
94
+ fireworks = []
95
+ for _ in range(random.randint(3, 6)):
96
+ start_x = random.randint(-300, 300)
97
+ peak_y = random.randint(150, 280)
98
+ fireworks.append({
99
+ "x": start_x,
100
+ "y": peak_y,
101
+ "particles": [
102
+ (random.uniform(0, 360), random.uniform(
103
+ 80, 220), random.choice(_ALL_VIBRANT_COLORS))
104
+ for _ in range(random.randint(100, 180))
105
+ ],
106
+ "color": random.choice(["white", "gold", "yellow"])
107
+ })
108
+
109
+ for y in range(-250, 0, 80):
110
+ t.clear()
111
+ for fw in fireworks:
112
+ t.penup()
113
+ t.goto(fw["x"], y)
114
+ t.dot(8, "white")
115
+ screen.update()
116
+ time.sleep(0.01)
117
+
118
+ steps = 12
119
+ for step in range(1, steps + 1):
120
+ t.clear()
121
+ scale = step / steps
122
+ fade = 1 - scale * 0.7
123
+ for fw in fireworks:
124
+ for angle, dist, color in fw["particles"]:
125
+ r, g, b = screen.cv.winfo_rgb(color)
126
+ r, g, b = int((r / 256) * fade), int((g / 256)
127
+ * fade), int((b / 256) * fade)
128
+ fade_color = f"#{r:02x}{g:02x}{b:02x}"
129
+ x = fw["x"] + math.cos(math.radians(angle)) * \
130
+ (dist * scale ** 1.5)
131
+ y = fw["y"] + math.sin(math.radians(angle)) * \
132
+ (dist * scale ** 1.5)
133
+ t.penup()
134
+ t.goto(x, y)
135
+ if random.random() > 0.1:
136
+ t.dot(max(2, 10 - step * 0.3), fade_color)
137
+ if step < 4:
138
+ t.penup()
139
+ t.goto(fw["x"], fw["y"])
140
+ t.dot(40 - step * 4, fw["color"])
141
+ screen.update()
142
+ time.sleep(0.03)
143
+
144
+ for i in range(3):
145
+ t.clear()
146
+ for fw in fireworks:
147
+ t.penup()
148
+ t.goto(fw["x"], fw["y"])
149
+ if i % 2 == 0:
150
+ t.dot(25, "white")
151
+ else:
152
+ t.dot(18, "gold")
153
+ screen.update()
154
+ time.sleep(0.05)
155
+
156
+ t.clear()
157
+ screen.update()
158
+
159
+
160
+ def _effect_heart(screen):
161
+ """快速跳动的爱心效果"""
162
+ t = turtle.Turtle(visible=False)
163
+ t.speed(0)
164
+ screen.tracer(False)
165
+ t.color("red", "red")
166
+
167
+ for s in [0.5, 0.7, 0.9, 1.1, 0.9, 1.1, 0.9, 0.7, 0.5]:
168
+ t.clear()
169
+ t.begin_fill()
170
+ t.setheading(140)
171
+ t.forward(120 * s)
172
+ t.circle(-60 * s, 200)
173
+ t.left(120)
174
+ t.circle(-60 * s, 200)
175
+ t.forward(120 * s)
176
+ t.end_fill()
177
+ screen.update()
178
+ time.sleep(0.05)
179
+
180
+ t.clear()
181
+ t.penup()
182
+ t.goto(0, -100)
183
+ t.pendown()
184
+ t.color("black")
185
+ t.write("点赞!", align="center", font=("Arial", 24, "bold"))
186
+ screen.update()
187
+ time.sleep(0.5)
188
+ t.clear()
189
+ screen.update()
190
+
191
+
192
+ # ---------------------------
193
+ # 统一接口
194
+ # ---------------------------
195
+ def effects(screen, effect_name: str):
196
+ if effect_name == "stars":
197
+ _effect_stars(screen)
198
+ elif effect_name == "like":
199
+ _effect_like(screen)
200
+ elif effect_name == "fireworks":
201
+ _effect_fireworks(screen)
202
+ elif effect_name == "heart":
203
+ _effect_heart(screen)
204
+ else:
205
+ print(f"未知特效: {effect_name}")
XMWAI/fortune_core.py ADDED
@@ -0,0 +1,527 @@
1
+ import requests
2
+ import re
3
+ import os
4
+ from bs4 import BeautifulSoup
5
+ import urllib.parse
6
+
7
+
8
+ def fate(constellation):
9
+ dict_ = {"水瓶座": "aquarius",
10
+ "双鱼座": "pisces",
11
+ "白羊座": "aries",
12
+ "金牛座": "taurus",
13
+ "双子座": "gemini",
14
+ "巨蟹座": "cancer",
15
+ "狮子座": "leo",
16
+ "处女座": "virgo",
17
+ "天秤座": "libra",
18
+ "天蝎座": "scorpio",
19
+ "射手座": "sagittarius",
20
+ "摩羯座": "capricorn"}
21
+
22
+ url = "https://www.xzw.com/fortune/" + dict_[constellation] + "/"
23
+ headers = {
24
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
25
+ }
26
+
27
+ try:
28
+ response = requests.get(url, headers=headers, timeout=10)
29
+ response.encoding = 'utf-8'
30
+ content = response.text
31
+ except Exception as e:
32
+ print(f"获取数据失败: {e}")
33
+ return get_default_data()
34
+
35
+ # 使用BeautifulSoup解析HTML
36
+ soup = BeautifulSoup(content, 'html.parser')
37
+
38
+ # 获取详细运势数据
39
+ fortune_data = {}
40
+ indices = {}
41
+
42
+ # 提取综合运势评分(星星数量)
43
+ fortune_data['综合运势评分'] = 3 # 默认值
44
+
45
+ try:
46
+ # 方法1:从星星评分条提取 - 最准确的方法
47
+ star_bar = soup.select_one('span.star_m.star_blue em')
48
+ if star_bar:
49
+ style_width = star_bar.get('style', '')
50
+ width_match = re.search(r'width:\s*(\d+)px', style_width)
51
+ if width_match:
52
+ width_px = int(width_match.group(1))
53
+ # 每颗星20px,总宽度100px(5星)
54
+ stars_count = round(width_px / 20)
55
+ if 1 <= stars_count <= 5:
56
+ fortune_data['综合运势评分'] = stars_count
57
+
58
+ # 方法2:从图表数据中提取 - 备用方法
59
+ if fortune_data['综合运势评分'] == 3:
60
+ # 查找图表中的综合指数
61
+ chart_data_match = re.search(
62
+ r'"综合指数".*?data:\s*\[(.*?)\]', str(soup))
63
+ if chart_data_match:
64
+ data_str = chart_data_match.group(1)
65
+ numbers = re.findall(r'(\d+(?:\.\d+)?)', data_str)
66
+ if numbers:
67
+ # 取最新的综合指数
68
+ latest_score = float(numbers[-1])
69
+ # 将0-5分转换为1-5星
70
+ stars_count = max(1, min(5, round(latest_score)))
71
+ fortune_data['综合运势评分'] = stars_count
72
+
73
+ # 方法3:从其他星星评分元素中提取
74
+ if fortune_data['综合运势评分'] == 3:
75
+ # 查找其他可能的星星评分
76
+ star_elements = soup.find_all(
77
+ class_=re.compile(r'star_m|star_rating'))
78
+ for elem in star_elements:
79
+ em_elem = elem.find('em')
80
+ if em_elem and em_elem.get('style'):
81
+ width_match = re.search(
82
+ r'width:\s*(\d+)px', em_elem.get('style', ''))
83
+ if width_match:
84
+ width_px = int(width_match.group(1))
85
+ stars_count = round(width_px / 20)
86
+ if 1 <= stars_count <= 5:
87
+ fortune_data['综合运势评分'] = stars_count
88
+ break
89
+
90
+ except Exception as e:
91
+ print(f"提取星星评分失败: {e}")
92
+ fortune_data['综合运势评分'] = 3
93
+
94
+ # 提取各项指数 - 更精确的正则表达式
95
+ text_content = soup.get_text()
96
+
97
+ # 健康指数
98
+ health_patterns = [
99
+ r'健康指数[::]\s*(\d+)%?',
100
+ r'健康.*?\s*(\d+)%?',
101
+ r'健康.*?指数[::]\s*(\d+)'
102
+ ]
103
+ indices['健康指数'] = extract_number(text_content, health_patterns, 75)
104
+
105
+ # 商谈指数
106
+ discuss_patterns = [
107
+ r'商谈指数[::]\s*(\d+)%?',
108
+ r'商谈.*?\s*(\d+)%?',
109
+ r'商谈.*?指数[::]\s*(\d+)'
110
+ ]
111
+ indices['商谈指数'] = extract_number(text_content, discuss_patterns, 70)
112
+
113
+ # 幸运颜色
114
+ # 幸运颜色 - 修复提取逻辑,避免包含"幸运数字"
115
+ color_patterns = [
116
+ r'幸运颜色[::]\s*([\u4e00-\u9fa5]+?)(?:幸运数字|$)', # 只提取颜色,到"幸运数字"为止
117
+ r'幸运色[::]\s*([\u4e00-\u9fa5]+?)(?:幸运数字|$)', # 只提取颜色,到"幸运数字"为止
118
+ r'颜色.*?幸运[::]\s*([\u4e00-\u9fa5]+?)(?:幸运数字|$)' # 只提取颜色,到"幸运数字"为止
119
+ ]
120
+ indices['幸运颜色'] = extract_text(text_content, color_patterns, '蓝色')
121
+
122
+ # 幸运数字
123
+ number_patterns = [
124
+ r'幸运数字[::]\s*(\d+)',
125
+ r'幸运.*?数字[::]\s*(\d+)',
126
+ r'数字.*?幸运[::]\s*(\d+)'
127
+ ]
128
+ indices['幸运数字'] = extract_number(text_content, number_patterns, 7)
129
+
130
+ # 获取今日综合运势解读
131
+ comprehensive_text = extract_fortune_description(soup, text_content)
132
+
133
+ return {
134
+ 'indices': indices,
135
+ 'comprehensive_text': comprehensive_text,
136
+ 'stars': fortune_data['综合运势评分']
137
+ }
138
+
139
+
140
+ def extract_number(text, patterns, default):
141
+ """从文本中提取数字"""
142
+ for pattern in patterns:
143
+ match = re.search(pattern, text, re.IGNORECASE)
144
+ if match:
145
+ try:
146
+ return int(match.group(1))
147
+ except:
148
+ continue
149
+ return default
150
+
151
+
152
+ def extract_text(text, patterns, default):
153
+ """从文本中提取文本"""
154
+ for pattern in patterns:
155
+ match = re.search(pattern, text, re.IGNORECASE)
156
+ if match:
157
+ return match.group(1).strip()
158
+ return default
159
+
160
+
161
+ def extract_fortune_description(soup, text_content):
162
+ """提取运势描述"""
163
+ try:
164
+ # 尝试多种方式提取运势描述
165
+ description = ""
166
+
167
+ # 方式1:查找综合运势区域
168
+ fortune_section = soup.find('div', class_='c_cont')
169
+ if fortune_section:
170
+ desc_text = fortune_section.find('span')
171
+ if desc_text:
172
+ description = desc_text.get_text().strip()
173
+
174
+ # 方式2:使用正则表达式
175
+ if not description:
176
+ patterns = [
177
+ r'综合运势</strong><span>(.*?)</span>',
178
+ r'整体运势[::]?(.*?)[。!?]',
179
+ r'今日运势[::]?(.*?)[。!?]',
180
+ r'今日.*?运势[::]?(.*?)[。!?]'
181
+ ]
182
+
183
+ for pattern in patterns:
184
+ match = re.search(pattern, str(soup), re.DOTALL)
185
+ if match:
186
+ description = match.group(1).strip()
187
+ break
188
+
189
+ # 清理HTML标签和多余空格
190
+ if description:
191
+ description = re.sub(r'<[^>]+>', '', description)
192
+ description = re.sub(r'\s+', ' ', description)
193
+
194
+ # 彻底清理所有可能的干扰信息 - 使用更通用的正则表达式
195
+ # 匹配"星"后面跟着任意字符(包括字母、数字、特殊符号)再跟着"座"的模式
196
+ description = re.sub(
197
+ r'星[^\u4e00-\u9fa5]*座[^\u4e00-\u9fa5]*屋?', '', description)
198
+
199
+ # 清理单独的"星"字后面跟着非中文字符
200
+ description = re.sub(r'星[^\u4e00-\u9fa5\w]*', '', description)
201
+
202
+ # 清理"座"字后面跟着非中文字符
203
+ description = re.sub(r'座[^\u4e00-\u9fa5\w]*', '', description)
204
+
205
+ # 清理"屋"字前面可能有的干扰字符
206
+ description = re.sub(r'[^\u4e00-\u9fa5\w]*屋', '', description)
207
+
208
+ # 清理残留的英文、数字、特殊字符组合
209
+ description = re.sub(r'[a-zA-Z0-9]+$', '', description)
210
+
211
+ description = description.strip()
212
+
213
+ if len(description) > 10: # 确保有有效内容
214
+ return description
215
+
216
+ # 方式3:从页面主要内容中提取
217
+ if not description:
218
+ main_content = soup.find('div', class_='main')
219
+ if main_content:
220
+ paragraphs = main_content.find_all('p')
221
+ for p in paragraphs:
222
+ text = p.get_text().strip()
223
+ if len(text) > 20 and '运势' in text:
224
+ description = text
225
+ break
226
+
227
+ return description if description else "今日运势平稳,保持积极心态,顺其自然即可。"
228
+
229
+ except Exception as e:
230
+ print(f"提取运势描述失败: {e}")
231
+ return "今日运势平稳,保持积极心态,顺其自然即可。"
232
+
233
+
234
+ def get_default_data():
235
+ """返回默认数据"""
236
+ return {
237
+ 'indices': {
238
+ '健康指数': 75,
239
+ '商谈指数': 70,
240
+ '幸运颜色': '蓝色',
241
+ '幸运数字': 7
242
+ },
243
+ 'comprehensive_text': "今日运势平稳,保持积极心态,顺其自然即可。",
244
+ 'stars': 3
245
+ }
246
+
247
+
248
+ def download_image(url, save_path):
249
+ """下载图片到本地"""
250
+ try:
251
+ headers = {
252
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
253
+ }
254
+ response = requests.get(url, timeout=15, headers=headers)
255
+ response.raise_for_status()
256
+
257
+ if len(response.content) > 1024:
258
+ with open(save_path, 'wb') as f:
259
+ f.write(response.content)
260
+ return True
261
+ except Exception as e:
262
+ print(f"下载图片失败: {url} - {e}")
263
+ return False
264
+
265
+
266
+ def web(avatar, zodiac, trait, fortune_data):
267
+ output_dir = "output"
268
+ if not os.path.exists(output_dir):
269
+ os.makedirs(output_dir)
270
+
271
+ # 修复路径分隔符问题
272
+ avatar_relative_path = avatar.replace('\\', '/')
273
+ if not avatar_relative_path.startswith('../'):
274
+ avatar_relative_path = '../' + avatar_relative_path
275
+
276
+ # 获取运势数据
277
+ indices = fortune_data.get('indices', {})
278
+ comprehensive_text = fortune_data.get('comprehensive_text', trait)
279
+ stars = fortune_data.get('stars', 3)
280
+
281
+ # 生成星星图标
282
+ star_icons = '⭐' * stars + '☆' * (5 - stars)
283
+
284
+ # 完整的HTML页面
285
+ html_code = f"""<!DOCTYPE html>
286
+ <html lang="zh-CN">
287
+ <head>
288
+ <meta charset="UTF-8">
289
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
290
+ <title>今日运势 - {zodiac}</title>
291
+ <script src="https://cdn.tailwindcss.com"></script>
292
+ <style>
293
+ body {{
294
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
295
+ min-height: 100vh;
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ padding: 20px;
300
+ font-family: 'Arial', sans-serif;
301
+ }}
302
+
303
+ .fortune-container {{
304
+ background: rgba(255, 255, 255, 0.1);
305
+ backdrop-filter: blur(10px);
306
+ border-radius: 20px;
307
+ padding: 40px;
308
+ max-width: 600px;
309
+ width: 100%;
310
+ box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
311
+ border: 1px solid rgba(255, 255, 255, 0.18);
312
+ color: white;
313
+ }}
314
+
315
+ .zodiac-header {{
316
+ text-align: center;
317
+ margin-bottom: 30px;
318
+ }}
319
+
320
+ .zodiac-avatar {{
321
+ width: 100px;
322
+ height: 100px;
323
+ border-radius: 50%;
324
+ margin: 0 auto 20px;
325
+ background: rgba(255, 255, 255, 0.2);
326
+ display: flex;
327
+ align-items: center;
328
+ justify-content: center;
329
+ border: 3px solid rgba(255, 255, 255, 0.3);
330
+ }}
331
+
332
+ .zodiac-avatar img {{
333
+ width: 80px;
334
+ height: 80px;
335
+ border-radius: 50%;
336
+ }}
337
+
338
+ .stars-display {{
339
+ text-align: center;
340
+ margin-bottom: 25px;
341
+ background: rgba(255, 255, 255, 0.1);
342
+ padding: 20px;
343
+ border-radius: 15px;
344
+ }}
345
+
346
+ .stars {{
347
+ font-size: 28px;
348
+ margin: 10px 0;
349
+ letter-spacing: 2px;
350
+ }}
351
+
352
+ .score {{
353
+ font-size: 20px;
354
+ font-weight: bold;
355
+ color: #ffd700;
356
+ margin-top: 5px;
357
+ }}
358
+
359
+ .indices-grid {{
360
+ display: grid;
361
+ grid-template-columns: 1fr 1fr;
362
+ gap: 15px;
363
+ margin-bottom: 25px;
364
+ }}
365
+
366
+ .index-item {{
367
+ background: rgba(255, 255, 255, 0.15);
368
+ padding: 15px;
369
+ border-radius: 12px;
370
+ text-align: center;
371
+ border: 1px solid rgba(255, 255, 255, 0.1);
372
+ transition: transform 0.3s ease;
373
+ }}
374
+
375
+ .index-item:hover {{
376
+ transform: translateY(-2px);
377
+ background: rgba(255, 255, 255, 0.2);
378
+ }}
379
+
380
+ .label {{
381
+ display: block;
382
+ font-size: 14px;
383
+ margin-bottom: 8px;
384
+ opacity: 0.8;
385
+ font-weight: 500;
386
+ }}
387
+
388
+ .value {{
389
+ font-size: 18px;
390
+ font-weight: bold;
391
+ color: #fff;
392
+ }}
393
+
394
+ .color-badge {{
395
+ padding: 4px 12px;
396
+ border-radius: 20px;
397
+ color: white;
398
+ font-size: 14px;
399
+ display: inline-block;
400
+ }}
401
+
402
+ .comprehensive-section {{
403
+ background: rgba(255, 255, 255, 0.15);
404
+ padding: 25px;
405
+ border-radius: 15px;
406
+ margin-top: 20px;
407
+ border-left: 4px solid #ffd700;
408
+ }}
409
+
410
+ .comprehensive-section h3 {{
411
+ color: #ffd700;
412
+ font-size: 20px;
413
+ margin-bottom: 15px;
414
+ font-weight: bold;
415
+ }}
416
+
417
+ .comprehensive-text {{
418
+ font-size: 16px;
419
+ line-height: 1.8;
420
+ text-align: justify;
421
+ opacity: 0.95;
422
+ }}
423
+
424
+ .footer {{
425
+ text-align: center;
426
+ margin-top: 30px;
427
+ font-size: 14px;
428
+ opacity: 0.7;
429
+ font-style: italic;
430
+ }}
431
+ </style>
432
+ </head>
433
+ <body>
434
+ <div class="fortune-container">
435
+ <div class="zodiac-header">
436
+ <div class="zodiac-avatar">
437
+ <img src="{avatar_relative_path}" alt="{zodiac}">
438
+ </div>
439
+ <h1 style="font-size: 32px; margin-bottom: 10px;">{zodiac}</h1>
440
+ <p style="font-size: 18px; opacity: 0.8;">{trait}</p>
441
+ </div>
442
+
443
+ <div class="stars-display">
444
+ <h3 style="font-size: 22px; margin-bottom: 10px;">✨ 今日综合运势 ✨</h3>
445
+ <div class="stars">{star_icons}</div>
446
+ <div class="score">{stars}/5 星</div>
447
+ </div>
448
+
449
+ <div class="indices-grid">
450
+ <div class="index-item">
451
+ <span class="label">💗 健康指数</span>
452
+ <span class="value">{indices.get('健康指数', 75)}%</span>
453
+ </div>
454
+ <div class="index-item">
455
+ <span class="label">💬 商谈指数</span>
456
+ <span class="value">{indices.get('商谈指数', 70)}%</span>
457
+ </div>
458
+ <div class="index-item">
459
+ <span class="label">🎨 幸运颜色</span>
460
+ <span class="value">
461
+ <span class="color-badge" style="background-color: {indices.get('幸运颜色', '蓝色')}">
462
+ {indices.get('幸运颜色', '蓝色')}
463
+ </span>
464
+ </span>
465
+ </div>
466
+ <div class="index-item">
467
+ <span class="label">🔢 幸运数字</span>
468
+ <span class="value">{indices.get('幸运数字', 7)}</span>
469
+ </div>
470
+ </div>
471
+
472
+ <div class="comprehensive-section">
473
+ <h3>📊 今日综合运势解读</h3>
474
+ <div class="comprehensive-text">
475
+ {comprehensive_text}
476
+ </div>
477
+ </div>
478
+
479
+ <div class="footer">
480
+ ✨ 星座运势仅供参考,保持积极心态最重要 ✨
481
+ </div>
482
+ </div>
483
+ </body>
484
+ </html>"""
485
+
486
+ output_path = os.path.join(output_dir, f"{zodiac}.html")
487
+ with open(output_path, "w", encoding="utf-8") as f:
488
+ f.write(html_code)
489
+
490
+ print(f"✅ 已生成网页: {output_path}")
491
+
492
+ try:
493
+ os.startfile(output_path)
494
+ except:
495
+ try:
496
+ os.system(f"start {output_path}")
497
+ except:
498
+ print(f"请手动打开: {output_path}")
499
+
500
+ # 向后兼容的原始函数
501
+
502
+
503
+ def fate_old(constellation):
504
+ dict_ = {"水瓶座": "aquarius",
505
+ "双鱼座": "pisces",
506
+ "白羊座": "aries",
507
+ "金牛座": "taurus",
508
+ "双子座": "gemini",
509
+ "巨蟹座": "cancer",
510
+ "狮子座": "leo",
511
+ "处女座": "virgo",
512
+ "天秤座": "libra",
513
+ "天蝎座": "scorpio",
514
+ "射手座": "sagittarius",
515
+ "摩羯座": "capricorn"}
516
+
517
+ url = "https://www.xzw.com/fortune/" + dict_[constellation] + "/"
518
+ response = requests.get(url)
519
+ response.encoding = 'utf-8'
520
+ content = response.text
521
+
522
+ try:
523
+ detail_comprehensive = re.findall(
524
+ '综合运势</strong><span>(.*?)</span>', content)[0]
525
+ except:
526
+ detail_comprehensive = "暂无数据"
527
+ return detail_comprehensive
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: XMWAI
3
- Version: 0.4.7
3
+ Version: 0.4.9
4
4
  Summary: Small code King AI related library
5
5
  Home-page: https://github.com/Tonykai88/XMWAI.git
6
6
  Author: pydevelopment
@@ -18,6 +18,7 @@ Requires-Dist: numpy>=1.26.0
18
18
  Requires-Dist: flask>=3.1.0
19
19
  Requires-Dist: pyecharts>=2.0.8
20
20
  Requires-Dist: cvzone>=1.6.1
21
+ Requires-Dist: beautifulsoup4>=4.13.3
21
22
  Dynamic: author
22
23
  Dynamic: author-email
23
24
  Dynamic: classifier
@@ -1,19 +1,14 @@
1
- XMWAI/__init__.py,sha256=RFHMM1PF3ELvHSMeKe2G2E0astGPum2ezuf8Hjw4D6g,968
1
+ XMWAI/__init__.py,sha256=feCsI7uB0dSe6wRdXRruYfiIYIWIznnESaqBclnl2D8,1320
2
2
  XMWAI/bomb_core.py,sha256=h2ZPH3SuoG2L_XOf1dcK8p3lhw5QzhneWl2yMLj1RiU,1819
3
3
  XMWAI/core.py,sha256=rOXj7FnewSdnzBcFLjpnBtrOTCsvMfiycIcdPDagxho,10012
4
+ XMWAI/effects_core.py,sha256=QDcU08HZ6ODLRD7l_wZ2M_UtC6Iayg2vt3oFcY6bsHk,5915
5
+ XMWAI/fortune_core.py,sha256=s-QlM48s6-0LGof9Jltt-rtbV9thQ8mzNYh8G9d5nPk,18046
4
6
  XMWAI/idiom_core.py,sha256=yU-VHhqqoutVm6GVikcjL3m9yuB1hUsFBpPYvwY4n5g,1689
5
7
  XMWAI/magic_core.py,sha256=Ms4b12PJ8rjsmceg1VUqWCWx2ebvdhLp4sIF6K_Vaok,3491
6
- XMWAI/snake_core.py,sha256=fMm65EUfEmJK9nat9EBKz-ysmo8luTDx8N3vZ320aZE,16055
7
8
  XMWAI/trial_class.py,sha256=fPsl7BZvhzch2FOIG4azr999kjtoly53Acm3LqL8f98,9724
8
9
  XMWAI/web_core.py,sha256=7awPg1kYW3lYrbgylqJvUF3g050bn6H21PgmQ7Kv1wA,10927
9
- XMWAI/assets/1.png,sha256=eEuKH_M_q3tc_O2bYnuLOsRP-NlJHIbNg0pgrKXEEjw,139720
10
- XMWAI/assets/g.png,sha256=hr9hlKJ7y95X-g6-tllrzDNgL1WQkbq5cA5L4jASEAM,11686
11
- XMWAI/assets/h.png,sha256=qO-kJJOPA8qUth5rqLeOVa_6_n7tU-ABQ14O0EW_YCE,14929
12
- XMWAI/assets/l.png,sha256=Urm6LxH33HID6ZZbs2oMViUk4GiZ3upLPdsrNU8FlP0,9921
13
- XMWAI/assets/m.png,sha256=4tl9Rb2JoMD7XLMj3w8jg-92y6D7O-1u0sZCYEoeUtk,10303
14
- XMWAI/assets/s.png,sha256=v_qmHGmSPhG838vXQdUR2ZX_Z5KLI8T46kaR4P_ktUg,15120
15
- XMWAI/assets/t.png,sha256=GebzA2UWhXn4u62UeuUjitdpwnJvnxfZ2z_4MFlxvm8,12838
16
- XMWAI/assets/微软雅黑.ttf,sha256=dpc4EmGE1ojdwHjfIwTdr3olyEDAk3FwyreQSC9AdQ8,15043584
10
+ XMWAI/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ XMWAI/assets/like.png,sha256=KOdVR3AXLSHTD-fZ0Efspr52qUpN3QrVjoTmww9AyRw,41801
17
12
  XMWAI/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
13
  XMWAI/file/idiom.json,sha256=HUtPRUzhxBbWoasjadbmbA_5ngQ5AXLu9weQSZ4hzhk,10319857
19
14
  XMWAI/gif/0.gif,sha256=LGpAReVTyZEb1J-bWYTpxxHbIxmLJ-3wA0lw4cBqdsY,303
@@ -116,8 +111,8 @@ XMWAI/static/images/tomato.png,sha256=FEOEAOdUhW_BDFgTpxOkYc0I5Iu29_gtHb3RIPEej0
116
111
  XMWAI/templates/burger.html,sha256=vDnxpSW8phetyScySsalScZnFKl3LNpy5lJjKxGXgbI,3320
117
112
  XMWAI/templates/nutrition_pie.html,sha256=yJVXI28i-UfvF0xOXGSNLMb8oCJNhh2J3zoRDr5_7DM,5567
118
113
  XMWAI/templates/创意菜谱.html,sha256=RcDgH58QLyUJ9A59wobu3wvchGBY1snVsXcZQZam5M0,4805
119
- xmwai-0.4.7.dist-info/licenses/LICENSE.txt,sha256=bcaIQMrIhdQ3O-PoZlexjmW6h-wLGvHxh5Oksl4ohtc,1066
120
- xmwai-0.4.7.dist-info/METADATA,sha256=TFk6r7_ikJKUfVXu41zaNzzL1LppOUdYJGplWcZHQeg,1227
121
- xmwai-0.4.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
122
- xmwai-0.4.7.dist-info/top_level.txt,sha256=yvGcDI-sggK5jqd9wz0saipZvk3oIE3hNGHlqUjxf0c,6
123
- xmwai-0.4.7.dist-info/RECORD,,
114
+ xmwai-0.4.9.dist-info/licenses/LICENSE.txt,sha256=bcaIQMrIhdQ3O-PoZlexjmW6h-wLGvHxh5Oksl4ohtc,1066
115
+ xmwai-0.4.9.dist-info/METADATA,sha256=g_4FQgFK8XJ4uxIRvOr7nXQGk2m_lDxKKbS7E1eB16M,1266
116
+ xmwai-0.4.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
117
+ xmwai-0.4.9.dist-info/top_level.txt,sha256=yvGcDI-sggK5jqd9wz0saipZvk3oIE3hNGHlqUjxf0c,6
118
+ xmwai-0.4.9.dist-info/RECORD,,
XMWAI/assets/1.png DELETED
Binary file
XMWAI/assets/g.png DELETED
Binary file
XMWAI/assets/h.png DELETED
Binary file
XMWAI/assets/l.png DELETED
Binary file
XMWAI/assets/m.png DELETED
Binary file
XMWAI/assets/s.png DELETED
Binary file
XMWAI/assets/t.png DELETED
Binary file
XMWAI/snake_core.py DELETED
@@ -1,417 +0,0 @@
1
- import cv2
2
- import math
3
- import random
4
- import cvzone
5
- import numpy as np
6
- from cvzone.HandTrackingModule import HandDetector
7
- from PIL import ImageFont, ImageDraw, Image
8
- import time
9
- from importlib.resources import files
10
- from pathlib import Path
11
- import os
12
-
13
-
14
- def get_resource_path(filename: str) -> str:
15
- """返回 snake_core 模块内资源的绝对路径"""
16
- return str(files("XMWAI.assets").joinpath(filename))
17
-
18
-
19
- class SnakeGame:
20
- def __init__(self, width=720, height=720, snakeInitLength=150, snakeGrowth=50,
21
- snakeLineWidth=10, snakeHeadSize=15, foodPaths=None, foodNames=None, foodScores=None,
22
- obstaclePaths=None, fontPath=None):
23
-
24
- self.resolution = (width, height)
25
- self.snakeInitLength = snakeInitLength
26
- self._snakeGrowth = snakeGrowth
27
- self._snakeHeadSize = snakeHeadSize
28
- self._foodScores = foodScores if foodScores is not None else [3, 2, 1]
29
- self.snakeLineWidth = snakeLineWidth
30
- self.fontPath = fontPath or get_resource_path("微软雅黑.ttf")
31
-
32
- if foodPaths is None:
33
- foodPaths = [get_resource_path(f)
34
- for f in ["h.png", "s.png", "t.png"]]
35
- if foodNames is None:
36
- foodNames = ["汉堡", "薯条", "甜甜圈"]
37
- if obstaclePaths is None:
38
- obstaclePaths = [get_resource_path(f)
39
- for f in ["g.png", "l.png", "m.png"]]
40
-
41
- self.foodPaths = foodPaths
42
- self.foodNames = foodNames
43
- self.obstaclePaths = obstaclePaths
44
-
45
- self.cap = None
46
- self.detector = None
47
- self.snake = None
48
- self.foodManager = None
49
- self.obstacleManager = None
50
- self.img = None
51
- self.overlayTexts = []
52
-
53
- self.timer = 30
54
- self.start_time = None
55
-
56
- self._init_game_objects()
57
- self.open_window()
58
-
59
- # ---------------- 属性自动同步 ----------------
60
- @property
61
- def snakeHeadSize(self):
62
- return self._snakeHeadSize
63
-
64
- @snakeHeadSize.setter
65
- def snakeHeadSize(self, value):
66
- self._snakeHeadSize = value
67
- if self.snake:
68
- self.snake.headSize = value
69
-
70
- @property
71
- def foodScores(self):
72
- return self._foodScores
73
-
74
- @foodScores.setter
75
- def foodScores(self, scores):
76
- self._foodScores = scores
77
- if self.foodManager:
78
- self.foodManager.foodScores = scores
79
-
80
- @property
81
- def snakeGrowth(self):
82
- return self._snakeGrowth
83
-
84
- @snakeGrowth.setter
85
- def snakeGrowth(self, value):
86
- self._snakeGrowth = value
87
-
88
- # ---------------- 工具函数 ----------------
89
- def _putChineseText(self, img, text, pos, fontSize=40, color=(0, 0, 255)):
90
- img_pil = Image.fromarray(img)
91
- draw = ImageDraw.Draw(img_pil)
92
- try:
93
- font = ImageFont.truetype(self.fontPath, fontSize)
94
- except OSError:
95
- font = ImageFont.load_default()
96
- draw.text(pos, text, font=font, fill=color)
97
- return np.array(img_pil)
98
-
99
- def _init_game_objects(self):
100
- self.snake = self.Snake(color=(0, 0, 255), initLength=self.snakeInitLength,
101
- lineWidth=self.snakeLineWidth, headSize=self._snakeHeadSize)
102
- self.foodManager = self.FoodManager(
103
- self.foodPaths, self.foodNames, self._foodScores)
104
- self.obstacleManager = self.ObstacleManager(self.obstaclePaths)
105
- self.obstacleManager.randomObstacles()
106
-
107
- def _render_frame(self, show_food=True, show_obstacle=True):
108
- if self.cap is None:
109
- return
110
- success, self.img = self.cap.read()
111
- if not success:
112
- return
113
- self.img = cv2.flip(self.img, 1)
114
- hands, self.img = self.detector.findHands(self.img, flipType=False)
115
- player_head = tuple(hands[0]['lmList'][8][0:2]) if hands else None
116
-
117
- if not self.snake.gameOver and player_head:
118
- self.snake.update(self.img, player_head, self.obstacleManager)
119
-
120
- if not self.snake.gameOver and player_head and show_food:
121
- cx, cy = player_head
122
- rx, ry = self.foodManager.foodPoint
123
- w, h = self.foodManager.wFood, self.foodManager.hFood
124
- if rx - w//2 <= cx <= rx + w//2 and ry - h//2 <= cy <= ry + h//2:
125
- self.snake.score += self.foodManager.foodScores[self.foodManager.foodIndex]
126
- self.snake.allowedLength += self._snakeGrowth
127
- self.foodManager.randomFoodLocation(self.obstacleManager)
128
-
129
- if show_obstacle:
130
- self.img = self.obstacleManager.draw(self.img)
131
- self.obstacleManager.moveObstacles(*self.resolution)
132
-
133
- if show_food:
134
- self.img = self.foodManager.draw(self.img)
135
-
136
- # ---------------- 对外接口 ----------------
137
- def open_window(self):
138
- self.cap = cv2.VideoCapture(0)
139
- self.cap.set(3, self.resolution[0])
140
- self.cap.set(4, self.resolution[1])
141
- self.detector = HandDetector(detectionCon=0.7, maxHands=1)
142
- success, self.img = self.cap.read()
143
- if not success:
144
- print("摄像头打开失败")
145
- return
146
- self.img = cv2.flip(self.img, 1)
147
- cv2.imshow("AI Snake", self.img)
148
- cv2.waitKey(1)
149
-
150
- def hand(self):
151
- if self.cap is None:
152
- print("请先调用 open_window()")
153
- return
154
- if self.detector is None:
155
- self.detector = HandDetector(detectionCon=0.8, maxHands=1)
156
- while True:
157
- success, self.img = self.cap.read()
158
- if not success:
159
- break
160
- self.img = cv2.flip(self.img, 1)
161
- hands, self.img = self.detector.findHands(self.img, flipType=False)
162
- cv2.imshow("AI Snake", self.img)
163
- key = cv2.waitKey(1) & 0xFF
164
- if hands or key == ord('q'):
165
- break
166
-
167
- def display(self):
168
- if self.img is None:
169
- self._render_frame(show_food=False)
170
- self.foodManager.randomFoodLocation(self.obstacleManager)
171
- self._render_frame(show_food=True)
172
- self.overlayTexts = [
173
- (f'玩家分数:{self.snake.score}', (50, 50), 30, (255, 0, 255)),
174
- (f'倒计时:{self.timer} 秒', (50, 120), 30, (255, 0, 255))
175
- ]
176
- img_copy = self.img.copy()
177
- for txt, pos, size, color in self.overlayTexts:
178
- img_copy = self._putChineseText(img_copy, txt, pos, size, color)
179
- cv2.imshow("AI Snake", img_copy)
180
- cv2.waitKey(1)
181
-
182
- # ---------------- 重置游戏 ----------------
183
- def reset_game(self):
184
- self.snake.reset()
185
- self.snake.headSize = self._snakeHeadSize
186
- self.obstacleManager.randomObstacles()
187
- self.foodManager.foodScores = self._foodScores
188
- self.foodManager.randomFoodLocation(self.obstacleManager)
189
- self.start_time = time.time()
190
- self.timer = 30
191
- if self.cap is None or not self.cap.isOpened():
192
- self.open_window()
193
-
194
- # ---------------- 游戏结束 ----------------
195
- def gameover(self, path=None, size=(100, 100)):
196
- if path is None:
197
- path = get_resource_path("1.png")
198
-
199
- if os.path.exists(path):
200
- gameover_img = cv2.imread(path)
201
- gameover_img = cv2.resize(gameover_img, self.resolution)
202
- else:
203
- gameover_img = np.zeros(
204
- (self.resolution[1], self.resolution[0], 3), np.uint8)
205
- cv2.putText(gameover_img, "Game Over Image Missing!", (50, 100),
206
- cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 3)
207
-
208
- gameover_img = self._putChineseText(
209
- gameover_img, f"最终分数:{self.snake.score}", size, 40, (68, 84, 106))
210
-
211
- while True:
212
- cv2.imshow("AI Snake", gameover_img)
213
- key = cv2.waitKey(0) & 0xFF
214
- if key == ord('r'): # 重启游戏
215
- self.reset_game()
216
- self.start()
217
- break
218
- elif key == ord('q'): # 退出游戏
219
- if self.cap is not None:
220
- self.cap.release()
221
- cv2.destroyAllWindows()
222
- break
223
-
224
- # ---------------- 游戏主循环 ----------------
225
- def start(self):
226
- if self.cap is None or not self.cap.isOpened():
227
- self.open_window()
228
- self.start_time = time.time()
229
- self.timer = 30
230
-
231
- while True:
232
- if self.snake.gameOver or self.timer == 0:
233
- self.gameover()
234
- break
235
-
236
- self._render_frame(show_food=True, show_obstacle=True)
237
- elapsed = int(time.time() - self.start_time)
238
- self.timer = max(0, 30 - elapsed)
239
-
240
- self.overlayTexts = [
241
- (f'玩家分数:{self.snake.score}', (50, 50), 30, (255, 0, 255)),
242
- (f'倒计时:{self.timer} 秒', (50, 120), 30, (255, 0, 255))
243
- ]
244
-
245
- if self.img is not None:
246
- img_copy = self.img.copy()
247
- for txt, pos, size, color in self.overlayTexts:
248
- img_copy = self._putChineseText(
249
- img_copy, txt, pos, size, color)
250
- cv2.imshow("AI Snake", img_copy)
251
-
252
- key = cv2.waitKey(1)
253
- if key == ord('r'): # 游戏中途重置
254
- self.reset_game()
255
- elif key == ord('q'):
256
- if self.cap is not None:
257
- self.cap.release()
258
- cv2.destroyAllWindows()
259
- break
260
-
261
- # ---------------- 内部类 ----------------
262
- class Snake:
263
- def __init__(self, color, initLength=150, lineWidth=10, headSize=15):
264
- self.points = []
265
- self.currentLength = 0
266
- self.allowedLength = initLength
267
- self.previousHead = None
268
- self.score = 0
269
- self.color = color
270
- self.gameOver = False
271
- self.lineWidth = lineWidth
272
- self.headSize = headSize
273
-
274
- def reset(self):
275
- self.points = []
276
- self.currentLength = 0
277
- self.allowedLength = 150
278
- self.previousHead = None
279
- self.score = 0
280
- self.gameOver = False
281
-
282
- def update(self, imgMain, currentHead, obstacleManager=None):
283
- if self.gameOver:
284
- return
285
- cx, cy = currentHead
286
- if cx is None or cy is None:
287
- return
288
- if self.previousHead is None:
289
- self.previousHead = (cx, cy)
290
- px, py = self.previousHead
291
-
292
- alpha = 0.7
293
- cx = int(px * (1 - alpha) + cx * alpha)
294
- cy = int(py * (1 - alpha) + cy * alpha)
295
-
296
- maxStep = 50
297
- dx = cx - px
298
- dy = cy - py
299
- distance = math.hypot(dx, dy)
300
- if distance > maxStep:
301
- steps = int(distance / maxStep)
302
- for i in range(1, steps + 1):
303
- ix = int(px + dx * i / steps)
304
- iy = int(py + dy * i / steps)
305
- self.points.append((ix, iy))
306
- self.currentLength += maxStep
307
- else:
308
- self.points.append((cx, cy))
309
- self.currentLength += distance
310
-
311
- self.previousHead = (cx, cy)
312
-
313
- while self.currentLength > self.allowedLength:
314
- if len(self.points) > 1:
315
- removed_dx = self.points[1][0] - self.points[0][0]
316
- removed_dy = self.points[1][1] - self.points[0][1]
317
- removed_dist = math.hypot(removed_dx, removed_dy)
318
- self.currentLength -= removed_dist
319
- self.points.pop(0)
320
-
321
- for i in range(1, len(self.points)):
322
- cv2.line(
323
- imgMain, self.points[i-1], self.points[i], self.color, self.lineWidth)
324
-
325
- snakeHeadColor = (random.randint(0, 255), random.randint(
326
- 0, 255), random.randint(0, 255))
327
- cv2.circle(imgMain, (cx, cy), self.headSize,
328
- snakeHeadColor, cv2.FILLED)
329
-
330
- h, w, _ = imgMain.shape
331
- margin = 5
332
- if cx <= margin or cx >= w - margin or cy <= margin or cy >= h - margin:
333
- self.gameOver = True
334
-
335
- if obstacleManager:
336
- for ox, oy, ow, oh, *_ in obstacleManager.obstacles:
337
- if ox <= cx <= ox + ow and oy <= cy <= oy + oh:
338
- self.gameOver = True
339
-
340
- # ---------------- 内部类 ----------------
341
- class FoodManager:
342
- def __init__(self, foodPaths, foodNames, foodScores):
343
- self.foodImages = []
344
- for path in foodPaths:
345
- if os.path.exists(path):
346
- self.foodImages.append(
347
- cv2.imread(path, cv2.IMREAD_UNCHANGED))
348
- else:
349
- self.foodImages.append(np.zeros((50, 50, 4), np.uint8))
350
- self.foodNames = foodNames
351
- self.foodScores = foodScores
352
- self.foodIndex = 0
353
- self.hFood, self.wFood = 0, 0
354
- self.foodPoint = 0, 0
355
- self.randomFoodLocation()
356
-
357
- def randomFoodLocation(self, obstacleManager=None):
358
- max_attempts = 100
359
- for _ in range(max_attempts):
360
- self.foodPoint = random.randint(
361
- 50, 670), random.randint(50, 670)
362
- self.foodIndex = random.randint(0, len(self.foodImages)-1)
363
- self.hFood, self.wFood, _ = self.foodImages[self.foodIndex].shape
364
- if obstacleManager:
365
- overlap = False
366
- for ox, oy, ow, oh, *_ in obstacleManager.obstacles:
367
- if ox < self.foodPoint[0] < ox + ow and oy < self.foodPoint[1] < oy + oh:
368
- overlap = True
369
- break
370
- if not overlap:
371
- return
372
- return
373
-
374
- def draw(self, imgMain):
375
- rx, ry = self.foodPoint
376
- imgMain = cvzone.overlayPNG(imgMain, self.foodImages[self.foodIndex],
377
- (rx - self.wFood//2, ry - self.hFood//2))
378
- return imgMain
379
-
380
- # ---------------- 内部类 ----------------
381
- class ObstacleManager:
382
- def __init__(self, obstaclePaths):
383
- self.obstacleImages = []
384
- for path in obstaclePaths:
385
- if os.path.exists(path):
386
- self.obstacleImages.append(
387
- cv2.imread(path, cv2.IMREAD_UNCHANGED))
388
- else:
389
- self.obstacleImages.append(np.zeros((50, 50, 4), np.uint8))
390
- self.obstacles = []
391
-
392
- def randomObstacles(self):
393
- self.obstacles.clear()
394
- for img in self.obstacleImages:
395
- h, w, _ = img.shape
396
- x = random.randint(150, 570)
397
- y = random.randint(50, 570)
398
- dx = random.choice([-5, 5])
399
- dy = random.choice([-5, 5])
400
- self.obstacles.append([x, y, w, h, dx, dy, img])
401
-
402
- def moveObstacles(self, wMax, hMax):
403
- for obs in self.obstacles:
404
- x, y, ow, oh, dx, dy, img = obs
405
- x += dx
406
- y += dy
407
- if x <= 0 or x + ow >= wMax:
408
- dx *= -1
409
- if y <= 0 or y + oh >= hMax:
410
- dy *= -1
411
- obs[0], obs[1], obs[4], obs[5] = x, y, dx, dy
412
-
413
- def draw(self, imgMain):
414
- for obs in self.obstacles:
415
- x, y, w, h, dx, dy, img = obs
416
- imgMain = cvzone.overlayPNG(imgMain, img, (int(x), int(y)))
417
- return imgMain
File without changes