redToJson.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. import json
  2. import os
  3. import re
  4. import xml.etree.ElementTree as ET
  5. import random
  6. import copy
  7. # 关卡配置文件目录
  8. configFileDirPath = "/Users/red/ScrewGame/Resources/res_ScrewGame/关卡/"
  9. # 关卡模板文件目录
  10. templateFileDirPath = "/Users/red/ScrewGame/Resources/res_ScrewGame/关卡/关卡模板/"
  11. # 盘子文件目录
  12. plateConfigFileDirPath = "/Users/red/ScrewGame/Resources/res_ScrewGame/ccb/盘子上位置情况/"
  13. # 关卡JSON文件夹目录
  14. levelConfigJSONDirPath = "/Users/red/ScrewGame/Resources/res_ScrewGame/config/"
  15. # 关卡配置文件最终目录
  16. finalLevelConfigPath = "/Users/red/ScrewGame/Resources/res_ScrewGame/config/最终关卡配置/"
  17. # 模拟接入C++关卡生成的钉子种类数
  18. nScrewTypes = 3
  19. """ 显示一下关卡配置文件路径 要修改的话改一下 """
  20. def askAndSetNewConfigFileDirPath():
  21. global configFileDirPath
  22. tipStr = "当前默认的关卡配置文件夹路径为: {}, 设置新的关卡配置文件夹路径? [y/n]".format(configFileDirPath)
  23. needNewConfigDirPath = input(tipStr)
  24. # 重新设置关卡配置文件夹路径
  25. if needNewConfigDirPath == 'y' or not configFileDirPath:
  26. configFileDirPath = input("输入新的关卡配置文件夹路径(路径结尾要带斜杠/):")
  27. setNewConfigDirPath()
  28. tipStr = "当前默认的关卡模板文件夹路径为: {}, 设置新的关卡模板配置文件夹路径? [y/n]".format(templateFileDirPath)
  29. # TODO: 设置新的关卡模板配置文件路径
  30. # TODO: 询问是否设置新的盘子文件目录
  31. # TODO: 设置新的盘子文件目录
  32. # TODO: 询问是否修改关卡JSON文件目录
  33. # TODO: 修改关卡JSON文件目录
  34. """修改本文件中的configFileDirPath, 下一次使用的时候默认使用这个新路径"""
  35. def setNewConfigDirPath() -> None:
  36. with open(__file__, "r") as curFile:
  37. content = curFile.read()
  38. pattern = r"configFileDirPath = \"\s\""
  39. replacement = r"configFileDirPath = \"{}\"".format(configFileDirPath)
  40. newContent = re.sub(pattern, replacement, content, count=1)
  41. with open(__file__, "w") as curFile:
  42. curFile.write(newContent)
  43. """ 测试 将一个关卡配置文件转化为 JSON """
  44. def testToConvert1():
  45. configFilePath = configFileDirPath + "关卡1test.red"
  46. with open(configFilePath) as configFile:
  47. configFileTree = ET.parse(configFile)
  48. fileRoot = configFileTree.getroot()
  49. # print(fileRoot.keys())
  50. dataPyDict = xmlToDict(fileRoot[0])
  51. # print(dataPyDict)
  52. templateIDList = getTemplateIDFromDict(dataPyDict)
  53. # print(templateIDList)
  54. templateDatas = getTemplatesPyDictForm(templateIDList)
  55. plateIDs = getPlateIDsFromTemplateFilePyDictForm(templateDatas)
  56. # print(plateIDs)
  57. plateConfigsPyDictForm = getPlateConfigPyDictForms(plateIDs)
  58. # print(plateConfigsPyDictForm)
  59. levelConfigs = generateLevelConfigWithPlateConfig(plateConfigsPyDictForm)
  60. # print(levelConfigs)
  61. saveLevelConfigToJsonFile(levelConfigs)
  62. """ 从转化为dict的关卡配置中获取使用的关卡模板号 """
  63. def getTemplateIDFromDict(dataPyDict: dict) -> list:
  64. templateList = dataPyDict['nodeGraph']['children'][0]['children']
  65. # print(templateList)
  66. templateIDList = []
  67. for template in templateList:
  68. templateIDList.append(int(template['displayName']))
  69. return templateIDList
  70. """ 从传入的关卡模板号对应的关卡模板配置文件中获得py-dict格式的模板数据 """
  71. def getTemplatesPyDictForm(templateIDList) -> list:
  72. ret = []
  73. # 这里的ID是关卡模板ID
  74. for ID in templateIDList:
  75. templateFileName = "{}模板{}.red".format(templateFileDirPath, ID)
  76. # print(templateFileName)
  77. with open(templateFileName, "r") as templateFile:
  78. templateFileRoot = ET.parse(templateFile).getroot()
  79. templateFilePyDictForm = xmlToDict(templateFileRoot[0])
  80. # print(templateFilePyDictForm)
  81. ret.append(templateFilePyDictForm)
  82. return ret
  83. """ 从转化为py-dict的templateFile中获取使用到的plate的ID的list """
  84. def getPlateIDsFromTemplateFilePyDictForm(templateFilePyDictForms: list) -> list:
  85. ret = []
  86. for templateFilePyDictForm in templateFilePyDictForms:
  87. for plates in templateFilePyDictForm["nodeGraph"]["children"][0]["children"]:
  88. ret.append(plates["displayName"])
  89. return ret
  90. """ 根据盘子ID获得py-dict格式的盘子配置 """
  91. def getPlateConfigPyDictForms(plateIDs: list) -> list:
  92. ret = []
  93. for plateID in plateIDs:
  94. plateConfigFileName = "{}SG-盘子{}情况.red".format(plateConfigFileDirPath, int(plateID))
  95. # print(plateConfigFileName)
  96. with open(plateConfigFileName, "r") as plateConfigFile:
  97. plateConfigPyDictForm = xmlToDict(ET.parse(plateConfigFile).getroot()[0])
  98. # print(plateConfigPyDictForm)
  99. ret.append(plateConfigPyDictForm)
  100. return ret
  101. """ 需要获得每个 plate 的 position, scale, rotate, plateId, zorder """
  102. def generateLevelConfigWithPlateConfig(plateConfigPyDictForms: list) -> list:
  103. plateDatas = []
  104. for plate in plateConfigPyDictForms:
  105. properties = plate["nodeGraph"]["children"][0]["properties"]
  106. # print(propertiesDict)
  107. plateConfig = {}
  108. for property in properties:
  109. propertyName = property["name"]
  110. if propertyName == "position":
  111. position = property["value"][:2]
  112. position = [round(float(n), 1) for n in position]
  113. plateConfig["position"] = position
  114. elif propertyName == "scale":
  115. scale = round(float(property["value"][0]), 1)
  116. plateConfig["scale"] = scale
  117. elif propertyName == "rotation":
  118. rotate = round(float(property["value"]), 1)
  119. plateConfig["rotate"] = rotate
  120. if "position" not in plateConfig:
  121. # 这个默认的位置可能不对 随便设置的
  122. plateConfig["position"] = [500.0, 500.0]
  123. if "scale" not in plateConfig:
  124. plateConfig["scale"] = 1.000
  125. if "rotate" not in plateConfig:
  126. plateConfig["rotate"] = 0.000
  127. plateName = plate["nodeGraph"]["children"][0]["displayName"]
  128. pattern = r"\d*$"
  129. plateID = re.findall(pattern, plateName)[0]
  130. plateConfig["plateId"] = int(plateID)
  131. plateConfig["zorder"] = 1
  132. plateConfig["holes"] = getHolePositionsFromPlateConfig(plate)
  133. # print(plateConfig)
  134. plateDatas.append(plateConfig)
  135. return plateDatas
  136. def getHolePositionsFromPlateConfig(plateConfigPyDictForms: list) -> list:
  137. ret = []
  138. plateConditions = plateConfigPyDictForms["nodeGraph"]["children"][0]["children"]
  139. # 对应plate的每种情况 比如 "一个钉子" "五个钉子"
  140. for condition in plateConditions:
  141. holeConfigs = {}
  142. holes = condition["children"]
  143. holePositions = []
  144. for hole in holes:
  145. holeProperties = hole["properties"]
  146. for property in holeProperties:
  147. if property["name"] == "position":
  148. holePositions.append([round(float(property["value"][0]), 1), round(float(property["value"][1]), 1)])
  149. break
  150. holeConfigs["position"] = holePositions
  151. ret.append(holeConfigs)
  152. return ret
  153. """ 将关卡配置保存到JSON中 """
  154. def saveLevelConfigToJsonFile(levelConfigs: list, fileName: str) -> None:
  155. # print("saving json files: ", fileName)
  156. fileName = fileName.replace("red", "json")
  157. with open(levelConfigJSONDirPath + fileName, "w") as levelConfigFile:
  158. finalData = {"plateData": levelConfigs}
  159. json.dump(finalData, levelConfigFile)
  160. """ 将.red文件的内容转化为 python-dict 格式 """
  161. def xmlToDict(fileRoot: ET.Element) -> dict | list | str:
  162. rootTag = fileRoot.tag
  163. # print(rootTag)
  164. resList = []
  165. resDict = {}
  166. if rootTag == 'dict':
  167. idx = 0
  168. while idx < len(fileRoot):
  169. if fileRoot[idx].tag == 'key':
  170. nextTag = fileRoot[idx + 1].tag
  171. if nextTag == 'true':
  172. resDict[fileRoot[idx].text] = True
  173. elif nextTag == 'false':
  174. resDict[fileRoot[idx].text] = False
  175. else:
  176. resDict[fileRoot[idx].text] = xmlToDict(fileRoot[idx + 1])
  177. idx += 1
  178. idx += 1
  179. return resDict
  180. elif rootTag == 'array':
  181. for child in fileRoot:
  182. resList.append(xmlToDict(child))
  183. return resList
  184. else:
  185. return fileRoot.text
  186. ''' 测试 将关卡的redream转化成json '''
  187. def testToConvertAll() -> None:
  188. # configFilePath = configFileDirPath + "关卡1test.red"
  189. allLevelConfigs = []
  190. for file in os.listdir(configFileDirPath):
  191. # print(file)
  192. if file != ".DS_Store":
  193. absolutePath = configFileDirPath + file
  194. if not os.path.isdir(absolutePath):
  195. # print(absolutePath)
  196. with open(configFileDirPath + file) as configFile:
  197. configFileTree = ET.parse(configFile)
  198. fileRoot = configFileTree.getroot()
  199. # print(fileRoot.keys())
  200. dataPyDict = xmlToDict(fileRoot[0])
  201. # print(dataPyDict)
  202. templateIDList = getTemplateIDFromDict(dataPyDict)
  203. # print(templateIDList)
  204. templateDatas = getTemplatesPyDictForm(templateIDList)
  205. plateIDs = getPlateIDsFromTemplateFilePyDictForm(templateDatas)
  206. # print(plateIDs)
  207. plateConfigsPyDictForm = getPlateConfigPyDictForms(plateIDs)
  208. # print(plateConfigsPyDictForm)
  209. levelConfigs = generateLevelConfigWithPlateConfig(plateConfigsPyDictForm)
  210. # print(levelConfigs)
  211. # TODO 在模拟生成最终的关卡数据 所以先不save了
  212. allLevelConfigs.append(levelConfigs)
  213. # saveLevelConfigToJsonFile(levelConfigs, file)
  214. finalLevelConfigs = simulateToGenerateRealLevelConfig(allLevelConfigs)
  215. saveFinalLevelConfigToJsonFile(finalLevelConfigs, file)
  216. deleteAllHolesInConfigFile()
  217. ''' 使用之前生成的关卡配置 模拟接入C++关卡生成代码后生成最终的关卡配置文件 '''
  218. ''' 返回最终的 levelConfig '''
  219. def simulateToGenerateRealLevelConfig(levelConfigs: list) -> list:
  220. for levelConfig in levelConfigs:
  221. # print(levelConfig)
  222. # 随机挑选完情况后 关卡的钉子总数
  223. nScrewsOfLevel = 0
  224. conditionsSelected = []
  225. for plate in levelConfig:
  226. # 随机挑选一种情况
  227. holes = plate['holes']
  228. conditionID = random.randint(0, len(holes) - 1)
  229. # print(conditionID)
  230. conditionsSelected.append(conditionID)
  231. nScrewsOfLevel += len(holes[conditionID]['position'])
  232. # print(nScrewsOfLevel)
  233. # 在钉子种类范围内 随机选取钉子编号
  234. screwIDList = [random.randint(1, nScrewTypes) for i in range(nScrewsOfLevel)]
  235. random.shuffle(screwIDList)
  236. # print("随机挑选的钉子的ID: ", screwIDList)
  237. # print("选取的盘子情况的ID: ", conditionsSelected)
  238. screwPositions = []
  239. i = 0
  240. for plate in levelConfig:
  241. # print(plate['holes'][conditionsSelected[i]]['position'])
  242. for position in plate['holes'][conditionsSelected[i]]['position']:
  243. screwPositions.append(position)
  244. i += 1
  245. # print(screwPositions)
  246. # screwConfigs = [{
  247. # "screwId": screwIDList[i],
  248. # "scale": 1.0,
  249. # "rotate": 0.0,
  250. # "position": screwPositions[i]
  251. # } for i in range(len(screwIDList))]
  252. # print(screwConfigs)
  253. # 每个盘子的添加screwConfig 其中钉子的数量是被选择的情况的空位数
  254. nScrewAdded = 0
  255. i = 0
  256. for plate in levelConfig:
  257. nScrewsToAdd = len(plate['holes'][conditionsSelected[i]]['position'])
  258. screwConfigs = [{
  259. "screwId": screwIDList[nScrewAdded + j],
  260. "scale": 1.0,
  261. "rotate": 0.0,
  262. "position": screwPositions[nScrewAdded + j]
  263. } for j in range(nScrewsToAdd)]
  264. nScrewAdded += nScrewsToAdd
  265. i += 1
  266. plate['screws'] = screwConfigs
  267. # print(plate)
  268. # 修改plateConfig后的levelConfig
  269. # print(levelConfig)
  270. # 修改后的levelConfigs
  271. # print(levelConfigs)
  272. return levelConfigs
  273. ''' 获得盘子最少和最多能安放的钉子数 '''
  274. def getMinMaxScrewsOfPlate(holes: list) -> tuple:
  275. for condition in holes:
  276. maxSizeOfCondition = 0
  277. minSizeOfCondition = 1
  278. positions = condition['position']
  279. if len(positions) > maxSizeOfCondition:
  280. maxSizeOfCondition = len(positions)
  281. if len(positions) < minSizeOfCondition:
  282. minSizeOfCondition = len(positions)
  283. return minSizeOfCondition, maxSizeOfCondition
  284. def saveFinalLevelConfigToJsonFile(finalLevelConfig: list, file: str) -> None:
  285. fileName = finalLevelConfigPath + file.replace("red", "json")
  286. copyOfFinalConfig = copy.copy(finalLevelConfig)
  287. with open(fileName, "w") as finalLevelConfigFile:
  288. # print(finalLevelConfigFile)
  289. finalData = {"plateData": copyOfFinalConfig}
  290. json.dump(finalData, finalLevelConfigFile)
  291. def deleteAllHolesInConfigFile() -> None:
  292. for filePath in os.listdir(finalLevelConfigPath):
  293. # .DS_Store 必须忽略 不然会出现编码错误
  294. if filePath != ".DS_Store":
  295. with open(finalLevelConfigPath + filePath, 'r') as file:
  296. # rawData = file.read()
  297. # rawData = rawData.encode("utf-8", errors="ignore")
  298. # rawData = json.loads(rawData)
  299. rawData = json.load(file)
  300. for levelConfig in rawData['plateData']:
  301. for plate in levelConfig:
  302. if 'holes' in plate:
  303. del plate['holes']
  304. # TODO: 不知道为什么有的是dict有的是list
  305. if type(rawData['plateData']) == list:
  306. if type(rawData['plateData'][0]) == list:
  307. rawData['plateData'] = rawData['plateData'][0]
  308. else:
  309. rawData['plateData'] = [rawData['plateData']]
  310. # print(type(rawData['plateData']))
  311. # print(rawData['plateData'])
  312. with open(finalLevelConfigPath + filePath, 'w') as file:
  313. json.dump(rawData, file)
  314. if __name__ == "__main__":
  315. # print("Hello")
  316. # askAndSetNewConfigFileDirPath()
  317. # testToConvert1()
  318. # TODO: 注意一下 里面没有save为中间态json
  319. testToConvertAll()