1#!/usr/bin/env python 2 3#file: make-tree.py 4#Copyright (C) 2008 aes and FunnyMan3595 5#This file is part of Endgame: Singularity. 6 7#Endgame: Singularity is free software; you can redistribute it and/or modify 8#it under the terms of the GNU General Public License as published by 9#the Free Software Foundation; either version 2 of the License, or 10#(at your option) any later version. 11 12#Endgame: Singularity is distributed in the hope that it will be useful, 13#but WITHOUT ANY WARRANTY; without even the implied warranty of 14#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15#GNU General Public License for more details. 16 17#You should have received a copy of the GNU General Public License 18#along with Endgame: Singularity; if not, write to the Free Software 19#Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 21#This file is used to generate a visual representation of the tech tree using 22#graphviz. 23import collections 24from os import system 25import os.path as osp 26import sys 27 28if __name__ == '__main__': 29 myname = sys.argv[0] 30 mydir = osp.dirname(myname) 31 esdir = osp.abspath(osp.join(osp.dirname(myname), '..')) 32 sys.path.insert(0,esdir) 33else: 34 myname = __file__ 35 mydir = osp.dirname(myname) 36 esdir = osp.abspath(osp.join(osp.dirname(myname), '..')) 37 sys.path.append(esdir) 38 39try: 40 from singularity.code import g, dirs, i18n, data, tech 41 dirs.create_directories(False) 42 i18n.set_language() 43 data.load_regions() 44 data.load_locations() 45 data.load_techs() 46 data.load_item_types() 47 data.load_items() 48 data.load_tasks() 49except ImportError: 50 sys.exit("Could not find game's code.g") 51 52so_far = "" 53 54 55def cost(buy_spec): 56 c = [ k/f for f,k in zip([1, 86400, 24*60], buy_spec.cost)] 57 s = ', '.join(['%s %s' % (g.to_money(k), label) for label, k in zip(["money", "CPU", "days"], c) if k]) 58 if hasattr(buy_spec, 'danger') and buy_spec.danger > 0: 59 d = "Safety needed: %s" % buy_spec.danger 60 if s: 61 s += '\\n' 62 s += d 63 return s and '\\n'+s or '' 64 65 66j = {v.name: ',fillcolor="#ffcccc"' for k, v in g.tasks.items() if v.type != "jobs"} 67 68f = open("techs.dot", 'w') 69s = ("""\ 70digraph g { 71ranksep=0.15; 72nodesep=0.10; 73ratio=.75; 74edge [arrowsize=0.75]; 75node [shape=record,fontname=FreeSans,fontsize=7,height=0.01,width=0.01 76 style=filled,fillcolor=white]; 77""") 78 79f.write(s) 80so_far += s 81 82for l in sum([ [ '"%s"->"%s";' % (p,k) 83 for p in v.prerequisites ] 84 for k,v in g.techs.items() if k != "unknown_tech"], 85 []): 86 f.write(l+'\n') 87 so_far += l+'\n' 88 89f.write('\n') 90so_far += '\n' 91 92for n, t in g.techs.items(): 93 if n == "unknown_tech": 94 continue 95 s = '"%s" [label="%s%s"%s];\n' % (n, n, cost(t), j.get(n,'')) 96 f.write(s) 97 so_far += s 98 99f.write("\n}\n") 100so_far += '\n' 101f.close() 102 103try: system("dot -Tpng -o techs.png techs.dot") 104except: pass 105 106f = open('items.dot', 'w') 107f.write(so_far) 108s = 'node [fillcolor="#ccccff"];\n' 109f.write(s) 110so_far += s 111 112for name,item in g.items.items(): 113 if not item.prerequisites: continue 114 for pre in item.prerequisites: 115 p = g.techs[pre] 116 s = '"%s" -> "%s-item"' % (pre, name) 117 f.write(s) 118 so_far += s 119 120 s = '"%s-item" [label="%s\\n' % (name, name) + cost(item) + '"];\n' 121 f.write(s) 122 so_far += s 123 124s = 'node [fillcolor="#99ffff"];\n' 125f.write(s) 126so_far += s 127 128for name,base in g.base_type.items(): 129 if not base.prerequisites: continue 130 for pre in base.prerequisites: 131 p = g.techs[pre] 132 s = '"%s" -> "%s-base"' % (pre, name) 133 f.write(s) 134 so_far += s 135 136 s = '"%s-base" [label="%s\\n' % (name, name) + cost(base) + '"];\n' 137 f.write(s) 138 so_far += s 139 140s = 'node [fillcolor="#aaffaa"];\n' 141f.write(s) 142so_far += s 143 144blue = False 145def set_or(state): 146 global blue 147 if blue != state: 148 blue = state 149 if blue: 150 f.write('edge [arrowhead=empty,color="#0000FF"];\n') 151 else: 152 f.write('edge [arrowhead=normal,color="#000000"];\n') 153 154 155SAFETY2LOCATIONS = collections.defaultdict(list) 156 157for name, loc in g.locations.items(): 158 if not loc.prerequisites: 159 continue 160 if "impossible" in loc.prerequisites: 161 continue 162 set_or(False) 163 for pre in loc.prerequisites: 164 if pre == "OR": 165 set_or(True) 166 continue 167 p = g.techs[pre] 168 s = '"%s" -> "%s-loc"' % (pre, name) 169 f.write(s) 170 so_far += s 171 172 if loc.safety > 0: 173 SAFETY2LOCATIONS[loc.safety].append(loc) 174 s = '"%s-loc" [label="%s"];\n' % (name, name) 175 f.write(s) 176 so_far += s 177 178# When there are multiple locations providing the same 179# safety level, we inject a safety node. This reduces 180# the number of edges from L * T to L + T. 181for safety_level in sorted(SAFETY2LOCATIONS): 182 locs = SAFETY2LOCATIONS[safety_level] 183 if len(locs) == 1: 184 continue 185 s = '"safety-%s" [label="Safety level %s", shape="hexagon"];\n' % (safety_level, safety_level) 186 f.write(s) 187 so_far += s 188 for loc in locs: 189 s = '"%s-loc" -> "safety-%s"' % (loc.id, safety_level) 190 f.write(s) 191 so_far += s 192 193for tech_spec in g.techs.values(): 194 pre = tech_spec.prerequisites_in_cnf_format() 195 if not pre or tech_spec.danger < 1: 196 continue 197 # Safety requirement is the highest safety required 198 # between each "AND" and the lowest between "OR". 199 # MAX( 200 # MIN(a1.danger, [OR] a2.danger, [OR] ...), [AND] 201 # MIN(b2.danger, [OR] b2.danger, [OR] ...), [AND] 202 # ... 203 # ) 204 safety_required = max( 205 min(g.techs[t].danger for t in dep_group) 206 for dep_group in pre 207 ) 208 # We only emit the edge for safety when the tech bumps 209 # the minimum requirement. This is to reduce the number 210 # of edges in the graph. 211 if tech_spec.danger > safety_required: 212 source = 'safety-%s' % tech_spec.danger 213 if len(SAFETY2LOCATIONS[tech_spec.danger]) == 1: 214 source = '%s-loc' % SAFETY2LOCATIONS[tech_spec.danger][0].id 215 s = '"%s" -> "%s"' % (source, tech_spec.id) 216 f.write(s) 217 so_far += s 218 219 220f.write("\n}\n") 221so_far += '\n' 222f.close() 223 224try: system("unflatten -l10 items.dot | dot -Tpng -o items.png") 225except: pass 226