"LevelsData - parse a levels.txt file and build a data structure" import os, string import pygame from pygame.locals import * import random, math import game from nesteddict import NestedDict # errors WARNING = 0 FATAL = 1 # note- this could be a lot better done with custom object containers instead of sequences... argh. class Actor: def __init__(self,name,probability,max_instances,class_string,parameters_sequence,is_objective,interval=0): self.name = name self.class_string = class_string self.parameters_sequence = parameters_sequence self.is_objective = is_objective # if this is not None, it means how many instances are allowed on screen self.max_instances = max_instances # interval used by the refuel ship self.interval = interval # normalize probability- we only check once for adds at intervals, but probabilities # are in probability-per-frame self.probability = probability * game.add_actor_interval # the class that makes a sprite - filled in by the maker self.class_object = None # parameters that will be passed to the sprite - filled in by the maker self.parameters = [] def __repr__(self): return "Actor('%s',%f,%s,'%s',%s,%d)" % (self.name,self.probability,self.max_instances, self.class_string,self.parameters_sequence,self.interval) class Mission: def __init__(self,name,description,order,parts,refuel_actor): self.name = name self.description = description self.order = order # type int self.parts = parts # type [] self.refuel_actor = refuel_actor def __repr__(self): return "Mission('%s','%s',%d,%s)" % (self.name,self.description,self.order,self.parts) class MissionPart: def __init__(self,name,time,actors): self.name = name self.time = time # type int self.actors = actors # type [] def __repr__(self): return "MissionPart('%s',%d,%s)" % (self.name,self.time,self.actors) # needs refactoring class LevelsData: """Parse a text string into a Levels data structure On success, self.missions holds the missions array. """ def __init__(self,levels_text): self.debug = 0 levels_dict = NestedDict(levels_text) if levels_dict.data == None: # major error parsing text into NestedDict format raise "Error parsing levels_text." self.missions = None self.parse_levels(levels_dict) if self.debug: self.show() def parse_levels(self,levels_dict): """Turn a nested dict into a levels array of the form: (mission1, mission2, ... missionn) The input dictionary, levels_dict, must have the following keys defined: components: a dict of names and tuples, tuples = (class parameters) missions: a dict of dictionaries, one for each mission The missions dict must have the following keys defined: name: the name of the mission order: the order in the list of missions objective: an component name (destroying this completes mission) Other keys in this dict are of the form component_name: probability per frame """ #print levels_dict levels_data = levels_dict.data #print levels_data if levels_data.has_key('debug'): self.debug = string.atoi(levels_data['debug']) #print "set debug to: ",self.debug else: self.debug = 0 if levels_data.has_key('actors'): self.actors_dict = levels_data['actors'] else: self.error("Missing 'actors' dictionary.",FATAL) # now validate self.actors_dict self.check_type(self.actors_dict,{},FATAL) for key in self.actors_dict.keys(): value = self.actors_dict[key] # must be tuple self.check_type(value,[],FATAL) if levels_data.has_key('missions'): self.missions_dict = levels_data['missions'] else: self.error("Missing 'missions' dictionary.",FATAL) self.check_type(self.missions_dict,{},FATAL) # parse the missions self.missions = [] for mission_name in self.missions_dict.keys(): mission_data = self.missions_dict[mission_name] if mission_data.has_key('description'): mission_descr = mission_data['description'] del(mission_data['description']) self.check_type(mission_descr,'',FATAL) else: self.error("No mission description for %s." % mission_name) continue if mission_data.has_key('order'): # no need to check type, atoi does that mission_order = string.atoi(mission_data['order']) del(mission_data['order']) else: self.error("No order for %s." % mission_name) continue if mission_data.has_key('objective'): mission_objective = mission_data['objective'] del(mission_data['objective']) if not self.actors_dict.has_key(mission_objective): self.error("Objective %s not in actors dictionary for mission %s." % (mission_objective, mission_name)) continue else: self.error("No objective for %s." % mission_name) continue refuel_actor = None if mission_data.has_key('refuel'): refuel_str = mission_data['refuel'].strip() refuel_actor_name, refuel_interval_str = string.split(refuel_str) refuel_interval = string.atoi(refuel_interval_str) del(mission_data['refuel']) if not self.actors_dict.has_key(refuel_actor_name): self.error("Refuel Actor %s not in actors dictionary for mission %s." % (mission_objective, mission_name)) continue else: # build the actor object for the refuel ship if self.actors_dict.has_key(refuel_actor_name): # get tuple of object class, parameters class_string, parameters_sequence = self.actors_dict[refuel_actor_name] else: self.error("Refuel Actor %s not in Actors dictionary." % refuel_actor_name) continue refuel_actor = Actor(refuel_actor_name, 1.0 , 1, class_string, parameters_sequence,0,refuel_interval) else: # It's not an error to have no refuel ship pass # set up the default mission parameters if mission_data.has_key('default'): default_part_data = mission_data['default'] del(mission_data['default']) self.check_type(default_part_data,{},FATAL) else: default_part = {} parts = [] #print "mission_data:",mission_data for part_name in mission_data.keys(): #print "part: ",part_name part_data = mission_data[part_name] actors = [] if part_data.has_key('time'): # no need to check type, atoi does that time = string.atoi(part_data['time']) del(part_data['time']) else: time = 0 self.error("%s: %s has no time value." %(mission_name,part_name)) for actor in default_part_data.keys() + part_data.keys(): if self.actors_dict.has_key(actor): # get tuple of object class, parameters class_string, parameters_sequence = self.actors_dict[actor] else: self.error("Actor %s not in Actors dictionary." % actor) continue max_instances = None probability = 0.0 if part_data.has_key(actor): actor_data_str = part_data[actor].strip() actor_data_list = string.split(actor_data_str) if len(actor_data_list) > 0: probability = string.atof(actor_data_list[0]) if len(actor_data_list) > 1: max_instances = string.atoi(actor_data_list[1]) else: self.error("No probability for Actor %s in %s:%s." % (actor,mission_name,part_name)) else: # if not there, get it from default. actor_data_str = default_part_data[actor].strip() actor_data_list = string.split(actor_data_str) if len(actor_data_list) > 0: probability = string.atof(actor_data_list[0]) if len(actor_data_list) > 1: max_instances = string.atoi(actor_data_list[1]) else: self.error("No probability for Actor %s in %s:%s." % (actor,mission_name,part_name)) if actor == mission_objective: is_objective = 1 else: is_objective = 0 actors.append(Actor(actor, probability, max_instances, class_string, parameters_sequence,is_objective)) parts.append(MissionPart(part_name, time, actors)) parts.sort(self.compare_parts) if len(parts) < 1: self.error("Mission %s must have at least one part!" % mission_name,FATAL) self.missions.append(Mission(mission_name,mission_descr,mission_order,parts,refuel_actor)) self.missions.sort(self.compare_missions) if len(self.missions) < 1: self.error("There must be at least one mission!",FATAL) #print "Finished parsing." #print "self.missions:",self.missions def show(self): if self.debug: print "Showing..." for mission in self.missions: print "%s:" %mission.name print " descr: %s" % mission.description print " order %d" % mission.order print " refuel actor:",mission.refuel_actor parts = mission.parts for part in parts: print " %s: time: %d" % (part.name,part.time) actors = part.actors for actor in actors: print " ",actor def compare_parts(self,x,y): "compare two part arrays- sort by time" if x.time < y.time: return -1 elif x.time > y.time: return 1 else: return 0 def compare_missions(self,x,y): "compare two mission arrays- sort by order" if x.order < y.order: return -1 elif x.order > y.order: return 1 else: return 0 def copy(self,item): "return a copy of item - only works for {}, [], or () ; returns None otherwise" t = type(item) if t == type({}): item_copy = {} for key in item.keys(): item_copy[key] = item[key] return item_copy elif t == type([]): item_copy = [] for thing in item: item_copy.append(thing) return item_copy elif t == type(()): item_copy = () for thing in item: item_copy.append(thing) return item_copy else: return None def check_type(self,var,desired_type,errorlevel): "check to see if var is of type type; if not, raise error." if type(var) != type(desired_type): self.error("%s is not of type %s." % (var,desired_type),errorlevel) def error(self,message,errorlevel=WARNING): if self.debug: errortype = 'Warning' if errorlevel == FATAL: errortype = 'Fatal error' print "%s: %s" % (errortype,message) if errorlevel == FATAL: raise SystemExit if __name__ == "__main__": text = """ # FarBlazer levels file # must use spaces for indents # time is ticks * 100, roughly equal to seconds # print parse errors (1) or not (0) debug: 1 actors: st0: ['objstoragetank0.Storagetank0', [] ] st1: ['objstoragetank1.Storagetank1', [] ] cac: ['objcactus.Cactus', [] ] rl0: ['objrocketlauncher.RocketLauncher', ['self.airobjs','self.exhaust'] ] rad: ['objradar.Radar', [] ] tk0: ['objtank0.tank0', [] ] # define the missions missions: mission1: description: 'Bomb the Radar' order: 1 objective: rad default: st0: 0.002 1 st1: 0.002 cac: 0.001 rad: 0.0 part1: time: 60 rl0: 0.001 3 part2: time: 120 rl0: 0.0 part3: time: 180 rl0: 0.0 part4: time: 240 rl0: 0.0 mission2: description: 'Bomb The Tank' order: 2 objective: tk0 default: st0: 0.002 st1: 0.002 cac: 0.001 rad: 0.0 part1: time: 60 rl0: 0.001 part2: time: 120 rl0: 0.0 part3: time: 180 rl0: 0.0 part4: time: 240 rl0: 0.0 """ levels_data = LevelsData(text) levels_data.show()