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
(coordinating)
Line 74: Line 74:
 
runconfig = 'resources/run.config'
 
runconfig = 'resources/run.config'
 
ascii_codes = None
 
ascii_codes = None
+
#graphics_tokens_by_file = {}
 +
 
 
properties = {
 
properties = {
 
               'target':['dir'],
 
               'target':['dir'],
Line 83: Line 84:
 
               'ascii':['file']  
 
               'ascii':['file']  
 
               }
 
               }
 
+
 
 
def default_run():
 
def default_run():
load_templates() # Guessing at syntax, don't have that func here atm
+
    load_run_config()
graphics_tags_by_file = load_graphics_source()
+
    load_all_templates()    # Guessing at syntax, don't have that func here atm
target_tags_by_file = load_target_raws()
+
    graphics_tags_by_file = load_graphics_source()
merged_tags_by_file = apply_graphics(target_tags_by_file,graphics_tags_by_file)
+
    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 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():
 +
    global ascii_codes
 +
    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:]
 +
 
 
def load_graphics_source():
 
def load_graphics_source():
 
     global properties
 
     global properties
Line 96: Line 179:
 
         print("Undefined graphics source file. Please add a 'source' property in ",runconfig,".")
 
         print("Undefined graphics source file. Please add a 'source' property in ",runconfig,".")
 
     else:
 
     else:
return _walk_rawfiles_into_tagnode_collection(properties['source'][1])
+
        return _walk_rawfiles_into_tagnode_collection(properties['source'][1])
         
+
'''    else:
 +
        for root, dirs, files in os.walk(properties['source'][1]):
 +
            for rawfile in files:
 +
                global template_tree
 +
                curr_template_node = template_tree
 +
                curr_graphics_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_template_match(tag)
 +
                        if matching_node != None:
 +
                            curr_template_node = matching_node
 +
                            if curr_graphics_node == None or matching_node in template_tree._children:
 +
                                curr_graphics_node = TagNode(rawfile,None,matching_node,tag)
 +
                            else:
 +
                                while matching_node._tag_template not in curr_graphics_node._template._children:
 +
                                    curr_graphics_node = curr_graphics_node._parent
 +
                                curr_graphics_node = TagNode(rawfile,curr_graphics_node,matching_node,tag)
 +
                openfile.close()
 +
'''
 +
 
 
def load_target_raws():
 
def load_target_raws():
global properties
+
    global properties
if len(properties['target']) < 2:
+
    if len(properties['target']) < 2:
# TODO error handling
+
        # TODO error handling
print("Undefined target raw file. Please add a 'target' property in ",runconfig,".")
+
        print("Undefined target raw file. Please add a 'target' property in ",runconfig,".")
else:
+
    else:
return _walk_rawfiles_into_tagnode_collection(properties['target'][1])
+
        return _walk_rawfiles_into_tagnode_collection(properties['target'][1])
+
               
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 _walk_rawfiles_into_tagnode_collection(directory):
 
def _walk_rawfiles_into_tagnode_collection(directory):
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:
global template_tree
+
            global template_tree
curr_template_node = template_tree
+
            curr_template_node = template_tree
curr_graphics_node = None
+
            curr_graphics_node = None
tarpath = os.path.join(root, rawfile)
+
            tarpath = os.path.join(root, rawfile)
for line in open(tarpath,encoding='cp437'):
+
            openfile = open(tarpath,encoding='cp437')
for tag in tags(line):
+
            for line in openfile:
matching_node = curr_template_node.find_template_match(tag)
+
                for tag in tags(line):
if matching_node != None:
+
                    matching_node = curr_template_node.find_template_match(tag)
curr_template_node = matching_node
+
                    if matching_node != None:
if curr_graphics_node == None or matching_node in template_tree._children:
+
                        curr_template_node = matching_node
curr_graphics_node = TagNode(rawfile,matching_node,tag)
+
                        if curr_graphics_node == None or matching_node in template_tree._children:
if rawfile not in node_collection:
+
                            curr_graphics_node = TagNode(rawfile,matching_node,tag)
node_collection[rawfile] = { }
+
                        else:
node_collection[rawfile][tag] = curr_graphics_node
+
                            while curr_graphics_node != None and matching_node._tag_template not in curr_graphics_node._template._children:
else:
+
                                curr_graphics_node = curr_graphics_node._parent
while matching_node._tag_template not in curr_graphics_node._template._children:
+
                            curr_graphics_node = TagNode(rawfile,matching_node,tag,curr_graphics_node)
curr_graphics_node = curr_graphics_node._parent
+
                        if rawfile not in node_collection:
curr_graphics_node = TagNode(rawfile,matching_node,tag,curr_graphics_node)
+
                            node_collection[rawfile] = { }
return node_collection
+
                        if curr_graphics_node._parent == None:
 +
                            node_collection[rawfile][tag] = curr_graphics_node
 +
                         
 +
            openfile.close()
 +
    return node_collection
 +
 
 +
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 escape_problematic_literals(rawline):
 +
    global ascii_codes
 +
    if ascii_codes == None:
 +
        _load_ascii_conversions()
 +
   
 +
    line = rawline.strip()
 +
    # Replace literal key characters with number codes
 +
    # 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.
 +
    bracketscount = 0        # If odd, we are inside a tag. If even, we are outside a tag.
 +
    count = 0                # Where we are in the string
 +
    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:
Line 189: Line 328:
 
             return_possibilities = []
 
             return_possibilities = []
 
             for child in self._children:
 
             for child in self._children:
                 return_nodes = self._children[child].get_template_match(tag)
+
                 return_node = self._children[child].get_template_match(tag)
                 if len(return_nodes) > 0:
+
                 if return_node != None:
 
                     return_possibilities.append(self._children[child])
 
                     return_possibilities.append(self._children[child])
 
             if len(return_possibilities) == 1:
 
             if len(return_possibilities) == 1:
Line 211: Line 350:
 
         ii = 0
 
         ii = 0
 
         while ii < len(candidate_tokens) and len(template_token_bag) > 0:
 
         while ii < len(candidate_tokens) and len(template_token_bag) > 0:
 +
            good_to_go = False
 
             for var in template_token_bag:
 
             for var in template_token_bag:
                 # I decided this isn't necessarily true, as ranges might have 0's
+
                 if ii >= len(var):
                #if len(var) > len(candidate_tokens):
+
                    template_token_bag.remove(var)
                #    template_token_bag.remove(var)
+
                 elif '&' == var[ii] or '?' == var[ii] or '$' == var[ii] or var[ii] == candidate_tokens[ii]:
                 if '&' == var[ii] or '?' == var[ii] or '$' == var[ii] or var[ii] == candidate_tokens[ii]:
 
 
                     # This case is an auto-pass
 
                     # This case is an auto-pass
                     ii = ii+1
+
                     good_to_go = True
 
                 else:
 
                 else:
 
                     if '&' in var[ii] or '?' in var[ii] or '$' in var[ii]:
 
                     if '&' in var[ii] or '?' in var[ii] or '$' in var[ii]:
Line 223: Line 362:
 
                         varii_range = var[ii][2:var[ii].index(')')].split(',')
 
                         varii_range = var[ii][2:var[ii].index(')')].split(',')
 
                         # If len(varii_range) == 1 then we have a range of format (x,), indicating any number of :'s
 
                         # If len(varii_range) == 1 then we have a range of format (x,), indicating any number of :'s
                         if len(varii_range) == 1:
+
                         if len(varii_range[1]) == 0:
                             varii_range[1] = len(candidate_tokens)-len(var)  
+
                             varii_range[1] = len(candidate_tokens)-len(var) + 1
 
                         # For every possible length (the +1 is because range is exclusive-end and my notation is inclusive-end)
 
                         # For every possible length (the +1 is because range is exclusive-end and my notation is inclusive-end)
                         for jj in range(varii_range[0],varii_range[1]+1):
+
                         for jj in range(int(varii_range[0]),int(varii_range[1])+1):
 
                             # Make a copy of var
 
                             # Make a copy of var
                             new_var = var.copy()
+
                             new_var = var[:]
 
                             # Remove the range item
 
                             # Remove the range item
 
                             del new_var[ii]
 
                             del new_var[ii]
Line 237: Line 376:
 
                             # Place the new variant in the token bag for evaluation
 
                             # Place the new variant in the token bag for evaluation
 
                             template_token_bag.append(new_var)
 
                             template_token_bag.append(new_var)
# No counting, there is a new template_token_bag[ii]
+
                    # No counting, there is a new template_token_bag[ii]
 
                     template_token_bag.remove(var)
 
                     template_token_bag.remove(var)
         return template_token_bag
+
            if good_to_go:
 +
                ii += 1
 +
         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):
 
     def how_many_generations(self):
Line 249: Line 401:
 
             count = count + 1
 
             count = count + 1
 
         return count
 
         return count
+
   
def resolve(self, target_tag, graphics_tag):
+
    def resolve(self, target_tag, graphics_tag):
target_tag_bag = get_template_match(target_tag)
+
        target_tag_bag = self.get_template_match(target_tag)
graphics_tag_bag = get_template_match(graphics_tag)
+
        graphics_tag_bag = self.get_template_match(graphics_tag)
shared_tag_bag = []
+
        shared_tag_bag = []
for tarlist in target_tag_bag:
+
        for tarlist in target_tag_bag:
for graphlist in graphics_tag_bag:
+
            for graphlist in graphics_tag_bag:
if len(tarlist) == len(graphlist):
+
                if len(tarlist) == len(graphlist):
found_flaw = False
+
                    found_flaw = False
ii = 0
+
                    ii = 0
while ii < len(graphlist) and not found_flaw:
+
                    while ii < len(graphlist) and not found_flaw:
found_flaw = graphlist[ii] != tarlist[ii]
+
                        found_flaw = graphlist[ii] != tarlist[ii]
if not found_flaw:
+
                    if not found_flaw:
shared_tag_bag.append(graphlist[ii])
+
                        shared_tag_bag.append(graphlist[ii])
if len(shared_tag_bag) == 0:
+
        if len(shared_tag_bag) == 0:
return None
+
            return None
elif len(shared_tag_bag) > 1:
+
        elif len(shared_tag_bag) > 1:
print("Hey Button there's too many shared tag_bags in TemplateNode.resolve.")
+
            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
+
        # 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]
+
        template_tag_bag = shared_tag_bag[0]
target_tag_bag = target_tag.split(':')
+
        target_tag_bag = target_tag.split(':')
graphics_tag_bag = graphics_tag.split(':')
+
        graphics_tag_bag = graphics_tag.split(':')
+
       
irreconcilable = False
+
        irreconcilable = False
ii = 0
+
        ii = 0
while ii < len(template_tag_bag) and not irreconcilable:
+
        while ii < len(template_tag_bag) and not irreconcilable:
if template_tag_bag[ii] == '$':
+
            if template_tag_bag[ii] == '$':
irreconcilable = target_tag_bag[ii] != graphics_tag_bag[ii]
+
                irreconcilable = target_tag_bag[ii] != graphics_tag_bag[ii]
elif template_tag_bag[ii] == '&':
+
            elif template_tag_bag[ii] == '&':
continue
+
                continue
elif template_tag_bag[ii] == '?':
+
            elif template_tag_bag[ii] == '?':
target_tag_bag[ii] = graphics_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]
+
                irreconcilable = target_tag_bag[ii] != graphics_tag_bag[ii] or target_tag_bag != template_tag_bag[ii]
if not irreconcilable:
+
        if not irreconcilable:
return ":".join(target_tag_bag)
+
            return ":".join(target_tag_bag)
else:
+
        else:
return None
+
            return None
 
 
      
 
      
 
class TagNode:
 
class TagNode:
Line 299: Line 450:
 
         self._children = {}
 
         self._children = {}
 
          
 
          
 +
        global graphics_tokens_by_file
 
         if parent != None:
 
         if parent != None:
             parent._children.append(self)
+
             parent._children[tag] = self
+
           
def recursively_apply_graphics(self,graphics_tag_node):
+
    def recursively_apply_graphics(self,graphics_tag_node):
continue_ok = True
+
        continue_ok = True
if self.compatible_with(graphics_tag_node):
+
        if self.compatible_with(graphics_tag_node):
for child in self._children.keys():
+
            for child in self._children.keys():
continue_ok = continue_ok and self._children[child].recursively_apply_graphics(graphics_tag_node._children[child])
+
                continue_ok = continue_ok and self._children[child].recursively_apply_graphics(graphics_tag_node._children[child])
if continue_ok:
+
            if continue_ok:
continue_ok = self.graphicsify(graphics_tag_node)
+
                continue_ok = self.graphicsify(graphics_tag_node)
return continue_ok
+
        return continue_ok
+
   
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 graphicsify(self, graphics_tag_node):
+
    def graphicsify(self, graphics_tag_node):
upcoming_tag = self._template.resolve(self._tag,graphics_tag_node._tag)
+
        upcoming_tag = self._template.resolve(self._tag,graphics_tag_node._tag)
if upcoming_tag != None:
+
        if upcoming_tag != None:
self._tag = upcoming_tag
+
            self._tag = upcoming_tag
return True
+
            return True
else:
+
        else:
return False
+
            return False
+
           
def abbrev_write(self,tabnums=0,output_stream=None):
+
    def abbrev_write(self,tabnums=0,output_stream=None):
if output_stream == None:
+
        if output_stream == None:
outfile = self._filename.open('w',encoding='cp437')
+
            outfile = self._filename.open('w',encoding='cp437')
else:
+
        else:
outfile = output_stream
+
            outfile = output_stream
print(tabnums*'\t','[',self._tag,']\n',outfile)
+
        print(tabnums*'\t','[',self._tag,']\n',outfile)
for child in self._children.keys():
+
        for child in self._children.keys():
self._children[child].abbrev_write(tabnums+1,outfile)
+
            self._children[child].abbrev_write(tabnums+1,outfile)
if output_stream == None:
+
        if output_stream == None:
close(outfile)
+
            outfile.close()</pre>
 
def walk()</pre>
 

Revision as of 06:13, 6 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

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 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():
    global ascii_codes
    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:]

def load_graphics_source():
    global properties
    if len(properties['source']) < 2:
        # TODO error handling
        print("Undefined graphics source file. Please add a 'source' property in ",runconfig,".")
    else:
        return _walk_rawfiles_into_tagnode_collection(properties['source'][1])
'''    else:
        for root, dirs, files in os.walk(properties['source'][1]):
            for rawfile in files:
                global template_tree
                curr_template_node = template_tree
                curr_graphics_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_template_match(tag)
                        if matching_node != None:
                            curr_template_node = matching_node
                            if curr_graphics_node == None or matching_node in template_tree._children:
                                curr_graphics_node = TagNode(rawfile,None,matching_node,tag)
                            else:
                                while matching_node._tag_template not in curr_graphics_node._template._children:
                                    curr_graphics_node = curr_graphics_node._parent
                                curr_graphics_node = TagNode(rawfile,curr_graphics_node,matching_node,tag)
                openfile.close()
'''
   
def load_target_raws():
    global properties
    if len(properties['target']) < 2:
        # TODO error handling
        print("Undefined target raw file. Please add a 'target' property in ",runconfig,".")
    else:
        return _walk_rawfiles_into_tagnode_collection(properties['target'][1])
                
def _walk_rawfiles_into_tagnode_collection(directory):
    node_collection = {}
    for root, dirs, files in os.walk(directory):
        for rawfile in files:
            global template_tree
            curr_template_node = template_tree
            curr_graphics_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_template_match(tag)
                    if matching_node != None:
                        curr_template_node = matching_node
                        if curr_graphics_node == None or matching_node in template_tree._children:
                            curr_graphics_node = TagNode(rawfile,matching_node,tag)
                        else:
                            while curr_graphics_node != None and matching_node._tag_template not in curr_graphics_node._template._children:
                                curr_graphics_node = curr_graphics_node._parent
                            curr_graphics_node = TagNode(rawfile,matching_node,tag,curr_graphics_node)
                        if rawfile not in node_collection:
                            node_collection[rawfile] = { }
                        if curr_graphics_node._parent == None:
                            node_collection[rawfile][tag] = curr_graphics_node
                           
            openfile.close()
    return node_collection

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 escape_problematic_literals(rawline):
    global ascii_codes
    if ascii_codes == None:
        _load_ascii_conversions()
    
    line = rawline.strip()
    # Replace literal key characters with number codes
    # 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.
    bracketscount = 0        # If odd, we are inside a tag. If even, we are outside a tag.
    count = 0                # Where we are in the string
    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:
    
    #self._tag_template #string
    #self._children    # list of TemplateNodes
    #self._parent    # TemplateNode
    #self._is_graphics_tag    # Boolean
    
    #string does not contain the character '|'.
    def __init__(self, parent, string=""):
        self._is_graphics_tag = False
        global template_tree
        if parent == None:
            self._parent = None
            template_tree = self
            self._children={}
        else:
            if template_tree == None:
                self._parent = TemplateNode(None, "")
            else:
                self._parent = parent

            self._tag_template = string
            self._children = {}
            
            parent.add_child(self)
        
    def add_child(self, node):
        if node._tag_template in self._children.keys():
            return self._children[node._tag_template]
        else:
            self._children[node._tag_template] = node
            return node
        
    def find_template_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_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():
            return self._children[tag]
        else:
            return_possibilities = []
            for child in self._children:
                return_node = self._children[child].get_template_match(tag)
                if return_node != None:
                    return_possibilities.append(self._children[child])
            if len(return_possibilities) == 1:
                return return_possibilities[0]
            elif len(return_possibilities) == 0:
                return None
            else:
                # 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:
                    if '&' in var[ii] or '?' in var[ii] or '$' in var[ii]:
                        varii_type = var[ii][0]
                        varii_range = var[ii][2:var[ii].index(')')].split(',')
                        # If len(varii_range) == 1 then we have a range of format (x,), indicating any number of :'s
                        if len(varii_range[1]) == 0:
                            varii_range[1] = len(candidate_tokens)-len(var) + 1
                        # 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
        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:
                irreconcilable = target_tag_bag[ii] != graphics_tag_bag[ii] or target_tag_bag != template_tag_bag[ii]
        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()