pyscreeps-arena 0.5.7.1__py3-none-any.whl → 0.5.8.0__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.
@@ -3,6 +3,10 @@ import sys
3
3
  import shutil
4
4
  import py7zr
5
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
+
6
10
  def CMD_NewProject():
7
11
  """
8
12
  cmd:
@@ -55,7 +59,6 @@ def CMD_OpenUI():
55
59
 
56
60
  # 检查是否使用mapviewer
57
61
  if len(sys.argv) > 1 and sys.argv[1] == '-m':
58
- from pyscreeps_arena.ui.mapviewer import run_mapviewer
59
62
  # 检查语言参数
60
63
  if len(sys.argv) > 2 and sys.argv[2] == '-e':
61
64
  from pyscreeps_arena.core import config
@@ -63,16 +66,13 @@ def CMD_OpenUI():
63
66
  run_mapviewer()
64
67
  # 检查是否使用creeplogic edit
65
68
  elif len(sys.argv) > 1 and sys.argv[1] == '-c':
66
- from pyscreeps_arena.ui.creeplogic_edit import run_creeplogic_edit
67
69
  run_creeplogic_edit()
68
70
  elif len(sys.argv) > 1 and sys.argv[1] == '-e':
69
- from pyscreeps_arena.ui.creeplogic_edit import run_creeplogic_edit
70
71
  from pyscreeps_arena.core import config
71
72
  config.language = 'en'
72
73
  run_creeplogic_edit()
73
74
  # 默认启用project ui
74
75
  else:
75
- from pyscreeps_arena.ui.project_ui import run_project_creator
76
76
  run_project_creator()
77
77
  except ImportError as e:
78
78
  print(f"错误: 无法导入UI模块 - {e}")
@@ -17,6 +17,10 @@ python_version_info = sys.version_info
17
17
  python_version_info = f"{python_version_info.major}.{python_version_info.minor}.{python_version_info.micro}"
18
18
 
19
19
 
20
+ class MatchCaseError(Exception):
21
+ pass
22
+
23
+
20
24
  def replace_src_prefix(file_list):
21
25
  """
22
26
  将列表中以'./src.'开头的字符串替换为'./'
@@ -28,16 +32,17 @@ def replace_src_prefix(file_list):
28
32
  替换后的新列表
29
33
  """
30
34
  _ = []
31
-
35
+
32
36
  for item in file_list:
33
37
  if isinstance(item, str) and item.startswith('./src.'):
34
38
  _new = item.replace('./src.', './', 1)
35
39
  if _new in file_list:
36
40
  continue
37
41
  _.append(item)
38
-
42
+
39
43
  return _
40
44
 
45
+
41
46
  # def InsertPragmaBefore(content:str) -> str:
42
47
  # """
43
48
  # 在content的开头插入__pragma__('noalias', 'undefined')等内容 |
@@ -159,8 +164,9 @@ class GameConstructionBoost{{
159
164
  }}
160
165
  }};
161
166
  import {{ Portal as GamePortal}} from 'arena/season_{config.season}/{const.ARENA_GREEN}/{"advanced" if config.level in ["advance", "advanced"] else "basic"}/prototypes';
162
-
163
167
  """,
168
+ # import {Portal} from 'arena/season_1/portal_exploration/basic/prototypes';
169
+
164
170
  const.ARENA_BLUE: lambda: f"""
165
171
  const ARENA_COLOR_TYPE = "BLUE";
166
172
  const GameScoreCollector = GameStructureSpawn;
@@ -414,8 +420,7 @@ class Compiler_Utils(Compiler_Const):
414
420
  py_files.append(item)
415
421
  elif os.path.isdir(_path):
416
422
  rel = os.path.relpath(final_dir_path, project_path)
417
- core.warn(f'Compiler.expand_folder_imports', core.lformat(LOC_DIR_UNDER_NONINIT_DIR, [item, rel]), end='', head='\n', ln=config.language )
418
-
423
+ core.warn(f'Compiler.expand_folder_imports', core.lformat(LOC_DIR_UNDER_NONINIT_DIR, [item, rel]), end='', head='\n', ln=config.language)
419
424
 
420
425
  # 为每个 .py 文件生成导入语句
421
426
  if py_files:
@@ -435,7 +440,6 @@ class Compiler_Utils(Compiler_Const):
435
440
  with open(fpath, 'w', encoding='utf-8') as f:
436
441
  f.write(new_content)
437
442
 
438
-
439
443
  def find_chain_import(self, fpath: str, search_dirs: list[str], project_path: str = None, records: dict[str, None] = None) -> list[str]:
440
444
  r"""
441
445
  查找文件中的所有import语句,并返回所有import的文件路径 | find all import statements in a file and return the paths of all imported files
@@ -571,13 +575,13 @@ class Compiler_Utils(Compiler_Const):
571
575
  return imps
572
576
 
573
577
  @staticmethod
574
- def relist_pyimports_to_jsimports(base_dir:str, pyimps:list[str]) -> list[str]:
578
+ def relist_pyimports_to_jsimports(base_dir: str, pyimps: list[str]) -> list[str]:
575
579
  """
576
580
  将python的imports路径列表转换为js的imports路径列表 | convert a list of python imports paths to a list of js imports paths
577
581
  """
578
582
  jsimps = []
579
583
  for pyimp in pyimps:
580
- rel_path_nodes:list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
584
+ rel_path_nodes: list[str] = os.path.relpath(pyimp, base_dir).replace('\\', '/').split('/')
581
585
  if rel_path_nodes[-1] == '__init__.py':
582
586
  rel_path_nodes.pop()
583
587
  else:
@@ -643,7 +647,7 @@ class Compiler_Utils(Compiler_Const):
643
647
  result = '\n'.join(calls_to_add) + '\n' + result
644
648
 
645
649
  return result
646
-
650
+
647
651
  @staticmethod
648
652
  def process_mate_code(code):
649
653
  # 用于存储匹配到的信息
@@ -677,12 +681,11 @@ class Compiler_Utils(Compiler_Const):
677
681
  for variable_name, class_name in mate_assignments:
678
682
  output_string = f"# > insert Object.defineProperty ({class_name}, '{variable_name}', property.call ({class_name}, {class_name}.{variable_name}._MateGet_, {class_name}.{variable_name}._MateSet_));"
679
683
  output_strings.append(output_string)
680
-
681
- return code + '\n'.join(output_strings)
682
684
 
685
+ return code + '\n'.join(output_strings)
683
686
 
684
687
  @staticmethod
685
- def remove_long_docstring(content:str) -> str:
688
+ def remove_long_docstring(content: str) -> str:
686
689
  """
687
690
  移除长注释 | remove long docstring
688
691
  """
@@ -1081,7 +1084,6 @@ class Compiler(CompilerBase):
1081
1084
  with open(fpath, 'w', encoding='utf-8') as f:
1082
1085
  f.write(content)
1083
1086
 
1084
-
1085
1087
  core.lprint(GREEN.format('[2/6]'), LOC_DONE, " ", LOC_PREPROCESSING_FINISH, sep="", head="\r", ln=config.language)
1086
1088
  return _imports, _js_imports, _pre_sort_, _pre_define_, _js_replace_
1087
1089
 
@@ -1152,7 +1154,7 @@ class Compiler(CompilerBase):
1152
1154
 
1153
1155
  content = self.auto_read(self.target_js)
1154
1156
  if modules is None: modules = []
1155
- new_modules, new_content = [],""
1157
+ new_modules, new_content = [], ""
1156
1158
  for line in content.split('\n'):
1157
1159
  m = re.search(self.JS_IMPORT_PAT, line)
1158
1160
  if not m:
@@ -1198,7 +1200,7 @@ class Compiler(CompilerBase):
1198
1200
  """
1199
1201
  return re.sub(r'import[^\n]*\n', '', raw)
1200
1202
 
1201
- def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces) -> str:
1203
+ def generate_total_js(self, usr_modules, t_imps: list[str], f_sorts, f_replaces, g_replaces, min_js_files=None) -> str:
1202
1204
  """
1203
1205
  生成总的main.js
1204
1206
  按照如下顺序组合:
@@ -1212,6 +1214,7 @@ class Compiler(CompilerBase):
1212
1214
  :param f_sorts: dict{module_name: sort_priority}
1213
1215
  :param f_replaces: dict{module_name: dict{old: new}}
1214
1216
  :param g_replaces: dict{old: new}
1217
+ :param min_js_files: list[str] # .min.js文件路径列表
1215
1218
  :return: str
1216
1219
  """
1217
1220
  arena_name = const.ARENA_NAMES.get(config.arena, const.ARENA_NAMES['green']) # like green -> spawn_and_swamp
@@ -1219,6 +1222,13 @@ class Compiler(CompilerBase):
1219
1222
  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"
1220
1223
  total_js += f"const __AUTHOR__ = '{const.AUTHOR}';\nconst __AUTHOR_CN__ = '{const.BILIBILI_NAME}';"
1221
1224
 
1225
+ # 添加.min.js文件的import语句
1226
+ if min_js_files:
1227
+ for min_js_path in min_js_files:
1228
+ min_js_filename = os.path.basename(min_js_path)
1229
+ total_js += f"\nimport \"./{min_js_filename}\";"
1230
+ total_js += "\n"
1231
+
1222
1232
  core.lprint(WAIT, LOC_GENERATING_TOTAL_MAIN_JS, end="", ln=config.language)
1223
1233
 
1224
1234
  # TODO: IMPS donot work
@@ -1284,19 +1294,29 @@ class Compiler(CompilerBase):
1284
1294
  def find_add_pure_js_files(self, sorts, modules):
1285
1295
  """
1286
1296
  找到所有的纯js文件,并添加到modules中
1297
+ 忽略.min.js文件,这些文件会被单独处理
1287
1298
  :param sorts:
1288
1299
  :param modules:
1289
- :return:
1300
+ :return: list 返回所有.min.js文件的列表
1290
1301
  """
1302
+ min_js_files = []
1291
1303
  for root, dirs, files in os.walk(self.lib_dir):
1292
1304
  for file in files:
1293
1305
  if file.endswith('.js') and file not in modules:
1294
- fpath = str(os.path.join(root, file))
1295
- fname = file.replace('\\', '/')
1296
- # copy file to target
1297
- shutil.copy(fpath, os.path.join(self.target_dir, fname))
1298
- sorts[fname] = self.__parse_js_file_sort(fpath)
1299
- modules.append("./" + fname)
1306
+ # 如果是.min.js文件,不拷贝到target,而是记录到单独列表
1307
+ if file.endswith('.min.js'):
1308
+ fpath = str(os.path.join(root, file))
1309
+ min_js_files.append(fpath)
1310
+ else:
1311
+ # 普通js文件,按原逻辑处理
1312
+ fpath = str(os.path.join(root, file))
1313
+ fname = file.replace('\\', '/')
1314
+ # copy file to target
1315
+ shutil.copy(fpath, os.path.join(self.target_dir, fname))
1316
+ sorts[fname] = self.__parse_js_file_sort(fpath)
1317
+ modules.append("./" + fname)
1318
+
1319
+ return min_js_files
1300
1320
 
1301
1321
  def compile(self, paste=False):
1302
1322
  """
@@ -1307,8 +1327,7 @@ class Compiler(CompilerBase):
1307
1327
  imps, jimps, sorts, defs, reps = self.pre_compile()
1308
1328
  self.transcrypt_cmd()
1309
1329
  imports, modules = self.analyze_rebuild_main_js(defs, jimps)
1310
- self.find_add_pure_js_files(sorts, modules)
1311
- total_js = imports + "\n" + self.generate_total_js(replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps)
1330
+ min_js_files = self.find_add_pure_js_files(sorts, modules)
1312
1331
 
1313
1332
  core.lprint(WAIT, LOC_EXPORTING_TOTAL_MAIN_JS, end="", ln=config.language)
1314
1333
 
@@ -1319,12 +1338,28 @@ class Compiler(CompilerBase):
1319
1338
  if not mjs_path.endswith('js'):
1320
1339
  mjs_path = os.path.join(mjs_path, 'main.mjs')
1321
1340
 
1341
+ # 获取目标目录路径
1342
+ dir_path = os.path.dirname(mjs_path)
1343
+ build_dir_path = os.path.dirname(build_main_mjs)
1344
+
1345
+ # 复制.min.js文件到目标目录
1346
+ for min_js_path in min_js_files:
1347
+ min_js_filename = os.path.basename(min_js_path)
1348
+ # 复制到build目录
1349
+ shutil.copy(min_js_path, os.path.join(build_dir_path, min_js_filename))
1350
+ # 复制到最终导出目录
1351
+ shutil.copy(min_js_path, os.path.join(dir_path, min_js_filename))
1352
+
1353
+ # 生成total_js,传入.min.js文件列表
1354
+ total_js = imports + "\n" + self.generate_total_js(
1355
+ replace_src_prefix(modules), imps, sorts, self.FILE_STRONG_REPLACE, reps, min_js_files
1356
+ )
1357
+
1322
1358
  # write main.mjs
1323
1359
  with open(build_main_mjs, 'w', encoding='utf-8') as f:
1324
1360
  f.write(total_js)
1325
1361
 
1326
1362
  # export main.mjs
1327
- dir_path = os.path.dirname(mjs_path)
1328
1363
  if not os.path.exists(dir_path):
1329
1364
  core.error('Compiler.compile', core.lformat(LOC_EXPORT_DIR_PATH_NOT_EXISTS, [dir_path]), head='\n', ln=config.language)
1330
1365
  with open(mjs_path, 'w', encoding='utf-8') as f:
@@ -9,7 +9,7 @@
9
9
  #
10
10
  import re
11
11
 
12
- VERSION = "0.5.7.1"
12
+ VERSION = "0.5.8.0"
13
13
  AUTHOR = "●ω<🤍♪"
14
14
  STEAM_ID = "1029562896"
15
15
  GITHUB_NAME = "EagleBaby"
Binary file
@@ -0,0 +1 @@
1
+ from pyscreeps_arena.ui.qmapker.qmapmarker import QPSAMapMarker
@@ -0,0 +1,339 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ QPSA Map Marker Component - 地图标记组件
4
+ 左右大布局,右边是QPSAMapViewer,左边是信息栏
5
+ """
6
+ from typing import List, Dict, Any, Optional
7
+ import json
8
+ from PyQt6.QtWidgets import (QWidget, QHBoxLayout, QVBoxLayout, QPushButton,
9
+ QListWidget, QListWidgetItem, QFrame, QSpacerItem,
10
+ QSizePolicy, QApplication)
11
+ from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
12
+ from PyQt6.QtGui import QPixmap
13
+
14
+ from pyscreeps_arena.ui.qmapv.qmapv import QPSAMapViewer
15
+ from pyscreeps_arena.ui.qmapv.qcinfo import QPSACellInfo
16
+ from pyscreeps_arena.ui.qmapker.qvariable import QPSACoVariable
17
+ from pyscreeps_arena.ui.qmapker.to_code import json2code
18
+
19
+
20
+ class AddVariableWidget(QWidget):
21
+ """Special widget with centered + button to add new variable."""
22
+
23
+ # Signal emitted when add button is clicked
24
+ addRequested = pyqtSignal()
25
+
26
+ def __init__(self, parent=None):
27
+ super().__init__(parent)
28
+ self._init_ui()
29
+
30
+ def _init_ui(self):
31
+ """Initialize UI components."""
32
+ # Main layout with center alignment
33
+ layout = QVBoxLayout()
34
+ layout.setContentsMargins(0, 0, 0, 0)
35
+ layout.setSpacing(0)
36
+ layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
37
+
38
+ # Add button with + symbol
39
+ self._add_button = QPushButton("➕")
40
+ self._add_button.setFixedSize(40, 40)
41
+ self._add_button.setStyleSheet("""
42
+ QPushButton {
43
+ border: 2px dashed #ccc;
44
+ border-radius: 20px;
45
+ background-color: #f9f9f9;
46
+ font-size: 20px;
47
+ color: #666;
48
+ }
49
+ QPushButton:hover {
50
+ border-color: #2196f3;
51
+ background-color: #e3f2fd;
52
+ color: #2196f3;
53
+ }
54
+ QPushButton:pressed {
55
+ background-color: #bbdefb;
56
+ }
57
+ """)
58
+ self._add_button.clicked.connect(self.addRequested.emit)
59
+ layout.addWidget(self._add_button)
60
+
61
+ self.setLayout(layout)
62
+
63
+
64
+ class QPSAMapMarker(QWidget):
65
+ """Map marker component with left info panel and right map viewer."""
66
+
67
+ # Signals
68
+ onVariableChanged = pyqtSignal() # Emitted when any variable is changed
69
+
70
+ def __init__(self, parent=None):
71
+ super().__init__(parent)
72
+ self._variables: List[QPSACoVariable] = []
73
+ self._selected_cell_info: Optional[Dict[str, Any]] = None
74
+ self._init_ui()
75
+
76
+ def _init_ui(self):
77
+ """Initialize UI components."""
78
+ # Main horizontal layout (left-right)
79
+ main_layout = QHBoxLayout()
80
+ main_layout.setContentsMargins(6, 6, 6, 6)
81
+ main_layout.setSpacing(8)
82
+
83
+ # Left panel (info栏)
84
+ left_panel = QWidget()
85
+ left_layout = QVBoxLayout(left_panel)
86
+ left_layout.setContentsMargins(0, 0, 0, 0)
87
+ left_layout.setSpacing(8)
88
+
89
+ # First row - selected cell info
90
+ self._cell_info = QPSACellInfo()
91
+ self._cell_info.setMinimumWidth(200)
92
+ # self._cell_info.setMaximumHeight(200)
93
+ left_layout.addWidget(self._cell_info)
94
+
95
+ # Second row - variables list
96
+ self._variables_list = QListWidget()
97
+ self._variables_list.setFrameStyle(QFrame.Shape.Box)
98
+ self._variables_list.setStyleSheet("""
99
+ QListWidget {
100
+ border: 1px solid #ccc;
101
+ border-radius: 4px;
102
+ background-color: white;
103
+ }
104
+ QListWidget::item {
105
+ border-bottom: 1px solid #eee;
106
+ }
107
+ QListWidget::item:selected {
108
+ background-color: #e3f2fd;
109
+ }
110
+ """)
111
+ self._variables_list.setSpacing(4)
112
+ self._variables_list.setMinimumWidth(200)
113
+ # self._variables_list.setMaximumHeight(200)
114
+ left_layout.addWidget(self._variables_list)
115
+
116
+ # Third row - control buttons
117
+ button_layout = QHBoxLayout()
118
+ button_layout.setContentsMargins(0, 0, 0, 0)
119
+ button_layout.setSpacing(8)
120
+
121
+ # Reset button
122
+ self._reset_button = QPushButton("Reset")
123
+ self._reset_button.setFixedHeight(30)
124
+ self._reset_button.setFixedWidth(80)
125
+ self._reset_button.setStyleSheet("""
126
+ QPushButton {
127
+ border: 1px solid #ccc;
128
+ border-radius: 4px;
129
+ background-color: #f44336;
130
+ color: white;
131
+ font-size: 12px;
132
+ font-weight: bold;
133
+ }
134
+ QPushButton:hover {
135
+ background-color: #d32f2f;
136
+ }
137
+ QPushButton:pressed {
138
+ background-color: #b71c1c;
139
+ }
140
+ """)
141
+ self._reset_button.clicked.connect(self._on_reset_clicked)
142
+ button_layout.addWidget(self._reset_button)
143
+
144
+ # Copy button
145
+ self._copy_button = QPushButton("Copy")
146
+ self._copy_button.setFixedHeight(30)
147
+ self._copy_button.setFixedWidth(80)
148
+ self._copy_button.setStyleSheet("""
149
+ QPushButton {
150
+ border: 1px solid #ccc;
151
+ border-radius: 4px;
152
+ background-color: #2196f3;
153
+ color: white;
154
+ font-size: 12px;
155
+ font-weight: bold;
156
+ }
157
+ QPushButton:hover {
158
+ background-color: #1976d2;
159
+ }
160
+ QPushButton:pressed {
161
+ background-color: #1565c0;
162
+ }
163
+ """)
164
+ self._copy_button.clicked.connect(self._on_copy_clicked)
165
+ button_layout.addWidget(self._copy_button)
166
+
167
+ button_layout.addStretch()
168
+ left_layout.addLayout(button_layout)
169
+
170
+ # Add initial add widget
171
+ self._add_add_widget()
172
+
173
+ main_layout.addWidget(left_panel, 1) # Left panel takes 1/4 space
174
+
175
+ # Right panel - map viewer
176
+ self._map_viewer = QPSAMapViewer()
177
+ main_layout.addWidget(self._map_viewer, 3) # Map viewer takes 3/4 space
178
+
179
+ self.setLayout(main_layout)
180
+
181
+ # Connect map viewer signals
182
+ self._map_viewer.selectChanged.connect(self._on_cell_selected)
183
+
184
+ def _add_add_widget(self):
185
+ """Add the special add widget to the list."""
186
+ add_widget = AddVariableWidget()
187
+ add_widget.addRequested.connect(self._on_add_variable)
188
+
189
+ # Create list item
190
+ list_item = QListWidgetItem()
191
+ list_item.setSizeHint(add_widget.sizeHint())
192
+
193
+ self._variables_list.addItem(list_item)
194
+ self._variables_list.setItemWidget(list_item, add_widget)
195
+
196
+ def _add_variable_widget(self, variable: QPSACoVariable):
197
+ """Add a variable widget to the list."""
198
+ # Create list item first
199
+ list_item = QListWidgetItem()
200
+ list_item.setSizeHint(variable.sizeHint())
201
+
202
+ # Connect variable signals
203
+ variable.onItemChanged.connect(self._on_variable_changed)
204
+ variable.removeRequested.connect(lambda: self._on_remove_variable(variable))
205
+
206
+ # Connect to dual button clicks to update size
207
+ variable._dual_button.clicked.connect(lambda: self._on_variable_dual_changed(variable, list_item))
208
+
209
+ self._variables_list.insertItem(self._variables_list.count() - 1, list_item)
210
+ self._variables_list.setItemWidget(list_item, variable)
211
+
212
+ self._variables.append(variable)
213
+
214
+ @pyqtSlot()
215
+ def _on_add_variable(self):
216
+ """Handle add variable button click."""
217
+ print("[DEBUG] Adding new variable")
218
+ new_variable = QPSACoVariable()
219
+ new_variable.showRemoveButton = True # Set remove button to be visible
220
+ self._add_variable_widget(new_variable)
221
+
222
+ def _on_remove_variable(self, variable: QPSACoVariable):
223
+ """Handle remove variable request."""
224
+ print("[DEBUG] Removing variable")
225
+
226
+ # Find the variable in the list
227
+ if variable in self._variables:
228
+ # Find the corresponding list item
229
+ for i in range(self._variables_list.count()):
230
+ item = self._variables_list.item(i)
231
+ if item:
232
+ widget = self._variables_list.itemWidget(item)
233
+ if widget == variable:
234
+ # Remove from list
235
+ self._variables_list.takeItem(i)
236
+ # Remove from variables list
237
+ self._variables.remove(variable)
238
+ # Delete the widget
239
+ variable.deleteLater()
240
+ print(f"[DEBUG] Variable removed at index {i}")
241
+ break
242
+
243
+ self.onVariableChanged.emit()
244
+
245
+ def _on_variable_dual_changed(self, variable: QPSACoVariable, list_item: QListWidgetItem):
246
+ """Handle dual mode change for a variable."""
247
+ # Update the list item size hint when dual mode changes
248
+ new_size = variable.sizeHint()
249
+ list_item.setSizeHint(new_size)
250
+ print(f"[DEBUG] Dual mode changed, updated item size: {new_size}")
251
+
252
+ @pyqtSlot()
253
+ def _on_copy_clicked(self):
254
+ """Handle copy button click to copy variables to clipboard as Python code."""
255
+ print("[DEBUG] Copying variables to clipboard as Python code")
256
+
257
+ # Get variables data
258
+ variables_data = self.variables
259
+ print(f"[DEBUG] Variables data: {variables_data}")
260
+
261
+ # Convert to Python code using json2code function
262
+ try:
263
+ code = json2code(variables_data)
264
+ print(f"[DEBUG] Generated code: {code}")
265
+
266
+ # Copy to clipboard
267
+ clipboard = QApplication.clipboard()
268
+ clipboard.setText(code)
269
+ print("[DEBUG] Python code copied to clipboard")
270
+ except Exception as e:
271
+ print(f"[DEBUG] Error generating code: {e}")
272
+ # Fallback to JSON if code generation fails
273
+ json_str = json.dumps(variables_data, ensure_ascii=False, indent=2)
274
+ clipboard = QApplication.clipboard()
275
+ clipboard.setText(json_str)
276
+ print("[DEBUG] JSON fallback copied to clipboard")
277
+
278
+ @pyqtSlot()
279
+ def _on_reset_clicked(self):
280
+ """Handle reset button click."""
281
+ print("[DEBUG] Resetting info panel")
282
+
283
+ # Clear cell info
284
+ self._cell_info.data = None
285
+ self._selected_cell_info = None
286
+
287
+ # Clear all variables
288
+ for variable in self._variables:
289
+ variable.reset()
290
+
291
+ # Remove all variable widgets from list (keep only add widget)
292
+ while self._variables_list.count() > 1:
293
+ item = self._variables_list.takeItem(0)
294
+ if item:
295
+ widget = self._variables_list.itemWidget(item)
296
+ if widget and widget != self._variables_list.itemWidget(self._variables_list.item(self._variables_list.count() - 1)):
297
+ widget.deleteLater()
298
+
299
+ self._variables.clear()
300
+
301
+ @pyqtSlot(object)
302
+ def _on_cell_selected(self, cell_info):
303
+ """Handle cell selection from map viewer."""
304
+ print(f"[DEBUG] Cell selected: {cell_info}")
305
+ if cell_info:
306
+ self._cell_info.data = cell_info
307
+ # Transfer screenshot to QPSACellInfo
308
+ self._cell_info.image = self._map_viewer.image
309
+ self._selected_cell_info = {
310
+ 'x': cell_info.x,
311
+ 'y': cell_info.y,
312
+ 'terrain': cell_info.terrain,
313
+ 'objects': cell_info.objects.copy() if cell_info.objects else []
314
+ }
315
+ else:
316
+ self._cell_info.data = None
317
+ self._selected_cell_info = None
318
+
319
+ @pyqtSlot()
320
+ def _on_variable_changed(self):
321
+ """Handle variable changes."""
322
+ print("[DEBUG] Variable changed")
323
+ self.onVariableChanged.emit()
324
+
325
+ # Properties
326
+ @property
327
+ def variables(self) -> List[Dict[str, Any]]:
328
+ """Get all variables as list of dictionaries."""
329
+ return [var.data for var in self._variables]
330
+
331
+ @property
332
+ def selectedCellInfo(self) -> Optional[Dict[str, Any]]:
333
+ """Get selected cell information."""
334
+ return self._selected_cell_info.copy() if self._selected_cell_info else None
335
+
336
+ @property
337
+ def mapViewer(self) -> QPSAMapViewer:
338
+ """Get the map viewer component."""
339
+ return self._map_viewer
@@ -0,0 +1,303 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ QPSA Co Variable Component - 对称变量组件
4
+ """
5
+ from typing import List, Dict, Any
6
+ from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
7
+ QLineEdit, QPushButton, QFrame, QSpacerItem,
8
+ QSizePolicy)
9
+ from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, QSize
10
+ from PyQt6.QtGui import QPalette, QColor
11
+
12
+ from pyscreeps_arena.ui.qmapv import QPSACellObject
13
+
14
+ # Import configuration from build.py
15
+ import sys
16
+ import os
17
+ # Add the project root directory to Python path
18
+ from pyscreeps_arena import config
19
+
20
+ # Language mapping
21
+ LANG = {
22
+ 'cn': {
23
+ 'symmetric': '对称',
24
+ },
25
+ 'en': {
26
+ 'symmetric': 'Symmetric',
27
+ }
28
+ }
29
+
30
+ def lang(key: str) -> str:
31
+ """Helper function to get translated text"""
32
+ return LANG[config.language if hasattr(config, 'language') and config.language in LANG else 'cn'][key]
33
+
34
+
35
+ class QPSACoVariable(QWidget):
36
+ """Co-variable component with symmetric option."""
37
+
38
+ # Class variable to track instance count
39
+ _instance_count = 0
40
+
41
+ # Signals
42
+ onItemChanged = pyqtSignal() # Emitted when any item is added or removed
43
+ removeRequested = pyqtSignal() # Emitted when remove button is clicked
44
+
45
+ def __init__(self, parent=None):
46
+ super().__init__(parent)
47
+ self._dual = False
48
+ self._true_objects = []
49
+ self._false_objects = []
50
+ self._show_remove_btn = False # New property to control remove button
51
+
52
+ # Increment instance count and get the current number
53
+ QPSACoVariable._instance_count += 1
54
+ self._instance_number = QPSACoVariable._instance_count
55
+
56
+ self._init_ui()
57
+
58
+ def _init_ui(self):
59
+ """Initialize UI components."""
60
+ # Main vertical layout
61
+ main_layout = QVBoxLayout()
62
+ main_layout.setContentsMargins(4, 4, 4, 4) # Reduced margins
63
+ main_layout.setSpacing(3) # Reduced spacing
64
+
65
+ # First row - input, checkbox, and remove button in 7:3:2 ratio
66
+ top_row = QHBoxLayout()
67
+ top_row.setContentsMargins(0, 0, 0, 0)
68
+ top_row.setSpacing(2) # Reduced spacing
69
+
70
+ # Input box with placeholder (70%)
71
+ self._input = QLineEdit()
72
+ self._input.setPlaceholderText(f"VAR{self._instance_number}")
73
+ self._input.setStyleSheet("font-size: 11px;") # Reduced font size
74
+ self._input.setFixedHeight(24) # Reduced height
75
+ top_row.addWidget(self._input, 7)
76
+
77
+ # Button-style checkbox for dual mode (30%)
78
+ self._dual_button = QPushButton(lang('symmetric'))
79
+ self._dual_button.setCheckable(True)
80
+ self._dual_button.setFixedHeight(24) # Reduced from 22 to 18
81
+ self._dual_button.setStyleSheet("""
82
+ QPushButton {
83
+ border: 1px solid #ccc;
84
+ border-radius: 2px;
85
+ background-color: #f0f0f0;
86
+ font-size: 9px;
87
+ padding: 1px;
88
+ }
89
+ QPushButton:checked {
90
+ background-color: #e3f2fd;
91
+ border-color: #2196f3;
92
+ }
93
+ """)
94
+ self._dual_button.clicked.connect(self._on_dual_changed)
95
+ top_row.addWidget(self._dual_button, 3)
96
+
97
+ # Remove button (20% - shown based on property)
98
+ self._remove_btn = QPushButton("⛔")
99
+ self._remove_btn.setFixedHeight(24)
100
+ self._remove_btn.setFixedWidth(24) # Make it square
101
+ self._remove_btn.setStyleSheet("""
102
+ QPushButton {
103
+ border: 1px solid #ff6b6b;
104
+ border-radius: 12px;
105
+ background-color: #ffe6e6;
106
+ color: #ff6b6b;
107
+ font-size: 10px;
108
+ font-weight: bold;
109
+ }
110
+ QPushButton:hover {
111
+ background-color: #ff6b6b;
112
+ color: white;
113
+ }
114
+ QPushButton:pressed {
115
+ background-color: #ff5252;
116
+ }
117
+ """)
118
+ self._remove_btn.clicked.connect(self._on_remove_clicked)
119
+ self._remove_btn.setVisible(self._show_remove_btn)
120
+ top_row.addWidget(self._remove_btn, 2)
121
+
122
+ main_layout.addLayout(top_row)
123
+
124
+ # Second row - QPSACellObject components
125
+ objects_widget = QWidget()
126
+ self._objects_layout = QVBoxLayout(objects_widget)
127
+ self._objects_layout.setContentsMargins(0, 0, 0, 0)
128
+ self._objects_layout.setSpacing(2) # Reduced spacing
129
+
130
+ # True objects section
131
+ self._true_objects_widget = QPSACellObject()
132
+ self._true_objects_widget.objectAdded.connect(self._on_item_changed)
133
+ self._true_objects_widget.objectRemoved.connect(self._on_item_changed)
134
+ self._true_objects_widget.itemChanged.connect(self._on_item_changed)
135
+ self._objects_layout.addWidget(self._true_objects_widget)
136
+
137
+ # Separator for dual mode
138
+ self._mid_separator = QFrame()
139
+ self._mid_separator.setFrameShape(QFrame.Shape.HLine)
140
+ self._mid_separator.setFrameShadow(QFrame.Shadow.Sunken)
141
+ self._mid_separator.setVisible(False)
142
+ self._objects_layout.addWidget(self._mid_separator)
143
+
144
+ # False objects section (for dual mode)
145
+ self._false_objects_widget = QPSACellObject()
146
+ self._false_objects_widget.objectAdded.connect(self._on_item_changed)
147
+ self._false_objects_widget.objectRemoved.connect(self._on_item_changed)
148
+ self._false_objects_widget.itemChanged.connect(self._on_item_changed)
149
+ # Set background color to #FFF0F5 (light red) for the list content
150
+ self._false_objects_widget.set_list_background_color("#FFF0F5")
151
+ self._false_objects_widget.setVisible(False)
152
+ self._objects_layout.addWidget(self._false_objects_widget)
153
+
154
+ main_layout.addWidget(objects_widget)
155
+
156
+ self.setLayout(main_layout)
157
+ self.setMinimumWidth(182)
158
+ self.setMaximumWidth(182)
159
+
160
+ @pyqtSlot(bool)
161
+ def _on_dual_changed(self, checked: bool):
162
+ """Handle dual mode toggle."""
163
+ self._dual = checked
164
+ self._false_objects_widget.setVisible(checked)
165
+ self._mid_separator.setVisible(checked)
166
+ self._on_item_changed()
167
+
168
+ @pyqtSlot()
169
+ def _on_remove_clicked(self):
170
+ """Handle remove button click."""
171
+ self.removeRequested.emit() # Request removal from parent
172
+
173
+ @pyqtSlot()
174
+ def _on_item_changed(self):
175
+ """Handle item changes in QPSACellObject components."""
176
+ # Update internal lists
177
+ self._true_objects = self._true_objects_widget.objects
178
+ if self._dual:
179
+ self._false_objects = self._false_objects_widget.objects
180
+ else:
181
+ self._false_objects = []
182
+
183
+ # Emit callback
184
+ self.onItemChanged.emit()
185
+
186
+ # Properties
187
+ @property
188
+ def dual(self) -> bool:
189
+ """Get/set dual mode status."""
190
+ return self._dual
191
+
192
+ @dual.setter
193
+ def dual(self, value: bool):
194
+ """Set dual mode status."""
195
+ self._dual = value
196
+ self._dual_button.setChecked(value)
197
+ self._false_objects_widget.setVisible(value)
198
+ self._mid_separator.setVisible(value)
199
+ self._on_item_changed()
200
+
201
+ # Recalculate height by updating size hint and notifying parent
202
+ self.updateGeometry()
203
+ if self.parent():
204
+ # Notify parent layout to recalculate
205
+ self.parent().updateGeometry()
206
+
207
+ # If this is in a QListWidget, update the item size
208
+ if hasattr(self.parent(), 'parent') and self.parent().parent():
209
+ list_widget = self.parent().parent()
210
+ if hasattr(list_widget, 'itemWidget'):
211
+ # Find the corresponding item and update its size hint
212
+ for i in range(list_widget.count()):
213
+ item = list_widget.item(i)
214
+ if item and list_widget.itemWidget(item) == self:
215
+ item.setSizeHint(self.sizeHint())
216
+ print(f"[DEBUG] Updated item size hint for dual mode: {self.sizeHint()}")
217
+ break
218
+
219
+ @property
220
+ def trueObjects(self) -> List[Dict[str, Any]]:
221
+ """Get true objects list."""
222
+ return self._true_objects.copy()
223
+
224
+ @trueObjects.setter
225
+ def trueObjects(self, value: List[Dict[str, Any]]):
226
+ """Set true objects list."""
227
+ # Clear current objects
228
+ self._true_objects_widget.clear_objects()
229
+ # Add new objects
230
+ for obj in value:
231
+ self._true_objects_widget.add_object(obj)
232
+ self._on_item_changed()
233
+
234
+ @property
235
+ def falseObjects(self) -> List[Dict[str, Any]]:
236
+ """Get false objects list."""
237
+ return self._false_objects.copy()
238
+
239
+ @falseObjects.setter
240
+ def falseObjects(self, value: List[Dict[str, Any]]):
241
+ """Set false objects list."""
242
+ # Clear current objects
243
+ self._false_objects_widget.clear_objects()
244
+ # Add new objects
245
+ for obj in value:
246
+ self._false_objects_widget.add_object(obj)
247
+ self._on_item_changed()
248
+
249
+ @property
250
+ def showRemoveButton(self) -> bool:
251
+ """Get/set whether to show the remove button."""
252
+ return self._show_remove_btn
253
+
254
+ @showRemoveButton.setter
255
+ def showRemoveButton(self, value: bool):
256
+ """Set whether to show the remove button."""
257
+ self._show_remove_btn = value
258
+ self._remove_btn.setVisible(value)
259
+
260
+ def reset(self):
261
+ """Reset component to default state."""
262
+ self._input.clear()
263
+ self.dual = False
264
+ self._true_objects_widget.clear_objects()
265
+ self._false_objects_widget.clear_objects()
266
+
267
+ def sizeHint(self):
268
+ """Calculate preferred size based on dual mode."""
269
+ # Base height for single mode
270
+ base_height = 120
271
+
272
+ # Add height for dual mode (additional objects widget)
273
+ if self._dual:
274
+ base_height += 80 # Additional space for false objects
275
+
276
+ # Add height based on content
277
+ true_count = len(self._true_objects_widget.objects)
278
+ false_count = len(self._false_objects_widget.objects) if self._dual else 0
279
+
280
+ # Estimate height based on object count (roughly 25px per object)
281
+ content_height = max(true_count, false_count) * 25
282
+ total_height = base_height + content_height
283
+
284
+ # Keep width fixed
285
+ return QSize(182, min(total_height, 400)) # Cap at 400px max height
286
+
287
+ @property
288
+ def data(self) -> Dict[str, Any]:
289
+ """Get component data as a dictionary."""
290
+ # Use placeholder text as key if input is empty
291
+ key = self._input.text() if self._input.text() else self._input.placeholderText()
292
+ return {
293
+ 'key': key,
294
+ 'dual': self._dual,
295
+ 'pos': self._true_objects.copy(),
296
+ 'neg': self._false_objects.copy()
297
+ }
298
+
299
+ # Static method to get current instance count
300
+ @staticmethod
301
+ def get_instance_count() -> int:
302
+ """Get current instance count."""
303
+ return QPSACoVariable._instance_count
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Test script for compact QPSACoVariable component
5
+ """
6
+ import sys
7
+ import os
8
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
9
+
10
+ from PyQt6.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QLabel
11
+ from PyQt6.QtCore import Qt
12
+ from pyscreeps_arena.ui.qmapker.qvariable import QPSACoVariable
13
+
14
+
15
+ def test_compact_variable():
16
+ """Test the compact QPSACoVariable component."""
17
+ app = QApplication(sys.argv)
18
+
19
+ # Create main window
20
+ window = QWidget()
21
+ window.setWindowTitle("Compact QPSACoVariable Test")
22
+ window.resize(400, 300)
23
+
24
+ # Create layout
25
+ layout = QVBoxLayout()
26
+ layout.setContentsMargins(10, 10, 10, 10)
27
+ layout.setSpacing(5)
28
+
29
+ # Add title
30
+ title = QLabel("Compact QPSACoVariable Components")
31
+ title.setStyleSheet("font-weight: bold; font-size: 14px;")
32
+ title.setAlignment(Qt.AlignmentFlag.AlignCenter)
33
+ layout.addWidget(title)
34
+
35
+ # Create multiple compact QPSACoVariable components
36
+ for i in range(3):
37
+ variable = QPSACoVariable()
38
+ variable._input.setText(f"Test{i+1}")
39
+ layout.addWidget(variable)
40
+
41
+ # Add button to test dual mode
42
+ def toggle_dual():
43
+ for widget in window.findChildren(QPSACoVariable):
44
+ widget.dual = not widget.dual
45
+
46
+ test_button = QPushButton("Toggle Dual Mode")
47
+ test_button.clicked.connect(toggle_dual)
48
+ layout.addWidget(test_button)
49
+
50
+ # Add stretch to push everything up
51
+ layout.addStretch()
52
+
53
+ window.setLayout(layout)
54
+ window.show()
55
+
56
+ print("[DEBUG] Compact QPSACoVariable test window initialized")
57
+ sys.exit(app.exec())
58
+
59
+
60
+ if __name__ == "__main__":
61
+ test_compact_variable()
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Test script for QPSAMapMarker component
5
+ """
6
+ import sys
7
+ import os
8
+
9
+ # Add the parent directory to the path
10
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
11
+
12
+ from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
13
+ from PyQt6.QtCore import Qt
14
+ from pyscreeps_arena.ui.qmapker.qmapmarker import QPSAMapMarker
15
+ from pyscreeps_arena.core import config
16
+ config.language = 'en'
17
+
18
+ class TestWindow(QMainWindow):
19
+ def __init__(self):
20
+ super().__init__()
21
+ self.setWindowTitle("QPSAMapMarker Test")
22
+ self.setGeometry(100, 100, 1200, 800)
23
+
24
+ # Central widget
25
+ central_widget = QWidget()
26
+ self.setCentralWidget(central_widget)
27
+
28
+ # Main layout
29
+ layout = QVBoxLayout()
30
+
31
+ # Title
32
+ title_label = QLabel("QPSAMapMarker Component Test")
33
+ title_label.setStyleSheet("font-size: 18px; font-weight: bold; padding: 10px;")
34
+ title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
35
+ layout.addWidget(title_label)
36
+
37
+ # Create map marker component
38
+ self.map_marker = QPSAMapMarker()
39
+ layout.addWidget(self.map_marker)
40
+
41
+ # Status label
42
+ self.status_label = QLabel("Select a cell on the map to see its info")
43
+ self.status_label.setStyleSheet("font-size: 12px; color: #666; padding: 10px;")
44
+ self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
45
+ layout.addWidget(self.status_label)
46
+
47
+ # Connect signals
48
+ self.map_marker.onVariableChanged.connect(self.on_variable_changed)
49
+
50
+ central_widget.setLayout(layout)
51
+
52
+ print("[DEBUG] QPSAMapMarker test window initialized")
53
+
54
+ def on_variable_changed(self):
55
+ """Handle variable changes."""
56
+ variables = self.map_marker.variables
57
+ self.status_label.setText(f"Variables updated: {len(variables)} variables")
58
+ print(f"[DEBUG] Variables changed: {len(variables)} variables")
59
+
60
+ # Print variable details
61
+ for i, variable in enumerate(variables):
62
+ print(f" Variable {i+1}: {len(variable['pos'])} true objects, {len(variable['neg'])} false objects")
63
+
64
+ if __name__ == "__main__":
65
+ app = QApplication(sys.argv)
66
+ window = TestWindow()
67
+ window.show()
68
+ sys.exit(app.exec())
69
+
70
+
71
+
@@ -0,0 +1,49 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Test script for QPSACoVariable component
4
+ """
5
+ import sys
6
+ from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
7
+ from pyscreeps_arena.ui.qmapker.qvariable import QPSACoVariable
8
+
9
+
10
+ class TestWindow(QMainWindow):
11
+ """Test window to display QPSACoVariable component."""
12
+
13
+ def __init__(self):
14
+ super().__init__()
15
+ self._init_ui()
16
+
17
+ def _init_ui(self):
18
+ """Initialize UI."""
19
+ self.setWindowTitle("QPSACoVariable Test")
20
+ self.setGeometry(100, 100, 800, 400)
21
+
22
+ # Central widget
23
+ central_widget = QWidget()
24
+ self.setCentralWidget(central_widget)
25
+
26
+ # Main layout
27
+ main_layout = QVBoxLayout(central_widget)
28
+
29
+ # Add multiple QPSACoVariable components to test instance counting
30
+ for i in range(3):
31
+ co_var = QPSACoVariable()
32
+ co_var.onItemChanged.connect(self._on_item_changed)
33
+ main_layout.addWidget(co_var)
34
+
35
+ def _on_item_changed(self):
36
+ """Handle item changed signal."""
37
+ print("Item changed!")
38
+
39
+
40
+ def main():
41
+ """Main function."""
42
+ app = QApplication(sys.argv)
43
+ window = TestWindow()
44
+ window.show()
45
+ sys.exit(app.exec())
46
+
47
+
48
+ if __name__ == '__main__':
49
+ main()
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ import json
4
+
5
+
6
+ def json2code(data: list) -> str:
7
+ """
8
+ Convert data to Python code according to the specified rules.
9
+ Args:
10
+ data: List of variable dictionaries to convert
11
+ Returns: Generated Python code as a string
12
+ """
13
+ # Ensure the data is a list
14
+ if not isinstance(data, list):
15
+ raise ValueError("Data must be a list")
16
+
17
+ generated_code = []
18
+
19
+ for element in data:
20
+ key = element.get('key', '')
21
+ dual = element.get('dual', False)
22
+ pos = element.get('pos', [])
23
+ neg = element.get('neg', [])
24
+
25
+ # Check if pos is empty
26
+ if not pos:
27
+ print(f"Warning: pos is empty for key '{key}', skipping this element.")
28
+ continue
29
+
30
+ # Check if neg is empty when dual is enabled
31
+ if dual and not neg:
32
+ print(f"Warning: neg is empty for key '{key}' with dual=True, skipping this element.")
33
+ continue
34
+
35
+ # Step 2: Generate pk and epk if dual is enabled
36
+ pk = key
37
+ epk = None
38
+ if dual:
39
+ if pk.startswith('E'):
40
+ epk = f'E_{pk}'
41
+ else:
42
+ epk = f'E{pk}'
43
+
44
+ # Step 3: Process pos list
45
+ pos_translations = []
46
+ for item in pos:
47
+ method = item.get('method', '')
48
+ x = item.get('x', 0)
49
+ y = item.get('y', 0)
50
+ pos_translations.append(f"{method}({x}, {y})")
51
+
52
+ # Format posv based on length
53
+ if len(pos_translations) > 1:
54
+ posv = f"[{', '.join(pos_translations)}]"
55
+ else:
56
+ posv = pos_translations[0]
57
+
58
+ # Step 4: Process neg list if dual is enabled
59
+ negv = None
60
+ if dual:
61
+ neg_translations = []
62
+ for item in neg:
63
+ method = item.get('method', '')
64
+ x = item.get('x', 0)
65
+ y = item.get('y', 0)
66
+ neg_translations.append(f"{method}({x}, {y})")
67
+
68
+ # Format negv based on length
69
+ if len(neg_translations) > 1:
70
+ negv = f"[{', '.join(neg_translations)}]"
71
+ else:
72
+ negv = neg_translations[0]
73
+
74
+ # Step 6: Generate code lines for this element
75
+ element_code = []
76
+
77
+ # Base line for pos
78
+ element_code.append(f"{pk} = {posv}")
79
+
80
+ # Additional lines if dual is enabled
81
+ if dual and epk and negv:
82
+ element_code.append(f"{epk} = {negv}")
83
+ element_code.append(f"if SIDE == 1: {pk}, {epk} = {epk}, {pk}")
84
+
85
+ element_code.append('') # empty
86
+
87
+ # Add to generated code
88
+ generated_code.extend(element_code)
89
+
90
+ # Step 7: Join all lines and return
91
+ return '\n'.join(generated_code)
92
+
93
+
94
+ # Test the function
95
+ if __name__ == "__main__":
96
+ # Read the test.json file for testing
97
+ with open('test.json', 'r', encoding='utf-8') as f:
98
+ data = json.load(f)
99
+ code = json2code(data)
100
+ print(code)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyscreeps-arena
3
- Version: 0.5.7.1
3
+ Version: 0.5.8.0
4
4
  Summary: Python api|interface to play game: Screeps: Arena.
5
5
  Author-email: 2229066748@qq.com
6
6
  Maintainer: Eagle'sBaby
@@ -1,12 +1,12 @@
1
- pyscreeps_arena/__init__.py,sha256=VXTTz1lCdcq5VLyp3Jx9l09Z-Bc2jqNM6GBOr4hLYFs,3337
1
+ pyscreeps_arena/__init__.py,sha256=GWrCpZnrzPwbBzhNqk8gTvV4VSwkP2DbxK8lR0E_SzY,3223
2
2
  pyscreeps_arena/build.py,sha256=DQeGLnID2FjpsWTbnqwt2cOp28olWK68U67bfbFJSOU,177
3
- pyscreeps_arena/compiler.py,sha256=2p9x7seCy48tGJ52mNFY7gYNnNZ0yhsreFUj23ubVEQ,65255
3
+ pyscreeps_arena/compiler.py,sha256=RdJNwEvWOzS36UhwM6wxK2_bV2JtMBaNg6dLotVKj2k,66843
4
4
  pyscreeps_arena/localization.py,sha256=Dr0G6n8DvT6syfC0urqwjccYpEPEfcwYyJx3f9s-6a8,6031
5
- pyscreeps_arena/project.7z,sha256=fTe2G3eAcy4zHHn-C4g_Qyeq0IGtl_mKElnGyHjk1Ek,325402
5
+ pyscreeps_arena/project.7z,sha256=XGYV8SGezbvaCs19rP7eFgcGNE_wanLkjmQbKsoihAI,570035
6
6
  pyscreeps_arena/core/__init__.py,sha256=qoP_rx1TpbDLJoTm5via4XPwEPaV1FXr1SYvoVoHGms,41
7
7
  pyscreeps_arena/core/basic.py,sha256=DFvyDTsTXf2bQtnS9s254TrkshvRwajaHcvTyVvJyqw,2790
8
8
  pyscreeps_arena/core/config.py,sha256=x_JhVHlVZqB3qA7UyACVnwZjg2gZU-BIs49UxZzwCoE,637
9
- pyscreeps_arena/core/const.py,sha256=1LVq52Se0qvZbKHpxXznWUrgZ3VZGiblnTqyduhtDS0,590
9
+ pyscreeps_arena/core/const.py,sha256=UTWnIas44Mb53pVaAy0tRWedbh1VBrgbqwGT1m4qovM,590
10
10
  pyscreeps_arena/core/core.py,sha256=3Nty8eLKPNgwnYk_sVNBPrWuKxBXI2od8nfEezsEAZQ,5157
11
11
  pyscreeps_arena/core/main.py,sha256=-FNSOEjksNlDfCbUqsjtPSUW8vT3qxEdfzXqT5Tdsik,170
12
12
  pyscreeps_arena/core/utils.py,sha256=N9OOkORvrqnJakayaFp9qyS0apWhB9lBK4xyyYkhFdo,215
@@ -20,6 +20,13 @@ pyscreeps_arena/ui/rs_icon.py,sha256=5v8YdTv2dds4iAp9x3MXpqB_5XIOYd0uPqaq83ZNySM
20
20
  pyscreeps_arena/ui/qcreeplogic/__init__.py,sha256=FVuJ6TZyDh5WnkYlHjCWjEKGPBWl27LzqIAab-4aeuI,75
21
21
  pyscreeps_arena/ui/qcreeplogic/model.py,sha256=_Ba5jbHcFsKQ1el36UcCnXGUimvvWSRHgzo979SpVzs,2833
22
22
  pyscreeps_arena/ui/qcreeplogic/qcreeplogic.py,sha256=9-DX_8IslJ7Dte2mTfNTN0C54QUSmuwkfhLoeGzhUMQ,29815
23
+ pyscreeps_arena/ui/qmapker/__init__.py,sha256=nRBglmh5p37MYlEiaqAMcUSyF8Fsb8IRPdmCKIw9NMY,65
24
+ pyscreeps_arena/ui/qmapker/qmapmarker.py,sha256=jjaK2dY7Wta5tYr2lZF9nOWWsN0PqqSzHeEd_tLvJVs,12610
25
+ pyscreeps_arena/ui/qmapker/qvariable.py,sha256=ZkXpuz3bhwxiI2n5WpO6SUSiWM1B51zIOrdDEy5QuZo,11554
26
+ pyscreeps_arena/ui/qmapker/test_compact_variable.py,sha256=Yr09ROlpYkYTRQ8PS1uzOSM3Jyd28IQwXiyCJ4Dzv6s,1747
27
+ pyscreeps_arena/ui/qmapker/test_qmapmarker.py,sha256=s4qX9Am_L1kyBAI9jjnCk47YGkdUrXf9P8T9tOhd6l0,2426
28
+ pyscreeps_arena/ui/qmapker/test_qvariable.py,sha256=N_txXfAu5mTYvQztIHCiXKdWVrmbKySyRohbcvUafxA,1296
29
+ pyscreeps_arena/ui/qmapker/to_code.py,sha256=x7CcD1ukJmz__raa4r0ddiXl_wETh82-AHgt3YZJ-90,3166
23
30
  pyscreeps_arena/ui/qmapv/__init__.py,sha256=zjOIxK20lqdYZLWwEi5KfHumSWOzN3nDjW5Yomy9ocM,241
24
31
  pyscreeps_arena/ui/qmapv/qcinfo.py,sha256=6WSMpn-u0ZmyvSwA-KZfwLNW1WetAi9y11HW0aIHGQU,21381
25
32
  pyscreeps_arena/ui/qmapv/qco.py,sha256=dn33250XYoCzTGPiuUzId8FI6yhweMDSes6A2UARacU,17889
@@ -33,8 +40,8 @@ pyscreeps_arena/ui/qmapv/test_simple_array.py,sha256=hPnLXcFrn6I1X4JXFM3RVpBPhU_
33
40
  pyscreeps_arena/ui/qrecipe/__init__.py,sha256=2Guvr9k5kGkZboiVC0aNF4u48LRbmcCm2dqOhEF52Tw,59
34
41
  pyscreeps_arena/ui/qrecipe/model.py,sha256=s3lr_DXtsBgt8bVg1_wLz-dX88QKi77mNkqM5VJsGwE,13200
35
42
  pyscreeps_arena/ui/qrecipe/qrecipe.py,sha256=z57VLmlpMripdpGtVCkKR0csJQhw5-WpocZK5l2xTVg,39398
36
- pyscreeps_arena-0.5.7.1.dist-info/METADATA,sha256=CuJZzgjREwhpC06GYYYMTfUpQeH9_heoNEm9jx5_TcI,2356
37
- pyscreeps_arena-0.5.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
38
- pyscreeps_arena-0.5.7.1.dist-info/entry_points.txt,sha256=pnpuPPadwQsxQPaR1rXzUo0fUvhOcC7HTHlf7TYXr7M,141
39
- pyscreeps_arena-0.5.7.1.dist-info/top_level.txt,sha256=l4uLyMR2NOy41ngBMh795jOHTFk3tgYKy64-9cgjVng,16
40
- pyscreeps_arena-0.5.7.1.dist-info/RECORD,,
43
+ pyscreeps_arena-0.5.8.0.dist-info/METADATA,sha256=2-TE_geOR8Bnzm46WbraKjibiY7RdTuDRAfiE_GY-Fc,2356
44
+ pyscreeps_arena-0.5.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ pyscreeps_arena-0.5.8.0.dist-info/entry_points.txt,sha256=pnpuPPadwQsxQPaR1rXzUo0fUvhOcC7HTHlf7TYXr7M,141
46
+ pyscreeps_arena-0.5.8.0.dist-info/top_level.txt,sha256=l4uLyMR2NOy41ngBMh795jOHTFk3tgYKy64-9cgjVng,16
47
+ pyscreeps_arena-0.5.8.0.dist-info/RECORD,,