pyscreeps-arena 0.3a0__py3-none-any.whl → 0.3a2__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.
@@ -12,9 +12,9 @@ from colorama import Fore
12
12
 
13
13
  WAIT = Fore.YELLOW + ">>>" + Fore.RESET
14
14
  GREEN = Fore.GREEN + "{}" + Fore.RESET
15
- version_info = sys.version_info
16
- python_version_cn = f"Python版本: {version_info.major}.{version_info.minor}.{version_info.micro}"
17
- python_version_en = f"Compiler py-version: {version_info.major}.{version_info.minor}.{version_info.micro}"
15
+ python_version_info = sys.version_info
16
+ python_version_info = f"{python_version_info.major}.{python_version_info.minor}.{python_version_info.micro}"
17
+
18
18
 
19
19
  # def InsertPragmaBefore(content:str) -> str:
20
20
  # """
@@ -32,7 +32,7 @@ class Compiler_Const:
32
32
  "!=": "!==",
33
33
  }
34
34
  }
35
- PYFILE_IGNORE_CHECK_FNAMES = ['game/const.py', 'game/proto.py', 'game/utils.py']
35
+ PYFILE_IGNORE_CHECK_FNAMES = ['builtin/const.py', 'builtin/proto.py', 'builtin/utils.py']
36
36
 
37
37
  PYFILE_PRAGMA_INSERTS = """
38
38
  # __pragma__('noalias', 'undefined')
@@ -42,12 +42,35 @@ class Compiler_Const:
42
42
  """
43
43
 
44
44
  TOTAL_INSERT_AT_HEAD = """
45
- import {arenaInfo} from "game";
45
+ import { createConstructionSite, findClosestByPath, findClosestByRange, findInRange, findPath, getCpuTime, getDirection, getHeapStatistics, getObjectById, getObjects, getObjectsByPrototype, getRange, getTerrainAt, getTicks,} from 'game/utils';
46
+ import { ConstructionSite, Creep, GameObject, OwnedStructure, Resource, Source, Structure, StructureContainer, StructureExtension, StructureRampart, StructureRoad, StructureSpawn, StructureTower, StructureWall,} from 'game/prototypes';
47
+ import { ATTACK, ATTACK_POWER, BODYPART_COST, BODYPART_HITS, BOTTOM, BOTTOM_LEFT, BOTTOM_RIGHT, BUILD_POWER, CARRY, CARRY_CAPACITY, CONSTRUCTION_COST, CONSTRUCTION_COST_ROAD_SWAMP_RATIO, CONSTRUCTION_COST_ROAD_WALL_RATIO, CONTAINER_CAPACITY, CONTAINER_HITS, CREEP_SPAWN_TIME, DISMANTLE_COST, DISMANTLE_POWER, ERR_BUSY, ERR_FULL, ERR_INVALID_ARGS, ERR_INVALID_TARGET, ERR_NAME_EXISTS, ERR_NOT_ENOUGH_ENERGY, ERR_NOT_ENOUGH_EXTENSIONS, ERR_NOT_ENOUGH_RESOURCES, ERR_NOT_FOUND, ERR_NOT_IN_RANGE, ERR_NOT_OWNER, ERR_NO_BODYPART, ERR_NO_PATH, ERR_TIRED, EXTENSION_ENERGY_CAPACITY, EXTENSION_HITS, HARVEST_POWER, HEAL, HEAL_POWER, LEFT, MAX_CONSTRUCTION_SITES, MAX_CREEP_SIZE, MOVE, OBSTACLE_OBJECT_TYPES, OK, RAMPART_HITS, RAMPART_HITS_MAX, RANGED_ATTACK, RANGED_ATTACK_DISTANCE_RATE, RANGED_ATTACK_POWER, RANGED_HEAL_POWER, REPAIR_COST, REPAIR_POWER, RESOURCES_ALL, RESOURCE_DECAY, RESOURCE_ENERGY, RIGHT, ROAD_HITS, ROAD_WEAROUT, SOURCE_ENERGY_REGEN, SPAWN_ENERGY_CAPACITY, SPAWN_HITS, STRUCTURE_PROTOTYPES, TERRAIN_PLAIN, TERRAIN_SWAMP, TERRAIN_WALL, TOP, TOP_LEFT, TOP_RIGHT, TOUGH, TOWER_CAPACITY, TOWER_COOLDOWN, TOWER_ENERGY_COST, TOWER_FALLOFF, TOWER_FALLOFF_RANGE, TOWER_HITS, TOWER_OPTIMAL_RANGE, TOWER_POWER_ATTACK, TOWER_POWER_HEAL, TOWER_POWER_REPAIR, TOWER_RANGE, WALL_HITS, WALL_HITS_MAX, WORK} from 'game/constants';
48
+
49
+ import {arenaInfo} from "game";
50
+ import {Visual} from "game/visual"
51
+ import {searchPath} from "game/path-finder"
46
52
  """
47
53
 
48
54
  TOTAL_INSERT_BEFORE_MAIN = """
49
55
  """
50
56
 
57
+ TOTAL_APPEND_ATEND = """
58
+ export var sch = Scheduler();
59
+
60
+ know.now = 0;
61
+ export var loop = function () {
62
+ know.now = get.ticks ();
63
+ if (know.now === 1) {
64
+ init (know);
65
+ }
66
+
67
+ know.update_(know);
68
+ step (know);
69
+ sch.handle();
70
+ std.show_usage ();
71
+ };
72
+ """
73
+
51
74
  TOTAL_SIMPLE_REPLACE_WITH = {
52
75
  }
53
76
 
@@ -67,34 +90,12 @@ class Compiler_Const:
67
90
 
68
91
  JS_VM = "org.transcrypt.__runtime__.js"
69
92
 
70
- GAME_UTILS = './builtin.utils.js'
71
- GAME_PROTO = './builtin.proto.js'
72
- GAME_CONST = './builtin.const.js'
73
93
  BUILTIN_TRANS = ["engine.js"]
74
94
  OTHER_IGNORE_WITH = "./builtin"
75
95
 
76
- SYSTEM_MODULES_IGNORE = {
77
- GAME_UTILS: ['CircleVisualStyle', 'Color', 'CostMatrix', 'CreateConstructionSiteResult', 'Direction', 'DoesZapCodeSpaceFlag',
78
- 'FindPathOptions', 'Goal', 'HeapInfo',
79
- 'LineStyle', 'LineVisualStyle', 'PolyVisualStyle', 'RectVisualStyle', 'SearchPathOptions', 'SearchPathResult', 'Terrain',
80
- 'TextAlign', 'TextVisualStyle',
81
- 'Visual', 'getAt', 'searchPath'],
82
- GAME_PROTO: ['BodyPartType', 'CreepAttackResult', 'CreepBuildResult', 'CreepDropResult', 'CreepHarvestResult', 'CreepHealResult',
83
- 'CreepMoveResult', 'CreepPickupResult',
84
- 'CreepPullResult', 'CreepRangedAttackResult', 'CreepRangedHealResult', 'CreepRangedMassAttackResult',
85
- 'CreepTransferResult', 'CreepWithdrawResult',
86
- 'ResourceType', 'SpawnCreepResult', 'Store', 'TowerAttackResult', 'TowerHealResult', 'Spawning', 'ScoreController',
87
- 'Flag', 'Position'],
88
- GAME_CONST: ['RESOURCE_SCORE'],
89
- }
90
- SYSTEM_MODULES_TRANSNAME = { # 对应的在js中的名字 | Corresponding name in js
91
- GAME_UTILS: "game/utils",
92
- GAME_PROTO: "game/prototypes",
93
- GAME_CONST: "game/constants",
94
- }
95
- GENERATE_IGNORE_PYFILES = ['config.py'] # Won't be compiled into the final js code, only for defines.
96
-
97
96
  JS_IMPORT_PAT = re.compile(r'from\s+[\'\"]([^\']+)[\'\"]')
97
+ JS_EXPORT_PAT = re.compile(r'export\s+{([^}]+)}')
98
+ PY_IMPORT_PAT = re.compile(r'from\s+(.+)(?=\s+import)\s+import\s+\*')
98
99
  INSERT_PAT = re.compile(r'#\s*insert\s+([^\n]*)') # 因为被判定的string为单line,所以不需要考虑多行的情况
99
100
 
100
101
  TRANSCRYPT_ERROR_REPLACE = {
@@ -102,6 +103,25 @@ class Compiler_Const:
102
103
  r"new\s+set\s*\(": r"set(",
103
104
  }
104
105
 
106
+ ARENA_IMPORTS_GETTER = {
107
+ const.ARENA_GREEN: lambda: f"""
108
+
109
+ """,
110
+ const.ARENA_BLUE: lambda: f"""
111
+ import {{ Flag, StructureTower }} from 'arena/season_{config.season}/capture_the_flag/basic';
112
+ """,
113
+ const.ARENA_RED: lambda: f"""
114
+ import {{ RESOURCE_SCORE, RESOURCE_SCORE_X, RESOURCE_SCORE_Y, RESOURCE_SCORE_Z, ScoreCollector, AreaEffect, EFFECT_DAMAGE, EFFECT_FREEZE }} from 'arena/season_{config.season}/collect_and_control/basic';
115
+ """,
116
+ const.ARENA_GRAY: lambda: f"""
117
+ // import {{ Flag }} from 'game/prototypes'
118
+ import("game/prototypes")
119
+ .then((module) => {{ const Flag = module.Flag; }})
120
+ .catch((error) => {{ }});
121
+ """,
122
+ }
123
+
124
+
105
125
  class Compiler_Utils(Compiler_Const):
106
126
  last_output = False # 一个小标志位,我只想输出一次此类告警信息
107
127
 
@@ -134,7 +154,6 @@ class Compiler_Utils(Compiler_Const):
134
154
  with open(fpath, 'r', encoding=encoding) as f: # 使用检测到的编码打开文件
135
155
  return f.read()
136
156
 
137
-
138
157
  def copy_to(self) -> list:
139
158
  """
140
159
  复制src到build目录 | copy all files in src to build
@@ -149,6 +168,7 @@ class Compiler_Utils(Compiler_Const):
149
168
  if os.path.exists(self.build_dir):
150
169
  shutil.rmtree(self.build_dir)
151
170
  shutil.copytree(self.src_dir, self.build_dir)
171
+ shutil.copytree(self.src_dir, os.path.join(self.build_dir, "src"))
152
172
  srcs = [] # src下所有python文件的路径 | paths of all python files under src
153
173
  for root, dirs, files in os.walk(self.build_dir):
154
174
  for file in files:
@@ -165,7 +185,7 @@ class Compiler_Utils(Compiler_Const):
165
185
  return srcs
166
186
 
167
187
  @staticmethod
168
- def potential_check(fpath:str, fname:str) -> bool:
188
+ def potential_check(fpath: str, fname: str) -> bool:
169
189
  """
170
190
  检查某个py文件内是否有潜在问题 | check if there are potential problems in a py file
171
191
 
@@ -175,8 +195,8 @@ class Compiler_Utils(Compiler_Const):
175
195
  bool: 是否有警告
176
196
  """
177
197
  # 文件路径检查
178
- if fpath.endswith('__init__.py') and fpath.find("builtin") == -1:
179
- core.error("potential_check", LOC_NOT_SUPPORT_PYFILE_INIT, ln=config.language, ecode=-1, head='\n')
198
+ # if fpath.endswith('__init__.py') and fpath.find("builtin") == -1:
199
+ # core.error("potential_check", LOC_NOT_SUPPORT_PYFILE_INIT, ln=config.language, ecode=-1, head='\n')
180
200
  if fname in Compiler.PYFILE_IGNORE_CHECK_FNAMES:
181
201
  return False
182
202
 
@@ -212,10 +232,10 @@ class Compiler_Utils(Compiler_Const):
212
232
  for i, line in enumerate(lines): # 遍历源代码的每一行 | iterate over each line of source code
213
233
  striped = line.strip() # 去掉行首尾的空格和换行符 | strip leading and trailing whitespace
214
234
  # 使用正则表达式匹配不同的条件语句 | use regex to match different conditional statements
215
- if_match = re.match(r'#\s*>\s*if\s+([^:.]*)$', striped) # 匹配 '# > if' 语句 | match '# > if' statement
216
- elif_match = re.match(r'#\s*>\s*elif\s+([^:.]*)$', striped) # 匹配 '# > elif' 语句 | match '# > elif' statement
217
- else_match = re.match(r'#\s*>\s*else$', striped) # 匹配 '# > else' 语句 | match '# > else' statement
218
- endif_match = re.match(r'#\s*>\s*endif$', striped) # 匹配 '# > endif' 语句 | match '# > endif' statement
235
+ if_match = re.match(r'#\s*>\s*if\s+([^:.]*)$', striped) # 匹配 '# > if' 语句 | match '# > if' statement
236
+ elif_match = re.match(r'#\s*>\s*elif\s+([^:.]*)$', striped) # 匹配 '# > elif' 语句 | match '# > elif' statement
237
+ else_match = re.match(r'#\s*>\s*else$', striped) # 匹配 '# > else' 语句 | match '# > else' statement
238
+ endif_match = re.match(r'#\s*>\s*endif$', striped) # 匹配 '# > endif' 语句 | match '# > endif' statement
219
239
 
220
240
  if if_match: # 如果当前行是 '# > if' 语句 | if it's a '# > if' statement
221
241
  condition = if_match.group(1) # 提取条件表达式 | extract the condition expression
@@ -233,6 +253,80 @@ class Compiler_Utils(Compiler_Const):
233
253
 
234
254
  return '\n'.join(result) # 将处理后的所有代码行连接成一个字符串,并返回最终结果 | join all processed lines into a string and return
235
255
 
256
+ def find_chain_import(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
257
+ """
258
+ 查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
259
+ PY_IMPORT_PAT: re.compile(r'\s+from\s+(.+)(?=\s+import)\s+import\s+\*')
260
+ :param fpath: str 目标文件路径 | target file path
261
+ :param search_dirs: list[str] 搜索目录 | search directories
262
+ :param project_path=None: str python项目中的概念,指根文件所在的目录。如果不指定,默认使用第一次调用时给定的fpath,并且稍后的递归会全部使用此路径 |
263
+ concept in python-project, refers to the directory where the root file is located. If not specified, the fpath given at the first call is used by default, and all subsequent recursions will use this path
264
+ :param records=None: dict[str, None] 记录已经查找过的文件路径,避免重复查找 | record the file paths that have been searched to avoid duplicate searches
265
+ """
266
+ if records is None:
267
+ records = {}
268
+ if not os.path.exists(fpath):
269
+ core.error('Compiler.find_chain_import', core.lformat(LOC_FILE_NOT_EXISTS, [fpath]), head='\n', ln=config.language)
270
+ imps = []
271
+ content = self.auto_read(fpath)
272
+ project_path = project_path or os.path.dirname(fpath)
273
+ for no, line in enumerate(content.split('\n')):
274
+ m = self.PY_IMPORT_PAT.match(line)
275
+ if m:
276
+ target = m.group(1)
277
+ target_path = project_path
278
+
279
+ ## 向前定位 | locate forward
280
+ if target.startswith('.'):
281
+ target_path = os.path.dirname(fpath) # 因为使用了相对路径,所以需要先定位到当前文件所在的目录 |
282
+ # because relative path is used, need to locate the directory where the current file is located first
283
+ count = 0
284
+ for c in target:
285
+ if c == '.':
286
+ count += 1
287
+ else:
288
+ break
289
+ if count > 1:
290
+ for _ in range(count - 1):
291
+ target_path = os.path.dirname(target_path)
292
+
293
+ ## 向后定位 | locate backward
294
+ while (_idx := target.find('.')) != -1:
295
+ first_name = target[:_idx]
296
+ target_path = os.path.join(target_path, first_name)
297
+ target = target[_idx + 1:]
298
+
299
+ ## 检查是否存在 | check if exists
300
+ this_path = os.path.join(target_path, target)
301
+ if os.path.isdir(this_path):
302
+ this_path = os.path.join(this_path, '__init__.py')
303
+ else:
304
+ this_path += '.py'
305
+
306
+ if not os.path.exists(this_path):
307
+ core.error('Compiler.find_chain_import', core.lformat(LOC_CHAIN_FILE_NOT_EXISTS, [fpath, no + 1, this_path]), head='\n', ln=config.language)
308
+ if this_path not in records:
309
+ records[this_path] = None
310
+ tmp = self.find_chain_import(this_path, search_dirs, project_path, records) + [this_path]
311
+ imps.extend(tmp)
312
+
313
+ return imps
314
+
315
+ @staticmethod
316
+ def relist_pyimports_to_jsimports(base_dir:str, pyimps:list[str]) -> list[str]:
317
+ """
318
+ 将python的imports路径列表转换为js的imports路径列表 | convert a list of python imports paths to a list of js imports paths
319
+ """
320
+ jsimps = []
321
+ for pyimp in pyimps:
322
+ rel_path_nodes:list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
323
+ if rel_path_nodes[-1] == '__init__.py':
324
+ rel_path_nodes.pop()
325
+ else:
326
+ rel_path_nodes[-1] = rel_path_nodes[-1][:-3]
327
+ jsimps.append('./' + '.'.join(rel_path_nodes) + '.js')
328
+ return jsimps
329
+
236
330
 
237
331
  class CompilerBase(Compiler_Utils):
238
332
 
@@ -259,7 +353,6 @@ class CompilerBase(Compiler_Utils):
259
353
  self.target_dir = os.path.join(self.build_dir, '__target__')
260
354
  self.build_name = os.path.basename(self.build_dir)
261
355
 
262
-
263
356
  @property
264
357
  def builtin_py(self) -> str:
265
358
  """
@@ -282,13 +375,13 @@ class CompilerBase(Compiler_Utils):
282
375
  return os.path.join(self.target_dir, 'main.js')
283
376
 
284
377
 
285
-
286
378
  class Compiler(CompilerBase):
287
379
  def pre_compile(self):
288
380
  """
289
381
  预编译 | Precompile
290
382
  """
291
- src_paths = self.copy_to()
383
+ src_paths: list[str] = self.copy_to() # 复制src到build目录 | copy all files in src to build
384
+ # 获取src目录下的所有.py文件的路径 | get the paths of all .py files under src
292
385
 
293
386
  core.lprint(WAIT, LOC_PREPROCESSING, end="", ln=config.language)
294
387
  py_fpath, py_names, warn_flag = [], [], False
@@ -300,9 +393,7 @@ class Compiler(CompilerBase):
300
393
  # 将PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "")插入到文件开头
301
394
  content = self.auto_read(fpath)
302
395
  content = self.PYFILE_PRAGMA_INSERTS.replace("\t", "").replace(" ", "") + content
303
- # 如果是main.py,则将self.built_dir下的__init__.py文件内容插入到main.py文件开头
304
- if fpath == self.target_py:
305
- content = self.auto_read(self.builtin_py) + content
396
+
306
397
  with open(fpath, 'w', encoding='utf-8') as f: # 注意,这里修改的是build目录下的文件,不是源文件 | Note that the file under the build directory is modified here, not the source file
307
398
  f.write(content)
308
399
 
@@ -316,42 +407,28 @@ class Compiler(CompilerBase):
316
407
 
317
408
  _usubs_ = [] # update_subclass
318
409
  _pre_import_, _pre_imp_detail_ = [], {} # > import
410
+ _imports = [] # chain import
319
411
  _pre_sort_ = {} # > sort
320
412
  _pre_define_ = {} # > define
321
413
  _js_replace_, _insert_id_ = {}, 0 # > insert
322
414
 
323
-
324
- # ---------------------------------- UPDATABLE ---------------------------------- #
325
- # 获取在src目录内定义的所有继承了Updatable或CreepLogic的类 |
326
- # get all classes that inherit from Updatable or CreepLogic defined in the src directory
327
- for i, fpath in enumerate(py_fpath):
328
- if fpath in src_paths:
329
- content = self.auto_read(fpath)
330
- for line in content.split('\n'):
331
- m = re.search(r'class\s+([^\s(]+)\s*\((Updatable|CreepLogic)', line)
332
- if m:
333
- _usubs_.append(m.group(1))
334
-
335
- # change REPALCE: '/// $UPDATABLE.UPDATES$ ///'
336
- _rp_key = "/// $UPDATABLE.UPDATES$ ///"
337
- _rp_txt = ""
338
- for usub in _usubs_:
339
- _rp_txt += "try{" + f"if ({usub} && !Updatable.UPDATES.includes({usub}))Updatable.UPDATES.append({usub});" + "}catch(e){ if (!(e instanceof ReferenceError)) throw e; }\n"
340
- self.TOTAL_SIMPLE_REPLACE_WITH[_rp_key] = _rp_txt
341
-
342
-
343
- # ----------------------------------- IMPORT ----------------------------------- #
344
- # 获取所有.py文件中的所有import的内容,并记录下来fname:[imp1, imp2, ...]
345
- # | get all import in .py files, and record them fname:[imp1, imp2, ...]
415
+ # -------------------------------- ONLY IMPORT * -------------------------------- #
416
+ # 只允许from xxx import *的情况 | only allow from xxx import *
346
417
  for i, fpath in enumerate(py_fpath):
347
- fname = py_names[i][:-3] + '.js'
348
418
  content = self.auto_read(fpath)
349
- for i, line in enumerate(content.split('\n')):
350
- m = re.search(r'#\s*>\s*import\s+([^\n]+)', line)
419
+ for line in content.split('\n'):
420
+ # 1. 检查 import xxx的情况 | check import xxx
421
+ m = re.match(r'\s*import\s+([^\s]+)', line)
351
422
  if m:
352
- _pre_import_.append('./' + re.sub(r'\s', '', m.group(1)) + '.js')
353
- # 记录文件/lineio信息
354
- _pre_imp_detail_[_pre_import_[-1]] = f'{fname}/line:{i + 1}'
423
+ core.error('Compiler.pre_compile', core.lformat(LOC_IMPORT_STAR_ERROR, [m.group(1), m.group(1)]), head='\n', ln=config.language)
424
+ # 2. 检查 from xxx import yyys的情况(yyys不能是*) | check from xxx import yyys(yyys can't be *)
425
+ m = re.match(r'\n\s*from\s+([^\s]+)\s+import\s+([^\s]+)', line)
426
+ if m and (not m.group(2) or m.group(2)[0] != '*'):
427
+ 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)
428
+
429
+ # -------------------------------- EXPAND IMPORT * -------------------------------- #
430
+ _imports = self.find_chain_import(self.target_py, [os.path.dirname(self.src_dir), self.src_dir])
431
+ _js_imports = self.relist_pyimports_to_jsimports(self.build_dir, _imports)
355
432
 
356
433
  # ----------------------------------- REMOVE ----------------------------------- #
357
434
  # 移除所有# > remove所在行的内容
@@ -370,7 +447,11 @@ class Compiler(CompilerBase):
370
447
  # 获取所有.py文件中的所有# > sort的内容,并记录下来(不存在则默认为65535)
371
448
  # | get all # > sort in .py files, and record them (default 65535 if not exists)
372
449
  for i, fpath in enumerate(py_fpath):
373
- fname = py_names[i][:-3] + '.js'
450
+ fname = py_names[i]
451
+ if fname.endswith('__init__.py'):
452
+ fname = fname[:-12] + '.js'
453
+ else:
454
+ fname = fname[:-3] + '.js'
374
455
  content = self.auto_read(fpath)
375
456
  m = re.search(r'#\s*>\s*sort\s+(\d+)', content)
376
457
  if m:
@@ -416,11 +497,6 @@ class Compiler(CompilerBase):
416
497
  for fpath in py_fpath:
417
498
  content = self.auto_read(fpath)
418
499
 
419
- # std.py PYSCREEPS_ARENA_PYTHON_VERSION replace
420
- if os.path.basename(fpath).lower() == 'std.py':
421
- content = content.replace('PYSCREEPS_ARENA_PYTHON_VERSION_EN', f'\"{python_version_en}\"')
422
- content = content.replace('PYSCREEPS_ARENA_PYTHON_VERSION_CN', f'\"{python_version_cn}\"')
423
-
424
500
  for key in _def_keys:
425
501
  content = re.sub(r'[^_A-Za-z0-9]' + key, self._kfc_wrapper(_pre_define_[key]), content)
426
502
 
@@ -462,18 +538,7 @@ class Compiler(CompilerBase):
462
538
  f.write(new_content)
463
539
 
464
540
  core.lprint(GREEN.format('[2/6]'), LOC_DONE, " ", LOC_PREPROCESSING_FINISH, sep="", head="\r", ln=config.language)
465
- return _pre_import_, _pre_imp_detail_, _pre_sort_, _pre_define_, _js_replace_
466
-
467
- def clear_not_generate_pyfile(self):
468
- """
469
- 清空不需要编译的py文件 | clear not generate py file
470
- :return:
471
- """
472
- for root, dirs, files in os.walk(self.build_dir):
473
- for file in files:
474
- if file.endswith('.py') and file in self.GENERATE_IGNORE_PYFILES:
475
- with open(os.path.join(root, file), 'w', encoding='utf-8') as f:
476
- f.write('')
541
+ return _imports, _js_imports, _pre_sort_, _pre_define_, _js_replace_
477
542
 
478
543
  def transcrypt_cmd(self):
479
544
  # 执行cmd命令: transcrypt -b -m -n -s -e 6 target | execute cmd: transcrypt -b -m -n -s -e 6 target
@@ -497,7 +562,7 @@ class Compiler(CompilerBase):
497
562
  return '{' if matched.group(0)[0] == '{' else ''
498
563
 
499
564
  @staticmethod
500
- def _keep_first_char(matched:re.Match) -> str:
565
+ def _keep_first_char(matched: re.Match) -> str:
501
566
  """
502
567
  保留第一个字符 | keep the first char
503
568
  :param matched: re.match object | re.match对象
@@ -506,7 +571,7 @@ class Compiler(CompilerBase):
506
571
  return matched.group(0)[0]
507
572
 
508
573
  @staticmethod
509
- def _kfc_wrapper(replace:str) -> callable:
574
+ def _kfc_wrapper(replace: str) -> callable:
510
575
  """
511
576
  获取一个保留第一个字符的函数 | get a function to keep the first char
512
577
  :param replace: str
@@ -518,7 +583,7 @@ class Compiler(CompilerBase):
518
583
 
519
584
  return _kfc
520
585
 
521
- def analyze_rebuild_main_js(self, defs:dict[str, object]) -> tuple[str, list[str]]:
586
+ def analyze_rebuild_main_js(self, defs: dict[str, object], modules=None) -> tuple[str, list[str]]:
522
587
  """
523
588
  分析main.js中导入的模块名称和先后顺序, 并重新生成main.js | analyze the module names and order imported in main.js, and rebuild main.js
524
589
  * 主要移除非SYSTEM_MODULES_IGNORE中的模块导入语句 | mainly remove the module import statements that are not in SYSTEM_MODULES_IGNORE
@@ -532,16 +597,17 @@ class Compiler(CompilerBase):
532
597
  # create undefined
533
598
  imports = ""
534
599
 
535
- if defs.get('USE_TUTORIAL_FLAG', '0') == '0' and defs.get('USE_ARENA_FLAG', '0') == '0':
536
- imports += 'var Flag = undefined;\n'
537
- if defs.get('USE_SCORE_COLLECTOR', '0') == '0':
538
- imports += 'var ScoreController = undefined;\nvar RESOURCE_SCORE = undefined;\n'
539
- imports += '\n'
600
+ # if defs.get('USE_TUTORIAL_FLAG', '0') == '0' and defs.get('USE_ARENA_FLAG', '0') == '0':
601
+ # imports += 'var Flag = undefined;\n'
602
+ # if defs.get('USE_SCORE_COLLECTOR', '0') == '0':
603
+ # imports += 'var ScoreController = undefined;\nvar RESOURCE_SCORE = undefined;\n'
604
+ # imports += '\n'
540
605
 
541
606
  core.lprint(WAIT, LOC_ANALYZING_AND_REBUILDING_MAIN_JS, end="", ln=config.language)
542
607
 
543
608
  content = self.auto_read(self.target_js)
544
- modules, new_content = [], ""
609
+ if modules is None: modules = []
610
+ new_modules, new_content = [],""
545
611
  for line in content.split('\n'):
546
612
  m = re.search(self.JS_IMPORT_PAT, line)
547
613
  if not m:
@@ -549,22 +615,22 @@ class Compiler(CompilerBase):
549
615
  continue
550
616
  # remove ignore if in SYSTEM_MODULES_IGNORE
551
617
  module = m.group(1)
552
- for ignore in self.SYSTEM_MODULES_IGNORE.get(module, []):
553
- line = re.sub(r'[\s{]' + ignore + ',?', self._keep_lbracket, line)
554
-
555
- # do not add in modules if in system_modules_ignores
556
- if module in self.SYSTEM_MODULES_IGNORE:
557
- if module in self.SYSTEM_MODULES_TRANSNAME:
558
- line = line.replace(module, self.SYSTEM_MODULES_TRANSNAME[module]) # 调整为js中的名称
559
- imports += line + '\n'
560
- continue
561
618
 
562
619
  _ignore = False
563
620
  if module in modules: _ignore = True
621
+ if module in new_modules: _ignore = True
622
+ if not _ignore: new_modules.append(module)
623
+
624
+ # conbine modules
625
+ modules = new_modules + modules
626
+ new_modules = []
627
+ for module in modules:
628
+ _ignore = False
564
629
  if not _ignore and module.startswith(self.OTHER_IGNORE_WITH): _ignore = True
565
630
  for keeps in self.BUILTIN_TRANS:
566
631
  if module.endswith(keeps): _ignore = False
567
- if not _ignore: modules.append(module)
632
+ if not _ignore: new_modules.append(module)
633
+ modules = new_modules
568
634
 
569
635
  # save raw main.js
570
636
  with open(self.target_js[:-3] + ".raw.js", 'w', encoding='utf-8') as f:
@@ -587,7 +653,7 @@ class Compiler(CompilerBase):
587
653
  """
588
654
  return re.sub(r'import[^\n]*\n', '', raw)
589
655
 
590
- def generate_total_js(self, usr_modules, t_imps: dict, f_sorts, f_replaces, g_replaces) -> str:
656
+ def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces) -> str:
591
657
  """
592
658
  生成总的main.js
593
659
  按照如下顺序组合:
@@ -597,31 +663,23 @@ class Compiler(CompilerBase):
597
663
  ./game.utils.js # IGNORE
598
664
  {usr_modules}
599
665
  :param usr_modules: list[str] # js vm + 用户自定义模块
600
- :param t_imps: dict{module_name: detail}
666
+ :param t_imps: list[str] # main前需要导入的模块
601
667
  :param f_sorts: dict{module_name: sort_priority}
602
668
  :param f_replaces: dict{module_name: dict{old: new}}
603
669
  :param g_replaces: dict{old: new}
604
670
  :return: str
605
671
  """
606
- total_js = "" + self.TOTAL_INSERT_AT_HEAD
672
+ arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES['green']) # like green -> spawn_and_swamp
673
+ self.TOTAL_INSERT_AT_HEAD += self.ARENA_IMPORTS_GETTER[arena_name]() # add arena imports
674
+ total_js = f"const __VERSION__ = '{const.VERSION}';\nconst __PYTHON_VERSION__ = '{python_version_info}';" + self.TOTAL_INSERT_AT_HEAD + f"\nexport var LANGUAGE = '{config.language}';\n"
607
675
 
608
676
  core.lprint(WAIT, LOC_GENERATING_TOTAL_MAIN_JS, end="", ln=config.language)
609
677
 
678
+ # TODO: IMPS donot work
679
+
610
680
  # resort modules
611
681
  f_sorts[self.JS_VM] = -1
612
682
 
613
- err_flag = False
614
- for module in usr_modules:
615
- if module[2:] not in f_sorts:
616
- print(Fore.RED + '\n[Error] ' + Fore.RESET + f'"{module[2:-3]}.py" is not a user module.')
617
- imp_detail = t_imps.get(module, None)
618
- if imp_detail:
619
- print("\t\t-- May be imported by user in: %s" % imp_detail)
620
- else:
621
- print("\t\t-- Please move this file into the 'src' directory.")
622
- err_flag = True
623
- if err_flag:
624
- sys.exit(1)
625
683
  for i in range(len(usr_modules)):
626
684
  for j in range(i + 1, len(usr_modules)):
627
685
  if f_sorts[usr_modules[i][2:]] > f_sorts[usr_modules[j][2:]]:
@@ -639,7 +697,6 @@ class Compiler(CompilerBase):
639
697
  total_js += "----------------------------------------\n\n"
640
698
  total_js += content + '\n'
641
699
 
642
-
643
700
  total_js += self.TOTAL_INSERT_BEFORE_MAIN
644
701
 
645
702
  # write main.js
@@ -648,6 +705,12 @@ class Compiler(CompilerBase):
648
705
  content = re.sub(old, new, content)
649
706
  total_js += content
650
707
 
708
+ # TOTAL_APPEND_ATEND
709
+ total_js += self.TOTAL_APPEND_ATEND
710
+
711
+ # replace export-pat
712
+ total_js = re.sub(self.JS_EXPORT_PAT, "", total_js)
713
+
651
714
  # global replace
652
715
  for old, new in g_replaces.items():
653
716
  total_js = re.sub(old, new, total_js)
@@ -672,7 +735,6 @@ class Compiler(CompilerBase):
672
735
  return int(m.group(1))
673
736
  return 65535
674
737
 
675
-
676
738
  def find_add_pure_js_files(self, sorts, modules):
677
739
  """
678
740
  找到所有的纯js文件,并添加到modules中
@@ -690,31 +752,24 @@ class Compiler(CompilerBase):
690
752
  sorts[fname] = self.__parse_js_file_sort(fpath)
691
753
  modules.append("./" + fname)
692
754
 
693
- def compile(self, export_to=None, paste=False):
755
+ def compile(self, paste=False):
694
756
  """
695
757
  编译
696
- :param export_to: 指定main.mjs的输出路径(/main.mjs)
697
758
  :param paste: 是否复制到剪贴板
698
759
  :return:
699
760
  """
700
- imps, imp_ts, sorts, defs, reps = self.pre_compile()
701
- self.clear_not_generate_pyfile() # 清空不需要编译的py文件(请确保在pre_compile之后)
761
+ imps, jimps, sorts, defs, reps = self.pre_compile()
702
762
  self.transcrypt_cmd()
703
- imports, modules = self.analyze_rebuild_main_js(defs)
763
+ imports, modules = self.analyze_rebuild_main_js(defs, jimps)
704
764
  self.find_add_pure_js_files(sorts, modules)
705
- total_js = imports + "\n" + self.generate_total_js(list(set(modules + imps)), imp_ts, sorts, self.FILE_STRONG_REPLACE, reps)
765
+ total_js = imports + "\n" + self.generate_total_js(modules, imps, sorts, self.FILE_STRONG_REPLACE, reps)
706
766
 
707
767
  core.lprint(WAIT, LOC_EXPORTING_TOTAL_MAIN_JS, end="", ln=config.language)
708
768
 
709
769
  # ensure exported main.mjs path
710
770
  build_main_mjs = os.path.join(self.build_dir, 'main.mjs')
711
771
 
712
- if not export_to:
713
- # 尝试读取defs
714
- mjs_path = defs.get('MAIN_JS_PATH', build_main_mjs)
715
- else:
716
- mjs_path = export_to
717
-
772
+ mjs_path = config.target if config.target is not None else config.TARGET_GETTER()
718
773
  if not mjs_path.endswith('js'):
719
774
  mjs_path = os.path.join(mjs_path, 'main.mjs')
720
775
 
@@ -7,7 +7,12 @@
7
7
  # .2025 01 01 - v0.1:
8
8
  # Created.
9
9
  #
10
-
10
+ import os
11
11
  from pyscreeps_arena.core import const
12
-
12
+ arena = "green"
13
+ season = "beta"
13
14
  language = 'cn'
15
+ # 默认路径: 用户 + ScreepsArena + beta-spawn_and_swamp + main.mjs
16
+ target = None
17
+ TARGET_GETTER = lambda: os.path.join(os.path.expanduser('~'), 'ScreepsArena', f'{season}-{const.ARENA_NAMES.get(arena, "spawn_and_swamp")}', 'main.mjs')
18
+
@@ -9,8 +9,19 @@
9
9
  #
10
10
  import re
11
11
 
12
- VERSION = "0.1"
12
+ VERSION = "0.3a2"
13
13
  AUTHOR = "我阅读理解一直可以的"
14
14
  STEAM_ID = "1029562896"
15
15
  GITHUB_NAME = "EagleBaby"
16
16
 
17
+ ARENA_GREEN = "spawn_and_swamp"
18
+ ARENA_BLUE = "capture_the_flag"
19
+ ARENA_RED = "collect_and_control"
20
+ ARENA_GRAY = "tutorial"
21
+
22
+ ARENA_NAMES = {
23
+ "green": ARENA_GREEN,
24
+ "blue" : ARENA_BLUE,
25
+ "red" : ARENA_RED,
26
+ "gray" : ARENA_GRAY,
27
+ }
@@ -25,6 +25,18 @@ LOC_PREPROCESSING_FINISH = {
25
25
  'en': "Preprocessing finished.",
26
26
  'cn': "预处理完成。",
27
27
  }
28
+ LOC_IMPORT_STAR_ERROR = {
29
+ 'en': "Only 'from xxx import *' is allowed, but found 'import {}'." + Fore.LIGHTBLUE_EX + "Maybe you can use 'from {} import *' instead." + Fore.RESET,
30
+ 'cn': "只允许'from xxx import *',但是发现了'import {}'。" + Fore.LIGHTBLUE_EX + "也许你可以使用'from {} import *'代替之。" + Fore.RESET,
31
+ }
32
+ LOC_IMPORT_STAR2_ERROR = {
33
+ 'en': "Only 'from xxx import *' is allowed, but found 'from {} import {}'." + Fore.LIGHTBLUE_EX + "Maybe you can use 'from {} import *' instead." + Fore.RESET,
34
+ 'cn': "只允许'from xxx import *',但是发现了'from {} import {}'。 " + Fore.LIGHTBLUE_EX + "也许你可以使用'from {} import *'代替之。" + Fore.RESET,
35
+ }
36
+ LOC_CHAIN_FILE_NOT_EXISTS = {
37
+ 'en': "During search chain-import at {}, lineno {}:\n\tfile not exists: {}",
38
+ 'cn': "在搜索链式导入时,位于 {},行号 {}:\n\t文件不存在: {}",
39
+ }
28
40
  LOC_TRANSCRYPTING = {
29
41
  'en': "Transcrypting ... # cmd:{}",
30
42
  'cn': "转译中 ... # cmd命令:{}",
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyscreeps-arena
3
- Version: 0.3a0
3
+ Version: 0.3a2
4
4
  Summary: Python api|interface to play game: Screeps: Arena.
5
5
  Author-email: 2229066748@qq.com
6
6
  Maintainer: Eagle'sBaby
@@ -14,6 +14,7 @@ Description-Content-Type: text/markdown
14
14
  Requires-Dist: pyperclip
15
15
  Requires-Dist: colorama
16
16
  Requires-Dist: py7zr
17
+ Requires-Dist: chardet
17
18
  Requires-Dist: Transcrypt ==3.9.3
18
19
  Requires-Dist: mkdocs
19
20
  Requires-Dist: mkdocstrings[python]
@@ -0,0 +1,17 @@
1
+ pyscreeps_arena/__init__.py,sha256=3O7Oa_IofW8YQYsnrU88SGYNxn-CMR-PpmV8P2hhEhc,800
2
+ pyscreeps_arena/build.py,sha256=DQeGLnID2FjpsWTbnqwt2cOp28olWK68U67bfbFJSOU,177
3
+ pyscreeps_arena/compiler.py,sha256=tM6LZkQVIaLRAGzpU33t0QksJhOq9ghlQiG1y6N77VA,41251
4
+ pyscreeps_arena/localization.py,sha256=gBbrNybIJAHlkzeD6maF-ie6RKSwPBbnCNzg_1ge8-c,5656
5
+ pyscreeps_arena/project.7z,sha256=NAY1brX7-Y78s-YUkJEcVh4D5MXuDmSGkEfyRuzdJhw,55542
6
+ pyscreeps_arena/core/__init__.py,sha256=qoP_rx1TpbDLJoTm5via4XPwEPaV1FXr1SYvoVoHGms,41
7
+ pyscreeps_arena/core/basic.py,sha256=DFvyDTsTXf2bQtnS9s254TrkshvRwajaHcvTyVvJyqw,2790
8
+ pyscreeps_arena/core/config.py,sha256=7GXJ82BgXwaZK6H5RHNqovAS5q4HQvl4hL_7-cBWOio,518
9
+ pyscreeps_arena/core/const.py,sha256=eCWbMSZiyWmGaq2qjslzH_7RN8SOkJGkvI8CyPoVlsE,552
10
+ pyscreeps_arena/core/core.py,sha256=3Nty8eLKPNgwnYk_sVNBPrWuKxBXI2od8nfEezsEAZQ,5157
11
+ pyscreeps_arena/core/main.py,sha256=-FNSOEjksNlDfCbUqsjtPSUW8vT3qxEdfzXqT5Tdsik,170
12
+ pyscreeps_arena/core/utils.py,sha256=N9OOkORvrqnJakayaFp9qyS0apWhB9lBK4xyyYkhFdo,215
13
+ pyscreeps_arena-0.3a2.dist-info/METADATA,sha256=psowb1AqrgiA7NZ9YHhRIuLM-ds6_cdZxr6T1nAOpP0,2079
14
+ pyscreeps_arena-0.3a2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
15
+ pyscreeps_arena-0.3a2.dist-info/entry_points.txt,sha256=dqhZN327jfcNypYjEaEJ2t-ABUZ0sVhtDDrD7GiJBfo,67
16
+ pyscreeps_arena-0.3a2.dist-info/top_level.txt,sha256=l4uLyMR2NOy41ngBMh795jOHTFk3tgYKy64-9cgjVng,16
17
+ pyscreeps_arena-0.3a2.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- pyscreeps_arena/__init__.py,sha256=3O7Oa_IofW8YQYsnrU88SGYNxn-CMR-PpmV8P2hhEhc,800
2
- pyscreeps_arena/build.py,sha256=DQeGLnID2FjpsWTbnqwt2cOp28olWK68U67bfbFJSOU,177
3
- pyscreeps_arena/compiler.py,sha256=iCpiG4GqZiA4MtqfqP10IAF_LuZEyygk0sbKH4br_D0,37179
4
- pyscreeps_arena/localization.py,sha256=_biHwuBLVCczniQrZET88pB_V8f8TtBpe8xKQvL_e7c,4733
5
- pyscreeps_arena/project.7z,sha256=OYGh2AspHQ5K9pIat2rWT_W2gmzG1uiQW-osDEZkWdM,49177
6
- pyscreeps_arena/core/__init__.py,sha256=qoP_rx1TpbDLJoTm5via4XPwEPaV1FXr1SYvoVoHGms,41
7
- pyscreeps_arena/core/basic.py,sha256=DFvyDTsTXf2bQtnS9s254TrkshvRwajaHcvTyVvJyqw,2790
8
- pyscreeps_arena/core/config.py,sha256=ATIWu8jdHlD0b80gsXptsI8yKeHdYNhlCMxlGlT2SfE,233
9
- pyscreeps_arena/core/const.py,sha256=HSZ-zodgUlK_vWGAswpK54NBTXlWZiWGWX56toEO35w,298
10
- pyscreeps_arena/core/core.py,sha256=3Nty8eLKPNgwnYk_sVNBPrWuKxBXI2od8nfEezsEAZQ,5157
11
- pyscreeps_arena/core/main.py,sha256=-FNSOEjksNlDfCbUqsjtPSUW8vT3qxEdfzXqT5Tdsik,170
12
- pyscreeps_arena/core/utils.py,sha256=N9OOkORvrqnJakayaFp9qyS0apWhB9lBK4xyyYkhFdo,215
13
- pyscreeps_arena-0.3a0.dist-info/METADATA,sha256=MFU6vrQaANq9nTT6J1gaj0DfuE939jFled_1K0s2zTg,2055
14
- pyscreeps_arena-0.3a0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
15
- pyscreeps_arena-0.3a0.dist-info/entry_points.txt,sha256=dqhZN327jfcNypYjEaEJ2t-ABUZ0sVhtDDrD7GiJBfo,67
16
- pyscreeps_arena-0.3a0.dist-info/top_level.txt,sha256=l4uLyMR2NOy41ngBMh795jOHTFk3tgYKy64-9cgjVng,16
17
- pyscreeps_arena-0.3a0.dist-info/RECORD,,