XMWAI 0.4.4__tar.gz → 0.4.6__tar.gz

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.

Files changed (131) hide show
  1. {xmwai-0.4.4/XMWAI.egg-info → xmwai-0.4.6}/PKG-INFO +1 -1
  2. xmwai-0.4.6/XMWAI/snake_core.py +595 -0
  3. {xmwai-0.4.4 → xmwai-0.4.6/XMWAI.egg-info}/PKG-INFO +1 -1
  4. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI.egg-info/SOURCES.txt +0 -1
  5. {xmwai-0.4.4 → xmwai-0.4.6}/setup.py +2 -3
  6. xmwai-0.4.4/XMWAI/gif/__init__.py +0 -0
  7. xmwai-0.4.4/XMWAI/snake_core.py +0 -426
  8. {xmwai-0.4.4 → xmwai-0.4.6}/LICENSE.txt +0 -0
  9. {xmwai-0.4.4 → xmwai-0.4.6}/MANIFEST.in +0 -0
  10. {xmwai-0.4.4 → xmwai-0.4.6}/README.md +0 -0
  11. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/__init__.py +0 -0
  12. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets/1.png +0 -0
  13. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets/g.png +0 -0
  14. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets/h.png +0 -0
  15. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets/l.png +0 -0
  16. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets/m.png +0 -0
  17. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets/s.png +0 -0
  18. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets/t.png +0 -0
  19. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/assets//345/276/256/350/275/257/351/233/205/351/273/221.ttf" +0 -0
  20. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/bomb_core.py +0 -0
  21. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/core.py +0 -0
  22. {xmwai-0.4.4/XMWAI/assets → xmwai-0.4.6/XMWAI/file}/__init__.py +0 -0
  23. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/file/idiom.json +0 -0
  24. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/0.gif +0 -0
  25. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/1.gif +0 -0
  26. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/10.gif +0 -0
  27. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/11.gif +0 -0
  28. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/12.gif +0 -0
  29. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/13.gif +0 -0
  30. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/14.gif +0 -0
  31. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/15.gif +0 -0
  32. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/16.gif +0 -0
  33. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/17.gif +0 -0
  34. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/18.gif +0 -0
  35. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/19.gif +0 -0
  36. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/2.gif +0 -0
  37. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/20.gif +0 -0
  38. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/21.gif +0 -0
  39. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/22.gif +0 -0
  40. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/23.gif +0 -0
  41. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/24.gif +0 -0
  42. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/25.gif +0 -0
  43. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/26.gif +0 -0
  44. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/27.gif +0 -0
  45. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/28.gif +0 -0
  46. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/29.gif +0 -0
  47. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/3.gif +0 -0
  48. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/30.gif +0 -0
  49. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/31.gif +0 -0
  50. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/32.gif +0 -0
  51. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/33.gif +0 -0
  52. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/34.gif +0 -0
  53. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/35.gif +0 -0
  54. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/36.gif +0 -0
  55. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/37.gif +0 -0
  56. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/38.gif +0 -0
  57. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/39.gif +0 -0
  58. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/4.gif +0 -0
  59. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/40.gif +0 -0
  60. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/41.gif +0 -0
  61. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/42.gif +0 -0
  62. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/43.gif +0 -0
  63. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/44.gif +0 -0
  64. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/45.gif +0 -0
  65. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/46.gif +0 -0
  66. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/47.gif +0 -0
  67. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/48.gif +0 -0
  68. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/49.gif +0 -0
  69. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/5.gif +0 -0
  70. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/50.gif +0 -0
  71. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/51.gif +0 -0
  72. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/52.gif +0 -0
  73. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/53.gif +0 -0
  74. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/54.gif +0 -0
  75. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/55.gif +0 -0
  76. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/56.gif +0 -0
  77. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/57.gif +0 -0
  78. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/58.gif +0 -0
  79. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/59.gif +0 -0
  80. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/6.gif +0 -0
  81. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/60.gif +0 -0
  82. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/61.gif +0 -0
  83. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/62.gif +0 -0
  84. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/63.gif +0 -0
  85. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/64.gif +0 -0
  86. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/65.gif +0 -0
  87. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/66.gif +0 -0
  88. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/67.gif +0 -0
  89. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/68.gif +0 -0
  90. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/69.gif +0 -0
  91. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/7.gif +0 -0
  92. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/70.gif +0 -0
  93. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/71.gif +0 -0
  94. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/72.gif +0 -0
  95. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/73.gif +0 -0
  96. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/74.gif +0 -0
  97. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/75.gif +0 -0
  98. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/76.gif +0 -0
  99. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/77.gif +0 -0
  100. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/78.gif +0 -0
  101. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/79.gif +0 -0
  102. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/8.gif +0 -0
  103. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/80.gif +0 -0
  104. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/81.gif +0 -0
  105. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/82.gif +0 -0
  106. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/83.gif +0 -0
  107. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/84.gif +0 -0
  108. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/85.gif +0 -0
  109. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/gif/9.gif +0 -0
  110. {xmwai-0.4.4/XMWAI/file → xmwai-0.4.6/XMWAI/gif}/__init__.py +0 -0
  111. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/idiom_core.py +0 -0
  112. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/magic_core.py +0 -0
  113. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/burger.js +0 -0
  114. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/BottomBun.png +0 -0
  115. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/TopBun.png +0 -0
  116. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/beef.png +0 -0
  117. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/bg.jpeg +0 -0
  118. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/bg.png +0 -0
  119. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/cheese.png +0 -0
  120. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/lettuce.png +0 -0
  121. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/sauce.png +0 -0
  122. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/static/images/tomato.png +0 -0
  123. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/templates/burger.html +0 -0
  124. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/templates/nutrition_pie.html +0 -0
  125. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/templates//345/210/233/346/204/217/350/217/234/350/260/261.html" +0 -0
  126. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/trial_class.py +0 -0
  127. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI/web_core.py +0 -0
  128. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI.egg-info/dependency_links.txt +0 -0
  129. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI.egg-info/requires.txt +0 -0
  130. {xmwai-0.4.4 → xmwai-0.4.6}/XMWAI.egg-info/top_level.txt +0 -0
  131. {xmwai-0.4.4 → xmwai-0.4.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: XMWAI
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Small code King AI related library
5
5
  Home-page: https://github.com/Tonykai88/XMWAI.git
6
6
  Author: pydevelopment
@@ -0,0 +1,595 @@
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
10
+ import math
11
+ import random
12
+
13
+ import cv2
14
+ import numpy as np
15
+ import cvzone
16
+ 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")
22
+
23
+
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)
35
+
36
+
37
+ class SnakeGame:
38
+ def __init__(self, width=720, height=720, snakeInitLength=150, snakeGrowth=50,
39
+ snakeLineWidth=10, snakeHeadSize=15, foodPaths=None, foodNames=None, foodScores=None,
40
+ obstaclePaths=None, fontPath=None):
41
+ """
42
+ 初始化游戏参数并加载资源(图片、字体)
43
+ 参数尽量与 main.py 调用保持一致
44
+ """
45
+ # 基本参数
46
+ # 宽, 高(cv2.set 采用 3 -> width, 4 -> height)
47
+ self.resolution = (width, height)
48
+ self.snakeInitLength = snakeInitLength
49
+ self._snakeGrowth = snakeGrowth
50
+ self._snakeHeadSize = snakeHeadSize
51
+ self.snakeLineWidth = snakeLineWidth
52
+ self._foodScores = foodScores if foodScores is not None else [3, 2, 1]
53
+
54
+ # 资源默认路径(包内 assets)
55
+ if fontPath is None:
56
+ fontPath = os.path.join(ASSETS_DIR, "微软雅黑.ttf")
57
+ 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
+ ]
63
+ if foodNames is None:
64
+ foodNames = ["汉堡", "薯条", "甜甜圈"]
65
+ 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
+ ]
71
+
72
+ # 赋值
73
+ self.fontPath = fontPath
74
+ self.foodPaths = foodPaths
75
+ self.foodNames = foodNames
76
+ self.obstaclePaths = obstaclePaths
77
+
78
+ # 摄像头 / 手势检测等对象
79
+ self.cap = None
80
+ self.detector = None
81
+ self.img = None
82
+
83
+ # 游戏对象(稍后初始化)
84
+ self.snake = None
85
+ self.foodManager = None
86
+ self.obstacleManager = None
87
+
88
+ # 覆盖文字(画面左上)
89
+ self.overlayTexts = []
90
+
91
+ # 计时器
92
+ self.timer = 30
93
+ self.start_time = None
94
+
95
+ # 加载并初始化内部游戏对象
96
+ self._init_game_objects()
97
+
98
+ # 注意:不在 init 中自动打开摄像头(让用户在 main 中调用 open_window 或 hand/start)
99
+ # 如果你希望自动打开摄像头,请调用 self.open_window()
100
+ # self.open_window()
101
+
102
+ # ---------------- 属性同步(方便外部设置) ----------------
103
+ @property
104
+ def snakeHeadSize(self):
105
+ return self._snakeHeadSize
106
+
107
+ @snakeHeadSize.setter
108
+ def snakeHeadSize(self, value):
109
+ self._snakeHeadSize = value
110
+ if self.snake:
111
+ self.snake.headSize = value
112
+
113
+ @property
114
+ def foodScores(self):
115
+ return self._foodScores
116
+
117
+ @foodScores.setter
118
+ def foodScores(self, scores):
119
+ self._foodScores = scores
120
+ if self.foodManager:
121
+ self.foodManager.foodScores = scores
122
+
123
+ @property
124
+ def snakeGrowth(self):
125
+ return self._snakeGrowth
126
+
127
+ @snakeGrowth.setter
128
+ def snakeGrowth(self, value):
129
+ self._snakeGrowth = value
130
+
131
+ # ----------------- 工具:在 OpenCV 图像上写中文 -----------------
132
+ 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)
142
+ draw = ImageDraw.Draw(img_pil)
143
+ try:
144
+ font = ImageFont.truetype(self.fontPath, fontSize)
145
+ except Exception:
146
+ 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
+ # ----------------- 初始化游戏对象 -----------------
155
+ def _init_game_objects(self):
156
+ # 内部类实例化
157
+ self.snake = self.Snake(color=(0, 0, 255), initLength=self.snakeInitLength,
158
+ lineWidth=self.snakeLineWidth, headSize=self._snakeHeadSize)
159
+ self.foodManager = self.FoodManager(
160
+ self.foodPaths, self.foodNames, self._foodScores)
161
+ self.obstacleManager = self.ObstacleManager(self.obstaclePaths)
162
+ # 随机生成障碍
163
+ self.obstacleManager.randomObstacles()
164
+
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
+ def _render_frame(self, show_food=True, show_obstacle=True):
204
+ """读取摄像头一帧并渲染蛇、食物和障碍(内部使用)"""
205
+ if self.cap is None:
206
+ return
207
+ success, self.img = self.cap.read()
208
+ if not success:
209
+ return
210
+ self.img = cv2.flip(self.img, 1)
211
+ hands, self.img = self.detector.findHands(self.img, flipType=False)
212
+ player_head = tuple(hands[0]['lmList'][8][0:2]) if hands else None
213
+
214
+ # 更新蛇的位置
215
+ if not self.snake.gameOver and player_head:
216
+ self.snake.update(self.img, player_head, self.obstacleManager)
217
+
218
+ # 检查是否吃到食物
219
+ if not self.snake.gameOver and player_head and show_food:
220
+ cx, cy = player_head
221
+ rx, ry = self.foodManager.foodPoint
222
+ 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
+ # 加分并延长蛇的允许长度
225
+ self.snake.score += self.foodManager.foodScores[self.foodManager.foodIndex]
226
+ self.snake.allowedLength += self._snakeGrowth
227
+ # 随机重新放置食物(避免与障碍重叠)
228
+ self.foodManager.randomFoodLocation(self.obstacleManager)
229
+
230
+ # 障碍物移动与绘制
231
+ if show_obstacle:
232
+ self.img = self.obstacleManager.draw(self.img)
233
+ self.obstacleManager.moveObstacles(
234
+ self.resolution[0], self.resolution[1])
235
+
236
+ # 食物绘制
237
+ if show_food:
238
+ self.img = self.foodManager.draw(self.img)
239
+
240
+ def display(self):
241
+ """
242
+ 在窗口上绘制分数和倒计时(只是渲染一帧供展示)
243
+ 调用者可以在 start() 前调用一次显示初始状态
244
+ """
245
+ if self.img is None:
246
+ # 尝试渲染一帧(不显示食物)
247
+ self._render_frame(show_food=False)
248
+ self.foodManager.randomFoodLocation(self.obstacleManager)
249
+ self._render_frame(show_food=True)
250
+
251
+ # 覆盖文字(玩家分数与倒计时)
252
+ self.overlayTexts = [
253
+ (f'玩家分数:{self.snake.score}', (50, 50), 30, (255, 0, 255)),
254
+ (f'倒计时:{self.timer} 秒', (50, 120), 30, (255, 0, 255))
255
+ ]
256
+ img_copy = self.img.copy()
257
+ for txt, pos, size, color in self.overlayTexts:
258
+ img_copy = self._putChineseText(img_copy, txt, pos, size, color)
259
+ cv2.imshow("AI Snake", img_copy)
260
+ cv2.waitKey(1)
261
+
262
+ # ----------------- 重置与结束 -----------------
263
+ def reset_game(self):
264
+ """重置游戏到初始状态"""
265
+ self.snake.reset()
266
+ self.snake.headSize = self._snakeHeadSize
267
+ self.obstacleManager.randomObstacles()
268
+ self.foodManager.foodScores = self._foodScores
269
+ self.foodManager.randomFoodLocation(self.obstacleManager)
270
+ self.start_time = time.time()
271
+ self.timer = 30
272
+ if self.cap is None or not self.cap.isOpened():
273
+ self.open_window()
274
+
275
+ def gameover(self, path=None, size=(100, 100)):
276
+ """
277
+ 显示游戏结束画面并提供 r(重启)或 q(退出)选项
278
+ path: 自定义结束图片路径(相对于包内 assets),默认使用 assets/1.png
279
+ size: 未使用但保留下来以兼容外部调用
280
+ """
281
+ 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)
287
+
288
+ if os.path.exists(path):
289
+ gameover_img = cv2.imread(path)
290
+ # 缩放到窗口大小(以覆盖窗口)
291
+ gameover_img = cv2.resize(gameover_img, self.resolution)
292
+ else:
293
+ # 如果没有找到图片,生成一个黑色背景并写提示
294
+ gameover_img = np.zeros(
295
+ (self.resolution[1], self.resolution[0], 3), np.uint8)
296
+ cv2.putText(gameover_img, "Game Over Image Missing!", (50, 100),
297
+ cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 3)
298
+
299
+ # 在结束画面写最终分数(使用中文绘制)
300
+ gameover_img = self._putChineseText(
301
+ gameover_img, f"最终分数:{self.snake.score}", (50, 200), 40, (68, 84, 106))
302
+
303
+ # 等待用户按键:r 重启, q 退出
304
+ while True:
305
+ cv2.imshow("AI Snake", gameover_img)
306
+ key = cv2.waitKey(0) & 0xFF
307
+ if key == ord('r'):
308
+ self.reset_game()
309
+ self.start()
310
+ break
311
+ elif key == ord('q'):
312
+ if self.cap is not None:
313
+ self.cap.release()
314
+ cv2.destroyAllWindows()
315
+ break
316
+
317
+ # ----------------- 游戏主循环 -----------------
318
+ def start(self):
319
+ """游戏主循环(会阻塞直到退出或游戏结束)"""
320
+ if self.cap is None or not getattr(self.cap, "isOpened", lambda: False)():
321
+ self.open_window()
322
+ if self.detector is None:
323
+ self.detector = HandDetector(detectionCon=0.7, maxHands=1)
324
+
325
+ self.start_time = time.time()
326
+ self.timer = 30
327
+
328
+ while True:
329
+ # 若游戏结束或倒计时结束,进入结束界面
330
+ if self.snake.gameOver or self.timer == 0:
331
+ self.gameover("1.png", (520, 520))
332
+ break
333
+
334
+ self._render_frame(show_food=True, show_obstacle=True)
335
+
336
+ # 更新倒计时
337
+ elapsed = int(time.time() - self.start_time)
338
+ self.timer = max(0, 30 - elapsed)
339
+
340
+ # 覆盖文字显示
341
+ self.overlayTexts = [
342
+ (f'玩家分数:{self.snake.score}', (50, 50), 30, (255, 0, 255)),
343
+ (f'倒计时:{self.timer} 秒', (50, 120), 30, (255, 0, 255))
344
+ ]
345
+
346
+ if self.img is not None:
347
+ img_copy = self.img.copy()
348
+ for txt, pos, size, color in self.overlayTexts:
349
+ img_copy = self._putChineseText(
350
+ img_copy, txt, pos, size, color)
351
+ cv2.imshow("AI Snake", img_copy)
352
+
353
+ key = cv2.waitKey(1)
354
+ if key == ord('r'): # 游戏中途重置
355
+ self.reset_game()
356
+ elif key == ord('q'): # 退出
357
+ if self.cap is not None:
358
+ self.cap.release()
359
+ cv2.destroyAllWindows()
360
+ break
361
+
362
+ # ================= 内部类:Snake =================
363
+ class Snake:
364
+ def __init__(self, color, initLength=150, lineWidth=10, headSize=15):
365
+ """
366
+ 简洁的蛇实现:维护点列表,根据 allowedLength 保持长度
367
+ color: BGR 颜色
368
+ """
369
+ self.points = []
370
+ self.currentLength = 0.0
371
+ self.allowedLength = initLength
372
+ self.previousHead = None
373
+ self.score = 0
374
+ self.color = color
375
+ self.gameOver = False
376
+ self.lineWidth = lineWidth
377
+ self.headSize = headSize
378
+
379
+ def reset(self):
380
+ """重置蛇的状态"""
381
+ self.points = []
382
+ self.currentLength = 0.0
383
+ self.allowedLength = 150
384
+ self.previousHead = None
385
+ self.score = 0
386
+ self.gameOver = False
387
+
388
+ def update(self, imgMain, currentHead, obstacleManager=None):
389
+ """
390
+ 根据当前手的位置更新蛇(平滑插值 + 限步 + 修剪超长部分)
391
+ currentHead: (x, y)
392
+ """
393
+ if self.gameOver:
394
+ return
395
+ if currentHead is None:
396
+ return
397
+
398
+ cx, cy = currentHead
399
+ if self.previousHead is None:
400
+ self.previousHead = (cx, cy)
401
+ px, py = self.previousHead
402
+
403
+ # 平滑插值,避免抖动
404
+ alpha = 0.7
405
+ cx = int(px * (1 - alpha) + cx * alpha)
406
+ cy = int(py * (1 - alpha) + cy * alpha)
407
+
408
+ # 限制最大步长(避免瞬移导致计算异常)
409
+ maxStep = 50
410
+ dx = cx - px
411
+ dy = cy - py
412
+ distance = math.hypot(dx, dy)
413
+ if distance > maxStep:
414
+ steps = int(distance / maxStep)
415
+ for i in range(1, steps + 1):
416
+ ix = int(px + dx * i / steps)
417
+ iy = int(py + dy * i / steps)
418
+ self.points.append((ix, iy))
419
+ self.currentLength += maxStep
420
+ else:
421
+ self.points.append((cx, cy))
422
+ self.currentLength += distance
423
+
424
+ self.previousHead = (cx, cy)
425
+
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
432
+ self.points.pop(0)
433
+
434
+ # 绘制蛇身(线段)
435
+ for i in range(1, len(self.points)):
436
+ cv2.line(
437
+ imgMain, self.points[i - 1], self.points[i], self.color, self.lineWidth)
438
+
439
+ # 绘制蛇头(变色圆)
440
+ snakeHeadColor = (random.randint(0, 255), random.randint(
441
+ 0, 255), random.randint(0, 255))
442
+ cv2.circle(imgMain, (cx, cy), self.headSize,
443
+ snakeHeadColor, cv2.FILLED)
444
+
445
+ # 窗口边界检测
446
+ h, w = imgMain.shape[:2]
447
+ margin = 5
448
+ if cx <= margin or cx >= w - margin or cy <= margin or cy >= h - margin:
449
+ self.gameOver = True
450
+
451
+ # 与障碍物碰撞检测(若存在障碍管理器)
452
+ if obstacleManager is not None:
453
+ for ox, oy, ow, oh, *_ in obstacleManager.obstacles:
454
+ if ox <= cx <= ox + ow and oy <= cy <= oy + oh:
455
+ self.gameOver = True
456
+
457
+ # ================= 内部类:FoodManager =================
458
+ class FoodManager:
459
+ def __init__(self, foodPaths, foodNames, foodScores):
460
+ """
461
+ foodPaths: list of 图片路径(带 alpha 的 PNG 推荐)
462
+ foodNames: list 名称(可选)
463
+ foodScores: list 对应分数(与 foodPaths 长度一致或可复用)
464
+ """
465
+ self.foodImages = []
466
+ # 读取每个图片(若不存在,则使用透明占位)
467
+ for path in foodPaths:
468
+ img = _safe_read_image(path)
469
+ self.foodImages.append(img)
470
+
471
+ self.foodNames = foodNames
472
+ self.foodScores = foodScores if foodScores is not None else [
473
+ 3] * len(self.foodImages)
474
+ self.foodIndex = 0
475
+ # 宽高初始化为占位值(读取后更新)
476
+ self.hFood, self.wFood = 50, 50
477
+ self.foodPoint = (100, 100)
478
+ # 初始放置
479
+ self.randomFoodLocation()
480
+
481
+ 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 时不会溢出
490
+ 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
+ # 如果传入障碍管理器,检查是否与任一障碍重叠
505
+ if obstacleManager:
506
+ overlap = False
507
+ for ox, oy, ow, oh, *_ in obstacleManager.obstacles:
508
+ if ox < rx < ox + ow and oy < ry < oy + oh:
509
+ overlap = True
510
+ break
511
+ if overlap:
512
+ continue
513
+ # 找到一个不重叠的位置
514
+ return
515
+ # 若多次尝试失败,则保留最后的值
516
+ return
517
+
518
+ def draw(self, imgMain):
519
+ """把当前食物图片覆盖到主图上(使用 cvzone.overlayPNG 以支持 alpha)"""
520
+ 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
540
+ return imgMain
541
+
542
+ # ================= 内部类:ObstacleManager =================
543
+ class ObstacleManager:
544
+ def __init__(self, obstaclePaths):
545
+ """
546
+ obstaclePaths: list 图片路径
547
+ self.obstacles: list of [x, y, w, h, dx, dy, img]
548
+ """
549
+ self.obstacleImages = []
550
+ for path in obstaclePaths:
551
+ img = _safe_read_image(path)
552
+ self.obstacleImages.append(img)
553
+ self.obstacles = []
554
+
555
+ def randomObstacles(self):
556
+ """基于 obstacleImages 生成一组随机位置和速度的障碍物"""
557
+ self.obstacles.clear()
558
+ 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))
566
+ dx = random.choice([-5, 5])
567
+ dy = random.choice([-5, 5])
568
+ self.obstacles.append([x, y, w, h, dx, dy, img])
569
+
570
+ def moveObstacles(self, wMax, hMax):
571
+ """更新每个障碍的位置并在边界反弹"""
572
+ for obs in self.obstacles:
573
+ x, y, ow, oh, dx, dy, img = obs
574
+ x += dx
575
+ y += dy
576
+ if x <= 0 or x + ow >= wMax:
577
+ dx *= -1
578
+ if y <= 0 or y + oh >= hMax:
579
+ dy *= -1
580
+ obs[0], obs[1], obs[4], obs[5] = x, y, dx, dy
581
+
582
+ def draw(self, imgMain):
583
+ """把所有障碍物绘制到主图上"""
584
+ for obs in self.obstacles:
585
+ 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
595
+ return imgMain
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: XMWAI
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Small code King AI related library
5
5
  Home-page: https://github.com/Tonykai88/XMWAI.git
6
6
  Author: pydevelopment
@@ -16,7 +16,6 @@ XMWAI.egg-info/dependency_links.txt
16
16
  XMWAI.egg-info/requires.txt
17
17
  XMWAI.egg-info/top_level.txt
18
18
  XMWAI/assets/1.png
19
- XMWAI/assets/__init__.py
20
19
  XMWAI/assets/g.png
21
20
  XMWAI/assets/h.png
22
21
  XMWAI/assets/l.png
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name="XMWAI", # 包名(pip install XMWAI)
6
- version="0.4.4", # 每次上传记得升级版本号
6
+ version="0.4.6", # 每次上传记得升级版本号
7
7
  author="pydevelopment", # 作者
8
8
  author_email="hekai@xiaoma.cn", # 邮箱
9
9
  description="Small code King AI related library", # 简短描述
@@ -22,8 +22,7 @@ setup(
22
22
  ],
23
23
  "XMWAI.gif": ["*.gif"], # gif 文件夹下的所有 GIF
24
24
  "XMWAI.file": ["*.json"], # file 文件夹下的 json
25
- "XMWAI.assets": ["*.png"], # assets 文件夹下的 png
26
- "XMWAI.assets": ["*.ttf"] # assets 文件夹下的 ttf
25
+ "XMWAI.assets": ["*.png", "*.ttf"], # 图片和字体
27
26
  },
28
27
  install_requires=[
29
28
  "requests>=2.32.3", # 依赖包
File without changes