pyscreeps-arena 0.3.0__py3-none-any.whl → 0.5.7b0__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.
- pyscreeps_arena/__init__.py +28 -3
- pyscreeps_arena/build.py +3 -615
- pyscreeps_arena/compiler.py +1402 -0
- pyscreeps_arena/core/__init__.py +1 -0
- pyscreeps_arena/core/basic.py +72 -0
- pyscreeps_arena/core/config.py +19 -0
- pyscreeps_arena/core/const.py +28 -0
- pyscreeps_arena/core/core.py +132 -0
- pyscreeps_arena/core/main.py +9 -0
- pyscreeps_arena/core/utils.py +11 -0
- pyscreeps_arena/localization.py +150 -0
- pyscreeps_arena/project.7z +0 -0
- pyscreeps_arena/ui/P2PY.py +108 -0
- pyscreeps_arena/ui/__init__.py +10 -0
- pyscreeps_arena/ui/creeplogic_edit.py +14 -0
- pyscreeps_arena/ui/project_ui.py +215 -0
- pyscreeps_arena/ui/qcreeplogic/__init__.py +3 -0
- pyscreeps_arena/ui/qcreeplogic/model.py +72 -0
- pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py +709 -0
- pyscreeps_arena/ui/qrecipe/__init__.py +1 -0
- pyscreeps_arena/ui/qrecipe/model.py +434 -0
- pyscreeps_arena/ui/qrecipe/qrecipe.py +914 -0
- pyscreeps_arena/ui/rs_icon.py +43 -0
- {pyscreeps_arena-0.3.0.dist-info → pyscreeps_arena-0.5.7b0.dist-info}/METADATA +16 -3
- pyscreeps_arena-0.5.7b0.dist-info/RECORD +28 -0
- {pyscreeps_arena-0.3.0.dist-info → pyscreeps_arena-0.5.7b0.dist-info}/WHEEL +1 -1
- pyscreeps_arena-0.5.7b0.dist-info/entry_points.txt +4 -0
- pyscreeps_arena-0.3.0.dist-info/RECORD +0 -8
- pyscreeps_arena-0.3.0.dist-info/entry_points.txt +0 -2
- {pyscreeps_arena-0.3.0.dist-info → pyscreeps_arena-0.5.7b0.dist-info}/top_level.txt +0 -0
pyscreeps_arena/__init__.py
CHANGED
|
@@ -2,11 +2,12 @@ import os
|
|
|
2
2
|
import sys
|
|
3
3
|
import shutil
|
|
4
4
|
import py7zr
|
|
5
|
-
|
|
5
|
+
from pyscreeps_arena.core import const, config
|
|
6
6
|
def CMD_NewProject():
|
|
7
7
|
"""
|
|
8
8
|
cmd:
|
|
9
9
|
pyscreeps-arena [project_path]
|
|
10
|
+
arena [project_path]
|
|
10
11
|
|
|
11
12
|
* 复制"src" "game" "build.py" 到指定目录
|
|
12
13
|
|
|
@@ -14,7 +15,7 @@ def CMD_NewProject():
|
|
|
14
15
|
|
|
15
16
|
"""
|
|
16
17
|
if len(sys.argv) < 2:
|
|
17
|
-
print("Usage: pyarena new [project_path]")
|
|
18
|
+
print("Usage: pyarena new [project_path]\n# or\narena new [project_path]")
|
|
18
19
|
return
|
|
19
20
|
project_path = sys.argv[1]
|
|
20
21
|
if not os.path.exists(project_path):
|
|
@@ -23,9 +24,33 @@ def CMD_NewProject():
|
|
|
23
24
|
extract_7z(os.path.join(this_path, 'project.7z'), project_path)
|
|
24
25
|
print("Project created at", project_path)
|
|
25
26
|
|
|
27
|
+
def CMD_OpenUI():
|
|
28
|
+
"""
|
|
29
|
+
cmd:
|
|
30
|
+
psaui
|
|
31
|
+
|
|
32
|
+
* 打开UI界面
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
# 检查是否带 -c
|
|
39
|
+
if len(sys.argv) > 1 and sys.argv[1] == '-c':
|
|
40
|
+
from pyscreeps_arena.ui.creeplogic_edit import run_creeplogic_edit
|
|
41
|
+
run_creeplogic_edit()
|
|
42
|
+
else:
|
|
43
|
+
from pyscreeps_arena.ui.project_ui import run_project_creator
|
|
44
|
+
run_project_creator()
|
|
45
|
+
except ImportError as e:
|
|
46
|
+
print(f"错误: 无法导入UI模块 - {e}")
|
|
47
|
+
print("请确保已安装PyQt6: pip install PyQt6")
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print(f"错误: 打开UI界面失败 - {e}")
|
|
26
50
|
|
|
27
51
|
def extract_7z(file_path, output_dir):
|
|
28
52
|
with py7zr.SevenZipFile(file_path, mode='r') as archive:
|
|
29
53
|
archive.extractall(path=output_dir)
|
|
30
54
|
|
|
31
|
-
|
|
55
|
+
if __name__ == '__main__':
|
|
56
|
+
CMD_OpenUI()
|
pyscreeps_arena/build.py
CHANGED
|
@@ -1,619 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import re
|
|
3
|
-
import sys
|
|
4
|
-
import shutil
|
|
5
|
-
import subprocess
|
|
6
|
-
import pyperclip
|
|
7
|
-
from colorama import Fore
|
|
8
|
-
|
|
9
|
-
version_info = sys.version_info
|
|
10
|
-
python_version = f"Compiler py-version: {version_info.major}.{version_info.minor}.{version_info.micro}"
|
|
11
|
-
|
|
12
|
-
class Compiler:
|
|
13
|
-
libs = ["game"]
|
|
14
|
-
FILE_STRONG_REPLACE = {
|
|
15
|
-
"std": {
|
|
16
|
-
"==": "===",
|
|
17
|
-
"!=": "!==",
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
PYFILE_IGNORE_CHECK_FNAMES = ['game/const.py', 'game/proto.py', 'game/utils.py', 'game/const.py']
|
|
21
|
-
|
|
22
|
-
PYFILE_WORD_WARNING_CHECK = {
|
|
23
|
-
r"\.\s*get\s*\(": "Please use 'dict.py_get'. (add '# > ignore' same line to ignore it if you sure right).",
|
|
24
|
-
r"\.\s*clear\s*\(": "Please use 'container.py_clear'. (add '# > ignore' same line to ignore it if you sure right).",
|
|
25
|
-
r"\[\s*-\s*1\s*\]": "Index by '[-1]' may not work in js. (add '# > ignore' same line to ignore it if you sure right).",
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
PYFILE_EXIST_WARNING_CHECK = {
|
|
29
|
-
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]undefined['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'undefined')'.",
|
|
30
|
-
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]Infinity['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'Infinity')'.",
|
|
31
|
-
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]clear['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'clear')'.",
|
|
32
|
-
r"__pragma__\s*\(\s*['\"]noalias['\"]\s*,\s*['\"]get['\"]\s*\)": "Strongly suggest to add '__pragma__('noalias', 'get')'.",
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
JS_VM = "org.transcrypt.__runtime__.js"
|
|
36
|
-
|
|
37
|
-
GAME_UTILS = './game.utils.js'
|
|
38
|
-
GAME_PROTO = './game.proto.js'
|
|
39
|
-
GAME_CONST = './game.const.js'
|
|
40
|
-
|
|
41
|
-
SYSTEM_MODULES_IGNORE = {
|
|
42
|
-
GAME_UTILS: ['CircleVisualStyle', 'Color', 'CostMatrix', 'CreateConstructionSiteResult', 'Direction', 'DoesZapCodeSpaceFlag',
|
|
43
|
-
'FindPathOptions', 'Goal', 'HeapInfo',
|
|
44
|
-
'LineStyle', 'LineVisualStyle', 'PolyVisualStyle', 'RectVisualStyle', 'SearchPathOptions', 'SearchPathResult', 'Terrain',
|
|
45
|
-
'TextAlign', 'TextVisualStyle',
|
|
46
|
-
'Visual', 'getAt', 'searchPath'],
|
|
47
|
-
GAME_PROTO: ['BodyPartType', 'CreepAttackResult', 'CreepBuildResult', 'CreepDropResult', 'CreepHarvestResult', 'CreepHealResult',
|
|
48
|
-
'CreepMoveResult', 'CreepPickupResult',
|
|
49
|
-
'CreepPullResult', 'CreepRangedAttackResult', 'CreepRangedHealResult', 'CreepRangedMassAttackResult',
|
|
50
|
-
'CreepTransferResult', 'CreepWithdrawResult',
|
|
51
|
-
'ResourceType', 'SpawnCreepResult', 'Store', 'TowerAttackResult', 'TowerHealResult', 'Spawning', 'ScoreController',
|
|
52
|
-
'Flag'],
|
|
53
|
-
GAME_CONST: ['RESOURCE_SCORE'],
|
|
54
|
-
}
|
|
55
|
-
SYSTEM_MODULES_TRANSNAME = {
|
|
56
|
-
GAME_UTILS: "game/utils",
|
|
57
|
-
GAME_PROTO: "game/prototypes",
|
|
58
|
-
GAME_CONST: "game/constants",
|
|
59
|
-
}
|
|
60
|
-
GENERATE_IGNORE_PYFILES = ['config.py'] # Won't be compiled into the final js code, only for defines.
|
|
61
|
-
|
|
62
|
-
JS_IMPORT_PAT = re.compile(r'from\s+[\'\"]([^\']+)[\'\"]')
|
|
63
|
-
INSERT_PAT = re.compile(r'#\s*insert\s+([^\n]*)') # 因为被判定的string为单line,所以不需要考虑多行的情况
|
|
64
|
-
|
|
65
|
-
TRANSCRYPT_ERROR_REPLACE = {
|
|
66
|
-
# 由于transcrypt的问题,导致编译后的js代码中存在一些错误,需要进行替换
|
|
67
|
-
r"new\s+set\s*\(": r"set(",
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
def __init__(self, src_dir, build_dir):
|
|
71
|
-
# check
|
|
72
|
-
if not os.path.exists(src_dir):
|
|
73
|
-
print(Fore.RED + '[Error] ' + Fore.RESET + 'src dir not exists')
|
|
74
|
-
sys.exit(1)
|
|
75
|
-
|
|
76
|
-
self.src_dir = os.path.abspath(src_dir)
|
|
77
|
-
self.build_dir = os.path.abspath(build_dir)
|
|
78
|
-
self.target_dir = os.path.join(self.build_dir, '__target__')
|
|
79
|
-
|
|
80
|
-
last_output = False
|
|
81
|
-
@staticmethod
|
|
82
|
-
def auto_read(fpath):
|
|
83
|
-
"""
|
|
84
|
-
自动用多种编码读取文件
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
fpath:
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
|
|
91
|
-
"""
|
|
92
|
-
if not os.path.exists(fpath):
|
|
93
|
-
if not Compiler.last_output:
|
|
94
|
-
Compiler.last_output = True
|
|
95
|
-
print()
|
|
96
|
-
print(Fore.YELLOW + '[CriticWarn] ' + Fore.RESET + f"JS file not exists: {fpath}. You can ignore if it's a not used file")
|
|
97
|
-
return ""
|
|
98
|
-
try:
|
|
99
|
-
with open(fpath, 'r', encoding='utf-8') as f:
|
|
100
|
-
return f.read()
|
|
101
|
-
except UnicodeDecodeError:
|
|
102
|
-
with open(fpath, 'r', encoding='gbk') as f:
|
|
103
|
-
return f.read()
|
|
104
|
-
|
|
105
|
-
def copy_to(self):
|
|
106
|
-
# copy to build dir
|
|
107
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + ' copying to build dir: %s ...' % self.build_dir, end='')
|
|
108
|
-
if os.path.exists(self.build_dir):
|
|
109
|
-
shutil.rmtree(self.build_dir)
|
|
110
|
-
shutil.copytree(self.src_dir, self.build_dir)
|
|
111
|
-
# add libs
|
|
112
|
-
for lib in self.libs:
|
|
113
|
-
shutil.copytree(lib, os.path.join(self.build_dir, lib))
|
|
114
|
-
|
|
115
|
-
# overwrite last to [Done]
|
|
116
|
-
print(Fore.GREEN + '\r[1/6][Done]' + Fore.RESET + ' copying to build dir: %s' % self.build_dir)
|
|
117
|
-
|
|
118
|
-
@property
|
|
119
|
-
def target_py(self):
|
|
120
|
-
return os.path.join(self.build_dir, 'main.py')
|
|
121
|
-
|
|
122
|
-
@property
|
|
123
|
-
def target_js(self):
|
|
124
|
-
return os.path.join(self.target_dir, 'main.js')
|
|
125
|
-
|
|
126
|
-
@staticmethod
|
|
127
|
-
def preprocess_if_block(source_code, variables):
|
|
128
|
-
lines = source_code.split('\n')
|
|
129
|
-
stack = []
|
|
130
|
-
result = []
|
|
131
|
-
|
|
132
|
-
for i, line in enumerate(lines):
|
|
133
|
-
striped = line.strip()
|
|
134
|
-
if_match = re.match(r'#\s*>\s*if\s+([^:.]*)$', striped)
|
|
135
|
-
elif_match = re.match(r'#\s*>\s*elif\s+([^:.]*)$', striped)
|
|
136
|
-
else_match = re.match(r'#\s*>\s*else$', striped)
|
|
137
|
-
endif_match = re.match(r'#\s*>\s*endif$', striped)
|
|
138
|
-
|
|
139
|
-
if if_match:
|
|
140
|
-
condition = if_match.group(1)
|
|
141
|
-
stack.append(eval(condition, variables))
|
|
142
|
-
elif elif_match and len(stack) > 0:
|
|
143
|
-
condition = elif_match.group(1)
|
|
144
|
-
stack[-1] = eval(condition, variables)
|
|
145
|
-
elif else_match and len(stack) > 0:
|
|
146
|
-
stack[-1] = not stack[-1]
|
|
147
|
-
elif endif_match:
|
|
148
|
-
stack.pop()
|
|
149
|
-
else:
|
|
150
|
-
if len(stack) == 0 or all(stack):
|
|
151
|
-
result.append(line)
|
|
152
|
-
|
|
153
|
-
return '\n'.join(result)
|
|
154
|
-
|
|
155
|
-
@staticmethod
|
|
156
|
-
def pyfile_warn_check(fpath, fname) -> bool:
|
|
157
|
-
"""
|
|
158
|
-
检查某个py文件内是否有警告
|
|
159
|
-
|
|
160
|
-
如果有的话,输出[Warn][{file_name}/{line_io}]{detail}
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
bool: 是否有警告
|
|
164
|
-
"""
|
|
165
|
-
# 文件路径检查
|
|
166
|
-
if fpath.endswith('__init__.py'):
|
|
167
|
-
print(Fore.RED + '\n[Error] ' + Fore.RESET + f'Not support __init__.py! Please remove it(the init file) and use from directory.xxx.py import xxxx instead.')
|
|
168
|
-
sys.exit(-1)
|
|
169
|
-
if fname in Compiler.PYFILE_IGNORE_CHECK_FNAMES:
|
|
170
|
-
return False
|
|
171
|
-
|
|
172
|
-
# # 文件内容检查
|
|
173
|
-
content = Compiler.auto_read(fpath)
|
|
174
|
-
warn_flag = False
|
|
175
|
-
# 内容存在性检查
|
|
176
|
-
for pat, detail in Compiler.PYFILE_EXIST_WARNING_CHECK.items():
|
|
177
|
-
if not re.search(pat, content):
|
|
178
|
-
warn_flag = True
|
|
179
|
-
print(Fore.YELLOW + f'\n[Warn]' + Fore.RESET + f'[{fname}]: {detail}', end='')
|
|
180
|
-
# 内容关键字检查
|
|
181
|
-
for pat, detail in Compiler.PYFILE_WORD_WARNING_CHECK.items():
|
|
182
|
-
for i, line in enumerate(content.split('\n')):
|
|
183
|
-
m = re.search(pat, line)
|
|
184
|
-
if m:
|
|
185
|
-
# 检查m前面同一行内是否有#,如果有则忽略
|
|
186
|
-
comment = re.search(r'#', line[:m.start()])
|
|
187
|
-
|
|
188
|
-
# 检查m后面同一行内是否有#\s*ignore;,如果有则忽略
|
|
189
|
-
ignore = re.search(r'#\s*>\s*ignore', line[m.end():])
|
|
190
|
-
|
|
191
|
-
if not comment and not ignore:
|
|
192
|
-
warn_flag = True
|
|
193
|
-
print(Fore.YELLOW + f'\n[Warn]' + Fore.RESET + f'[{fname}/line:{i + 1}]: {detail}', end='')
|
|
194
|
-
return warn_flag
|
|
195
|
-
|
|
196
|
-
def pre_compile(self):
|
|
197
|
-
self.copy_to()
|
|
198
|
-
|
|
199
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + 'preprocessing ...', end='')
|
|
200
|
-
py_fpath, py_names, warn_flag = [], [], False
|
|
201
|
-
for root, dirs, files in os.walk(self.build_dir):
|
|
202
|
-
for file in files:
|
|
203
|
-
if file.endswith('.py'):
|
|
204
|
-
fpath: str = str(os.path.join(root, file))
|
|
205
|
-
# 得到src目录后面的内容
|
|
206
|
-
rel_name = os.path.relpath(fpath, self.build_dir).replace('\\', '/')
|
|
207
|
-
py_names.append(rel_name.replace('/', '.'))
|
|
208
|
-
py_fpath.append(fpath)
|
|
209
|
-
warn_flag |= self.pyfile_warn_check(fpath, rel_name)
|
|
210
|
-
if warn_flag:
|
|
211
|
-
print() # 换行
|
|
212
|
-
|
|
213
|
-
_pre_import_, _pre_imp_detail_ = [], {} # import
|
|
214
|
-
_pre_sort_ = {} # sort
|
|
215
|
-
_pre_define_ = {} # define
|
|
216
|
-
_js_replace_, _insert_id_ = {}, 0 # insert
|
|
217
|
-
|
|
218
|
-
# ----------------------------------- IMPORT ----------------------------------- #
|
|
219
|
-
# 获取所有.py文件中的所有import的内容,并记录下来fname:[imp1, imp2, ...]
|
|
220
|
-
for i, fpath in enumerate(py_fpath):
|
|
221
|
-
fname = py_names[i][:-3] + '.js'
|
|
222
|
-
content = self.auto_read(fpath)
|
|
223
|
-
for i, line in enumerate(content.split('\n')):
|
|
224
|
-
m = re.search(r'#\s*>\s*import\s+([^\n]+)', line)
|
|
225
|
-
if m:
|
|
226
|
-
_pre_import_.append('./' + re.sub(r'\s', '', m.group(1)) + '.js')
|
|
227
|
-
# 记录文件/lineio信息
|
|
228
|
-
_pre_imp_detail_[_pre_import_[-1]] = f'{fname}/line:{i + 1}'
|
|
229
|
-
|
|
230
|
-
# ----------------------------------- REMOVE ----------------------------------- #
|
|
231
|
-
# 移除所有# > remove所在行的内容
|
|
232
|
-
for fpath in py_fpath:
|
|
233
|
-
content = self.auto_read(fpath)
|
|
234
|
-
new_content = ""
|
|
235
|
-
for line in content.split('\n'):
|
|
236
|
-
if not re.search(r'#\s*>\s*remove', line):
|
|
237
|
-
new_content += line + '\n'
|
|
238
|
-
|
|
239
|
-
with open(fpath, 'w', encoding='utf-8') as f:
|
|
240
|
-
f.write(new_content)
|
|
241
|
-
|
|
242
|
-
# ------------------------------------ SORT ------------------------------------ #
|
|
243
|
-
# 获取所有.py文件中的所有# sort的内容,并记录下来(不存在则默认为2^32-1)
|
|
244
|
-
for i, fpath in enumerate(py_fpath):
|
|
245
|
-
fname = py_names[i][:-3] + '.js'
|
|
246
|
-
content = self.auto_read(fpath)
|
|
247
|
-
m = re.search(r'#\s*>\s*sort\s+(\d+)', content)
|
|
248
|
-
if m:
|
|
249
|
-
try:
|
|
250
|
-
sort_num = int(m.group(1))
|
|
251
|
-
except ValueError:
|
|
252
|
-
print(Fore.YELLOW + '[Warn] ' + Fore.RESET + f'sort number error: "{m.group(1)}", use 2^32-1 instead.')
|
|
253
|
-
sort_num = 4294967295
|
|
254
|
-
_pre_sort_[fname] = sort_num
|
|
255
|
-
else:
|
|
256
|
-
_pre_sort_[fname] = 4294967295
|
|
257
|
-
|
|
258
|
-
# ------------------------------------ DEFINE ------------------------------------ #
|
|
259
|
-
# 扫描所有# define的内容,然后在.py中移除这些行,并记录下来
|
|
260
|
-
for fpath in py_fpath:
|
|
261
|
-
content = self.auto_read(fpath)
|
|
262
|
-
new_content = ""
|
|
263
|
-
for line in content.split('\n'):
|
|
264
|
-
# re.compile(r'#\s*define\s+([^\s]+)\s+([^\n]*)')
|
|
265
|
-
m = re.search(r'#\s*>\s*define\s+([^\s]+)\s+([^\n]*)', line)
|
|
266
|
-
if m:
|
|
267
|
-
_pre_define_[m.group(1)] = m.group(2)
|
|
268
|
-
new_content += '\n'
|
|
269
|
-
else:
|
|
270
|
-
new_content += line + '\n'
|
|
271
|
-
|
|
272
|
-
with open(fpath, 'w', encoding='utf-8') as f:
|
|
273
|
-
f.write(new_content)
|
|
274
|
-
|
|
275
|
-
# 按照keys的顺序,先用前面的key对应的内容去依次替换后面的key对应的value中
|
|
276
|
-
_def_keys = list(_pre_define_.keys())
|
|
277
|
-
_keys_len = len(_def_keys)
|
|
278
|
-
for i in range(_keys_len - 1):
|
|
279
|
-
for j in range(i + 1, _keys_len):
|
|
280
|
-
_pre_define_[_def_keys[j]] = _pre_define_[_def_keys[j]].replace(_def_keys[i], _pre_define_[_def_keys[i]])
|
|
281
|
-
|
|
282
|
-
# ------------------------------------ DEFINE:REPLACE ------------------------------------ #
|
|
283
|
-
# 将刚才记录的define替换到.py中(注意优先替换更长的串)(因此先排序)
|
|
284
|
-
_def_keys.sort(key=lambda x: len(x), reverse=True)
|
|
285
|
-
for fpath in py_fpath:
|
|
286
|
-
content = self.auto_read(fpath)
|
|
287
|
-
|
|
288
|
-
# std.py PYSCREEPS_ARENA_PYTHON_VERSION replace
|
|
289
|
-
if os.path.basename(fpath).lower() == 'std.py':
|
|
290
|
-
content = content.replace('PYSCREEPS_ARENA_PYTHON_VERSION', f'\"{python_version}\"')
|
|
291
|
-
|
|
292
|
-
for key in _def_keys:
|
|
293
|
-
content = re.sub(r'[^_A-Za-z0-9]' + key, self._kfc_wrapper(_pre_define_[key]), content)
|
|
294
|
-
|
|
295
|
-
with open(fpath, 'w', encoding='utf-8') as f:
|
|
296
|
-
f.write(content)
|
|
297
|
-
|
|
298
|
-
# ------------------------------------ IF BLOCK ------------------------------------ #
|
|
299
|
-
for fpath in py_fpath:
|
|
300
|
-
content = self.auto_read(fpath)
|
|
301
|
-
|
|
302
|
-
content = self.preprocess_if_block(content, _pre_define_)
|
|
303
|
-
|
|
304
|
-
with open(fpath, 'w', encoding='utf-8') as f:
|
|
305
|
-
f.write(content)
|
|
306
|
-
|
|
307
|
-
# ------------------------------------ INSERT ------------------------------------ #
|
|
308
|
-
# 扫描所有# insert的内容,然后在.py中将整行替换为# __pragma__("js", __JS_INSERT_{id})
|
|
309
|
-
for fpath in py_fpath:
|
|
310
|
-
content = self.auto_read(fpath)
|
|
311
|
-
new_content = ""
|
|
312
|
-
for line in content.split('\n'):
|
|
313
|
-
# re.compile(r'#\s*insert\s*([^\n]*)')
|
|
314
|
-
# '# insert if(obj && obj.body) for(var p of obj.body) if (p.type == MOVE) return true;'
|
|
315
|
-
m = re.search(r'#\s*>\s*insert\s+([^\n]*)', line)
|
|
316
|
-
if m:
|
|
317
|
-
_sign_index_ = line.find('#') # 必然存在
|
|
318
|
-
_js_key_ = f"__JS_INSERT_{_insert_id_:08d}"
|
|
319
|
-
_js_replace_[_js_key_] = m.group(1)
|
|
320
|
-
|
|
321
|
-
new_content += line[:_sign_index_] + f'# __pragma__("js", "{_js_key_}")\n'
|
|
322
|
-
_insert_id_ += 1
|
|
323
|
-
else:
|
|
324
|
-
new_content += line + '\n'
|
|
325
|
-
|
|
326
|
-
with open(fpath, 'w', encoding='utf-8') as f:
|
|
327
|
-
f.write(new_content)
|
|
328
|
-
|
|
329
|
-
print(Fore.GREEN + '\r[2/6][Done] ' + Fore.RESET + 'preprocess finish.')
|
|
330
|
-
|
|
331
|
-
return _pre_import_, _pre_imp_detail_, _pre_sort_, _pre_define_, _js_replace_
|
|
332
|
-
|
|
333
|
-
def clear_not_generate_pyfile(self):
|
|
334
|
-
"""
|
|
335
|
-
清空不需要编译的py文件
|
|
336
|
-
:return:
|
|
337
|
-
"""
|
|
338
|
-
for root, dirs, files in os.walk(self.build_dir):
|
|
339
|
-
for file in files:
|
|
340
|
-
if file.endswith('.py') and file in self.GENERATE_IGNORE_PYFILES:
|
|
341
|
-
with open(os.path.join(root, file), 'w', encoding='utf-8') as f:
|
|
342
|
-
f.write('')
|
|
343
|
-
|
|
344
|
-
def transcrypt_cmd(self):
|
|
345
|
-
# 执行cmd命令: transcrypt -b -m -n -s -e 6 target
|
|
346
|
-
# 并获取cmd得到的输出
|
|
347
|
-
cmd = 'transcrypt -b -m -n -s -e 6 %s' % self.target_py
|
|
348
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + f'"{cmd}" compiling ...', end='')
|
|
349
|
-
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
|
|
350
|
-
stdout, stderr = p.communicate()
|
|
351
|
-
if 'Error while compiling' in stdout.decode():
|
|
352
|
-
print('\r' + stdout.decode())
|
|
353
|
-
print(Fore.RED + '[Error] ' + Fore.RESET + 'transcrypt compile error')
|
|
354
|
-
sys.exit(1)
|
|
355
|
-
print('\r' + Fore.GREEN + '[3/6][Done] ' + Fore.RESET + f'"{cmd}" Ready.')
|
|
356
|
-
|
|
357
|
-
@staticmethod
|
|
358
|
-
def _keep_lbracket(matched) -> str:
|
|
359
|
-
"""
|
|
360
|
-
如果第一个字符是{, 则返回'{',否则返回''
|
|
361
|
-
:param matched:
|
|
362
|
-
:return:
|
|
363
|
-
"""
|
|
364
|
-
return '{' if matched.group(0)[0] == '{' else ''
|
|
365
|
-
|
|
366
|
-
@staticmethod
|
|
367
|
-
def _keep_first_char(matched) -> str:
|
|
368
|
-
"""
|
|
369
|
-
保留第一个字符
|
|
370
|
-
:param matched:
|
|
371
|
-
:return:
|
|
372
|
-
"""
|
|
373
|
-
return matched.group(0)[0]
|
|
374
|
-
|
|
375
|
-
@staticmethod
|
|
376
|
-
def _kfc_wrapper(replace):
|
|
377
|
-
"""
|
|
378
|
-
保留第一个字符
|
|
379
|
-
:param replace:
|
|
380
|
-
:return:
|
|
381
|
-
"""
|
|
382
|
-
|
|
383
|
-
def _kfc(matched) -> str:
|
|
384
|
-
return matched.group(0)[0] + replace
|
|
385
|
-
|
|
386
|
-
return _kfc
|
|
387
|
-
|
|
388
|
-
def analyze_rebuild_main_js(self, defs):
|
|
389
|
-
"""
|
|
390
|
-
分析main.js中导入的模块名称和先后顺序, 并重新生成main.js
|
|
391
|
-
* 主要移除非SYSTEM_MODULES_IGNORE中的模块导入语句
|
|
392
|
-
:param defs: dict{define: value}
|
|
393
|
-
:return: imports : str, modules (names: str)
|
|
394
|
-
"""
|
|
395
|
-
|
|
396
|
-
# create undefined
|
|
397
|
-
imports = ""
|
|
398
|
-
|
|
399
|
-
if defs.get('USE_TUTORIAL_FLAG', '0') == '0' and defs.get('USE_ARENA_FLAG', '0') == '0':
|
|
400
|
-
imports += 'var Flag = undefined;\n'
|
|
401
|
-
if defs.get('USE_SCORE_COLLECTOR', '0') == '0':
|
|
402
|
-
imports += 'var ScoreController = undefined;\nvar RESOURCE_SCORE = undefined;\n'
|
|
403
|
-
imports += '\n'
|
|
404
|
-
|
|
405
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + 'analyzing and rebuilding main.js ...', end='')
|
|
406
|
-
|
|
407
|
-
content = self.auto_read(self.target_js)
|
|
408
|
-
modules, new_content = [], ""
|
|
409
|
-
for line in content.split('\n'):
|
|
410
|
-
m = re.search(self.JS_IMPORT_PAT, line)
|
|
411
|
-
if not m:
|
|
412
|
-
new_content += line + '\n'
|
|
413
|
-
continue
|
|
414
|
-
# remove ignore if in SYSTEM_MODULES_IGNORE
|
|
415
|
-
module = m.group(1)
|
|
416
|
-
for ignore in self.SYSTEM_MODULES_IGNORE.get(module, []):
|
|
417
|
-
line = re.sub(r'[\s{]' + ignore + ',?', self._keep_lbracket, line)
|
|
418
|
-
|
|
419
|
-
# do not add in modules if in system_modules_ignores
|
|
420
|
-
if module in self.SYSTEM_MODULES_IGNORE:
|
|
421
|
-
if module in self.SYSTEM_MODULES_TRANSNAME:
|
|
422
|
-
line = line.replace(module, self.SYSTEM_MODULES_TRANSNAME[module]) # 调整为js中的名称
|
|
423
|
-
imports += line + '\n'
|
|
424
|
-
continue
|
|
425
|
-
if module not in modules:
|
|
426
|
-
modules.append(module)
|
|
427
|
-
|
|
428
|
-
# save raw main.js
|
|
429
|
-
with open(self.target_js[:-3] + ".raw.js", 'w', encoding='utf-8') as f:
|
|
430
|
-
f.write(content)
|
|
431
|
-
|
|
432
|
-
# write rebuild main.js
|
|
433
|
-
with open(self.target_js, 'w', encoding='utf-8') as f:
|
|
434
|
-
f.write(new_content)
|
|
435
|
-
|
|
436
|
-
print(Fore.GREEN + '\r[4/6][Done] ' + Fore.RESET + 'analyze and rebuild main.js successfully.')
|
|
437
|
-
|
|
438
|
-
return imports, modules
|
|
439
|
-
|
|
440
|
-
@staticmethod
|
|
441
|
-
def remove_js_import(raw) -> str:
|
|
442
|
-
"""
|
|
443
|
-
移除js中的import行
|
|
444
|
-
:param raw:
|
|
445
|
-
:return:
|
|
446
|
-
"""
|
|
447
|
-
return re.sub(r'import[^\n]*\n', '', raw)
|
|
448
|
-
|
|
449
|
-
def generate_total_js(self, usr_modules, t_imps: dict, f_sorts, f_replaces, g_replaces) -> str:
|
|
450
|
-
"""
|
|
451
|
-
生成总的main.js
|
|
452
|
-
按照如下顺序组合:
|
|
453
|
-
./org.transcrypt.__runtime__.js
|
|
454
|
-
./game.const.js # IGNORE
|
|
455
|
-
./game.proto.js # IGNORE
|
|
456
|
-
./game.utils.js # IGNORE
|
|
457
|
-
{usr_modules}
|
|
458
|
-
:param usr_modules: list[str] # js vm + 用户自定义模块
|
|
459
|
-
:param t_imps: dict{module_name: detail}
|
|
460
|
-
:param f_sorts: dict{module_name: sort_priority}
|
|
461
|
-
:param f_replaces: dict{module_name: dict{old: new}}
|
|
462
|
-
:param g_replaces: dict{old: new}
|
|
463
|
-
:return: str
|
|
464
|
-
"""
|
|
465
|
-
total_js = ""
|
|
466
|
-
|
|
467
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + 'generating total main.js ...', end='')
|
|
468
|
-
|
|
469
|
-
# resort modules
|
|
470
|
-
f_sorts[self.JS_VM] = -1
|
|
471
|
-
|
|
472
|
-
err_flag = False
|
|
473
|
-
for i, module in enumerate(usr_modules):
|
|
474
|
-
if module[2:] not in f_sorts:
|
|
475
|
-
if module[2:6] == 'src.' and module[6:] in f_sorts:
|
|
476
|
-
f_sorts[module[2:]] = f_sorts[module[6:]]
|
|
477
|
-
# 为了解决这样的问题:
|
|
478
|
-
# > import src.creeps.basic
|
|
479
|
-
# import src.creeps.basic
|
|
480
|
-
else:
|
|
481
|
-
print(Fore.RED + '\n[Error] ' + Fore.RESET + f'"{module[2:-3]}.py" is not a user module.')
|
|
482
|
-
imp_detail = t_imps.get(module, None)
|
|
483
|
-
if imp_detail:
|
|
484
|
-
print("\t\t-- May be imported by user in: %s" % imp_detail)
|
|
485
|
-
else:
|
|
486
|
-
print("\t\t-- Please move this file into the 'src' directory.")
|
|
487
|
-
err_flag = True
|
|
488
|
-
if err_flag:
|
|
489
|
-
sys.exit(1)
|
|
490
|
-
for i in range(len(usr_modules)):
|
|
491
|
-
for j in range(i + 1, len(usr_modules)):
|
|
492
|
-
if f_sorts[usr_modules[i][2:]] > f_sorts[usr_modules[j][2:]]:
|
|
493
|
-
usr_modules[i], usr_modules[j] = usr_modules[j], usr_modules[i]
|
|
494
|
-
|
|
495
|
-
# write modules
|
|
496
|
-
for module in usr_modules:
|
|
497
|
-
content = self.auto_read(os.path.join(self.target_dir, module))
|
|
498
|
-
content = self.remove_js_import(content)
|
|
499
|
-
for old, new in f_replaces.get(module, {}).items():
|
|
500
|
-
content = re.sub(old, new, content)
|
|
501
|
-
for old, new in self.TRANSCRYPT_ERROR_REPLACE.items():
|
|
502
|
-
content = re.sub(old, new, content)
|
|
503
|
-
total_js += f"\n// ---------------------------------------- Module:{module} "
|
|
504
|
-
total_js += "----------------------------------------\n\n"
|
|
505
|
-
total_js += content + '\n'
|
|
506
|
-
|
|
507
|
-
# write main.js
|
|
508
|
-
content = self.auto_read(self.target_js)
|
|
509
|
-
for old, new in self.TRANSCRYPT_ERROR_REPLACE.items():
|
|
510
|
-
content = re.sub(old, new, content)
|
|
511
|
-
total_js += content
|
|
512
|
-
|
|
513
|
-
# global replace
|
|
514
|
-
for old, new in g_replaces.items():
|
|
515
|
-
total_js = re.sub(old, new, total_js)
|
|
516
|
-
|
|
517
|
-
print(Fore.GREEN + '\r[5/6][Done] ' + Fore.RESET + 'generate total main.js successfully.')
|
|
518
|
-
|
|
519
|
-
return total_js
|
|
520
|
-
|
|
521
|
-
def compile(self, export_to=None, paste=False):
|
|
522
|
-
"""
|
|
523
|
-
编译
|
|
524
|
-
:param export_to: 指定main.mjs的输出路径(/main.mjs)
|
|
525
|
-
:param paste: 是否复制到剪贴板
|
|
526
|
-
:return:
|
|
527
|
-
"""
|
|
528
|
-
imps, imp_ts, sorts, defs, reps = self.pre_compile()
|
|
529
|
-
self.clear_not_generate_pyfile() # 清空不需要编译的py文件(请确保在pre_compile之后)
|
|
530
|
-
self.transcrypt_cmd()
|
|
531
|
-
imports, modules = self.analyze_rebuild_main_js(defs)
|
|
532
|
-
total_js = imports + "\n" + self.generate_total_js(list(set(modules + imps)), imp_ts, sorts, self.FILE_STRONG_REPLACE, reps)
|
|
533
|
-
|
|
534
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + 'exporting total main.js ...', end='')
|
|
535
|
-
|
|
536
|
-
# ensure exported main.mjs path
|
|
537
|
-
build_main_mjs = os.path.join(self.build_dir, 'main.mjs')
|
|
538
|
-
|
|
539
|
-
if not export_to:
|
|
540
|
-
# 尝试读取defs
|
|
541
|
-
mjs_path = defs.get('MAIN_JS_PATH', build_main_mjs)
|
|
542
|
-
else:
|
|
543
|
-
mjs_path = export_to
|
|
544
|
-
|
|
545
|
-
if not mjs_path.endswith('js'):
|
|
546
|
-
mjs_path = os.path.join(mjs_path, 'main.mjs')
|
|
547
|
-
|
|
548
|
-
# write main.mjs
|
|
549
|
-
with open(build_main_mjs, 'w', encoding='utf-8') as f:
|
|
550
|
-
f.write(total_js)
|
|
551
|
-
|
|
552
|
-
# export main.mjs
|
|
553
|
-
dir_path = os.path.dirname(mjs_path)
|
|
554
|
-
if not os.path.exists(dir_path):
|
|
555
|
-
print(Fore.RED + '\n[Error] ' + Fore.RESET + f'export dir path not exists: {dir_path}')
|
|
556
|
-
sys.exit(1)
|
|
557
|
-
with open(mjs_path, 'w', encoding='utf-8') as f:
|
|
558
|
-
f.write(total_js)
|
|
559
|
-
|
|
560
|
-
print(Fore.GREEN + '\r[6/6][Done] ' + Fore.RESET + 'export total main.js successfully.')
|
|
561
|
-
|
|
562
|
-
if mjs_path != build_main_mjs:
|
|
563
|
-
print(Fore.LIGHTBLUE_EX + '[Info] ' + Fore.RESET + f'usr export to {mjs_path}')
|
|
564
|
-
|
|
565
|
-
# copy to clipboard
|
|
566
|
-
if paste:
|
|
567
|
-
pyperclip.copy(total_js)
|
|
568
|
-
print(Fore.GREEN + '[Done] ' + Fore.RESET + 'copy to clipboard')
|
|
569
|
-
|
|
570
|
-
def clean(self):
|
|
571
|
-
"""
|
|
572
|
-
清除build目录下除了main.mjs之外的所有文件和目录
|
|
573
|
-
* 先复制main.mjs到src目录下,然后删除build目录,再将main.mjs剪切回build目录
|
|
574
|
-
:return:
|
|
575
|
-
"""
|
|
576
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + 'clean build dir ...', end='')
|
|
577
|
-
if not os.path.exists(self.build_dir):
|
|
578
|
-
print(Fore.RED + '\r[Error] ' + Fore.RESET + 'build dir not exists')
|
|
579
|
-
return
|
|
580
|
-
|
|
581
|
-
if not os.path.exists(os.path.join(self.build_dir, 'main.mjs')):
|
|
582
|
-
print(Fore.RED + '\r[Error] ' + Fore.RESET + 'main.mjs not exists')
|
|
583
|
-
return
|
|
584
|
-
|
|
585
|
-
# copy main.mjs to src
|
|
586
|
-
shutil.copy(os.path.join(self.build_dir, 'main.mjs'), os.path.join(self.src_dir, 'main.mjs'))
|
|
587
|
-
|
|
588
|
-
# remove build dir
|
|
589
|
-
shutil.rmtree(self.build_dir)
|
|
590
|
-
|
|
591
|
-
# create build dir
|
|
592
|
-
os.makedirs(self.build_dir)
|
|
593
|
-
|
|
594
|
-
# move main.mjs to build dir
|
|
595
|
-
shutil.move(os.path.join(self.src_dir, 'main.mjs'), os.path.join(self.build_dir, 'main.mjs'))
|
|
596
|
-
|
|
597
|
-
print(Fore.GREEN + '\r[Done] ' + Fore.RESET + 'clean build dir')
|
|
598
|
-
|
|
599
|
-
def clear(self):
|
|
600
|
-
"""
|
|
601
|
-
清除build目录下所有文件和目录
|
|
602
|
-
:return:
|
|
603
|
-
"""
|
|
604
|
-
print(Fore.YELLOW + '>>> ' + Fore.RESET + 'clear build dir ...', end='')
|
|
605
|
-
if not os.path.exists(self.build_dir):
|
|
606
|
-
print(Fore.RED + '\r[Error] ' + Fore.RESET + 'build dir not exists')
|
|
607
|
-
return
|
|
608
|
-
|
|
609
|
-
shutil.rmtree(self.build_dir)
|
|
610
|
-
os.makedirs(self.build_dir)
|
|
611
|
-
|
|
612
|
-
print(Fore.GREEN + '\r[Done] ' + Fore.RESET + 'clear build dir')
|
|
613
|
-
|
|
1
|
+
from pyscreeps_arena.compiler import Compiler
|
|
614
2
|
|
|
615
3
|
if __name__ == '__main__':
|
|
616
|
-
compiler = Compiler('src', 'build')
|
|
4
|
+
compiler = Compiler('src', 'library', 'build')
|
|
617
5
|
|
|
618
6
|
compiler.compile()
|
|
619
|
-
compiler.clean()
|
|
7
|
+
# compiler.clean()
|