v50 Steam/Premium information for editors
  • v50 information can now be added to pages in the main namespace. v0.47 information can still be found in the DF2014 namespace. See here for more details on the new versioning policy.
  • Use this page to report any issues related to the migration.
This notice may be cached—the current version can be found here.

Difference between revisions of "User:Button/ModestMod"

From Dwarf Fortress Wiki
Jump to navigation Jump to search
(→‎Some related LNP pseudocode: stuff I need to comment)
Line 69: Line 69:
  
 
<pre>
 
<pre>
import os
 
 
template_tree = None
 
runconfig = 'resources/run.config'
 
ascii_codes = None
 
#graphics_tokens_by_file = {}
 
 
properties = {
 
              'target':['dir'],
 
              'source':['dir'],
 
              'output':['dir'],
 
              'save':['dir'],
 
              'templates':['file'],
 
              'ascii':['file']
 
              }
 
 
def default_run():
 
    load_run_config()
 
    load_all_templates()    # Guessing at syntax, don't have that func here atm
 
    graphics_tags_by_file = load_graphics_source()
 
    target_tags_by_file = load_target_raws()
 
    #merged_tags_by_file = apply_graphics(target_tags_by_file,graphics_tags_by_file)
 
   
 
def apply_graphics(target_tags,graphics_tags):
 
    for filename in target_tags.keys():
 
        if filename in graphics_tags.keys():
 
            for root_tag in target_tags[filename].keys():
 
                if root_tag in graphics_tags[filename].keys():
 
                    target_tags[filename][root_tag].recursively_apply_graphics(graphics_tags[filename][root_tag])
 
 
def create_new_raws(targetdir, outputdir, target_tags, graphics_tags):
 
file_list = []
 
for child in target_tags._children.keys():
 
this_file_name = target_tags._children[child]._filename
 
if this_file_name not in file_list:
 
file_list.append(this_file_name)
 
 
### START HERE ###
 
 
# Walk the directory targetdir for reading
 
# For each file, create a corresponding file in outputdir. Remember you'll need to create its directory structure!
 
# If the name of the file is in file_list, call modify_file_as_its_copied(targetfilepath, outputfilepath, target_tags, graphics_tags), otherwise just copy the file over. Remember to close the file output stream after copying!
 
# Remember to close the target file after copying!
 
 
### END HERE ###
 
 
def modify_file_as_its_copied(targetfilepath, outputfilepath, target_tags, graphics_tags):
 
 
curr_node = target_tags
 
 
### START HERE ###
 
 
# open targetfile for read & outputfile for write
 
# read each line from targetfile
 
# for each tag in the line
 
# for each template_node in curr_node._template._childref[tag.split(':')[0]]
 
# new_tag = template_node.get_child_matching(tag)
 
 
### END HERE ###
 
 
def load_run_config():
 
    global runconfig
 
    runconfig_file = open(runconfig,'r')
 
    global properties
 
    for line in runconfig_file:
 
        props = line.strip().split('=')
 
        if len(props) == 0:
 
            continue
 
        elif len(props) != 2:
 
            print('Line "',line,'" in ',runconfig,' is improperly configured. Please format properties thus: "propertyname=value" (without quotes).')
 
        elif props[0] not in properties.keys():
 
            print('Unknown property "',props[0],'" in ',runconfig)
 
        elif len(properties[props[0]]) > 1:
 
            print('Property "',props[0],'" is already defined.')
 
        elif not os.path.exists(props[1]):
 
            print('Property "',props[0],'" (',props[1],') not found on disk.')
 
        elif properties[props[0]][0] == 'dir' and not os.path.isdir(props[1]):
 
            print('The value of property "',props[0],'" must point to a directory.')
 
        elif properties[props[0]][0] == 'file' and not os.path.isfile(props[1]):
 
            print('The value of property "',props[0],'" must point to a file.')
 
        else:
 
            properties[props[0]].append(props[1])
 
           
 
    runconfig_file.close()
 
   
 
def load_all_templates():
 
    global properties
 
    if properties['templates'][1] == None:
 
        # TODO error handling
 
        print("Undefined graphics template file. Please add a 'templates' property in ",runconfig,".")
 
    else:
 
        alltemplates = open(properties['templates'][1],'r')
 
        global template_tree
 
        if template_tree == None:
 
            # initialize the template tree
 
            template_tree = TemplateNode(None)
 
        for line in alltemplates:
 
            real_line = line.strip()
 
            # Starting at the root of the tree with each newline
 
            curr_node = template_tree
 
            if len(real_line) > 0:
 
                # TODO continue
 
                tags = real_line.split('|')
 
                for tag in tags:
 
                    if tag in curr_node._children.keys():
 
                        curr_node = curr_node._children[tag]
 
                    else:
 
                        curr_node = TemplateNode(curr_node,tag)
 
                       
 
                curr_node._is_graphics_tag = True
 
        alltemplates.close()
 
 
#def check_file(filehandle):
 
       
 
 
def _load_ascii_conversions():
 
def _load_ascii_conversions():
 
     global ascii_codes
 
     global ascii_codes
 +
    global verbose
 +
    if verbose:
 +
        print("Loading ASCII conversions...")
 
     if ascii_codes == None:
 
     if ascii_codes == None:
 
         ascii_codes = {}
 
         ascii_codes = {}
 
     if properties['ascii'][1] == None:
 
     if properties['ascii'][1] == None:
         print("Undefined ascii conversion file. Please add an 'ascii' property in ",runconfig,".")
+
         print("Undefined ascii conversion file. Please add an 'ascii' property in",runconfig,".")
 
     else:
 
     else:
 
         for line in open(properties['ascii'][1]):
 
         for line in open(properties['ascii'][1]):
Line 202: Line 91:
 
                 else:
 
                 else:
 
                     ascii_codes[real_line[:point]] = real_line[point+1:]
 
                     ascii_codes[real_line[:point]] = real_line[point+1:]
 +
        if verbose:
 +
            print("ASCII conversions loaded.")
  
def load_graphics_source():
+
def write_modified_raws(graphics_to_apply):
    global properties
+
     if len(properties['output']) < 2:
     if len(properties['source']) < 2:
+
         print("Undefined output directory. Please add an 'output' property in",runconfig,".")
        # TODO error handling
 
         print("Undefined graphics source file. Please add a 'source' property in ",runconfig,".")
 
 
     else:
 
     else:
         return _walk_rawfiles_into_tagnode_collection(properties['source'][1])
+
         if verbose:
'''    else:
+
            print("Writing modified raws...")
        for root, dirs, files in os.walk(properties['source'][1]):
+
        for root, dirs, files in os.walk(properties['target'][1]):
             for rawfile in files:
+
            for dir in dirs:
                 global template_tree
+
                targetdir = os.path.join(root,dir)
                 curr_template_node = template_tree
+
                targetdir = properties['output'][1] + targetdir[len(properties['target'][1]):]
                 curr_graphics_node = None
+
                if not os.path.exists(targetdir):
                 tarpath = os.path.join(root,rawfile)
+
                    if verbose:
                 openfile = open(tarpath,encoding='cp437')
+
                        print("Creating output directory",dir)
                for line in openfile:
+
                    os.mkdir(targetdir)
                    for tag in tags(line):
+
             for file in files:
                        matching_node = curr_template_node.find_template_match(tag)
+
                 targetpath = os.path.join(root,file)
                        if matching_node != None:
+
                 targetpath = properties['output'][1] + targetpath[len(properties['target'][1]):]
                            curr_template_node = matching_node
+
                if path_compatible(targetpath,properties['graphics_overwrite'][1:]):
                            if curr_graphics_node == None or matching_node in template_tree._children:
+
                    if verbose:
                                curr_graphics_node = TagNode(rawfile,None,matching_node,tag)
+
                        print("Skipping",file,": graphics overwrite TBI.")
                            else:
+
                    pass
                                 while matching_node._tag_template not in curr_graphics_node._template._children:
+
                 elif path_compatible(targetpath,properties['graphics_ignore'][1:]):
                                    curr_graphics_node = curr_graphics_node._parent
+
                    if verbose:
                                 curr_graphics_node = TagNode(rawfile,curr_graphics_node,matching_node,tag)
+
                        print("Skipping",file,": graphics ignore TBI.")
                openfile.close()
+
                    pass
'''
+
                 elif file not in graphics_to_apply.keys():
 
+
                    if verbose:
def load_target_raws():
+
                        print("No graphics to apply to",file,". Copying from target source...")
    global properties
+
                    targetpath = shutil.copyfile(os.path.join(root,file),targetpath)
    if len(properties['target']) < 2:
+
                    if verbose:
        # TODO error handling
+
                        print(file,"copied.")
        print("Undefined target raw file. Please add a 'target' property in ",runconfig,".")
+
                 else:
    else:
+
                    if verbose:
        return _walk_rawfiles_into_tagnode_collection(properties['target'][1])
+
                        print("Merging graphics into",file,"...")
               
+
                    curr_dict = graphics_to_apply[file]
 +
                    curr_node = None
 +
                    targetfile = open(targetpath,'wt',encoding='cp437')
 +
                    sourcefile = open(os.path.join(root,file),'rt',encoding='cp437')
 +
                    linecount = 0
 +
                    for line in sourcefile:
 +
                        linecount = linecount + 1
 +
                        modified_line = escape_problematic_literals(line)
 +
                        additional = []
 +
                        for tag in tags(line):
 +
                            matching_node = None
 +
                           
 +
                            if tag in curr_dict.keys():
 +
                                matching_node = curr_dict[tag]
 +
                            elif curr_node is not None:
 +
                                matching_node = curr_node.find_match(tag)
 +
                           
 +
                            if matching_node is not None:
 +
                                curr_node = matching_node
 +
                                matching_node.pop_self()
 +
                                if matching_node.is_there_a_difference():
 +
                                    merged_tag = matching_node.get_merged()
 +
                                    if merged_tag is not None:
 +
                                        replacement = matching_node.get_merged()
 +
                                        if verbose:
 +
                                            print("Replacing",tag,"with",replacement,"at line",linecount,".")
 +
                                        modified_line = modified_line.replace(tag,replacement)
 +
                                    else:
 +
                                        if verbose:
 +
                                            print("Removing tag",tag,"at line",linecount,".")
 +
                                        to_remove = "[" + tag + "]"
 +
                                        modified_line = modified_line.replace(to_remove,"")
 +
                                    #modified_line = modified_line[:-1] + " (BAMM)\n"
 +
                                 additional.extend(matching_node.pop_addl())
 +
                       
 +
                        targetfile.writelines(modified_line)
 +
                        for tag_node in additional:
 +
                            linecount = linecount + 1
 +
                            if verbose:
 +
                                 print("Adding tag",tag_node._tag,"at line",linecount,".")
 +
                            line_to_write = "[" + tag_node._tag + "] (BAMM)\n"
 +
                            targetfile.writelines(line_to_write)
 +
 
 +
                    targetfile.flush()
 +
                    if verbose:
 +
                        print("Finished outputting",file,".")
 +
                    targetfile.close()
 +
                    sourcefile.close()
 +
        if verbose:
 +
            print("All files written.")
 +
 
 
def _walk_rawfiles_into_tagnode_collection(directory):
 
def _walk_rawfiles_into_tagnode_collection(directory):
 +
    global verbose
 
     node_collection = {}
 
     node_collection = {}
 
     for root, dirs, files in os.walk(directory):
 
     for root, dirs, files in os.walk(directory):
 
         for rawfile in files:
 
         for rawfile in files:
 +
            if '.txt' not in rawfile:
 +
                if verbose:
 +
                    print("Skipping file",rawfile,"...")
 +
                continue
 +
            if verbose:
 +
                print("Loading graphics tags from",rawfile,"...")
 
             global template_tree
 
             global template_tree
# curr_template_node keeps track of what format of tag we've most recently seen, and thus what's valid next
+
            # curr_template_node keeps track of what format of tag we've most recently seen, and thus what's valid next
 
             curr_template_node = template_tree
 
             curr_template_node = template_tree
# curr_real_node keeps track of the tag we stored that corresponds to the most local instance of curr_template_node.
+
            # curr_real_node keeps track of the tag we stored that corresponds to the most local instance of curr_template_node.
 
             curr_real_node = None
 
             curr_real_node = None
 
             tarpath = os.path.join(root, rawfile)
 
             tarpath = os.path.join(root, rawfile)
Line 253: Line 199:
 
             for line in openfile:
 
             for line in openfile:
 
                 for tag in tags(line):
 
                 for tag in tags(line):
                     matching_node = curr_template_node.find_template_match(tag)
+
                     matching_node = curr_template_node.find_match(tag)
 
                     if matching_node != None:
 
                     if matching_node != None:
 
                         curr_template_node = matching_node
 
                         curr_template_node = matching_node
                         if curr_real_node == None or matching_node in template_tree._children:
+
                         if curr_real_node == None or matching_node._tag in template_tree._children:
 
                             curr_real_node = TagNode(rawfile,matching_node,tag)
 
                             curr_real_node = TagNode(rawfile,matching_node,tag)
 
                         else:
 
                         else:
                             while curr_real_node != None and matching_node._tag_template not in curr_real_node._template._children:
+
                             while curr_real_node != None and matching_node._tag not in curr_real_node._template._children:
 
                                 curr_real_node = curr_real_node._parent
 
                                 curr_real_node = curr_real_node._parent
 
                             curr_real_node = TagNode(rawfile,matching_node,tag,curr_real_node)
 
                             curr_real_node = TagNode(rawfile,matching_node,tag,curr_real_node)
Line 266: Line 212:
 
                         if curr_real_node._parent == None:
 
                         if curr_real_node._parent == None:
 
                             node_collection[rawfile][tag] = curr_real_node
 
                             node_collection[rawfile][tag] = curr_real_node
                         
+
 
 
             openfile.close()
 
             openfile.close()
 +
            if verbose:
 +
                print("Finished processing",rawfile,".")
 
     return node_collection
 
     return node_collection
 +
 +
def bind_graphics_to_targets(graphics_nodes,targets_nodes):
 +
    global verbose
 +
    if verbose:
 +
        print("Binding graphics source tags to target tags...")
 +
    to_return = {}
 +
    for filename in targets_nodes.keys():
 +
        if filename in graphics_nodes.keys() and filename not in to_return.keys():
 +
            if verbose:
 +
                print("Binding tags for",filename,"...")
 +
            to_return[filename] = {}
 +
            for top_level_tag in targets_nodes[filename].keys():
 +
                if top_level_tag in graphics_nodes[filename] and top_level_tag not in to_return[filename].keys():
 +
                    to_return[filename][top_level_tag] = BoundNode(targets_nodes[filename][top_level_tag],graphics_nodes[filename][top_level_tag])
 +
                    #to_return[filename][top_level_tag].create_child_nodes()
 +
            if verbose:
 +
                print(filename,"tags bound.")
 +
    if verbose:
 +
        print("Tag binding complete.")
 +
    return to_return
  
 
def tags(line):
 
def tags(line):
Line 280: Line 248:
 
     return to_return
 
     return to_return
  
def escape_problematic_literals(rawline):
+
def path_compatible(full_path,allowed_paths):
     global ascii_codes
+
     # TODO use regexes
     if ascii_codes == None:
+
     to_return = True
         _load_ascii_conversions()
+
    full_path = full_path.replace("/",os.pathsep)
 +
    full_path_tokens = full_path.split(os.pathsep)
 +
    for possible_path in allowed_paths:
 +
        possibility = possible_path.split('/')
 +
        match_tuples = []
 +
        for ii in range(0,len(possibility)):
 +
            if possibility[ii] == '*':
 +
                continue
 +
            elif possibility[ii] not in full_path_tokens[ii:]:
 +
                break
 +
            else:
 +
                match_tuples.append((ii,full_path_tokens[ii:].index(possibility[ii])))
 +
 
 +
class TreeNode():
 +
    def __init__(self,parent=None):
 +
         self._parent = parent
 +
        self._children = {}
 +
        self._tag = None
 +
        #if parent != None:
 +
        #    parent.add_child(self)
 +
       
 +
    def add_child(self, child_node):
 +
        self._children[child_node._tag] = child_node
 +
   
 +
    def find_match(self, tag):
 +
        curr_node = self
 +
        matching_node = None
 +
        out_of_parents = False
 +
        while matching_node == None and not out_of_parents:
 +
            #matching_node = curr_node.get_template_match(tag)[0]
 +
            #if matching_node == None:
 +
            matching_node = curr_node.get_child(tag)
 +
            if curr_node._parent == None:
 +
                out_of_parents = True
 +
            else:
 +
                curr_node = curr_node._parent
 +
        return matching_node
 
      
 
      
     line = rawline.strip()
+
     def get_child(self, tag):
    # Replace literal key characters with number codes
+
         if tag in self._children.keys():
    # Literal colons are going to require some special processing, because of the following case:  GROWTH:'r':'x': etc. That's why we can't just use a blind replaceAll.
+
             return self._children[tag]
    bracketscount = 0        # If odd, we are inside a tag. If even, we are outside a tag.
+
         else:
    count = 0                # Where we are in the string
+
             return None
    quotescount = 0
 
    while count < len(line)-2:
 
        # Going from inside a tag to outside or vice versa
 
         if (bracketscount%2 == 0 and line[count] == "[") or (bracketscount%2 == 1 and line[count] == "]"):
 
            bracketscount += 1
 
        # We are inside a tag and we have discovered a ' character beginning a literal value, with another 2 characters down on the other side.
 
        elif quotescount%2 == 0 and bracketscount%2 == 1 and line[count:count+3] in ascii_codes.keys():
 
             # If the character could be a problem for later processing, replace it with its ascii code.
 
            #TODO rejigger this syntax
 
            line = line[:count] + ascii_codes[line[count:count+3]] + line[count+3:]
 
         elif line[count] == "'":
 
             quotescount += 1
 
        elif bracketscount%2 == 1 and line[count] == ':':
 
            quotescount = 0
 
        count += 1   
 
    # line has now had its literal "use this tile" versions of its special characters replaced with their numbers.
 
    return line
 
  
class TemplateNode:
+
class TemplateNode(TreeNode):
   
+
 
     #self._tag_template #string
+
     #self._tag #string
 
     #self._children    # dict of TemplateNodes
 
     #self._children    # dict of TemplateNodes
#self._childref # dict of lists of TemplateNodes where the key is the first token
+
    #self._childref       # dict of lists of TemplateNodes where the key is the first token
 
     #self._parent    # TemplateNode
 
     #self._parent    # TemplateNode
 
     #self._is_graphics_tag    # Boolean
 
     #self._is_graphics_tag    # Boolean
   
+
 
 
     #string does not contain the character '|'.
 
     #string does not contain the character '|'.
 
     def __init__(self, parent, string=""):
 
     def __init__(self, parent, string=""):
 +
        TreeNode.__init__(self,parent)
 
         self._is_graphics_tag = False
 
         self._is_graphics_tag = False
self._children = {}
+
        self._childref = {}
self._childref = {}
+
        self._tag = None
 
         global template_tree
 
         global template_tree
 
         if parent == None:
 
         if parent == None:
Line 331: Line 319:
 
                 self._parent = parent
 
                 self._parent = parent
  
             self._tag_template = string
+
             self._tag = string
           
+
 
 
             parent.add_child(self)
 
             parent.add_child(self)
       
+
 
 
     def add_child(self, node):
 
     def add_child(self, node):
         if node._tag_template in self._children.keys():
+
         if node._tag in self._children.keys():
             return self._children[node._tag_template]
+
             return self._children[node._tag]
 
         else:
 
         else:
             self._children[node._tag_template] = node
+
             self._children[node._tag] = node
first_token = node._tag_template.split(':')[0]
+
            first_token = node._tag.split(':')[0]
if first_token not in self._childref.keys():
+
            if first_token not in self._childref.keys():
self._childref[first_token] = []
+
                self._childref[first_token] = []
self._childref[first_token].append(node)
+
            self._childref[first_token].append(node)
return node
+
            return node
       
+
 
     def find_template_match(self, tag):
+
     def get_child(self, tag):
         curr_node = self
+
         global verbose
        matching_node = None
 
        out_of_parents = False
 
        while matching_node == None and not out_of_parents:
 
            matching_node = curr_node.get_child_matching(tag)
 
            if curr_node._parent == None:
 
                out_of_parents = True
 
            else:
 
                curr_node = curr_node._parent
 
        return matching_node
 
           
 
    def get_child_matching(self, tag):
 
 
         if tag in self._children.keys():
 
         if tag in self._children.keys():
 
             return self._children[tag]
 
             return self._children[tag]
 
         else:
 
         else:
 
             return_possibilities = []
 
             return_possibilities = []
first_token = tag.split(':')[0]
+
            first_token = tag.split(':')[0]
             for child in self._childref[first_token]:
+
             if first_token in self._childref:
                return_node = child.get_template_match(tag)
+
                for child in self._childref[first_token]:
                if return_node != None:
+
                    return_node = child.get_template_match(tag)
                    return_possibilities.append(child)
+
                    if return_node != None:
            if len(return_possibilities) == 1:
+
                        return_possibilities.append(child)
                return return_possibilities[0]
+
                if len(return_possibilities) == 1:
            elif len(return_possibilities) == 0:
+
                    return return_possibilities[0]
                return None
+
                elif len(return_possibilities) == 0:
            else:
+
                    return None
                # TODO error handling
 
                print("Found more than one matching child. You put it together wrong. Matching children are: ")
 
                for poss in return_possibilities:
 
                    print(poss)
 
                return return_possibilities[0]
 
           
 
    # This tells if a single tag matches a single tag; that is, it assumes we've got one element of the |-separated list
 
    def get_template_match(self, tag_to_compare):
 
        template_token_bag = []
 
        template_token_bag.append(self._tag_template.split(':'))
 
        candidate_tokens = tag_to_compare.split(':')
 
       
 
        ii = 0
 
        while ii < len(candidate_tokens) and len(template_token_bag) > 0:
 
            good_to_go = False
 
            for var in template_token_bag:
 
                if ii >= len(var):
 
                    template_token_bag.remove(var)
 
                elif '&' == var[ii] or '?' == var[ii] or '$' == var[ii] or var[ii] == candidate_tokens[ii]:
 
                    # This case is an auto-pass
 
                    good_to_go = True
 
 
                 else:
 
                 else:
                     if '&' in var[ii] or '?' in var[ii] or '$' in var[ii]:
+
                     # TODO error handling
                        varii_type = var[ii][0]
+
                    if verbose:
                        varii_range = var[ii][2:var[ii].index(')')].split(',')
+
                         print("Found more than one matching child. Matching children are:")
                        # If len(varii_range) == 1 then we have a range of format (x,), indicating any number of :'s
+
                        for poss in return_possibilities:
                        if len(varii_range[1]) == 0:
+
                            print(poss)
                            varii_range[1] = len(candidate_tokens)-len(var) + 1
+
                     return return_possibilities[0]
                         # For every possible length (the +1 is because range is exclusive-end and my notation is inclusive-end)
 
                        for jj in range(int(varii_range[0]),int(varii_range[1])+1):
 
                            # Make a copy of var
 
                            new_var = var[:]
 
                            # Remove the range item
 
                            del new_var[ii]
 
                            # Replace it with (one of the possible lengths) times the multiplied symbol
 
                            # If jj is 0 the range item is just removed
 
                            for kk in range(0,jj):
 
                                new_var.insert(ii,varii_type)
 
                            # Place the new variant in the token bag for evaluation
 
                            template_token_bag.append(new_var)
 
                    # No counting, there is a new template_token_bag[ii]
 
                    template_token_bag.remove(var)
 
            if good_to_go:
 
                ii += 1
 
for tag in template_token_bag:
 
if len(tag) != len(candidate_tokens):
 
template_token_bag.remove(tag)
 
        if ii < len(candidate_tokens):
 
            return None
 
        elif len(template_token_bag) == 1:
 
            return template_token_bag
 
        else:
 
            # TODO error handling
 
# TODO plant GROWTH_PRINTs are throwing this when they end and there are templates of size n and n+1, fix that up plz
 
            print("Button, there's a problem, more than one template matched.\nTag: ",tag_to_compare,"Matches:")
 
            for template in template_token_bag:
 
                print(template)
 
            # Technically this does in fact have a matching template
 
            return template_token_bag
 
       
 
    def how_many_generations(self):
 
        temp_node = self
 
        count = -1
 
        global template_tree
 
        while temp_node != template_tree:
 
            temp_node = temp_node._parent
 
            count = count + 1
 
        return count
 
   
 
    def resolve(self, target_tag, graphics_tag):
 
        target_tag_bag = self.get_template_match(target_tag)
 
        graphics_tag_bag = self.get_template_match(graphics_tag)
 
        shared_tag_bag = []
 
        for tarlist in target_tag_bag:
 
            for graphlist in graphics_tag_bag:
 
                if len(tarlist) == len(graphlist):
 
                    found_flaw = False
 
                    ii = 0
 
                    while ii < len(graphlist) and not found_flaw:
 
                        found_flaw = graphlist[ii] != tarlist[ii]
 
                     if not found_flaw:
 
                        shared_tag_bag.append(graphlist[ii])
 
        if len(shared_tag_bag) == 0:
 
            return None
 
        elif len(shared_tag_bag) > 1:
 
            print("Hey Button there's too many shared tag_bags in TemplateNode.resolve.")
 
       
 
        # Just gonna go with the first valid shared tag bag until I see a multi-shared-tag_bag so I have a better idea of how to deal with it
 
        template_tag_bag = shared_tag_bag[0]
 
        target_tag_bag = target_tag.split(':')
 
        graphics_tag_bag = graphics_tag.split(':')
 
       
 
        irreconcilable = False
 
        ii = 0
 
        while ii < len(template_tag_bag) and not irreconcilable:
 
            if template_tag_bag[ii] == '$':
 
                irreconcilable = target_tag_bag[ii] != graphics_tag_bag[ii]
 
            elif template_tag_bag[ii] == '&':
 
                continue
 
            elif template_tag_bag[ii] == '?':
 
                target_tag_bag[ii] = graphics_tag_bag[ii]
 
 
             else:
 
             else:
                 irreconcilable = target_tag_bag[ii] != graphics_tag_bag[ii] or target_tag_bag != template_tag_bag[ii]
+
                 return None
        if not irreconcilable:
 
            return ":".join(target_tag_bag)
 
        else:
 
            return None
 
   
 
class TagNode:
 
   
 
    def __init__(self,filename,template,tag,parent=None):
 
        self._tag = tag
 
        self._filename = filename
 
        self._parent = parent
 
        self._template = template
 
        self._children = {}
 
       
 
        global graphics_tokens_by_file
 
        if parent != None:
 
            parent._children[tag] = self
 
           
 
'''    def recursively_apply_graphics(self,graphics_tag_node):
 
        continue_ok = True
 
        if self.compatible_with(graphics_tag_node):
 
            for child in self._children.keys():
 
                continue_ok = continue_ok and self._children[child].recursively_apply_graphics(graphics_tag_node._children[child])
 
            if continue_ok:
 
                continue_ok = self.graphicsify(graphics_tag_node)
 
        return continue_ok'''
 
   
 
    def compatible_with(self, graphics_tag_node):
 
        return self._template.resolve(self._tag,graphics_tag_node._tag) != None
 
   
 
'''    def graphicsify(self, graphics_tag_node):
 
        upcoming_tag = self._template.resolve(self._tag,graphics_tag_node._tag)
 
        if upcoming_tag != None:
 
            self._tag = upcoming_tag
 
            return True
 
        else:
 
            return False
 
           
 
    def abbrev_write(self,tabnums=0,output_stream=None):
 
        if output_stream == None:
 
            outfile = self._filename.open('w',encoding='cp437')
 
        else:
 
            outfile = output_stream
 
        print(tabnums*'\t','[',self._tag,']\n',outfile)
 
        for child in self._children.keys():
 
            self._children[child].abbrev_write(tabnums+1,outfile)
 
        if output_stream == None:
 
            outfile.close()'''</pre>
 
 
 
==== Minor breakthrough ====
 
  
<pre>graphics_nodes = _walk_filenames_into_tagnode_collection()
+
class TagNode(TreeNode):
target_nodes = _walk_filenames_into_tagnode_collection()
 
 
 
 
 
 
 
class TagNode:
 
  
 
     def __init__(self,filename,template,tag,parent=None):
 
     def __init__(self,filename,template,tag,parent=None):
 +
        TreeNode.__init__(self, parent)
 
         self._tag = tag
 
         self._tag = tag
 
         self._filename = filename
 
         self._filename = filename
        self._parent = parent
 
 
         self._template = template
 
         self._template = template
         self._lit_children = {}
+
         self._children = {}
self._pat_children = {}
+
        self._pat_children = {}
self._pattern = self.get_pattern()
+
        self._pattern = None
 +
        self._pattern = self.get_pattern()
  
 
         global graphics_tokens_by_file
 
         global graphics_tokens_by_file
Line 547: Line 376:
 
             parent.add_child(self)
 
             parent.add_child(self)
  
def add_child(self, child_tag_node):
+
    def add_child(self, child_tag_node):
self._lit_children[child_tag_node._tag] = child_tag_node
+
        self._children[child_tag_node._tag] = child_tag_node
self._pat_children[child_tag_node.get_pattern()] = child_tag_node
+
        self._pat_children[child_tag_node.get_pattern()] = child_tag_node
  
def apply_graphics(self, graphics_node):
+
    def apply_graphics(self, graphics_node):
tags = self._tag.split(':')
+
        global verbose
graphics = graphics_node._tag.split(':')
+
        if graphics_node == None:
tag_template = self._template.get_template_match(self._tag)[0]
+
            return None
merged = []
+
        tags = self._tag.split(':')
graphics_template = self._template.get_template_match(graphics_node._tag)[0]
+
        graphics = graphics_node._tag.split(':')
# print("Graphics cannot be applied from ",graphics_node._tag," onto ",self._tag)
+
        tag_template = self._template.get_template_match(self._tag)[0]
 +
        merged = []
 +
        graphics_template = self._template.get_template_match(graphics_node._tag)[0]
 +
        #   print("Graphics cannot be applied from",graphics_node._tag,"onto",self._tag)
  
for ii in range(0,len(tag_template)):
+
        for ii in range(0,len(tag_template)):
if tag_template[ii] != graphics_template[ii]:
+
            if tag_template[ii] != graphics_template[ii]:
print("Graphics cannot be applied from ",graphics_node._tag," onto ",self._tag," because their templates do not match.")
+
                if verbose:
elif tag_template[ii] != '&' and tag_template[ii] != '?':
+
                    print("Graphics cannot be applied from",graphics_node._tag,"onto",self._tag,"because their templates do not match.")
if tags[ii] != graphics[ii]:
+
            elif tag_template[ii] != '&' and tag_template[ii] != '?':
print("Tags are not compatible because token ",ii," does not match. Target: ",tags[ii]," Graphics: ",graphics[ii])
+
                if tags[ii] != graphics[ii]:
else:
+
                    if verbose:
merged.append(tags[ii])
+
                        print("Tags are not compatible because token",ii,"does not match. Target:",tags[ii]," Graphics:",graphics[ii])
elif tag_template[ii] == '&':
+
                else:
merged.append(tags[ii])
+
                    merged.append(tags[ii])
elif tag_template[ii] == '?':
+
            elif tag_template[ii] == '&':
merged.append(graphics[ii])
+
                merged.append(tags[ii])
else:
+
            elif tag_template[ii] == '?':
print("This block should never be reached. Big problem in TagNode.apply_graphics.")
+
                merged.append(graphics[ii])
 +
            else:
 +
                if verbose:
 +
                    print("This block should never be reached. Big problem in TagNode.apply_graphics.")
  
return ":".join(merged)
+
        return ":".join(merged)
  
# Too much work
+
    # Too much work
 
     # def compatible_with(self, graphics_tag_node):
 
     # def compatible_with(self, graphics_tag_node):
 
     #    return self._template.resolve(self._tag,graphics_tag_node._tag) != None
 
     #    return self._template.resolve(self._tag,graphics_tag_node._tag) != None
  
def get_pattern(self):
+
    def get_pattern(self):
if self._pattern == None:
+
        global verbose
to_return = self._tag.split(':')
+
        if self._pattern == None:
tag_tokens = self._tag.split(':')
+
            to_return = self._tag.split(':')
template_possibilities = self._template.get_template_match(self._tag)
+
            tag_tokens = self._tag.split(':')
if len(template_possibilities) != 1:
+
            template_possibilities = self._template.get_template_match(self._tag)
print("Tag ",self._tag," has ",len(template_possibilities)," possible configurations: ")
+
            if len(template_possibilities) != 1:
for config in template_possibilities:
+
                if verbose:
print(config,', ')
+
                    print("Tag",self._tag,"has",len(template_possibilities),"possible configurations:")
else:
+
                    for config in template_possibilities:
for ii in range(0,len(tag_tokens)):
+
                        print(config,', ')
if template_possibilities[0][ii] in [tag_tokens[ii],'$']:
+
            else:
to_return[ii] = tag_tokens[ii]
+
                for ii in range(0,len(tag_tokens)):
elif template_possibilities[0][ii] in ['?','&']:
+
                    if template_possibilities[0][ii] in [tag_tokens[ii],'$']:
to_return[ii] = template_possibilities[0][ii]
+
                        to_return[ii] = tag_tokens[ii]
else:
+
                    elif template_possibilities[0][ii] in ['?','&']:
print("Tag does not match its own template!! Tag: ",self._tag,"; Template: ",self._template._template_tag)
+
                        to_return[ii] = template_possibilities[0][ii]
self._pattern = ":".join(to_return)
+
                    elif verbose:
return self._pattern
+
                        print("Tag does not match its own template!! Tag:",self._tag,"; Template:",self._template._template_tag)
 +
            self._pattern = ":".join(to_return)
 +
        return self._pattern
  
def aligns_with(self,other_tag):
+
    def aligns_with(self,other_tag):
return self._template == other_tag._template and self.get_pattern() == other_tag.get_pattern()
+
        return self._template == other_tag._template and self.get_pattern() == other_tag.get_pattern()
 +
   
 +
    def is_graphics_tag(self):
 +
        return self._template._is_graphics_tag
  
class BoundNode:
+
class BoundNode(TreeNode):
def __init__(self,target_node,graphics_node,parent=None):
+
    def __init__(self,target_node,graphics_node,parent=None):
self._parent = parent
+
        TreeNode.__init__(self, parent)
self._children = {}
+
        self._tag = target_node._tag
self._popped_children = {}
+
        self._popped_children = {}
self._additional = []
+
        self._additional = []
self._target_node = target_node
+
        self._are_addl_popped = False
self._graphics_node = graphics_node
+
        self._target_node = target_node
if parent != None:
+
        self._graphics_node = graphics_node
parent.add_child(self)
+
        if parent != None:
#self._popped = False
+
            parent.add_child(self)
 +
        else:
 +
            self.create_child_nodes()
  
def add_child(self, child_node):
+
    def add_child(self, child_node):
self._children[child_node._target_node._tag]=child_node
+
        self._children[child_node._target_node._tag]=child_node
self._popped_children[child_node._target_node._tag]=False
+
        self._popped_children[child_node._target_node._tag]=False
  
def create_child_nodes(self):
+
    def create_child_nodes(self):
# Children with pattern keys in both target & graphics
+
        # Children with pattern keys in both target & graphics
for shared_key in self._target_node._pat_children.keys().intersection(self._graphics_node._pat_children.keys()):
+
        for shared_key in set(self._target_node._pat_children.keys()).intersection(set(self._graphics_node._pat_children.keys())):
new_node = BoundNode(self._target_node._pat_children[shared_key],self._graphics_node._pat_children[shared_key],self)
+
            new_node = BoundNode(self._target_node._pat_children[shared_key],self._graphics_node._pat_children[shared_key],self)
self.add_child(new_node)
+
            self.add_child(new_node)
new_node.create_child_nodes()
+
            new_node.create_child_nodes()
# Children with pattern keys in target but not in graphics can be ignored
+
        # Children with pattern keys in target but not in graphics
 +
        for target_key in set(self._target_node._pat_children.keys()) - set(self._graphics_node._pat_children.keys()):
 +
            if self._target_node._pat_children[target_key].is_graphics_tag() and not ('&' in self._target_node._template._tag or '$' in self._target_node._template._tag):
 +
                self.add_child(BoundNode(self._target_node._pat_children[target_key],None,self))
 +
        # Children with pattern keys in graphics but not in target
 +
        for graphics_key in set(self._graphics_node._pat_children.keys()) - set(self._target_node._pat_children.keys()):
 +
            graphics_in_question = self._graphics_node._pat_children[graphics_key]
 +
            if graphics_in_question.is_graphics_tag():
 +
                self._additional.append(graphics_in_question)
 +
        # End
  
# Children with pattern keys in graphics but not in target
+
    def are_all_children_popped(self):
for graphics_key in self._graphics_node._pat_children.keys() - self._target_node._pat_children.keys():
+
        to_return = True
graphics_in_question = self._graphics_node._pat_children[graphics_key]
+
        for child in self._popped_children.keys():
if graphics_in_question.is_graphics_tag():
+
            to_return &= self._popped_children[child]
self._additional.append(graphics_in_question)
+
        return to_return
# End
+
       
 +
    def pop_addl(self):
 +
        to_return = []
 +
        # If this hasn't had its additionals popped and is ready to pop
 +
        if (not self._are_addl_popped):
 +
            self._are_addl_popped = True
 +
            to_return.extend(self._additional)
 +
        if self._parent != None:
 +
            to_return.extend(self._parent.pop_addl())
 +
        return to_return
  
def are_all_children_popped(self):
+
    def get_merged(self):
to_return = True
+
        return self._target_node.apply_graphics(self._graphics_node)
for child in self._popped_children.keys():
 
to_return &= self._popped_children.keys()[child]
 
return to_return
 
  
def get_merge(self):
+
    def pop_child(self, target_tag):
return self._target_node.apply_graphics(self._graphics_node)
+
        if target_tag not in self._children.keys():
 +
            return self
 +
        else:
 +
            if self._popped_children[target_tag] and verbose:
 +
                print("Popping tag that has already been popped:",target_tag,"child of",self._tag)
  
def pop_child(self, target_tag):
+
            self._popped_children[target_tag] = True
if target_tag not in self._children.keys():
+
            return self._children[target_tag]
return self
+
       
else:
+
    def pop_self(self):
if self._popped_children[target_tag]:
+
        if self._parent != None:
print("Popping tag that has already been popped: ",target_tag)
+
            also_self = self._parent.pop_child(self._tag)
 
+
            if also_self != self and verbose:
self._popped_children[target_tag] = True
+
                print("Big problem: Bound Node with _tag",self._tag,"is not its parent's ._children[",self._tag,"]")
return self._children[target_tag]</pre>
+
        return self
 +
   
 +
    def is_there_a_difference(self):
 +
        if self._graphics_node == None:
 +
            return True
 +
        elif self._target_node._tag == self._graphics_node._tag:
 +
            return False
 +
        else:
 +
            return True
 +
</pre>

Revision as of 13:31, 21 May 2015

This is a directory page for my Modest Mod work.

I feel a little bad for clogging up the wiki with this stuff, but sometimes I get bored and I'm not at my home computer.

Plans

  • Moving the no-fear-of-commitment orientation mods out into a pair of creature variations, in c_variation_modest. May make a Module applying them to sapients as well. Eternal Lovers are kind of frustrating.
  • Could probably just make the plant growths that I currently have wrapping tree nuts, edible or pressable or whatever in their own right, and never have to deal with the seeds. OTOH, that might mess up grinding them to paste/pressing them? And would still need the hulling reaction for plantable legumes.
  • Could change implementation of all plants, including those brewed 'whole', into growths. Then would be able to share reactions better, and do seed/no seed on a purely FAKESEED basis.
    • Probably not worth it in vanilla, but maybe fore plant features, since we'll be needing to mill "whole plants" (e.g. cassava, whip vines) and also to mill seedheads.
  • Need to move Plump Helmets back to a usable plant instead of a fruit. Save it as fruit for the Basic Adventurer's Pack.
  • Need to look at the expanded creature variation replacement tokens in preparation for the vampire anti-sobriety module.

Accelerated

  • Need to look at the old Accelerated Modest Mod and see how much of it is applicable to the current version.
  • Need to see about thinning down plant materials, like a lot.
  • Need to remove useless plant growths, like cherry blossoms etc. Leaves? Or would that be too ugly?
  • Could probably reduce grass to a single species? Or would that make pandas shitty?

Plant Features

  • Could probably make grain use the fruit(like?) reactions for brewing, milling, etc.
  • Kahlua & Chajun & possibly the plant products that are used to make them could give a temporary [NOSLEEP] tag? Followed by a 'crash' of a drowsiness syndrome?
  • Jellies? Preserves? Juices?
  • More variation in material values

Basic Adventurer's Pack

This needs to be a basic pack. Don't get cocky.

Must include

  • Plump helmet to fruit
  • Backpacks, bags, quivers, waterskins.
    • Tanning
      • Stone knife
    • Spinning & weaving
      • Spindle
  • Bone ammo
    • Stone chisel
    • Arrow bamboo?
  • Trophy jewelry/crafts for trading

Might want to include

  • Leather armor.
  • Bone & shell armor.
  • Bone bows, crossbows, blowguns
    • Bamboo?
  • Plant fiber thread. Rope reeds, pig tail stalks, others?
    • For the merge with Plant Features, abaca/banana/pineapple leaves, cotton bolls, etc. A lot of the plant fabric stuff is Plant Features-necessary.

Probably going too far

  • Enable a whole bunch of sapients playable as outsiders. Not just the ones in the gear-already-exists size range.
    • Hahaha, fairies as a playable outsider race |D That would be silly
  • Cooking. Just biscuits? Or full-on recipes?
    • Flour? Oil? Good use for mortar & pestle...
    • Maybe a balanced meal would require a veg, a protein, & a sugar/starch, & would provide minor (temporary) attribute boosts?
  • Self-buff "cheats"?

Raws

Some related LNP pseudocode

def _load_ascii_conversions():
    global ascii_codes
    global verbose
    if verbose:
        print("Loading ASCII conversions...")
    if ascii_codes == None:
        ascii_codes = {}
    if properties['ascii'][1] == None:
        print("Undefined ascii conversion file. Please add an 'ascii' property in",runconfig,".")
    else:
        for line in open(properties['ascii'][1]):
            real_line = line.strip()
            if len(real_line) == 0:
                continue
            elif '=' not in real_line:
                print('ASCII conversion file contains the improperly-formatted line ',real_line,'.')
            else:
                point = real_line.rindex('=')
                if real_line[:point] in ascii_codes.keys():
                    print('Duplicate entry for ascii replacement ',real_line[:point])
                else:
                    ascii_codes[real_line[:point]] = real_line[point+1:]
        if verbose:
            print("ASCII conversions loaded.")

def write_modified_raws(graphics_to_apply):
    if len(properties['output']) < 2:
        print("Undefined output directory. Please add an 'output' property in",runconfig,".")
    else:
        if verbose:
            print("Writing modified raws...")
        for root, dirs, files in os.walk(properties['target'][1]):
            for dir in dirs:
                targetdir = os.path.join(root,dir)
                targetdir = properties['output'][1] + targetdir[len(properties['target'][1]):]
                if not os.path.exists(targetdir):
                    if verbose:
                        print("Creating output directory",dir)
                    os.mkdir(targetdir)
            for file in files:
                targetpath = os.path.join(root,file)
                targetpath = properties['output'][1] + targetpath[len(properties['target'][1]):]
                if path_compatible(targetpath,properties['graphics_overwrite'][1:]):
                    if verbose:
                        print("Skipping",file,": graphics overwrite TBI.")
                    pass
                elif path_compatible(targetpath,properties['graphics_ignore'][1:]):
                    if verbose:
                        print("Skipping",file,": graphics ignore TBI.")
                    pass
                elif file not in graphics_to_apply.keys():
                    if verbose:
                        print("No graphics to apply to",file,". Copying from target source...")
                    targetpath = shutil.copyfile(os.path.join(root,file),targetpath)
                    if verbose:
                        print(file,"copied.")
                else:
                    if verbose:
                        print("Merging graphics into",file,"...")
                    curr_dict = graphics_to_apply[file]
                    curr_node = None
                    targetfile = open(targetpath,'wt',encoding='cp437')
                    sourcefile = open(os.path.join(root,file),'rt',encoding='cp437')
                    linecount = 0
                    for line in sourcefile:
                        linecount = linecount + 1
                        modified_line = escape_problematic_literals(line)
                        additional = []
                        for tag in tags(line):
                            matching_node = None
                            
                            if tag in curr_dict.keys():
                                matching_node = curr_dict[tag]
                            elif curr_node is not None:
                                matching_node = curr_node.find_match(tag)
                            
                            if matching_node is not None:
                                curr_node = matching_node
                                matching_node.pop_self()
                                if matching_node.is_there_a_difference():
                                    merged_tag = matching_node.get_merged()
                                    if merged_tag is not None:
                                        replacement = matching_node.get_merged()
                                        if verbose:
                                            print("Replacing",tag,"with",replacement,"at line",linecount,".")
                                        modified_line = modified_line.replace(tag,replacement)
                                    else:
                                        if verbose:
                                            print("Removing tag",tag,"at line",linecount,".")
                                        to_remove = "[" + tag + "]"
                                        modified_line = modified_line.replace(to_remove,"")
                                    #modified_line = modified_line[:-1] + " (BAMM)\n"
                                additional.extend(matching_node.pop_addl())
                        
                        targetfile.writelines(modified_line)
                        for tag_node in additional:
                            linecount = linecount + 1
                            if verbose:
                                print("Adding tag",tag_node._tag,"at line",linecount,".")
                            line_to_write = "[" + tag_node._tag + "] (BAMM)\n"
                            targetfile.writelines(line_to_write)

                    targetfile.flush()
                    if verbose:
                        print("Finished outputting",file,".")
                    targetfile.close()
                    sourcefile.close()
        if verbose:
            print("All files written.")

def _walk_rawfiles_into_tagnode_collection(directory):
    global verbose
    node_collection = {}
    for root, dirs, files in os.walk(directory):
        for rawfile in files:
            if '.txt' not in rawfile:
                if verbose:
                    print("Skipping file",rawfile,"...")
                continue
            if verbose:
                print("Loading graphics tags from",rawfile,"...")
            global template_tree
            # curr_template_node keeps track of what format of tag we've most recently seen, and thus what's valid next
            curr_template_node = template_tree
            # curr_real_node keeps track of the tag we stored that corresponds to the most local instance of curr_template_node.
            curr_real_node = None
            tarpath = os.path.join(root, rawfile)
            openfile = open(tarpath,encoding='cp437')
            for line in openfile:
                for tag in tags(line):
                    matching_node = curr_template_node.find_match(tag)
                    if matching_node != None:
                        curr_template_node = matching_node
                        if curr_real_node == None or matching_node._tag in template_tree._children:
                            curr_real_node = TagNode(rawfile,matching_node,tag)
                        else:
                            while curr_real_node != None and matching_node._tag not in curr_real_node._template._children:
                                curr_real_node = curr_real_node._parent
                            curr_real_node = TagNode(rawfile,matching_node,tag,curr_real_node)
                        if rawfile not in node_collection:
                            node_collection[rawfile] = { }
                        if curr_real_node._parent == None:
                            node_collection[rawfile][tag] = curr_real_node

            openfile.close()
            if verbose:
                print("Finished processing",rawfile,".")
    return node_collection

def bind_graphics_to_targets(graphics_nodes,targets_nodes):
    global verbose
    if verbose:
        print("Binding graphics source tags to target tags...")
    to_return = {}
    for filename in targets_nodes.keys():
        if filename in graphics_nodes.keys() and filename not in to_return.keys():
            if verbose:
                print("Binding tags for",filename,"...")
            to_return[filename] = {} 
            for top_level_tag in targets_nodes[filename].keys():
                if top_level_tag in graphics_nodes[filename] and top_level_tag not in to_return[filename].keys():
                    to_return[filename][top_level_tag] = BoundNode(targets_nodes[filename][top_level_tag],graphics_nodes[filename][top_level_tag])
                    #to_return[filename][top_level_tag].create_child_nodes()
            if verbose:
                print(filename,"tags bound.")
    if verbose:
        print("Tag binding complete.")
    return to_return

def tags(line):
    processed_line = escape_problematic_literals(line)
    to_return = []        # list of strings, a la split()
    while '[' in processed_line and ']' in processed_line and processed_line.index('[') < processed_line.rindex(']'):
        if processed_line.index(']') < processed_line.index('['):
            processed_line = processed_line[processed_line.index('['):]
        to_return.append(processed_line[processed_line.index('[')+1:processed_line.index(']')])
        processed_line = processed_line[processed_line.index(']')+1:]
    return to_return

def path_compatible(full_path,allowed_paths):
    # TODO use regexes
    to_return = True
    full_path = full_path.replace("/",os.pathsep)
    full_path_tokens = full_path.split(os.pathsep)
    for possible_path in allowed_paths:
        possibility = possible_path.split('/')
        match_tuples = []
        for ii in range(0,len(possibility)):
            if possibility[ii] == '*':
                continue
            elif possibility[ii] not in full_path_tokens[ii:]:
                break
            else:
                match_tuples.append((ii,full_path_tokens[ii:].index(possibility[ii])))

class TreeNode():
    def __init__(self,parent=None):
        self._parent = parent
        self._children = {}
        self._tag = None
        #if parent != None:
        #    parent.add_child(self)
        
    def add_child(self, child_node):
        self._children[child_node._tag] = child_node
    
    def find_match(self, tag):
        curr_node = self
        matching_node = None
        out_of_parents = False
        while matching_node == None and not out_of_parents:
            #matching_node = curr_node.get_template_match(tag)[0]
            #if matching_node == None:
            matching_node = curr_node.get_child(tag)
            if curr_node._parent == None:
                out_of_parents = True
            else:
                curr_node = curr_node._parent
        return matching_node
    
    def get_child(self, tag):
        if tag in self._children.keys():
            return self._children[tag]
        else:
            return None

class TemplateNode(TreeNode):

    #self._tag #string
    #self._children    # dict of TemplateNodes
    #self._childref        # dict of lists of TemplateNodes where the key is the first token
    #self._parent    # TemplateNode
    #self._is_graphics_tag    # Boolean

    #string does not contain the character '|'.
    def __init__(self, parent, string=""):
        TreeNode.__init__(self,parent)
        self._is_graphics_tag = False
        self._childref = {}
        self._tag = None
        global template_tree
        if parent == None:
            self._parent = None
            template_tree = self
        else:
            if template_tree == None:
                self._parent = TemplateNode(None, "")
            else:
                self._parent = parent

            self._tag = string

            parent.add_child(self)

    def add_child(self, node):
        if node._tag in self._children.keys():
            return self._children[node._tag]
        else:
            self._children[node._tag] = node
            first_token = node._tag.split(':')[0]
            if first_token not in self._childref.keys():
                self._childref[first_token] = []
            self._childref[first_token].append(node)
            return node

    def get_child(self, tag):
        global verbose
        if tag in self._children.keys():
            return self._children[tag]
        else:
            return_possibilities = []
            first_token = tag.split(':')[0]
            if first_token in self._childref:
                for child in self._childref[first_token]:
                    return_node = child.get_template_match(tag)
                    if return_node != None:
                        return_possibilities.append(child)
                if len(return_possibilities) == 1:
                    return return_possibilities[0]
                elif len(return_possibilities) == 0:
                    return None
                else:
                    # TODO error handling
                    if verbose:
                        print("Found more than one matching child. Matching children are:")
                        for poss in return_possibilities:
                            print(poss)
                    return return_possibilities[0]
            else:
                return None

class TagNode(TreeNode):

    def __init__(self,filename,template,tag,parent=None):
        TreeNode.__init__(self, parent)
        self._tag = tag
        self._filename = filename
        self._template = template
        self._children = {}
        self._pat_children = {}
        self._pattern = None
        self._pattern = self.get_pattern()

        global graphics_tokens_by_file
        if parent != None:
            parent.add_child(self)

    def add_child(self, child_tag_node):
        self._children[child_tag_node._tag] = child_tag_node
        self._pat_children[child_tag_node.get_pattern()] = child_tag_node

    def apply_graphics(self, graphics_node):
        global verbose
        if graphics_node == None:
            return None
        tags = self._tag.split(':')
        graphics = graphics_node._tag.split(':')
        tag_template = self._template.get_template_match(self._tag)[0]
        merged = []
        graphics_template = self._template.get_template_match(graphics_node._tag)[0]
        #    print("Graphics cannot be applied from",graphics_node._tag,"onto",self._tag)

        for ii in range(0,len(tag_template)):
            if tag_template[ii] != graphics_template[ii]:
                if verbose:
                    print("Graphics cannot be applied from",graphics_node._tag,"onto",self._tag,"because their templates do not match.")
            elif tag_template[ii] != '&' and tag_template[ii] != '?':
                if tags[ii] != graphics[ii]:
                    if verbose:
                        print("Tags are not compatible because token",ii,"does not match. Target:",tags[ii]," Graphics:",graphics[ii])
                else:
                    merged.append(tags[ii])
            elif tag_template[ii] == '&':
                merged.append(tags[ii])
            elif tag_template[ii] == '?':
                merged.append(graphics[ii])
            else:
                if verbose:
                    print("This block should never be reached. Big problem in TagNode.apply_graphics.")

        return ":".join(merged)

    # Too much work
    # def compatible_with(self, graphics_tag_node):
    #    return self._template.resolve(self._tag,graphics_tag_node._tag) != None

    def get_pattern(self):
        global verbose
        if self._pattern == None:
            to_return = self._tag.split(':')
            tag_tokens = self._tag.split(':')
            template_possibilities = self._template.get_template_match(self._tag)
            if len(template_possibilities) != 1:
                if verbose:
                    print("Tag",self._tag,"has",len(template_possibilities),"possible configurations:")
                    for config in template_possibilities:
                        print(config,', ')
            else:
                for ii in range(0,len(tag_tokens)):
                    if template_possibilities[0][ii] in [tag_tokens[ii],'$']:
                        to_return[ii] = tag_tokens[ii]
                    elif template_possibilities[0][ii] in ['?','&']:
                        to_return[ii] = template_possibilities[0][ii]
                    elif verbose:
                        print("Tag does not match its own template!! Tag:",self._tag,"; Template:",self._template._template_tag)
            self._pattern = ":".join(to_return)
        return self._pattern

    def aligns_with(self,other_tag):
        return self._template == other_tag._template and self.get_pattern() == other_tag.get_pattern()
    
    def is_graphics_tag(self):
        return self._template._is_graphics_tag

class BoundNode(TreeNode):
    def __init__(self,target_node,graphics_node,parent=None):
        TreeNode.__init__(self, parent)
        self._tag = target_node._tag
        self._popped_children = {}
        self._additional = []
        self._are_addl_popped = False
        self._target_node = target_node
        self._graphics_node = graphics_node
        if parent != None:
            parent.add_child(self)
        else:
            self.create_child_nodes()

    def add_child(self, child_node):
        self._children[child_node._target_node._tag]=child_node
        self._popped_children[child_node._target_node._tag]=False

    def create_child_nodes(self):
        # Children with pattern keys in both target & graphics
        for shared_key in set(self._target_node._pat_children.keys()).intersection(set(self._graphics_node._pat_children.keys())):
            new_node = BoundNode(self._target_node._pat_children[shared_key],self._graphics_node._pat_children[shared_key],self)
            self.add_child(new_node)
            new_node.create_child_nodes()
        # Children with pattern keys in target but not in graphics
        for target_key in set(self._target_node._pat_children.keys()) - set(self._graphics_node._pat_children.keys()):
            if self._target_node._pat_children[target_key].is_graphics_tag() and not ('&' in self._target_node._template._tag or '$' in self._target_node._template._tag):
                self.add_child(BoundNode(self._target_node._pat_children[target_key],None,self))
        # Children with pattern keys in graphics but not in target
        for graphics_key in set(self._graphics_node._pat_children.keys()) - set(self._target_node._pat_children.keys()):
            graphics_in_question = self._graphics_node._pat_children[graphics_key]
            if graphics_in_question.is_graphics_tag():
                self._additional.append(graphics_in_question)
        # End

    def are_all_children_popped(self):
        to_return = True
        for child in self._popped_children.keys():
            to_return &= self._popped_children[child]
        return to_return
        
    def pop_addl(self):
        to_return = []
        # If this hasn't had its additionals popped and is ready to pop
        if (not self._are_addl_popped):
            self._are_addl_popped = True
            to_return.extend(self._additional)
        if self._parent != None:
            to_return.extend(self._parent.pop_addl())
        return to_return

    def get_merged(self):
        return self._target_node.apply_graphics(self._graphics_node)

    def pop_child(self, target_tag):
        if target_tag not in self._children.keys():
            return self
        else:
            if self._popped_children[target_tag] and verbose:
                print("Popping tag that has already been popped:",target_tag,"child of",self._tag)

            self._popped_children[target_tag] = True
            return self._children[target_tag]
        
    def pop_self(self):
        if self._parent != None:
            also_self = self._parent.pop_child(self._tag)
            if also_self != self and verbose:
                print("Big problem: Bound Node with _tag",self._tag,"is not its parent's ._children[",self._tag,"]")
        return self
    
    def is_there_a_difference(self):
        if self._graphics_node == None:
            return True
        elif self._target_node._tag == self._graphics_node._tag:
            return False
        else:
            return True