jsonToProto.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import os.path
  4. import sys
  5. import json
  6. import levelData_pb2
  7. import csv
  8. # 获取当前工作目录
  9. current_dir = os.getcwd()
  10. # 获取脚本所在的目录
  11. script_dir = os.path.dirname(os.path.abspath(__file__))
  12. # 改变当前工作目录
  13. os.chdir(script_dir)
  14. jsDir = "./loadable/"
  15. # 所有关卡文件的信息,key为关卡文件名,value是一个dict,包含关卡文件的信息
  16. lvFileInfo = {}
  17. templateInfo = {} # 模版信息
  18. # 所有关卡的信息,key为关卡id,value是一个dict,包含关卡的信息
  19. lvInfo = {}
  20. def parseLvid():
  21. global lvFileInfo, lvInfo
  22. with open("../config_level.csv", 'r') as lcf:
  23. # csv 文件,格式如下:
  24. # LevelId,Des,TemplateId,RandomType,DupPara1,DupPara2,DupPara3,DupPara4,DupPara5,DupPara6,DupPara7,DupPara8,DupPara9,DupPara10
  25. # string,string,string,int,array,array,array,array,array,array,array,array,array,array
  26. # 关卡 id,备注,模板 id,0=固定按第n次玩来取副本,1=随机取,第1次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第2次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第3次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第4次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第5次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第6次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第7次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第8次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第9次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数),第10次及之后玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数)
  27. # toolguide01,,undo01,0,1|0|100|1,1|0|100|1,1|0|100|1,1|0|100|1,1|0|100|1,1|0|100|1,1|0|100|1,1|0|100|1,1|0|100|1,1|0|100|1
  28. # 将 csv 文件转为 dict,存放到 lvFileInfo 中;忽略前两行,并将第3行作为 dict 的 key,后面的行作为 value
  29. lcf.readline()
  30. lcf.readline()
  31. keys = lcf.readline().strip().split(',')
  32. for line in lcf:
  33. line = line.strip()
  34. if len(line) == 0:
  35. continue
  36. if line[0] == '#':
  37. continue
  38. line = line.split(',')
  39. lvFileInfo[line[0]] = dict(zip(keys, line))
  40. with open("../config_level_list.csv") as cllf:
  41. # csv 文件,格式如下:
  42. # LevelIndex,LevelType,LevelCoin,RewardCoinMultiple,LevelId,DDALevelId1,DDALevelId2,DDALevelId3,DDAType,DDAMatrix,LevelDuration,LevelCoinPercentage
  43. # int,int,int,int,array,array,array,array,int,array,int,int
  44. # 第几关,关卡类型,0=普通,1=Hard,2=SuperHard,3=金币,基础金币奖励值。结算金币数=0时不显示。,用户看完广告后获得的金币倍数,关卡 id,关卡 id,关卡 id,关卡 id,DDA 类型,0=不走DDA(取 LevelId 列),1=走DDA,2=考试关(取 LevelId 列),DDA 参数,金币关的关卡时长(单位秒),最高100分钟,即6000s,金币tile的比例(单位为%,取值0-100)
  45. # 1,0,0,0,tempnm0017,tempnm0017,tempnm0017,tempnm0017,0,0|100,,
  46. # 将 csv 文件转为 dict,存放到 lvInfo 中;忽略前两行,并将第3行作为 dict 的 key,后面的行作为 value
  47. cllf.readline()
  48. cllf.readline()
  49. keys = cllf.readline().strip().split(',')
  50. for line in cllf:
  51. line = line.strip()
  52. if len(line) == 0:
  53. continue
  54. if line[0] == '#':
  55. continue
  56. line = line.split(',')
  57. lvInfo[line[0]] = dict(zip(keys, line))
  58. pass
  59. # 解析关卡的种子信息
  60. lvSeedInfo = {}
  61. def parseSeed():
  62. global lvSeedInfo
  63. with open("../lv_seed.txt") as cllf:
  64. # TileManor输出的文件
  65. # 每一行的格式:[Tile] resovler id: 30,735680|69576|719549|907289|550748|399652|246018|611094|979453|303995|269635|
  66. # 去掉头部:"[Tile] resovler id: " 之后的数据分成两部分,第一部分是lvid,后面是一系列种子的列表
  67. # 将这些数据存放到 lvSeedInfo 中,key为lvid,value为种子列表
  68. for line in cllf:
  69. line = line.strip()
  70. if len(line) == 0:
  71. continue
  72. if line[0] == '#':
  73. continue
  74. fs = line.split(':')[1].split(',')
  75. lvid = fs[0].strip()
  76. seeds = fs[1].split('|')
  77. lvSeedInfo[lvid] = seeds
  78. pass
  79. def converJosnToProto(tDir, tFileName, leveId):
  80. global lvFileInfo, lvInfo
  81. jsonFile = os.path.join(tDir, tFileName + '.json')
  82. with open(jsonFile, 'r') as f:#, encoding='utf-8'
  83. jsonData = json.load(f)
  84. levelData = levelData_pb2.LevelData()
  85. levelData.tileWidth = jsonData['LevelWidth']
  86. levelData.tileHeight = jsonData['LevelHeight']
  87. cntUndefined = 0
  88. tilesByPos = {}
  89. maxZ = 0
  90. maxX = 0
  91. maxY = 0
  92. for item in jsonData['LevelData']:
  93. tile = levelData.tiles.add()
  94. tile.x = int(item['X'])
  95. tile.y = int(item['Y'])
  96. tile.z = int(item['Z'])
  97. tiledata = tile.tileData
  98. tiledata.zv = 0
  99. tiledata.weight = 0
  100. tiledata.id = int(item['TileId'])
  101. if tiledata.id == -10:
  102. cntUndefined += 1
  103. tiledata.type = 1
  104. tiledata.subtype = 0
  105. # 一些统计信息
  106. tilesByPos[(tile.x, tile.y, tile.z)] = tile
  107. if tile.z > maxZ:
  108. maxZ = tile.z
  109. if tile.x > maxX:
  110. maxX = tile.x
  111. if tile.y > maxY:
  112. maxY = tile.y
  113. # 根据上面的布局信息,计算每个tile的几个信息:
  114. # 1. 每个tile的视觉层级(不同于上面z的信息,那是一个布局信息,相同的z可能出在不同的视觉层级)
  115. # 从maxZ开始,逐层向下计算;对于每一个位置,如果该位置有tile,则计算该tile的视觉层级
  116. # 对于每一个tile,如果其上方(从该tile的z+1层,一直到maxZ层)有tile,则其视觉层级为上方tile的视觉层级+1
  117. for z in range(0, maxZ+1):
  118. z = maxZ - z
  119. for x in range(0, maxX+1):
  120. for y in range(0, maxY+1):
  121. if (x,y,z) in tilesByPos:
  122. tile = tilesByPos[(x, y, z)]
  123. adjs = [(x,y),(x,y+1),(x,y-1),(x+1,y),(x+1,y+1),(x+1,y-1),(x-1,y),(x-1,y-1),(x-1,y+1)]
  124. # 确定视觉层级
  125. zvMax = -1
  126. for zup in range(z+1, maxZ+1):
  127. for adj in adjs:
  128. if (adj[0],adj[1],zup) in tilesByPos:
  129. zv = tilesByPos[adj[0],adj[1],zup].tileData.zv
  130. if zv > zvMax:
  131. zvMax = zv
  132. tile.tileData.zv = zvMax + 1
  133. # 计算权重
  134. # 从最底下开始,逐层向上计算
  135. # 每个tile的初始权重为4,然后加上其下方压住的tile的权重:如果压了全部,则该加上被压住的tile权重;
  136. # 如果压住了一半,则加上被压住的tile权重的一半;如果是压住1/4,则加上被压住的tile权重的1/4
  137. for z in range(0, maxZ+1):
  138. for x in range(0, maxX+1):
  139. for y in range(0, maxY+1):
  140. if (x,y,z) in tilesByPos:
  141. tile = tilesByPos[(x,y,z)]
  142. adjs = [(x,y,1.0),(x,y+1,0.5),(x,y-1,0.5),(x+1,y,0.5),(x+1,y+1,0.25),(x+1,y-1,0.25),(x-1,y,0.5),(x-1,y-1,0.25),(x-1,y+1,0.25)]
  143. weight = 4
  144. for adj in adjs:
  145. # 从临近层往下,在某个位置找到了tile,则该tile被压住了,就不再找了
  146. for zb in range(0, z):
  147. zb = z-1-zb
  148. if (adj[0], adj[1], zb) in tilesByPos:
  149. tileB = tilesByPos[(adj[0], adj[1], zb)]
  150. weight += tileB.tileData.weight * adj[2]
  151. break
  152. tile.tileData.weight = int(weight)
  153. pass
  154. for item in jsonData['StackData']:
  155. stack_data = levelData.stacks.add()
  156. stack_data.x = int(item['X'])
  157. stack_data.y = int(item['Y'])
  158. stack_data.direction = int(item['Direction'])
  159. templateInfo[tFileName] = {}
  160. if cntUndefined == 0:
  161. templateInfo[tFileName]['无未定义的tile'] = '1'
  162. elif cntUndefined == len(jsonData['LevelData']):
  163. templateInfo[tFileName]['所有tile都未被定义'] = '1'
  164. else:
  165. templateInfo[tFileName]['部分tile被定义'] = '1'
  166. if len(jsonData['StackData']) > 0:
  167. templateInfo[tFileName]['有stack数据'] = '1'
  168. # 数据序列化
  169. level_protobuf_data = levelData.SerializeToString()
  170. protoFile = os.path.join(jsDir, tFileName + '.bin')
  171. with open(protoFile, 'w+b') as f:
  172. f.write(level_protobuf_data)
  173. #关卡数据拼接map+trcuk
  174. def mergeLeveldata3(group = None):
  175. lvelsIndex = levelData_pb2.LevelsIndex()
  176. if group is not None:
  177. protoDataTrunk = os.path.join(jsDir, 'levels-' + str(group) + '.bin')
  178. protoDataIndex = os.path.join(jsDir, 'levelsIndex-' + str(group) + '.bin')
  179. else:
  180. protoDataTrunk = os.path.join(jsDir, 'levels' + '.bin')
  181. protoDataIndex = os.path.join(jsDir, 'levelsIndex' + '.bin')
  182. offset = 0
  183. for filename in os.listdir(jsDir):
  184. my_message = levelData_pb2.LevelData()
  185. binFile = os.path.splitext(filename)[0] + '.bin'
  186. binFile = os.path.join(jsDir, binFile)
  187. if os.path.exists(binFile) :
  188. # print(binFile)
  189. with open(binFile, "rb") as f:
  190. binary_data = f.read()
  191. my_message.ParseFromString(binary_data)
  192. os.remove(binFile)
  193. with open(protoDataTrunk, 'ab') as f:
  194. f.write(binary_data)
  195. lvelsIndex.LevelsIndex[os.path.splitext(filename)[0]].len = len(binary_data)
  196. lvelsIndex.LevelsIndex[os.path.splitext(filename)[0]].offset = offset
  197. offset += len(binary_data)
  198. level_protobuf_index = lvelsIndex.SerializePartialToString()
  199. with open(protoDataIndex, 'w+b') as f:
  200. f.write(level_protobuf_index)
  201. if __name__ == '__main__':
  202. parseLvid()
  203. parseSeed()
  204. folder_path = '../level_data2'
  205. for filename in os.listdir(folder_path):
  206. # 获取文件的绝对路径
  207. file_path = os.path.join(folder_path, filename)
  208. # 检查是否是文件而不是文件夹
  209. if os.path.isfile(file_path):
  210. base_name = os.path.splitext(filename)
  211. # 在文件转为bin
  212. converJosnToProto(folder_path, base_name[0],1)
  213. mergeLeveldata3()
  214. # 生成一些关卡的体验参数模板,在关卡实例中对应这个里面来
  215. with open("./loadable/paramTemplates.csv", 'w+') as f:
  216. fieldnames = ["name","tilevar","topN","matchRInTopN","maxUmZ","umMatchRs"]
  217. writer = csv.DictWriter(f, fieldnames=fieldnames)
  218. writer.writeheader()
  219. templates = {}
  220. # 容易:首层即有明显和高的匹配率,每一层的为匹配率也比较高
  221. writer.writerow({"name":"easy",
  222. "tilevar":"3",
  223. "topN":"1",
  224. "matchRInTopN":"50",
  225. "maxUmZ":"2",
  226. "umMatchRs":"40"})
  227. # 普通:首层的匹配率一般,到第二层的匹配率较高,后面的匹配率一般
  228. writer.writerow({"name":"normal",
  229. "tilevar":"3",
  230. "topN":"1",
  231. "matchRInTopN":"30",
  232. "maxUmZ":"3",
  233. "umMatchRs":"60|20|20|20|20"})
  234. # 普通.前难后易:首部2层的匹配率一般,但是后面的匹配率高
  235. writer.writerow({"name":"normal.hard_easy",
  236. "tilevar":"3",
  237. "topN":"2",
  238. "matchRInTopN":"60",
  239. "maxUmZ":"3",
  240. "umMatchRs":"40|40|40|40|40"})
  241. # 难:前两层的匹配率较高,后面的匹配率一般,同时tile的分布极端一点
  242. writer.writerow({"name":"hard",
  243. "tilevar":"4",
  244. "topN":"2",
  245. "matchRInTopN":"30",
  246. "maxUmZ":"4",
  247. "umMatchRs":"20|20|20|20|20"})
  248. # 将关卡id和模板的对应关系输出到文件
  249. lv_stat = {}
  250. got = False
  251. for k, v in lvInfo.items():
  252. levelId = v["关卡 id"]
  253. templateId = lvFileInfo[levelId]["模板 id"]
  254. if templateId not in templateInfo:
  255. templateId += '_m'
  256. if templateId in templateInfo:
  257. # 将所有信息综合到一块
  258. lv_stat[k] = lvFileInfo[levelId].copy()
  259. for key in templateInfo[templateId].keys():
  260. lv_stat[k][key] = templateInfo[templateId][key]
  261. lv_stat[k]["关卡 id"] = levelId
  262. lv_stat[k]["关卡类型"] = v["关卡类型,0=普通,1=Hard,2=SuperHard,3=金币"]
  263. lv_stat[k]["dda_type"] = v["DDA 类型,0=不走DDA(取 LevelId 列),1=走DDA,2=考试关(取 LevelId 列)"]
  264. lv_stat[k]["dda_para"] = v["DDA 参数"]
  265. lv_stat[k]["模板 id"] = templateId
  266. # 关卡与模版的对应关系,以及关卡本身的一些控制参数
  267. with open("./loadable/levelInfo.csv", 'w+') as f:
  268. fieldnames = ["lvid","lvinst","type(normal=0,hard=1,superHard=2)","dda_type","dda_para"]
  269. writer = csv.DictWriter(f, fieldnames=fieldnames)
  270. writer.writeheader()
  271. for lvid in sorted(lv_stat.keys(), key=lambda x:int(x)):
  272. d = {"lvid":lvid, "lvinst":"","type(normal=0,hard=1,superHard=2)":"","dda_type":"","dda_para":""}
  273. d["lvinst"] = lv_stat[lvid]["关卡 id"]
  274. d["type(normal=0,hard=1,superHard=2)"] = lv_stat[lvid]["关卡类型"]
  275. d["dda_type"] = lv_stat[lvid]["dda_type"]
  276. d["dda_para"] = lv_stat[lvid]["dda_para"]
  277. writer.writerow(d)
  278. # 实例模版的信息
  279. hdr4try = "(tileCnt|seed|param_template)"
  280. with open("./loadable/levelInstInfo.csv", 'w+') as f:
  281. all_insts = set()
  282. fieldnames = ["lvinst","template"]
  283. for tried in range(1,11):
  284. k = "try" + str(tried)+hdr4try
  285. fieldnames.append(k)
  286. writer = csv.DictWriter(f, fieldnames=fieldnames)
  287. writer.writeheader()
  288. for lvid in sorted(lv_stat.keys(), key=lambda x:int(x)):
  289. inst_id = lv_stat[lvid]["关卡 id"]
  290. if inst_id not in all_insts:
  291. all_insts.add(inst_id)
  292. else:
  293. continue
  294. d = {"lvinst":"", "template":""}
  295. for tried in range(1,11):
  296. k = "try" + str(tried)+hdr4try
  297. d[k] = ""
  298. d["lvinst"] = lv_stat[lvid]["关卡 id"]
  299. d["template"] = lv_stat[lvid]["模板 id"]
  300. seeds = []
  301. if lvid in lvSeedInfo:
  302. seeds = lvSeedInfo[lvid]
  303. for tried in range(1,11):
  304. k1 = "第" + str(tried) + "次玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数)"
  305. if tried == 10:
  306. k1 = "第10次及之后玩(ATile种类数 | 随机种子 | 塔副本幸运值 | BTile种类数)"
  307. if k1 in lv_stat[lvid]:
  308. try1 = lv_stat[lvid][k1]
  309. tileA = try1.split('|')[0]
  310. tileB = try1.split('|')[3]
  311. tileCnt = tileA if int(tileA) > int(tileB) else tileB
  312. # 默认都是normal的体验模式
  313. ps=[tileCnt,]
  314. if tried <= len(seeds):
  315. ps.append(seeds[tried-1])
  316. else:
  317. ps.append("100")
  318. ps.append("normal")
  319. d["try" + str(tried)+hdr4try] = "|".join(ps)
  320. else:
  321. d["try" + str(tried)+hdr4try] = ""
  322. writer.writerow(d)
  323. # 导出关卡id和模板的对应关系,并输出模板的信息
  324. else:
  325. print('ohoh')
  326. # 恢复原来的工作目录
  327. os.chdir(current_dir)