pyscreeps-arena 0.5.1.0__tar.gz → 0.5.7.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.
Files changed (54) hide show
  1. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/PKG-INFO +2 -1
  2. pyscreeps_arena-0.5.7.6/pyscreeps_arena/__init__.py +88 -0
  3. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/compiler.py +380 -35
  4. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/const.py +1 -1
  5. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/localization.py +10 -0
  6. pyscreeps_arena-0.5.7.6/pyscreeps_arena/project.7z +0 -0
  7. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/P2PY.py +108 -0
  8. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/__init__.py +12 -0
  9. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/creeplogic_edit.py +14 -0
  10. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/map_render.py +705 -0
  11. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/mapviewer.py +14 -0
  12. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/project_ui.py +215 -0
  13. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qcreeplogic/__init__.py +3 -0
  14. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qcreeplogic/model.py +72 -0
  15. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py +770 -0
  16. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/__init__.py +1 -0
  17. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/qmapmarker.py +339 -0
  18. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/qvariable.py +303 -0
  19. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/test_compact_variable.py +61 -0
  20. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/test_qmapmarker.py +71 -0
  21. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/test_qvariable.py +49 -0
  22. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapker/to_code.py +100 -0
  23. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/__init__.py +3 -0
  24. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/qcinfo.py +567 -0
  25. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/qco.py +441 -0
  26. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/qmapv.py +728 -0
  27. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_array_drag.py +191 -0
  28. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_drag.py +107 -0
  29. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_qcinfo.py +169 -0
  30. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_qco_drag.py +7 -0
  31. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_qmapv.py +224 -0
  32. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qmapv/test_simple_array.py +303 -0
  33. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qrecipe/__init__.py +1 -0
  34. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qrecipe/model.py +434 -0
  35. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/qrecipe/qrecipe.py +914 -0
  36. pyscreeps_arena-0.5.7.6/pyscreeps_arena/ui/rs_icon.py +43 -0
  37. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/PKG-INFO +2 -1
  38. pyscreeps_arena-0.5.7.6/pyscreeps_arena.egg-info/SOURCES.txt +49 -0
  39. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/entry_points.txt +1 -0
  40. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/requires.txt +1 -0
  41. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/setup.py +3 -1
  42. pyscreeps_arena-0.5.1.0/pyscreeps_arena/__init__.py +0 -32
  43. pyscreeps_arena-0.5.1.0/pyscreeps_arena/project.7z +0 -0
  44. pyscreeps_arena-0.5.1.0/pyscreeps_arena.egg-info/SOURCES.txt +0 -19
  45. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/build.py +0 -0
  46. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/__init__.py +0 -0
  47. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/basic.py +0 -0
  48. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/config.py +0 -0
  49. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/core.py +0 -0
  50. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/main.py +0 -0
  51. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena/core/utils.py +0 -0
  52. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/dependency_links.txt +0 -0
  53. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/pyscreeps_arena.egg-info/top_level.txt +0 -0
  54. {pyscreeps_arena-0.5.1.0 → pyscreeps_arena-0.5.7.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyscreeps-arena
3
- Version: 0.5.1.0
3
+ Version: 0.5.7.6
4
4
  Summary: Python api|interface to play game: Screeps: Arena.
5
5
  Author-email: 2229066748@qq.com
6
6
  Maintainer: Eagle'sBaby
@@ -16,6 +16,7 @@ Requires-Dist: colorama
16
16
  Requires-Dist: py7zr
17
17
  Requires-Dist: chardet
18
18
  Requires-Dist: Transcrypt==3.9.1
19
+ Requires-Dist: PyQt6
19
20
  Requires-Dist: mkdocs
20
21
  Requires-Dist: mkdocstrings[python]
21
22
  Requires-Dist: mkdocs-material
@@ -0,0 +1,88 @@
1
+ import os
2
+ import sys
3
+ import shutil
4
+ import py7zr
5
+ from pyscreeps_arena.core import const, config
6
+ from pyscreeps_arena.ui.mapviewer import run_mapviewer
7
+ from pyscreeps_arena.ui.project_ui import run_project_creator
8
+ from pyscreeps_arena.ui.creeplogic_edit import run_creeplogic_edit
9
+
10
+ def CMD_NewProject():
11
+ """
12
+ cmd:
13
+ pyscreeps-arena [project_path]
14
+ arena [project_path]
15
+
16
+ * 复制"src" "game" "build.py" 到指定目录
17
+
18
+ Returns:
19
+
20
+ """
21
+ if len(sys.argv) < 2:
22
+ print("Usage: pyarena new [project_path]\n# or\narena new [project_path]")
23
+ return
24
+ project_path = sys.argv[1]
25
+ if not os.path.exists(project_path):
26
+ os.makedirs(project_path)
27
+ this_path = os.path.dirname(os.path.abspath(__file__))
28
+ extract_7z(os.path.join(this_path, 'project.7z'), project_path)
29
+ print("Project created at", project_path)
30
+
31
+ def CMD_OpenUI():
32
+ """
33
+ cmd:
34
+ psaui 无参数,启用project ui
35
+ psaui -c/-e 启用creeplogic edit
36
+ psaui -m [-c/-e] 启用mapviewer. 默认cn,可以指定-c/-e
37
+ psaui -h 显示帮助信息
38
+
39
+ * 打开UI界面
40
+
41
+ Returns:
42
+
43
+ """
44
+ try:
45
+ # 显示帮助信息
46
+ if len(sys.argv) > 1 and sys.argv[1] == '-h':
47
+ print("Usage:")
48
+ print(" psaui 启用project ui")
49
+ print(" psaui -c/-e 启用creeplogic edit (-c: 中文, -e: 英文)")
50
+ print(" psaui -m [-c/-e] 启用mapviewer (-c: 中文, -e: 英文, 默认: 中文)")
51
+ print(" psaui -h 显示帮助信息")
52
+ print(" --------------------------------------------------------------")
53
+ print(" psaui run `project ui`")
54
+ print(" psaui -c/-e run `creeplogic edit` (-c: chinese, -e: english)")
55
+ print(" psaui -m [-c/-e] run `mapviewer` (-c: chinese, -e: english, default: chinese)")
56
+ print(" psaui -h Show this help message")
57
+
58
+ return
59
+
60
+ # 检查是否使用mapviewer
61
+ if len(sys.argv) > 1 and sys.argv[1] == '-m':
62
+ # 检查语言参数
63
+ if len(sys.argv) > 2 and sys.argv[2] == '-e':
64
+ from pyscreeps_arena.core import config
65
+ config.language = 'en'
66
+ run_mapviewer()
67
+ # 检查是否使用creeplogic edit
68
+ elif len(sys.argv) > 1 and sys.argv[1] == '-c':
69
+ run_creeplogic_edit()
70
+ elif len(sys.argv) > 1 and sys.argv[1] == '-e':
71
+ from pyscreeps_arena.core import config
72
+ config.language = 'en'
73
+ run_creeplogic_edit()
74
+ # 默认启用project ui
75
+ else:
76
+ run_project_creator()
77
+ except ImportError as e:
78
+ print(f"错误: 无法导入UI模块 - {e}")
79
+ print("请确保已安装PyQt6: pip install PyQt6")
80
+ except Exception as e:
81
+ print(f"错误: 打开UI界面失败 - {e}")
82
+
83
+ def extract_7z(file_path, output_dir):
84
+ with py7zr.SevenZipFile(file_path, mode='r') as archive:
85
+ archive.extractall(path=output_dir)
86
+
87
+ if __name__ == '__main__':
88
+ CMD_OpenUI()
@@ -9,6 +9,7 @@ import chardet
9
9
  import subprocess
10
10
  import pyperclip
11
11
  from colorama import Fore
12
+ from typing import List, Optional, Tuple, Union
12
13
 
13
14
  WAIT = Fore.YELLOW + ">>>" + Fore.RESET
14
15
  GREEN = Fore.GREEN + "{}" + Fore.RESET
@@ -16,6 +17,27 @@ python_version_info = sys.version_info
16
17
  python_version_info = f"{python_version_info.major}.{python_version_info.minor}.{python_version_info.micro}"
17
18
 
18
19
 
20
+ def replace_src_prefix(file_list):
21
+ """
22
+ 将列表中以'./src.'开头的字符串替换为'./'
23
+
24
+ 参数:
25
+ file_list: 字符串列表
26
+
27
+ 返回:
28
+ 替换后的新列表
29
+ """
30
+ _ = []
31
+
32
+ for item in file_list:
33
+ if isinstance(item, str) and item.startswith('./src.'):
34
+ _new = item.replace('./src.', './', 1)
35
+ if _new in file_list:
36
+ continue
37
+ _.append(item)
38
+
39
+ return _
40
+
19
41
  # def InsertPragmaBefore(content:str) -> str:
20
42
  # """
21
43
  # 在content的开头插入__pragma__('noalias', 'undefined')等内容 |
@@ -60,7 +82,7 @@ var monitor = Monitor(1);
60
82
  know.now = 0;
61
83
 
62
84
  StageMachineLogicMeta.__types__ = []; // 清空js首次构造时引入的数据
63
- __init_my_exists_creep_before_k__();
85
+ __init_before_k__();
64
86
  let knowCost = 0;
65
87
  let monitorCost = 0;
66
88
  let stepCost = 0;
@@ -71,24 +93,25 @@ export var loop = function () {
71
93
  timeLine = get.cpu_us();
72
94
  know.handle();
73
95
  knowCost = get.cpu_us() - timeLine;
74
- if (know.now === 1) {
75
- std.show_welcome();
76
- init (know);
77
-
78
- }
79
-
96
+
80
97
  timeLine = get.cpu_us();
81
98
  monitor.handle();
82
99
  monitorCost = get.cpu_us() - timeLine;
83
- for (const creep of get.creeps()){
100
+ for (const creep of know.creeps){
84
101
  creep.handle();
85
102
  }
103
+ if (know.now === 1) {
104
+ std.show_welcome();
105
+ init (know);
106
+
107
+ }
86
108
  step (know);
87
109
  timeLine = get.cpu_us();
88
- sch.handle();
110
+ if (get._SCH_FLAG) sch.handle();
89
111
  stepCost = get.cpu_us() - timeLine;
90
112
  std.show_usage ();
91
113
  print("knowCost:", knowCost, "monitorCost:", monitorCost, "stepCost:", stepCost);
114
+ if (know.draw) know.draw();
92
115
  };
93
116
  """
94
117
 
@@ -126,7 +149,7 @@ export var loop = function () {
126
149
 
127
150
  ARENA_IMPORTS_GETTER = {
128
151
  const.ARENA_GREEN: lambda: f"""
129
- const ARENA_COLOR_TYPE = "green";
152
+ const ARENA_COLOR_TYPE = "GREEN";
130
153
  class GameAreaEffect{{
131
154
  constructor(){{
132
155
  }}
@@ -136,8 +159,9 @@ class GameConstructionBoost{{
136
159
  }}
137
160
  }};
138
161
  import {{ Portal as GamePortal}} from 'arena/season_{config.season}/{const.ARENA_GREEN}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/prototypes';
139
-
140
162
  """,
163
+ # import {Portal} from 'arena/season_1/portal_exploration/basic/prototypes';
164
+
141
165
  const.ARENA_BLUE: lambda: f"""
142
166
  const ARENA_COLOR_TYPE = "BLUE";
143
167
  const GameScoreCollector = GameStructureSpawn;
@@ -178,11 +202,6 @@ class GameConstructionBoost{{
178
202
  constructor(){{
179
203
  }}
180
204
  }};
181
- let GameFlag = GameStructureSpawn;
182
- import * as GAME_PROTO_MODULE from 'game/prototypes';
183
- if (GAME_PROTO_MODULE.Flag){{
184
- GameFlag = GAME_PROTO_MODULE.Flag;
185
- }}
186
205
  """,
187
206
  }
188
207
 
@@ -212,12 +231,16 @@ class Compiler_Utils(Compiler_Const):
212
231
  return f.read()
213
232
  except UnicodeDecodeError:
214
233
  # 如果使用检测到的编码读取失败,尝试使用chardet检测编码
215
- with open(fpath, 'rb') as f: # 以二进制模式打开文件
216
- raw_data = f.read() # 读取文件的原始数据
217
- result = chardet.detect(raw_data) # 使用chardet检测编码
218
- encoding = result['encoding'] # 获取检测到的编码
219
- with open(fpath, 'r', encoding=encoding) as f: # 使用检测到的编码打开文件
220
- return f.read()
234
+ try:
235
+ with open(fpath, 'rb') as f: # 以二进制模式打开文件
236
+ raw_data = f.read() # 读取文件的原始数据
237
+ result = chardet.detect(raw_data) # 使用chardet检测编码
238
+ encoding = result['encoding'] # 获取检测到的编码
239
+ with open(fpath, 'r', encoding=encoding) as f: # 使用检测到的编码打开文件
240
+ return f.read()
241
+ except UnicodeDecodeError as e:
242
+ core.error('Compiler_Utils.auto_read', core.lformat(LOC_FILE_READ_FAILED, [fpath, "UnicodeError", e]), end='', head='\n', ln=config.language)
243
+ quit(-1)
221
244
 
222
245
  def copy_to(self) -> list:
223
246
  """
@@ -318,6 +341,102 @@ class Compiler_Utils(Compiler_Const):
318
341
 
319
342
  return '\n'.join(result) # 将处理后的所有代码行连接成一个字符串,并返回最终结果 | join all processed lines into a string and return
320
343
 
344
+ def expand_folder_imports(self, fpath: str, project_path: str = None):
345
+ """
346
+ 扩展文件夹导入语句:将 `from folder import *` 替换为 `from folder.module import *`
347
+ 仅在文件夹没有 __init__.py 时执行此操作
348
+
349
+ :param fpath: 要处理的文件路径
350
+ :param project_path: 项目根路径,用于解析相对导入,默认为 None(使用文件所在目录)
351
+ """
352
+ if not os.path.exists(fpath):
353
+ return
354
+
355
+ content = self.auto_read(fpath)
356
+ lines = content.split('\n')
357
+ new_lines = []
358
+ changed = False
359
+
360
+ for line in lines:
361
+ m = self.PY_IMPORT_PAT.match(line)
362
+ if not m:
363
+ new_lines.append(line)
364
+ continue
365
+
366
+ original_target = m.group(1)
367
+ target = original_target
368
+ target_path = project_path or os.path.dirname(fpath)
369
+
370
+ # 处理相对路径(向前定位)
371
+ if target.startswith('.'):
372
+ target_path = os.path.dirname(fpath)
373
+ count = 0
374
+ for c in target:
375
+ if c == '.':
376
+ count += 1
377
+ else:
378
+ break
379
+
380
+ # 向上移动目录
381
+ if count > 1:
382
+ for _ in range(count - 1):
383
+ target_path = os.path.dirname(target_path)
384
+
385
+ # 移除开头的点
386
+ target = target[count:]
387
+
388
+ # 如果 target 为空,跳过
389
+ if not target:
390
+ new_lines.append(line)
391
+ continue
392
+
393
+ # 向后定位,构建完整路径
394
+ temp_target = target
395
+ while (_idx := temp_target.find('.')) != -1:
396
+ part = temp_target[:_idx]
397
+ target_path = os.path.join(target_path, part)
398
+ temp_target = temp_target[_idx + 1:]
399
+
400
+ # 最终的文件夹路径
401
+ final_dir_path = os.path.join(target_path, temp_target) if temp_target else target_path
402
+
403
+ # 检查是否是文件夹且没有 __init__.py
404
+ if os.path.isdir(final_dir_path):
405
+ init_path = os.path.join(final_dir_path, '__init__.py')
406
+ if not os.path.exists(init_path):
407
+ # 找到所有 .py 文件(排除 __init__.py)| 如果包含子目录,产生一个警告
408
+ # try:
409
+ # py_files = [f for f in os.listdir(final_dir_path) if f.endswith('.py') and f != '__init__.py']
410
+ # except (FileNotFoundError, PermissionError):
411
+ py_files = []
412
+ for item in os.listdir(final_dir_path):
413
+ _path = os.path.join(final_dir_path, item)
414
+ if os.path.isfile(_path) and item.endswith('.py') and item != '__init__.py':
415
+ py_files.append(item)
416
+ elif os.path.isdir(_path):
417
+ rel = os.path.relpath(final_dir_path, project_path)
418
+ core.warn(f'Compiler.expand_folder_imports', core.lformat(LOC_DIR_UNDER_NONINIT_DIR, [item, rel]), end='', head='\n', ln=config.language )
419
+
420
+
421
+ # 为每个 .py 文件生成导入语句
422
+ if py_files:
423
+ for py_file in py_files:
424
+ module_name = py_file[:-3]
425
+ new_import = f"from {original_target}.{module_name} import *"
426
+ new_lines.append(new_import)
427
+ changed = True
428
+ continue
429
+
430
+ # 保留原行
431
+ new_lines.append(line)
432
+
433
+ # 如果文件有修改,写回
434
+ if changed:
435
+ new_content = '\n'.join(new_lines)
436
+ with open(fpath, 'w', encoding='utf-8') as f:
437
+ f.write(new_content)
438
+
439
+
321
440
  def find_chain_import(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
322
441
  r"""
323
442
  查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
@@ -468,15 +587,63 @@ class Compiler_Utils(Compiler_Const):
468
587
  return jsimps
469
588
 
470
589
  # ---------- 自定义函数 ---------- #
590
+
471
591
  @staticmethod
472
- def stage_recursive_replace(content:str) -> str:
473
- r"""
474
- 替换'@recursive'为'@recursive(<fname>)', 其中<fname>为被装饰器标记的函数名 |
475
- Replace '@recursive' with '@recursive(<fname>)', where <fname> is the name of the decorated function.
592
+ def stage_recursive_replace(content: str) -> str:
593
+ """
594
+ 移除 '@recursive' 装饰器行,并在文末添加对应的 _recursiveLogin 调用。
476
595
 
477
- @\s*recursive\s+def\s+([^\s\(]+)
596
+ 对于类方法: _recursiveLogin("ClassName", "method_name")
597
+ 对于普通函数: _recursiveLogin("", "function_name")
478
598
  """
479
- return re.sub(r'@\s*recursive(\s+def\s+)([^\s\(]+)', r'@recursive("\2")\1\2', content)
599
+ calls_to_add = []
600
+ deletions = []
601
+
602
+ # 1. 收集所有类定义的位置和缩进
603
+ class_pattern = re.compile(r'^(\s*)class\s+(\w+)', re.MULTILINE)
604
+ classes = [(m.start(), len(m.group(1)), m.group(2))
605
+ for m in class_pattern.finditer(content)]
606
+
607
+ # 2. 查找所有 @recursive 装饰器
608
+ decorator_pattern = re.compile(r'^\s*@\s*recursive\s*$\n?', re.MULTILINE)
609
+
610
+ for dec_match in decorator_pattern.finditer(content):
611
+ dec_end = dec_match.end()
612
+
613
+ # 查找接下来的函数定义(跳过可能的空行)
614
+ after_decorator = content[dec_end:]
615
+ func_match = re.search(r'^(\s*)def\s+([^\s\(]+)', after_decorator, re.MULTILINE)
616
+
617
+ if not func_match:
618
+ continue
619
+
620
+ func_indent_len = len(func_match.group(1))
621
+ func_name = func_match.group(2)
622
+
623
+ # 3. 确定类名:查找装饰器前最近的、缩进小于函数缩进的类
624
+ class_name = ""
625
+ for cls_pos, cls_indent_len, cls_name in reversed(classes):
626
+ if cls_pos < dec_match.start() and func_indent_len > cls_indent_len:
627
+ class_name = cls_name
628
+ break
629
+
630
+ # 4. 记录删除位置和调用信息
631
+ deletions.append((dec_match.start(), dec_end))
632
+ calls_to_add.append(f'_recursiveLogin("{class_name}", "{func_name}")')
633
+
634
+ # 5. 应用删除(倒序避免位置偏移)
635
+ if not deletions:
636
+ return content
637
+
638
+ result = content
639
+ for start, end in sorted(deletions, key=lambda x: x[0], reverse=True):
640
+ result = result[:start] + result[end:]
641
+
642
+ # 6. 在文末添加调用
643
+ if calls_to_add:
644
+ result = '\n'.join(calls_to_add) + '\n' + result
645
+
646
+ return result
480
647
 
481
648
  @staticmethod
482
649
  def process_mate_code(code):
@@ -524,6 +691,167 @@ class Compiler_Utils(Compiler_Const):
524
691
  code = re.sub(r"'''[^']*'''", '', code)
525
692
  return code
526
693
 
694
+ @classmethod
695
+ def _collect_logical_line(cls, lines: List[str], start_idx: int) -> Tuple[str, int]:
696
+ """收集从start_idx开始的逻辑行(处理多行语句,直到遇到:结尾)"""
697
+ if start_idx >= len(lines):
698
+ return "", start_idx
699
+
700
+ parts = [lines[start_idx].rstrip()]
701
+ i = start_idx
702
+
703
+ # 持续收集直到找到以:结尾的行
704
+ while i < len(lines) and not parts[-1].endswith(':'):
705
+ i += 1
706
+ if i < len(lines):
707
+ parts.append(lines[i].rstrip())
708
+
709
+ return " ".join(parts), i
710
+
711
+ @classmethod
712
+ def _convert_block(cls, lines: List[str], match_counter: Optional[List[int]] = None) -> List[str]:
713
+ if match_counter is None:
714
+ match_counter = [0]
715
+
716
+ result = []
717
+ i = 0
718
+
719
+ while i < len(lines):
720
+ line = lines[i].rstrip()
721
+
722
+ # 检测match语句(支持多行)
723
+ if re.match(r'^\s*match\s+', line):
724
+ full_match, end_idx = cls._collect_logical_line(lines, i)
725
+
726
+ match_stmt = re.match(r'^(\s*)match\s+(.+?)\s*:', full_match)
727
+ if not match_stmt:
728
+ result.append(line)
729
+ i += 1
730
+ continue
731
+
732
+ indent = match_stmt.group(1)
733
+ subject = match_stmt.group(2).strip()
734
+ i = end_idx + 1 # 跳过match语句
735
+
736
+ # 生成临时变量
737
+ var_name = f"__MATCH_{match_counter[0]}__"
738
+ match_counter[0] += 1
739
+ result.append(f"{indent}{var_name} = {subject}")
740
+
741
+ # 解析case语句
742
+ cases: List[Tuple[str, List[str]]] = []
743
+ case_indent = None
744
+
745
+ while i < len(lines):
746
+ case_line = lines[i].rstrip()
747
+
748
+ # 缩进检查 - case必须比match缩进更多
749
+ if not case_line.startswith(indent + ' ') and case_line.strip():
750
+ if re.match(r'^\s*case\s+', case_line):
751
+ raise MatchCaseError(f"第 {i + 1} 行: case 缩进必须大于 match")
752
+ break
753
+
754
+ # 检测case语句(不再使用_collect_logical_line,而是单独处理每一行)
755
+ case_match = re.match(r'^(\s+)case\s+(.+?)\s*:', case_line)
756
+ if case_match:
757
+ curr_case_indent = case_match.group(1)
758
+ case_val = case_match.group(2).strip()
759
+
760
+ # 验证缩进 - 允许不同的case缩进(用于嵌套)
761
+ if len(curr_case_indent) <= len(indent):
762
+ raise MatchCaseError(f"第 {i + 1} 行: case 缩进必须大于 match")
763
+
764
+ # 不再强制要求所有case缩进一致,允许嵌套情况下的不同缩进
765
+ if case_indent is None:
766
+ case_indent = curr_case_indent
767
+
768
+ # 提取内联代码(如果有)
769
+ inline_code = ""
770
+ if ':' in case_line:
771
+ after_colon = case_line.split(':', 1)[1].strip()
772
+ if after_colon:
773
+ inline_code = after_colon
774
+
775
+ i += 1
776
+
777
+ # 收集case块
778
+ block_lines = []
779
+ if inline_code:
780
+ block_lines.append(f"{curr_case_indent} {inline_code}")
781
+
782
+ while i < len(lines):
783
+ block_line = lines[i].rstrip()
784
+ if not block_line.strip():
785
+ block_lines.append(block_line)
786
+ i += 1
787
+ continue
788
+
789
+ # 检查是否是下一个case或者缩进回到当前match级别
790
+ if re.match(r'^\s*case\s+', block_line):
791
+ # 检查这个case是否属于当前match还是父级match
792
+ next_case_indent = re.match(r'^\s*', block_line).group(0)
793
+ if len(next_case_indent) <= len(indent):
794
+ # 属于父级match,退出当前match的处理
795
+ break
796
+ # 仍然属于当前match,继续收集
797
+ if block_line.startswith(curr_case_indent + ' '):
798
+ block_lines.append(block_line)
799
+ i += 1
800
+ continue
801
+ else:
802
+ break
803
+
804
+ if block_line.startswith(indent) and not block_line.startswith(curr_case_indent):
805
+ break
806
+
807
+ if block_line.startswith(curr_case_indent + ' '):
808
+ block_lines.append(block_line)
809
+ i += 1
810
+ continue
811
+
812
+ break
813
+
814
+ cases.append((case_val, block_lines))
815
+ else:
816
+ break
817
+
818
+ # 验证case
819
+ seen = set()
820
+ for idx, (val, _) in enumerate(cases):
821
+ if val == '_':
822
+ if idx != len(cases) - 1:
823
+ raise MatchCaseError(f"第 {i + 1} 行附近: case _ 必须在最后")
824
+ else:
825
+ if val in seen:
826
+ raise MatchCaseError(f"第 {i + 1} 行附近: 重复的 case 值 '{val}'")
827
+ seen.add(val)
828
+
829
+ # 生成if/elif/else
830
+ for idx, (case_val, blk_lines) in enumerate(cases):
831
+ keyword = "else" if case_val == '_' else ("if" if idx == 0 else "elif")
832
+ if keyword == "else":
833
+ result.append(f"{indent}else:")
834
+ else:
835
+ result.append(f"{indent}{keyword} {var_name} == {case_val}:")
836
+
837
+ if blk_lines:
838
+ # 递归处理block_lines,以支持嵌套match
839
+ converted_blk_lines = cls._convert_block(blk_lines, match_counter)
840
+ result.extend(converted_blk_lines)
841
+
842
+ continue
843
+
844
+ result.append(line)
845
+ i += 1
846
+
847
+ return result
848
+
849
+ @classmethod
850
+ def convert_match_to_if(cls, code: str) -> str:
851
+ lines = code.split('\n')
852
+ converted_lines = cls._convert_block(lines, [0])
853
+ return '\n'.join(converted_lines)
854
+
527
855
 
528
856
  class CompilerBase(Compiler_Utils):
529
857
 
@@ -626,6 +954,8 @@ class Compiler(CompilerBase):
626
954
  if m and (not m.group(2) or m.group(2)[0] != '*'):
627
955
  core.error('Compiler.pre_compile', core.lformat(LOC_IMPORT_STAR2_ERROR, [m.group(1), m.group(2), m.group(1)]), head='\n', ln=config.language)
628
956
 
957
+ self.expand_folder_imports(fpath, self.build_dir)
958
+
629
959
  # -------------------------------- EXPAND IMPORT * -------------------------------- #
630
960
  _imports = self.find_chain_import(self.target_py, [os.path.dirname(self.src_dir), self.src_dir])
631
961
  _js_imports = self.relist_pyimports_to_jsimports(self.build_dir, _imports)
@@ -664,10 +994,11 @@ class Compiler(CompilerBase):
664
994
  else:
665
995
  _pre_sort_[fname] = 65535
666
996
 
667
- # ------------------------------------ 自定义:调用process_mate_code ------------------------------------ #
997
+ # ------------------------------------ 自定义:mate & match ------------------------------------ #
668
998
  for fpath in py_fpath:
669
999
  content = self.auto_read(fpath)
670
1000
  content = self.process_mate_code(content) # 调用process_mate_code
1001
+ content = self.convert_match_to_if(content) # 调用convert_match_to_if
671
1002
  with open(fpath, 'w', encoding='utf-8') as f:
672
1003
  f.write(content)
673
1004
 
@@ -756,9 +1087,9 @@ class Compiler(CompilerBase):
756
1087
  return _imports, _js_imports, _pre_sort_, _pre_define_, _js_replace_
757
1088
 
758
1089
  def transcrypt_cmd(self):
759
- # 执行cmd命令: transcrypt -b -m -n -s -e 6 target | execute cmd: transcrypt -b -m -n -s -e 6 target
1090
+ # 执行cmd命令: python -m transcrypt -b -m -n -s -e 6 target | execute cmd: python -m transcrypt -b -m -n -s -e 6 target
760
1091
  # 并获取cmd得到的输出 | and get the output of the cmd
761
- cmd = 'transcrypt -b -m -n -s -e 6 %s' % self.target_py
1092
+ cmd = 'python -m transcrypt -b -m -n -s -e 6 %s' % self.target_py
762
1093
  core.lprint(WAIT, core.lformat(LOC_TRANSCRYPTING, [cmd]), end="", ln=config.language)
763
1094
  p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
764
1095
  stdout, stderr = p.communicate()
@@ -978,7 +1309,7 @@ class Compiler(CompilerBase):
978
1309
  self.transcrypt_cmd()
979
1310
  imports, modules = self.analyze_rebuild_main_js(defs, jimps)
980
1311
  self.find_add_pure_js_files(sorts, modules)
981
- total_js = imports + "\n" + self.generate_total_js(modules, imps, sorts, self.FILE_STRONG_REPLACE, reps)
1312
+ total_js = imports + "\n" + self.generate_total_js(replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps)
982
1313
 
983
1314
  core.lprint(WAIT, LOC_EXPORTING_TOTAL_MAIN_JS, end="", ln=config.language)
984
1315
 
@@ -1053,6 +1384,20 @@ class Compiler(CompilerBase):
1053
1384
 
1054
1385
 
1055
1386
  if __name__ == '__main__':
1056
- compiler = Compiler('src', 'library', 'build')
1057
- compiler.compile()
1058
- compiler.clean()
1387
+ # compiler = Compiler('src', 'library', 'build')
1388
+ # compiler.compile()
1389
+ # compiler.clean()
1390
+ test = """
1391
+
1392
+ def patrolling(self, c: Creep):
1393
+ e = self.center.nearest(k.civilian.enemies, 5)
1394
+ if e:
1395
+ match c.test(e,
1396
+ int(c.hpPer <= 0.9) * 2,
1397
+ int(c.info.melee) * -3
1398
+ ):
1399
+ case True: c.move(e, SWAMP_MOTION)
1400
+ case False: c.move(self.bpos, SWAMP_MOTION)
1401
+
1402
+ """
1403
+ print(f"res=\n{Compiler.convert_match_to_if(test)}")
@@ -9,7 +9,7 @@
9
9
  #
10
10
  import re
11
11
 
12
- VERSION = "0.5.1.0"
12
+ VERSION = "0.5.7.6"
13
13
  AUTHOR = "●ω<🤍♪"
14
14
  STEAM_ID = "1029562896"
15
15
  GITHUB_NAME = "EagleBaby"
@@ -17,6 +17,10 @@ LOC_FILE_NOT_EXISTS = {
17
17
  'en': "{} File not exists: {}. You can ignore if it's a not used file.",
18
18
  'cn': "{} 文件不存在: {}. 如果它是一个未使用的文件,您可以忽略它。",
19
19
  }
20
+ LOC_FILE_READ_FAILED = {
21
+ 'en': "Failed to read file: {}({})\nDetails:\n{}",
22
+ 'cn': "读取文件失败: {}({})\n详细信息:\n{}",
23
+ }
20
24
  LOC_PREPROCESSING = {
21
25
  'en': "Preprocessing ...",
22
26
  'cn': "预处理中 ...",
@@ -135,6 +139,12 @@ LOC_NOT_SUPPORT_PYFILE_INIT = {
135
139
  'cn': '不支持 __init__.py!请删除它(初始化文件)并使用 from directory.xxx import xxxx 代替之。',
136
140
  }
137
141
 
142
+ # V0.5.4
143
+ LOC_DIR_UNDER_NONINIT_DIR = {
144
+ 'en': "Directory [{}] is located under [{}] without __init__.py, therefore it is ignored.",
145
+ 'cn': "目录 [{}] 位于 `无__init__.py`的目录[{}]下,因此被忽略。",
146
+ }
147
+
138
148
  if __name__ == '__main__':
139
149
  lprint(TEMPLATE)
140
150