XMWAI 0.4.6__py3-none-any.whl → 0.4.7__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/snake_core.py CHANGED
@@ -1,105 +1,62 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- snake_core.py
4
- 标准版 SnakeGame(摄像头 + 手势控制)
5
- 与 main.py 配合使用:ai = XMWAI.SnakeGame(...); ai.hand(); ai.display(); ai.start(); ai.gameover(...)
6
- """
7
-
8
- import os
9
- import time
1
+ import cv2
10
2
  import math
11
3
  import random
12
-
13
- import cv2
14
- import numpy as np
15
4
  import cvzone
5
+ import numpy as np
16
6
  from cvzone.HandTrackingModule import HandDetector
17
- from PIL import Image, ImageDraw, ImageFont
18
-
19
- # ------------- 资源路径(包内 assets 目录) -------------
20
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
21
- ASSETS_DIR = os.path.join(BASE_DIR, "assets")
7
+ from PIL import ImageFont, ImageDraw, Image
8
+ import time
9
+ from importlib.resources import files
10
+ from pathlib import Path
11
+ import os
22
12
 
23
13
 
24
- def _safe_read_image(path, default_shape=(50, 50, 4)):
25
- """
26
- 读取 PNG(带 alpha)或返回空透明图像(避免 None 导致 shape 失败)
27
- """
28
- if path and os.path.exists(path):
29
- img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
30
- if img is None:
31
- return np.zeros(default_shape, dtype=np.uint8)
32
- return img
33
- else:
34
- return np.zeros(default_shape, dtype=np.uint8)
14
+ def get_resource_path(filename: str) -> str:
15
+ """返回 snake_core 模块内资源的绝对路径"""
16
+ return str(files("XMWAI.assets").joinpath(filename))
35
17
 
36
18
 
37
19
  class SnakeGame:
38
20
  def __init__(self, width=720, height=720, snakeInitLength=150, snakeGrowth=50,
39
21
  snakeLineWidth=10, snakeHeadSize=15, foodPaths=None, foodNames=None, foodScores=None,
40
22
  obstaclePaths=None, fontPath=None):
41
- """
42
- 初始化游戏参数并加载资源(图片、字体)
43
- 参数尽量与 main.py 调用保持一致
44
- """
45
- # 基本参数
46
- # 宽, 高(cv2.set 采用 3 -> width, 4 -> height)
23
+
47
24
  self.resolution = (width, height)
48
25
  self.snakeInitLength = snakeInitLength
49
26
  self._snakeGrowth = snakeGrowth
50
27
  self._snakeHeadSize = snakeHeadSize
51
- self.snakeLineWidth = snakeLineWidth
52
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")
53
31
 
54
- # 资源默认路径(包内 assets)
55
- if fontPath is None:
56
- fontPath = os.path.join(ASSETS_DIR, "微软雅黑.ttf")
57
32
  if foodPaths is None:
58
- foodPaths = [
59
- os.path.join(ASSETS_DIR, "h.png"),
60
- os.path.join(ASSETS_DIR, "s.png"),
61
- os.path.join(ASSETS_DIR, "t.png")
62
- ]
33
+ foodPaths = [get_resource_path(f)
34
+ for f in ["h.png", "s.png", "t.png"]]
63
35
  if foodNames is None:
64
36
  foodNames = ["汉堡", "薯条", "甜甜圈"]
65
37
  if obstaclePaths is None:
66
- obstaclePaths = [
67
- os.path.join(ASSETS_DIR, "g.png"),
68
- os.path.join(ASSETS_DIR, "l.png"),
69
- os.path.join(ASSETS_DIR, "m.png")
70
- ]
38
+ obstaclePaths = [get_resource_path(f)
39
+ for f in ["g.png", "l.png", "m.png"]]
71
40
 
72
- # 赋值
73
- self.fontPath = fontPath
74
41
  self.foodPaths = foodPaths
75
42
  self.foodNames = foodNames
76
43
  self.obstaclePaths = obstaclePaths
77
44
 
78
- # 摄像头 / 手势检测等对象
79
45
  self.cap = None
80
46
  self.detector = None
81
- self.img = None
82
-
83
- # 游戏对象(稍后初始化)
84
47
  self.snake = None
85
48
  self.foodManager = None
86
49
  self.obstacleManager = None
87
-
88
- # 覆盖文字(画面左上)
50
+ self.img = None
89
51
  self.overlayTexts = []
90
52
 
91
- # 计时器
92
53
  self.timer = 30
93
54
  self.start_time = None
94
55
 
95
- # 加载并初始化内部游戏对象
96
56
  self._init_game_objects()
57
+ self.open_window()
97
58
 
98
- # 注意:不在 init 中自动打开摄像头(让用户在 main 中调用 open_window 或 hand/start)
99
- # 如果你希望自动打开摄像头,请调用 self.open_window()
100
- # self.open_window()
101
-
102
- # ---------------- 属性同步(方便外部设置) ----------------
59
+ # ---------------- 属性自动同步 ----------------
103
60
  @property
104
61
  def snakeHeadSize(self):
105
62
  return self._snakeHeadSize
@@ -128,80 +85,26 @@ class SnakeGame:
128
85
  def snakeGrowth(self, value):
129
86
  self._snakeGrowth = value
130
87
 
131
- # ----------------- 工具:在 OpenCV 图像上写中文 -----------------
88
+ # ---------------- 工具函数 ----------------
132
89
  def _putChineseText(self, img, text, pos, fontSize=40, color=(0, 0, 255)):
133
- """
134
- 在 BGR numpy 图像上绘制中文(使用 PIL)
135
- pos: (x, y) 左上角
136
- color: BGR 元组(PIL 需要 RGB,但我们直接使用 BGR 也能显示)
137
- """
138
- try:
139
- img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
140
- except Exception:
141
- img_pil = Image.fromarray(img)
90
+ img_pil = Image.fromarray(img)
142
91
  draw = ImageDraw.Draw(img_pil)
143
92
  try:
144
93
  font = ImageFont.truetype(self.fontPath, fontSize)
145
- except Exception:
94
+ except OSError:
146
95
  font = ImageFont.load_default()
147
- # PIL 的颜色是 RGB
148
- # 给定的 color 是 BGR(一致化处理)
149
- b, g, r = color
150
- draw.text(pos, text, font=font, fill=(r, g, b))
151
- img = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
152
- return img
153
-
154
- # ----------------- 初始化游戏对象 -----------------
96
+ draw.text(pos, text, font=font, fill=color)
97
+ return np.array(img_pil)
98
+
155
99
  def _init_game_objects(self):
156
- # 内部类实例化
157
100
  self.snake = self.Snake(color=(0, 0, 255), initLength=self.snakeInitLength,
158
101
  lineWidth=self.snakeLineWidth, headSize=self._snakeHeadSize)
159
102
  self.foodManager = self.FoodManager(
160
103
  self.foodPaths, self.foodNames, self._foodScores)
161
104
  self.obstacleManager = self.ObstacleManager(self.obstaclePaths)
162
- # 随机生成障碍
163
105
  self.obstacleManager.randomObstacles()
164
106
 
165
- # ----------------- 摄像头与窗口控制 -----------------
166
- def open_window(self):
167
- """打开摄像头并显示第一帧(如失败会打印错误)"""
168
- self.cap = cv2.VideoCapture(0)
169
- # 设置分辨率(注意:cv2.set(3) 对应宽,(4) 对应高)
170
- self.cap.set(3, self.resolution[0])
171
- self.cap.set(4, self.resolution[1])
172
- self.detector = HandDetector(detectionCon=0.7, maxHands=1)
173
- success, self.img = self.cap.read()
174
- if not success:
175
- print("摄像头打开失败")
176
- return
177
- self.img = cv2.flip(self.img, 1)
178
- cv2.imshow("AI Snake", self.img)
179
- cv2.waitKey(1)
180
-
181
- def hand(self):
182
- """
183
- 快速测试手部检测(按 q 或检测到手后退出)
184
- 供用户检查摄像头与手势模块是否正常
185
- """
186
- if self.cap is None:
187
- self.open_window()
188
- if self.detector is None:
189
- self.detector = HandDetector(detectionCon=0.8, maxHands=1)
190
-
191
- while True:
192
- success, img = self.cap.read()
193
- if not success:
194
- break
195
- img = cv2.flip(img, 1)
196
- hands, img = self.detector.findHands(img, flipType=False)
197
- cv2.imshow("AI Snake", img)
198
- key = cv2.waitKey(1) & 0xFF
199
- if hands or key == ord('q'):
200
- break
201
-
202
- # ----------------- 渲染与显示 -----------------
203
107
  def _render_frame(self, show_food=True, show_obstacle=True):
204
- """读取摄像头一帧并渲染蛇、食物和障碍(内部使用)"""
205
108
  if self.cap is None:
206
109
  return
207
110
  success, self.img = self.cap.read()
@@ -211,44 +114,61 @@ class SnakeGame:
211
114
  hands, self.img = self.detector.findHands(self.img, flipType=False)
212
115
  player_head = tuple(hands[0]['lmList'][8][0:2]) if hands else None
213
116
 
214
- # 更新蛇的位置
215
117
  if not self.snake.gameOver and player_head:
216
118
  self.snake.update(self.img, player_head, self.obstacleManager)
217
119
 
218
- # 检查是否吃到食物
219
120
  if not self.snake.gameOver and player_head and show_food:
220
121
  cx, cy = player_head
221
122
  rx, ry = self.foodManager.foodPoint
222
123
  w, h = self.foodManager.wFood, self.foodManager.hFood
223
- if (rx - w // 2) <= cx <= (rx + w // 2) and (ry - h // 2) <= cy <= (ry + h // 2):
224
- # 加分并延长蛇的允许长度
124
+ if rx - w//2 <= cx <= rx + w//2 and ry - h//2 <= cy <= ry + h//2:
225
125
  self.snake.score += self.foodManager.foodScores[self.foodManager.foodIndex]
226
126
  self.snake.allowedLength += self._snakeGrowth
227
- # 随机重新放置食物(避免与障碍重叠)
228
127
  self.foodManager.randomFoodLocation(self.obstacleManager)
229
128
 
230
- # 障碍物移动与绘制
231
129
  if show_obstacle:
232
130
  self.img = self.obstacleManager.draw(self.img)
233
- self.obstacleManager.moveObstacles(
234
- self.resolution[0], self.resolution[1])
131
+ self.obstacleManager.moveObstacles(*self.resolution)
235
132
 
236
- # 食物绘制
237
133
  if show_food:
238
134
  self.img = self.foodManager.draw(self.img)
239
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
+
240
167
  def display(self):
241
- """
242
- 在窗口上绘制分数和倒计时(只是渲染一帧供展示)
243
- 调用者可以在 start() 前调用一次显示初始状态
244
- """
245
168
  if self.img is None:
246
- # 尝试渲染一帧(不显示食物)
247
169
  self._render_frame(show_food=False)
248
170
  self.foodManager.randomFoodLocation(self.obstacleManager)
249
171
  self._render_frame(show_food=True)
250
-
251
- # 覆盖文字(玩家分数与倒计时)
252
172
  self.overlayTexts = [
253
173
  (f'玩家分数:{self.snake.score}', (50, 50), 30, (255, 0, 255)),
254
174
  (f'倒计时:{self.timer} 秒', (50, 120), 30, (255, 0, 255))
@@ -259,9 +179,8 @@ class SnakeGame:
259
179
  cv2.imshow("AI Snake", img_copy)
260
180
  cv2.waitKey(1)
261
181
 
262
- # ----------------- 重置与结束 -----------------
182
+ # ---------------- 重置游戏 ----------------
263
183
  def reset_game(self):
264
- """重置游戏到初始状态"""
265
184
  self.snake.reset()
266
185
  self.snake.headSize = self._snakeHeadSize
267
186
  self.obstacleManager.randomObstacles()
@@ -272,72 +191,52 @@ class SnakeGame:
272
191
  if self.cap is None or not self.cap.isOpened():
273
192
  self.open_window()
274
193
 
194
+ # ---------------- 游戏结束 ----------------
275
195
  def gameover(self, path=None, size=(100, 100)):
276
- """
277
- 显示游戏结束画面并提供 r(重启)或 q(退出)选项
278
- path: 自定义结束图片路径(相对于包内 assets),默认使用 assets/1.png
279
- size: 未使用但保留下来以兼容外部调用
280
- """
281
196
  if path is None:
282
- path = os.path.join(ASSETS_DIR, "1.png")
283
- else:
284
- # 若传入相对文件名(如 "1.png"),优先在 assets 中寻找
285
- if not os.path.isabs(path):
286
- path = os.path.join(ASSETS_DIR, path)
197
+ path = get_resource_path("1.png")
287
198
 
288
199
  if os.path.exists(path):
289
200
  gameover_img = cv2.imread(path)
290
- # 缩放到窗口大小(以覆盖窗口)
291
201
  gameover_img = cv2.resize(gameover_img, self.resolution)
292
202
  else:
293
- # 如果没有找到图片,生成一个黑色背景并写提示
294
203
  gameover_img = np.zeros(
295
204
  (self.resolution[1], self.resolution[0], 3), np.uint8)
296
205
  cv2.putText(gameover_img, "Game Over Image Missing!", (50, 100),
297
206
  cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 3)
298
207
 
299
- # 在结束画面写最终分数(使用中文绘制)
300
208
  gameover_img = self._putChineseText(
301
- gameover_img, f"最终分数:{self.snake.score}", (50, 200), 40, (68, 84, 106))
209
+ gameover_img, f"最终分数:{self.snake.score}", size, 40, (68, 84, 106))
302
210
 
303
- # 等待用户按键:r 重启, q 退出
304
211
  while True:
305
212
  cv2.imshow("AI Snake", gameover_img)
306
213
  key = cv2.waitKey(0) & 0xFF
307
- if key == ord('r'):
214
+ if key == ord('r'): # 重启游戏
308
215
  self.reset_game()
309
216
  self.start()
310
217
  break
311
- elif key == ord('q'):
218
+ elif key == ord('q'): # 退出游戏
312
219
  if self.cap is not None:
313
220
  self.cap.release()
314
221
  cv2.destroyAllWindows()
315
222
  break
316
223
 
317
- # ----------------- 游戏主循环 -----------------
224
+ # ---------------- 游戏主循环 ----------------
318
225
  def start(self):
319
- """游戏主循环(会阻塞直到退出或游戏结束)"""
320
- if self.cap is None or not getattr(self.cap, "isOpened", lambda: False)():
226
+ if self.cap is None or not self.cap.isOpened():
321
227
  self.open_window()
322
- if self.detector is None:
323
- self.detector = HandDetector(detectionCon=0.7, maxHands=1)
324
-
325
228
  self.start_time = time.time()
326
229
  self.timer = 30
327
230
 
328
231
  while True:
329
- # 若游戏结束或倒计时结束,进入结束界面
330
232
  if self.snake.gameOver or self.timer == 0:
331
- self.gameover("1.png", (520, 520))
233
+ self.gameover()
332
234
  break
333
235
 
334
236
  self._render_frame(show_food=True, show_obstacle=True)
335
-
336
- # 更新倒计时
337
237
  elapsed = int(time.time() - self.start_time)
338
238
  self.timer = max(0, 30 - elapsed)
339
239
 
340
- # 覆盖文字显示
341
240
  self.overlayTexts = [
342
241
  (f'玩家分数:{self.snake.score}', (50, 50), 30, (255, 0, 255)),
343
242
  (f'倒计时:{self.timer} 秒', (50, 120), 30, (255, 0, 255))
@@ -353,21 +252,17 @@ class SnakeGame:
353
252
  key = cv2.waitKey(1)
354
253
  if key == ord('r'): # 游戏中途重置
355
254
  self.reset_game()
356
- elif key == ord('q'): # 退出
255
+ elif key == ord('q'):
357
256
  if self.cap is not None:
358
257
  self.cap.release()
359
258
  cv2.destroyAllWindows()
360
259
  break
361
260
 
362
- # ================= 内部类:Snake =================
261
+ # ---------------- 内部类 ----------------
363
262
  class Snake:
364
263
  def __init__(self, color, initLength=150, lineWidth=10, headSize=15):
365
- """
366
- 简洁的蛇实现:维护点列表,根据 allowedLength 保持长度
367
- color: BGR 颜色
368
- """
369
264
  self.points = []
370
- self.currentLength = 0.0
265
+ self.currentLength = 0
371
266
  self.allowedLength = initLength
372
267
  self.previousHead = None
373
268
  self.score = 0
@@ -377,35 +272,27 @@ class SnakeGame:
377
272
  self.headSize = headSize
378
273
 
379
274
  def reset(self):
380
- """重置蛇的状态"""
381
275
  self.points = []
382
- self.currentLength = 0.0
276
+ self.currentLength = 0
383
277
  self.allowedLength = 150
384
278
  self.previousHead = None
385
279
  self.score = 0
386
280
  self.gameOver = False
387
281
 
388
282
  def update(self, imgMain, currentHead, obstacleManager=None):
389
- """
390
- 根据当前手的位置更新蛇(平滑插值 + 限步 + 修剪超长部分)
391
- currentHead: (x, y)
392
- """
393
283
  if self.gameOver:
394
284
  return
395
- if currentHead is None:
396
- return
397
-
398
285
  cx, cy = currentHead
286
+ if cx is None or cy is None:
287
+ return
399
288
  if self.previousHead is None:
400
289
  self.previousHead = (cx, cy)
401
290
  px, py = self.previousHead
402
291
 
403
- # 平滑插值,避免抖动
404
292
  alpha = 0.7
405
293
  cx = int(px * (1 - alpha) + cx * alpha)
406
294
  cy = int(py * (1 - alpha) + cy * alpha)
407
295
 
408
- # 限制最大步长(避免瞬移导致计算异常)
409
296
  maxStep = 50
410
297
  dx = cx - px
411
298
  dy = cy - py
@@ -423,152 +310,96 @@ class SnakeGame:
423
310
 
424
311
  self.previousHead = (cx, cy)
425
312
 
426
- # 当长度大于允许长度时,从头部开始删除点
427
- while self.currentLength > self.allowedLength and len(self.points) > 1:
428
- removed_dx = self.points[1][0] - self.points[0][0]
429
- removed_dy = self.points[1][1] - self.points[0][1]
430
- removed_dist = math.hypot(removed_dx, removed_dy)
431
- self.currentLength -= removed_dist
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
432
319
  self.points.pop(0)
433
320
 
434
- # 绘制蛇身(线段)
435
321
  for i in range(1, len(self.points)):
436
322
  cv2.line(
437
- imgMain, self.points[i - 1], self.points[i], self.color, self.lineWidth)
323
+ imgMain, self.points[i-1], self.points[i], self.color, self.lineWidth)
438
324
 
439
- # 绘制蛇头(变色圆)
440
325
  snakeHeadColor = (random.randint(0, 255), random.randint(
441
326
  0, 255), random.randint(0, 255))
442
327
  cv2.circle(imgMain, (cx, cy), self.headSize,
443
328
  snakeHeadColor, cv2.FILLED)
444
329
 
445
- # 窗口边界检测
446
- h, w = imgMain.shape[:2]
330
+ h, w, _ = imgMain.shape
447
331
  margin = 5
448
332
  if cx <= margin or cx >= w - margin or cy <= margin or cy >= h - margin:
449
333
  self.gameOver = True
450
334
 
451
- # 与障碍物碰撞检测(若存在障碍管理器)
452
- if obstacleManager is not None:
335
+ if obstacleManager:
453
336
  for ox, oy, ow, oh, *_ in obstacleManager.obstacles:
454
337
  if ox <= cx <= ox + ow and oy <= cy <= oy + oh:
455
338
  self.gameOver = True
456
339
 
457
- # ================= 内部类:FoodManager =================
340
+ # ---------------- 内部类 ----------------
458
341
  class FoodManager:
459
342
  def __init__(self, foodPaths, foodNames, foodScores):
460
- """
461
- foodPaths: list of 图片路径(带 alpha 的 PNG 推荐)
462
- foodNames: list 名称(可选)
463
- foodScores: list 对应分数(与 foodPaths 长度一致或可复用)
464
- """
465
343
  self.foodImages = []
466
- # 读取每个图片(若不存在,则使用透明占位)
467
344
  for path in foodPaths:
468
- img = _safe_read_image(path)
469
- self.foodImages.append(img)
470
-
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))
471
350
  self.foodNames = foodNames
472
- self.foodScores = foodScores if foodScores is not None else [
473
- 3] * len(self.foodImages)
351
+ self.foodScores = foodScores
474
352
  self.foodIndex = 0
475
- # 宽高初始化为占位值(读取后更新)
476
- self.hFood, self.wFood = 50, 50
477
- self.foodPoint = (100, 100)
478
- # 初始放置
353
+ self.hFood, self.wFood = 0, 0
354
+ self.foodPoint = 0, 0
479
355
  self.randomFoodLocation()
480
356
 
481
357
  def randomFoodLocation(self, obstacleManager=None):
482
- """
483
- 随机放置食物位置,避免与障碍重叠(最多尝试 max_attempts 次)
484
- 坐标范围依据典型摄像头分辨率设定,可根据需要调整
485
- """
486
- max_attempts = 200
487
- # 摄像头分辨率默认使用 1280x720 或 self 所在外部被设置的分辨率
488
- # 这里我们使用一个可信任的范围:x in [50, self_max_w - 50], y in [50, self_max_h - 50]
489
- # 为了不依赖外部,我们使用默认 0..1280 / 0..720 范围,但在 draw 时不会溢出
358
+ max_attempts = 100
490
359
  for _ in range(max_attempts):
491
- # 随机食物类型
492
- self.foodIndex = random.randint(0, len(self.foodImages) - 1)
493
- img = self.foodImages[self.foodIndex]
494
- # 确保 shape 可用(img 可能是 2D/3D )
495
- try:
496
- h, w = img.shape[:2]
497
- except Exception:
498
- h, w = 50, 50
499
- self.hFood, self.wFood = h, w
500
- # 随机位置(靠内侧,避免边缘)
501
- rx = random.randint(50, max(50, 1280 - 50))
502
- ry = random.randint(50, max(50, 720 - 50))
503
- self.foodPoint = (rx, ry)
504
- # 如果传入障碍管理器,检查是否与任一障碍重叠
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
505
364
  if obstacleManager:
506
365
  overlap = False
507
366
  for ox, oy, ow, oh, *_ in obstacleManager.obstacles:
508
- if ox < rx < ox + ow and oy < ry < oy + oh:
367
+ if ox < self.foodPoint[0] < ox + ow and oy < self.foodPoint[1] < oy + oh:
509
368
  overlap = True
510
369
  break
511
- if overlap:
512
- continue
513
- # 找到一个不重叠的位置
514
- return
515
- # 若多次尝试失败,则保留最后的值
370
+ if not overlap:
371
+ return
516
372
  return
517
373
 
518
374
  def draw(self, imgMain):
519
- """把当前食物图片覆盖到主图上(使用 cvzone.overlayPNG 以支持 alpha)"""
520
375
  rx, ry = self.foodPoint
521
- # 防止越界
522
- try:
523
- h, w = self.foodImages[self.foodIndex].shape[:2]
524
- except Exception:
525
- h, w = 50, 50
526
- self.hFood, self.wFood = h, w
527
- # overlayPNG 需要左上角坐标
528
- top_left = (int(rx - w // 2), int(ry - h // 2))
529
- try:
530
- imgMain = cvzone.overlayPNG(
531
- imgMain, self.foodImages[self.foodIndex], top_left)
532
- except Exception:
533
- # 如果 overlay 失败,尝试简单贴图(忽略 alpha)
534
- try:
535
- imgMain[top_left[1]:top_left[1] + h, top_left[0]:top_left[0] + w] = \
536
- cv2.resize(
537
- self.foodImages[self.foodIndex][:, :, :3], (w, h))
538
- except Exception:
539
- pass
376
+ imgMain = cvzone.overlayPNG(imgMain, self.foodImages[self.foodIndex],
377
+ (rx - self.wFood//2, ry - self.hFood//2))
540
378
  return imgMain
541
379
 
542
- # ================= 内部类:ObstacleManager =================
380
+ # ---------------- 内部类 ----------------
543
381
  class ObstacleManager:
544
382
  def __init__(self, obstaclePaths):
545
- """
546
- obstaclePaths: list 图片路径
547
- self.obstacles: list of [x, y, w, h, dx, dy, img]
548
- """
549
383
  self.obstacleImages = []
550
384
  for path in obstaclePaths:
551
- img = _safe_read_image(path)
552
- self.obstacleImages.append(img)
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))
553
390
  self.obstacles = []
554
391
 
555
392
  def randomObstacles(self):
556
- """基于 obstacleImages 生成一组随机位置和速度的障碍物"""
557
393
  self.obstacles.clear()
558
394
  for img in self.obstacleImages:
559
- try:
560
- h, w = img.shape[:2]
561
- except Exception:
562
- h, w = 50, 50
563
- # 随机位置(避免太靠边)
564
- x = random.randint(150, max(150, 1280 - w - 10))
565
- y = random.randint(50, max(50, 720 - h - 10))
395
+ h, w, _ = img.shape
396
+ x = random.randint(150, 570)
397
+ y = random.randint(50, 570)
566
398
  dx = random.choice([-5, 5])
567
399
  dy = random.choice([-5, 5])
568
400
  self.obstacles.append([x, y, w, h, dx, dy, img])
569
401
 
570
402
  def moveObstacles(self, wMax, hMax):
571
- """更新每个障碍的位置并在边界反弹"""
572
403
  for obs in self.obstacles:
573
404
  x, y, ow, oh, dx, dy, img = obs
574
405
  x += dx
@@ -580,16 +411,7 @@ class SnakeGame:
580
411
  obs[0], obs[1], obs[4], obs[5] = x, y, dx, dy
581
412
 
582
413
  def draw(self, imgMain):
583
- """把所有障碍物绘制到主图上"""
584
414
  for obs in self.obstacles:
585
415
  x, y, w, h, dx, dy, img = obs
586
- try:
587
- imgMain = cvzone.overlayPNG(imgMain, img, (int(x), int(y)))
588
- except Exception:
589
- # fallback: 直接简单覆盖(忽略 alpha)
590
- try:
591
- imgMain[int(y):int(y) + h, int(x)
592
- :int(x) + w] = img[:, :, :3]
593
- except Exception:
594
- pass
416
+ imgMain = cvzone.overlayPNG(imgMain, img, (int(x), int(y)))
595
417
  return imgMain
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: XMWAI
3
- Version: 0.4.6
3
+ Version: 0.4.7
4
4
  Summary: Small code King AI related library
5
5
  Home-page: https://github.com/Tonykai88/XMWAI.git
6
6
  Author: pydevelopment
@@ -3,7 +3,7 @@ XMWAI/bomb_core.py,sha256=h2ZPH3SuoG2L_XOf1dcK8p3lhw5QzhneWl2yMLj1RiU,1819
3
3
  XMWAI/core.py,sha256=rOXj7FnewSdnzBcFLjpnBtrOTCsvMfiycIcdPDagxho,10012
4
4
  XMWAI/idiom_core.py,sha256=yU-VHhqqoutVm6GVikcjL3m9yuB1hUsFBpPYvwY4n5g,1689
5
5
  XMWAI/magic_core.py,sha256=Ms4b12PJ8rjsmceg1VUqWCWx2ebvdhLp4sIF6K_Vaok,3491
6
- XMWAI/snake_core.py,sha256=f22mPKUxcQYVMiQeNEGAOD5M1ex2jemIQmuwxJdy26c,24038
6
+ XMWAI/snake_core.py,sha256=fMm65EUfEmJK9nat9EBKz-ysmo8luTDx8N3vZ320aZE,16055
7
7
  XMWAI/trial_class.py,sha256=fPsl7BZvhzch2FOIG4azr999kjtoly53Acm3LqL8f98,9724
8
8
  XMWAI/web_core.py,sha256=7awPg1kYW3lYrbgylqJvUF3g050bn6H21PgmQ7Kv1wA,10927
9
9
  XMWAI/assets/1.png,sha256=eEuKH_M_q3tc_O2bYnuLOsRP-NlJHIbNg0pgrKXEEjw,139720
@@ -116,8 +116,8 @@ XMWAI/static/images/tomato.png,sha256=FEOEAOdUhW_BDFgTpxOkYc0I5Iu29_gtHb3RIPEej0
116
116
  XMWAI/templates/burger.html,sha256=vDnxpSW8phetyScySsalScZnFKl3LNpy5lJjKxGXgbI,3320
117
117
  XMWAI/templates/nutrition_pie.html,sha256=yJVXI28i-UfvF0xOXGSNLMb8oCJNhh2J3zoRDr5_7DM,5567
118
118
  XMWAI/templates/创意菜谱.html,sha256=RcDgH58QLyUJ9A59wobu3wvchGBY1snVsXcZQZam5M0,4805
119
- xmwai-0.4.6.dist-info/licenses/LICENSE.txt,sha256=bcaIQMrIhdQ3O-PoZlexjmW6h-wLGvHxh5Oksl4ohtc,1066
120
- xmwai-0.4.6.dist-info/METADATA,sha256=bbkrzu832BEoD09zAgjlhAFzPxCGqKi87RKJVnsHtaY,1227
121
- xmwai-0.4.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
122
- xmwai-0.4.6.dist-info/top_level.txt,sha256=yvGcDI-sggK5jqd9wz0saipZvk3oIE3hNGHlqUjxf0c,6
123
- xmwai-0.4.6.dist-info/RECORD,,
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,,
File without changes