chartflow 0.1__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.
- chartflow/__init__.py +28 -0
- chartflow/_verify_importexport.py +169 -0
- chartflow/edit/__init__.py +119 -0
- chartflow/edit/code_editor.py +1007 -0
- chartflow/edit/code_folder.py +523 -0
- chartflow/edit/completer.py +2652 -0
- chartflow/edit/context_analyzer.py +521 -0
- chartflow/edit/demo.py +469 -0
- chartflow/edit/line_number_area.py +548 -0
- chartflow/edit/minimap.py +581 -0
- chartflow/edit/python_editor.py +509 -0
- chartflow/edit/syntax_highlighter.py +291 -0
- chartflow/edit/test_editor.py +190 -0
- chartflow/flow/__init__.py +31 -0
- chartflow/flow/_demo.py +98 -0
- chartflow/flow/chart.py +1101 -0
- chartflow/flow/editor.py +375 -0
- chartflow/flow/flow.py +6 -0
- chartflow/flow/qchart.py +1250 -0
- chartflow/flow/test/__init__.py +0 -0
- chartflow/flow/test/test_chart.py +537 -0
- chartflow/flow/test_decorator_sync.py +102 -0
- chartflow/fsm/__init__.py +84 -0
- chartflow/fsm/decorators.py +395 -0
- chartflow/fsm/demo.py +381 -0
- chartflow/fsm/demo_pyqt6.py +881 -0
- chartflow/fsm/demo_tree.py +46 -0
- chartflow/fsm/logic.py +447 -0
- chartflow/fsm/meta.py +569 -0
- chartflow/fsm/scheduler.py +427 -0
- chartflow/fsm/stage.py +2079 -0
- chartflow/fsm/stdio.py +66 -0
- chartflow/fsm/test/__init__.py +7 -0
- chartflow/fsm/test/test_decorators.py +210 -0
- chartflow/fsm/test/test_events.py +202 -0
- chartflow/fsm/test/test_integration.py +596 -0
- chartflow/fsm/test/test_logic.py +371 -0
- chartflow/fsm/test/test_meta.py +192 -0
- chartflow/fsm/test/test_runtime_param.py +336 -0
- chartflow/fsm/test/test_scheduler.py +280 -0
- chartflow/fsm/test/test_stage.py +314 -0
- chartflow/fsm/test_behavior_events.py +263 -0
- chartflow/fsm/test_nested_parallel.py +80 -0
- chartflow/fsm/test_stage_spec.py +448 -0
- chartflow/fsm/utils.py +34 -0
- chartflow/node/__init__.py +66 -0
- chartflow/node/_node_base.py +727 -0
- chartflow/node/_node_interaction.py +488 -0
- chartflow/node/_node_interface.py +426 -0
- chartflow/node/_node_markers.py +648 -0
- chartflow/node/_node_ports.py +536 -0
- chartflow/node/_port_interface.py +111 -0
- chartflow/node/arrow_item.py +287 -0
- chartflow/node/color_utils.py +113 -0
- chartflow/node/connection_item.py +939 -0
- chartflow/node/demo.py +258 -0
- chartflow/node/demo_arrow_drag.py +46 -0
- chartflow/node/demo_decorator.py +63 -0
- chartflow/node/demo_ports.py +69 -0
- chartflow/node/enums.py +35 -0
- chartflow/node/example.py +84 -0
- chartflow/node/layout_utils.py +307 -0
- chartflow/node/main_window.py +196 -0
- chartflow/node/menu_bar.py +240 -0
- chartflow/node/node_canvas.py +1730 -0
- chartflow/node/node_item.py +754 -0
- chartflow/node/popmenu.py +472 -0
- chartflow/node/port_item.py +591 -0
- chartflow/node/qnode_editor.py +73 -0
- chartflow/node/selection_rect.py +53 -0
- chartflow/node/style_panel.py +615 -0
- chartflow/node/styles.py +63 -0
- chartflow/node/test_qnode.py +2336 -0
- chartflow/showcase.py +528 -0
- chartflow/theme.py +508 -0
- chartflow-0.1.dist-info/METADATA +23 -0
- chartflow-0.1.dist-info/RECORD +79 -0
- chartflow-0.1.dist-info/WHEEL +5 -0
- chartflow-0.1.dist-info/top_level.txt +1 -0
chartflow/fsm/demo.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
"""
|
|
3
|
+
QFSM Demo - 完整示例程序
|
|
4
|
+
|
|
5
|
+
展示:
|
|
6
|
+
1. 基本的FSM状态切换
|
|
7
|
+
2. 事件系统通信
|
|
8
|
+
3. 优先级调度
|
|
9
|
+
4. 实例间引用
|
|
10
|
+
5. 生命周期管理
|
|
11
|
+
|
|
12
|
+
场景: 简单的游戏AI系统
|
|
13
|
+
- Player: 玩家角色,可以移动和攻击
|
|
14
|
+
- Enemy: 敌人AI,有巡逻、追击、攻击状态
|
|
15
|
+
- GameManager: 游戏管理器,处理游戏事件
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import time
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from chartflow.fsm.logic import Logic
|
|
23
|
+
from chartflow.fsm.scheduler import Scheduler
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GameEntity:
|
|
27
|
+
"""游戏实体基类,模拟游戏对象"""
|
|
28
|
+
def __init__(self, name: str, x: float = 0, y: float = 0):
|
|
29
|
+
self.name = name
|
|
30
|
+
self.x = x
|
|
31
|
+
self.y = y
|
|
32
|
+
self.health = 100
|
|
33
|
+
self.is_active = True
|
|
34
|
+
|
|
35
|
+
def distance_to(self, other: GameEntity) -> float:
|
|
36
|
+
"""计算到另一个实体的距离"""
|
|
37
|
+
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
|
|
38
|
+
|
|
39
|
+
def __repr__(self) -> str:
|
|
40
|
+
return f"{self.name}({self.x:.1f}, {self.y:.1f})"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GameManager(Logic):
|
|
44
|
+
"""
|
|
45
|
+
游戏管理器 - 最高优先级
|
|
46
|
+
负责管理游戏整体状态和事件分发
|
|
47
|
+
"""
|
|
48
|
+
NAME = "GameManager"
|
|
49
|
+
PRIORITY = 100 # 最高优先级,先执行
|
|
50
|
+
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
super().__init__(None)
|
|
53
|
+
self.tick_count = 0
|
|
54
|
+
self.game_over = False
|
|
55
|
+
print("[GameManager] 游戏管理器启动")
|
|
56
|
+
|
|
57
|
+
def onStart(self) -> None:
|
|
58
|
+
"""游戏开始时调用"""
|
|
59
|
+
print("[GameManager] 游戏开始!")
|
|
60
|
+
# 监听游戏事件
|
|
61
|
+
self.listenFor("enemy_died", self._on_enemy_died)
|
|
62
|
+
self.listenFor("player_died", self._on_player_died)
|
|
63
|
+
|
|
64
|
+
def _on_enemy_died(self, data: Any) -> None:
|
|
65
|
+
"""敌人死亡事件处理"""
|
|
66
|
+
enemy_name = data.get("name", "Unknown") if isinstance(data, dict) else data
|
|
67
|
+
print(f"[GameManager] 敌人 {enemy_name} 被消灭!")
|
|
68
|
+
|
|
69
|
+
def _on_player_died(self, data: Any) -> None:
|
|
70
|
+
"""玩家死亡事件处理"""
|
|
71
|
+
print("[GameManager] 玩家死亡! 游戏结束!")
|
|
72
|
+
self.game_over = True
|
|
73
|
+
|
|
74
|
+
def onStep(self) -> None:
|
|
75
|
+
"""每tick执行"""
|
|
76
|
+
self.tick_count += 1
|
|
77
|
+
if self.tick_count % 10 == 0:
|
|
78
|
+
print(f"[GameManager] === Tick {self.tick_count} ===")
|
|
79
|
+
|
|
80
|
+
def idle(self) -> str | None:
|
|
81
|
+
"""空闲状态 - 检查游戏是否结束"""
|
|
82
|
+
if self.game_over:
|
|
83
|
+
return "game_over"
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def game_over(self) -> str | None:
|
|
87
|
+
"""游戏结束状态"""
|
|
88
|
+
print("[GameManager] 游戏结束状态")
|
|
89
|
+
# 通知所有对象清理
|
|
90
|
+
self.pushEvent("game_over", None)
|
|
91
|
+
self.kill() # 结束管理器
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Player(Logic):
|
|
96
|
+
"""
|
|
97
|
+
玩家控制器
|
|
98
|
+
状态: idle -> move -> attack -> idle
|
|
99
|
+
"""
|
|
100
|
+
NAME = "Player"
|
|
101
|
+
PRIORITY = 50
|
|
102
|
+
|
|
103
|
+
def __init__(self, entity: GameEntity) -> None:
|
|
104
|
+
super().__init__(entity)
|
|
105
|
+
self._inst: GameEntity = entity
|
|
106
|
+
self.target: GameEntity | None = None
|
|
107
|
+
self.attack_cooldown = 0
|
|
108
|
+
print(f"[Player] 玩家 {entity.name} 创建在 {entity}")
|
|
109
|
+
|
|
110
|
+
def onStart(self) -> None:
|
|
111
|
+
print(f"[Player] 玩家 {self._inst.name} 准备就绪")
|
|
112
|
+
|
|
113
|
+
def onStep(self) -> None:
|
|
114
|
+
"""每tick更新"""
|
|
115
|
+
if self.attack_cooldown > 0:
|
|
116
|
+
self.attack_cooldown -= 1
|
|
117
|
+
|
|
118
|
+
def idle(self) -> str | None:
|
|
119
|
+
"""空闲状态 - 寻找最近的敌人"""
|
|
120
|
+
# 获取所有敌人实例
|
|
121
|
+
enemies = self.ref("Enemy")
|
|
122
|
+
if enemies:
|
|
123
|
+
if isinstance(enemies, list):
|
|
124
|
+
# 找到最近的敌人
|
|
125
|
+
self.target = min(enemies, key=lambda e: e._inst.distance_to(self._inst))._inst
|
|
126
|
+
else:
|
|
127
|
+
self.target = enemies._inst
|
|
128
|
+
|
|
129
|
+
distance = self._inst.distance_to(self.target)
|
|
130
|
+
if distance > 2.0:
|
|
131
|
+
return "move"
|
|
132
|
+
else:
|
|
133
|
+
return "attack"
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def move(self) -> str | None:
|
|
137
|
+
"""移动状态 - 向目标移动"""
|
|
138
|
+
if not self.target or self.target.health <= 0:
|
|
139
|
+
return "idle"
|
|
140
|
+
|
|
141
|
+
# 简单的移动逻辑
|
|
142
|
+
dx = self.target.x - self._inst.x
|
|
143
|
+
dy = self.target.y - self._inst.y
|
|
144
|
+
distance = (dx ** 2 + dy ** 2) ** 0.5
|
|
145
|
+
|
|
146
|
+
if distance <= 2.0:
|
|
147
|
+
return "attack"
|
|
148
|
+
|
|
149
|
+
# 移动 (归一化方向 * 速度)
|
|
150
|
+
speed = 1.5
|
|
151
|
+
if distance > 0:
|
|
152
|
+
self._inst.x += (dx / distance) * speed
|
|
153
|
+
self._inst.y += (dy / distance) * speed
|
|
154
|
+
|
|
155
|
+
print(f"[Player] 移动到 ({self._inst.x:.1f}, {self._inst.y:.1f}), "
|
|
156
|
+
f"目标距离: {distance:.1f}")
|
|
157
|
+
return "move"
|
|
158
|
+
|
|
159
|
+
def attack(self) -> str | None:
|
|
160
|
+
"""攻击状态"""
|
|
161
|
+
if not self.target or self.target.health <= 0:
|
|
162
|
+
return "idle"
|
|
163
|
+
|
|
164
|
+
if self.attack_cooldown > 0:
|
|
165
|
+
return "attack"
|
|
166
|
+
|
|
167
|
+
# 执行攻击
|
|
168
|
+
damage = 25
|
|
169
|
+
self.target.health -= damage
|
|
170
|
+
self.attack_cooldown = 3 # 3 tick冷却
|
|
171
|
+
|
|
172
|
+
print(f"[Player] 攻击 {self.target.name} 造成 {damage} 伤害! "
|
|
173
|
+
f"剩余生命: {self.target.health}")
|
|
174
|
+
|
|
175
|
+
if self.target.health <= 0:
|
|
176
|
+
# 发送敌人死亡事件
|
|
177
|
+
self.pushEvent("enemy_died", {"name": self.target.name})
|
|
178
|
+
self.target = None
|
|
179
|
+
return "idle"
|
|
180
|
+
|
|
181
|
+
return "attack"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Enemy(Logic):
|
|
185
|
+
"""
|
|
186
|
+
敌人AI
|
|
187
|
+
状态: patrol -> chase -> attack -> patrol
|
|
188
|
+
"""
|
|
189
|
+
NAME = "Enemy"
|
|
190
|
+
PRIORITY = 40
|
|
191
|
+
|
|
192
|
+
def __init__(self, entity: GameEntity, patrol_points: list[tuple[float, float]]) -> None:
|
|
193
|
+
super().__init__(entity)
|
|
194
|
+
self._inst: GameEntity = entity
|
|
195
|
+
self.patrol_points = patrol_points
|
|
196
|
+
self.current_point = 0
|
|
197
|
+
self.player: Player | None = None
|
|
198
|
+
self.attack_cooldown = 0
|
|
199
|
+
self.alert_range = 5.0
|
|
200
|
+
self.attack_range = 2.0
|
|
201
|
+
print(f"[Enemy] 敌人 {entity.name} 创建在 {entity}")
|
|
202
|
+
|
|
203
|
+
def onStart(self) -> None:
|
|
204
|
+
print(f"[Enemy] 敌人 {self._inst.name} 开始巡逻")
|
|
205
|
+
# 监听游戏结束事件
|
|
206
|
+
self.listenFor("game_over", self._on_game_over)
|
|
207
|
+
|
|
208
|
+
def _on_game_over(self, data: Any) -> None:
|
|
209
|
+
"""游戏结束时停止"""
|
|
210
|
+
print(f"[Enemy] {self._inst.name} 收到游戏结束信号")
|
|
211
|
+
self.kill()
|
|
212
|
+
|
|
213
|
+
def onStep(self) -> None:
|
|
214
|
+
"""每tick更新"""
|
|
215
|
+
if self.attack_cooldown > 0:
|
|
216
|
+
self.attack_cooldown -= 1
|
|
217
|
+
|
|
218
|
+
# 死亡检查
|
|
219
|
+
if self._inst.health <= 0:
|
|
220
|
+
self.kill()
|
|
221
|
+
|
|
222
|
+
def _find_player(self) -> Player | None:
|
|
223
|
+
"""查找玩家实例"""
|
|
224
|
+
player = self.ref("Player")
|
|
225
|
+
return player[0] if isinstance(player, list) else player
|
|
226
|
+
|
|
227
|
+
def patrol(self) -> str | None:
|
|
228
|
+
"""巡逻状态"""
|
|
229
|
+
# 检查玩家是否在警戒范围
|
|
230
|
+
player = self._find_player()
|
|
231
|
+
if player and player._inst.health > 0:
|
|
232
|
+
distance = self._inst.distance_to(player._inst)
|
|
233
|
+
if distance <= self.alert_range:
|
|
234
|
+
print(f"[Enemy] {self._inst.name} 发现玩家! 距离: {distance:.1f}")
|
|
235
|
+
self.player = player
|
|
236
|
+
return "chase"
|
|
237
|
+
|
|
238
|
+
# 巡逻移动
|
|
239
|
+
target = self.patrol_points[self.current_point]
|
|
240
|
+
dx = target[0] - self._inst.x
|
|
241
|
+
dy = target[1] - self._inst.y
|
|
242
|
+
distance = (dx ** 2 + dy ** 2) ** 0.5
|
|
243
|
+
|
|
244
|
+
if distance < 0.5:
|
|
245
|
+
# 到达巡逻点,前往下一个
|
|
246
|
+
self.current_point = (self.current_point + 1) % len(self.patrol_points)
|
|
247
|
+
print(f"[Enemy] {self._inst.name} 到达巡逻点,前往下一个")
|
|
248
|
+
else:
|
|
249
|
+
# 移动
|
|
250
|
+
speed = 0.8
|
|
251
|
+
self._inst.x += (dx / distance) * speed
|
|
252
|
+
self._inst.y += (dy / distance) * speed
|
|
253
|
+
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def chase(self) -> str | None:
|
|
257
|
+
"""追击状态"""
|
|
258
|
+
if not self.player or self.player._inst.health <= 0:
|
|
259
|
+
return "patrol"
|
|
260
|
+
|
|
261
|
+
distance = self._inst.distance_to(self.player._inst)
|
|
262
|
+
|
|
263
|
+
# 如果距离太远,返回巡逻
|
|
264
|
+
if distance > self.alert_range * 1.5:
|
|
265
|
+
print(f"[Enemy] {self._inst.name} 丢失目标")
|
|
266
|
+
self.player = None
|
|
267
|
+
return "patrol"
|
|
268
|
+
|
|
269
|
+
# 如果在攻击范围内,切换攻击
|
|
270
|
+
if distance <= self.attack_range:
|
|
271
|
+
return "attack"
|
|
272
|
+
|
|
273
|
+
# 向玩家移动
|
|
274
|
+
dx = self.player._inst.x - self._inst.x
|
|
275
|
+
dy = self.player._inst.y - self._inst.y
|
|
276
|
+
speed = 1.2
|
|
277
|
+
self._inst.x += (dx / distance) * speed
|
|
278
|
+
self._inst.y += (dy / distance) * speed
|
|
279
|
+
|
|
280
|
+
print(f"[Enemy] {self._inst.name} 追击玩家到 ({self._inst.x:.1f}, {self._inst.y:.1f})")
|
|
281
|
+
return "chase"
|
|
282
|
+
|
|
283
|
+
def attack(self) -> str | None:
|
|
284
|
+
"""攻击状态"""
|
|
285
|
+
if not self.player or self.player._inst.health <= 0:
|
|
286
|
+
return "patrol"
|
|
287
|
+
|
|
288
|
+
distance = self._inst.distance_to(self.player._inst)
|
|
289
|
+
if distance > self.attack_range:
|
|
290
|
+
return "chase"
|
|
291
|
+
|
|
292
|
+
if self.attack_cooldown > 0:
|
|
293
|
+
return "attack"
|
|
294
|
+
|
|
295
|
+
# 执行攻击
|
|
296
|
+
damage = 15
|
|
297
|
+
self.player._inst.health -= damage
|
|
298
|
+
self.attack_cooldown = 4
|
|
299
|
+
|
|
300
|
+
print(f"[Enemy] {self._inst.name} 攻击玩家造成 {damage} 伤害! "
|
|
301
|
+
f"玩家剩余生命: {self.player._inst.health}")
|
|
302
|
+
|
|
303
|
+
if self.player._inst.health <= 0:
|
|
304
|
+
self.pushEvent("player_died", {"name": self.player._inst.name})
|
|
305
|
+
|
|
306
|
+
return "attack"
|
|
307
|
+
|
|
308
|
+
def onStop(self) -> None:
|
|
309
|
+
print(f"[Enemy] 敌人 {self._inst.name} 被销毁")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def main():
|
|
313
|
+
"""主程序 - 运行Demo"""
|
|
314
|
+
print("=" * 60)
|
|
315
|
+
print("QFSM Demo - 游戏AI状态机示例")
|
|
316
|
+
print("=" * 60)
|
|
317
|
+
|
|
318
|
+
# 创建调度器
|
|
319
|
+
scheduler = Scheduler()
|
|
320
|
+
|
|
321
|
+
# 注册所有逻辑类
|
|
322
|
+
scheduler.login(GameManager)
|
|
323
|
+
scheduler.login(Player)
|
|
324
|
+
scheduler.login(Enemy)
|
|
325
|
+
|
|
326
|
+
# 创建游戏管理器
|
|
327
|
+
game_manager = GameManager()
|
|
328
|
+
|
|
329
|
+
# 创建玩家
|
|
330
|
+
player_entity = GameEntity("Hero", x=0, y=0)
|
|
331
|
+
player = Player(player_entity)
|
|
332
|
+
|
|
333
|
+
# 创建敌人
|
|
334
|
+
enemy1_entity = GameEntity("Slime", x=10, y=10)
|
|
335
|
+
enemy1 = Enemy(enemy1_entity, patrol_points=[(10, 10), (15, 10), (15, 15), (10, 15)])
|
|
336
|
+
|
|
337
|
+
enemy2_entity = GameEntity("Goblin", x=-8, y=8)
|
|
338
|
+
enemy2 = Enemy(enemy2_entity, patrol_points=[(-8, 8), (-5, 8), (-5, 12), (-8, 12)])
|
|
339
|
+
|
|
340
|
+
print("\n" + "=" * 60)
|
|
341
|
+
print("开始游戏循环 (最多50个tick)")
|
|
342
|
+
print("=" * 60 + "\n")
|
|
343
|
+
|
|
344
|
+
# 游戏主循环
|
|
345
|
+
max_ticks = 50
|
|
346
|
+
tick_delay = 0.3 # 每个tick之间的延迟(秒)
|
|
347
|
+
|
|
348
|
+
for tick in range(max_ticks):
|
|
349
|
+
if not game_manager.alive:
|
|
350
|
+
print("\n[Main] 游戏管理器已停止")
|
|
351
|
+
break
|
|
352
|
+
|
|
353
|
+
scheduler.handle() # ← 执行一个tick
|
|
354
|
+
|
|
355
|
+
# 打印当前状态
|
|
356
|
+
print(f"\n[Tick {tick + 1}] 玩家HP: {player_entity.health}, "
|
|
357
|
+
f"敌人1 HP: {enemy1_entity.health}, 敌人2 HP: {enemy2_entity.health}")
|
|
358
|
+
|
|
359
|
+
# 检查是否所有敌人都被消灭
|
|
360
|
+
if not enemy1.alive and not enemy2.alive:
|
|
361
|
+
print("\n[Main] 所有敌人都被消灭! 胜利!")
|
|
362
|
+
break
|
|
363
|
+
|
|
364
|
+
# 玩家死亡检查
|
|
365
|
+
if player_entity.health <= 0:
|
|
366
|
+
print("\n[Main] 玩家死亡! 游戏结束!")
|
|
367
|
+
break
|
|
368
|
+
|
|
369
|
+
time.sleep(tick_delay)
|
|
370
|
+
|
|
371
|
+
print("\n" + "=" * 60)
|
|
372
|
+
print("Demo 结束")
|
|
373
|
+
print(f"总tick数: {game_manager.tick_count}")
|
|
374
|
+
print(f"玩家最终状态: {player_entity}")
|
|
375
|
+
print(f"敌人1存活: {enemy1.alive}")
|
|
376
|
+
print(f"敌人2存活: {enemy2.alive}")
|
|
377
|
+
print("=" * 60)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if __name__ == "__main__":
|
|
381
|
+
main()
|