''' Created on Mar 10, 2018 @author: Brett Paufler Copyright Brett Paufler Parsing of the Tech Tree File CIV4TechInfos.xml Need to pull from below and place in ref folder C:\civ_4\Beyond the Sword\Assets\XML\Technologies OrPreReqs: one or more of these techs required AndPreReqs: all of these techs required TECH_WRITING (as an example of multiple or's) TECH_PRIESTHOOD TECH_ANIMAL_HUSBANDRY TECH_POTTERY TECH_PRINTING_PRESS (example of multiple and's) TECH_MACHINERY TECH_ALPHABET No and's or or's (first line, worker techs) ''' #import xml.etree.ElementTree as ET import re #import networkx as nx #import plotly #import yellowbrick from copy import copy #from lxml import etree #print text_raw def left_align(text): '''Returns left aligned copy of passed text.''' text = '\n'.join( line.strip() for line in text.split('\n')) return text def find_all(tag, text): '''Returns list of strings found in text between xml tags of passed type tag (str): xml tag to search for text (str): string to search returns list of strings: [str, str...] or if nothing found the empty list: [] ''' #Covers for an empty Or/And PreRequisite Listing if not text: return [] pattern = re.compile( '<%s>(.*?)' % (tag, tag), re.IGNORECASE | re.DOTALL) listing = re.findall(pattern, text) return listing def find_one(tag, text): '''Returns a single string of text. as found between tag. If nothing found, returns the empty string '' ''' listing = find_all(tag, text) #print listing if listing: listing = listing[0] else: listing = '' return listing class Tech(object): def __init__(self, tech_str): self.Name = find_one('Type', tech_str)[5:] and_tech_str = find_one('AndPreReqs', tech_str) and_techs = find_all('PrereqTech', and_tech_str) self.AndPreReqs = [tech[5:] for tech in and_techs] or_tech_str = find_one('OrPreReqs', tech_str) or_techs = find_all('PrereqTech', or_tech_str) self.OrPreReqs = [tech[5:] for tech in or_techs] self.iGridX = int(find_one('iGridX', tech_str)) self.iGridY = int(find_one('iGridY', tech_str)) self.iCost = int(find_one('iCost', tech_str)) self.researched = False def __repr__(self): text = '%s (%d) (%d,%d)\n' % ( self.Name, self.iCost, self.iGridX, self.iGridY) text += '\tResearched: %s\n' % str(self.researched) text += '\tAnd: %s\n' % str(self.AndPreReqs) text += '\tOr: %s' % str(self.OrPreReqs) if self.Name == 'FUTURE_TECH': print 'Got a hit' return text class TechTree(object): def __init__(self, file_in): #print 'Init' with open(file_in) as f: text_raw = f.read() text_left_aligned = left_align(text_raw) tech_strings = find_all('TechInfo', text_left_aligned) techs = [Tech(tech_str) for tech_str in tech_strings] self.techs = techs self.paths = [] #print self.techs def get_tech_by_name(self, tech_name): for tech in self.techs: if tech.Name == tech_name: return tech def report(self): for tech in self.techs: print tech def set_all_research(self, val=True): '''Sets all research True or False depending upon val.''' for tech in self.techs: tech.researched = val #def all_research_false(self): # for tech in self.techs: # tech.researched = False def set_single_research(self, technology, val=True): '''Sets a single tech's researched value.''' for tech in self.techs: if tech.Name == technology: #print 'HIT' tech.researched = val def path_clear(self): self.paths = [] def path_add_goal(self, tech): if self.paths: for path in self.paths: path.append(tech) else: self.paths = [{tech}] def compute_paths(self): x = True while x: x = self._path_incremental_add_prereq() #print 'Loop' def _path_incremental_add_prereq(self): for path in self.paths: for tech_name in path: #print tech_name tech = self.get_tech_by_name(tech_name) #print tech #if not all(and_prereq in path # for and_prereq in tech.AndPreReqs): # path.update(tech.AndPreReqs) # return #if not tech.AndPreReqs: # all_and = True if not all([prereq in path for prereq in tech.AndPreReqs]): path.update(tech.AndPreReqs) #print 'All Update: returning', self.paths return True #print 'Never Get Here' #print self.paths #else: # print 'No And' #if not all_and: # for path in self.paths: # #for prerer # path.append(tech_name) if not tech.OrPreReqs: any_or = True else: any_or = any( [prereq in path for prereq in tech.OrPreReqs]) #print 'Any_Or', any_or, tech.OrPreReqs if not any_or: self.paths.remove(path) #new_path = copy(path) for or_tech in tech.OrPreReqs: #print 'Or Tech:', or_tech, new_path #path.update(or_tech) new_path = copy(path) new_path.update([or_tech]) #print 'Or_tech', or_tech #print 'New Path', new_path self.paths.append(new_path) #print self.paths #print 'Or:', self.paths return True #path_copy = path.copy() #Fell off edge, all prereqs in return False def icost_from_name(self, tech_name): iCost = [t.iCost for t in self.techs if tech_name == t.Name][0] return iCost def standard_tech_tree(): file_in = '.\\ref\\CIV4TechInfos.xml' tech_tree = TechTree(file_in) return tech_tree if __name__ == '__main__': #file_in = '.\\ref\\CIV4TechInfos.xml' tech_tree = standard_tech_tree() tech_tree.set_all_research(False) tech_tree.set_all_research(True) #tech_tree.report() tech_tree.set_single_research('FUTURE_TECH', False) #tech_tree.report() tech_tree.path_add_goal('FUTURE_TECH') #tech_tree._path_incremental_add_prereq() #print tech_tree.paths #tech_tree._path_incremental_add_prereq() #print tech_tree.paths #tech_tree._path_incremental_add_prereq() #print tech_tree.paths #print type(tech_tree.paths) tech_tree.compute_paths() print tech_tree.paths print 'RESULT' for path in tech_tree.paths: print path #edges = [] #for tech in techs: #DG = nx.DiGraph() #print DG #for tech in techs: #print tech #DG.add #OrPreReqs #AndPreReqs #iGridX #iGridY #iCost