Exemplo n.º 1
0
class DynamicCommander(Commander):
    """
    Initializes everything that the commander needs. This includes loading the different rulesets, distributing the roles and generating scripts for the bots.
    """
    def initialize(self):
        self.verbose = True
        self.statistics = Statistics()
        self.statistics.initialize(self)
        self.knowledge = Knowledge()
        self.knowledge.initialize(self)
        
        #todo: make this more generic,
        # i.e. allow for an arbitrary number of roles
        # load all rule bases
        self.loadMetaRules()
        self.loadAttackerRules()
        self.loadDefenderRules()
        self.loadCatcherRules()
        
        # distribute the roles to the bots 
        roleList = self.metaScript.runDynamicScript([len(self.game.team.members),self.statistics])
        if(roleList != None):
            self.distributeRoles(roleList)
        else: #If none of the rules apply, use a mixed team.
            self.distributeRoles(metaRules.mixedAttackers(len(self.game.team.members)))
        # and generate the corresponding scripts
        self.initializeRoles()
        self.initializeBotStats()

    """
    Loads the meta rules with their weights.
    """
    def loadMetaRules(self):
        self.log.info("Loading the meta rules")
        conn = open(sys.path[0]+"/dynamicscripting/meta.txt",'r')
        self.metaRuleBase = jsonpickle.decode(conn.read())
        conn.close()
        
        # Generate an initial meta script
        self.metaScript = DynamicScriptingInstance(DynamicScriptingClass(self.metaRuleBase,"metaRules"))
        self.metaScript.generateScript(3)
        self.metaScript.printScript()
    
    """
    Loads the attacker rules with their weights.
    """
    def loadAttackerRules(self):
        self.log.info("Loading the attacker rules")
        conn = open(sys.path[0]+"/dynamicscripting/attacker.txt",'r')
        self.attackerRulebase = jsonpickle.decode(conn.read())
        conn.close()    
        
    """
    Loads the defender rules with their weights.
    """
    def loadDefenderRules(self):
        self.log.info("Loading the defender rules")
        conn = open(sys.path[0]+"/dynamicscripting/defender.txt",'r')
        self.defenderRulebase = jsonpickle.decode(conn.read())
        conn.close()        

    """
    Loads the catcher rules with their weights.
    """
    def loadCatcherRules(self):
        self.log.info("Loading the catcher rules")
        conn = open(sys.path[0]+"/dynamicscripting/catcher.txt",'r')
        self.catcherRulebase = jsonpickle.decode(conn.read())
        conn.close()
    
    """
    Distributes the roles to the bots according to a given distribution.
    """
    def distributeRoles(self,roleList):
        self.log.info("Distributing the roles")       
        self.log.info("Rolelist: "+ str(roleList))
        #assign each bot a role
        for botIndex in range(len(roleList)):
            self.game.team.members[botIndex].role = roleList[botIndex]
            
    def initializeRoles(self):
        self.log.info("Initializing roles")
		# load the rulebases
        self.dsclassAttacker = DynamicScriptingClass(self.attackerRulebase,"attackerRules")
        self.dsclassDefender = DynamicScriptingClass(self.defenderRulebase,"defenderRules")
        self.dsclassCatcher = DynamicScriptingClass(self.catcherRulebase,"catcherRules")
        i = 1
		#assign each bot a role, depending on its role
        for bot in self.game.team.members:
            bot.id = i
            self.log.info("Bot #" + str(i) + ": Generating " + bot.role + " script")
            if(bot.role == "attacker"):
                bot.script = DynamicScriptingInstance(self.dsclassAttacker,botRole = bot.role,botNumber = i)
                bot.script.generateScript(4)
                # add default rule
                bot.script.insertInScript(Rule(attackerRules.default_attacker_rule))
            elif(bot.role == "defender"):
                bot.script = DynamicScriptingInstance(self.dsclassDefender,botRole = bot.role,botNumber = i)
                bot.script.generateScript(4)
                bot.script.insertInScript(Rule(defenderRules.default_defender_rule))
            else: #if(bot.role == "catcher"): #backup
                bot.script = DynamicScriptingInstance(self.dsclassCatcher,botRole = bot.role,botNumber = i)
                bot.script.generateScript(4)
                #for i in bot.script.rules: #to check if there are different type of rules at base
                #    print(i.rule_type)
                bot.script.insertInScript(Rule(catcherRules.default_catcher_rule))
            bot.script.printScript()
            self.log.info("")
            i += 1

    """
    Initialises the statistics for the bots.
    """
    def initializeBotStats(self):
    
        self.timeSinceLastCommand = {} # used to detect bots that are stuck
    
        for bot in self.game.team.members:
            resetBotStats(bot)
            self.timeSinceLastCommand[bot.id] = 0

    """
    Updates the weights for all bots and the meta script in accordance
    with the procedure given in Spronck's paper.
    """
    def updateWeights(self):
        self.log.info("Updating weights!")
        self.metaScript.adjustWeights(self.metaScript.calculateTeamFitness(self.knowledge),self)
        for bot in self.game.team.members:
            fitness = bot.script.calculateAgentFitness(bot, self.knowledge)
            self.log.info("Bot #" + str(bot.id) + "[" + bot.role + "] fitness:" + str(fitness))
           
            bot.script.adjustWeights(fitness,self)

    """
    Save all the weights back to the files.
    """
    def saveWeights(self):
        self.saveWeightsRulebase(self.attackerRulebase,"attacker")
        self.saveWeightsRulebase(self.defenderRulebase,"defender")
        self.saveWeightsRulebase(self.catcherRulebase,"catcher")
        self.saveWeightsRulebase(self.metaRuleBase,"meta")
    
	"""
	save a certain rulebase
	"""
    def saveWeightsRulebase(self,rulebase,name):
        conn = open(sys.path[0] + "/dynamicscripting/" + name + ".txt",'w')
        funcBackup = []
        # Replace function by function names
        for rule in rulebase:
            funcBackup.append(rule.func)
            rule.func = rule.func.__name__
        rulebaseEncoded = jsonpickle.encode(rulebase)
        conn.write(rulebaseEncoded)
        conn.close()
        
        # Put them back.
        for i in range(len(rulebase)):
            rulebase[i].func = funcBackup[i]

    """
    Update the statistics for the bots. 
	Statistics are used to calculate bot fitness
    """
    def updateBotStats(self):
        for event in self.game.match.combatEvents:
            if event.type == event.TYPE_FLAG_RESTORED:
                if event.subject.team == self.game.team:
                    for bot in self.game.team.members:
                        bot.flag_restored += 1
                continue
            elif event.type == event.TYPE_FLAG_CAPTURED:
                if event.subject.team == self.game.team:
                    for bot in self.game.team.members:
                        bot.flag_stolen += 1
                continue

            if event.instigator == None:
                continue
            if event.instigator.team == self.game.team:
                if event.type == event.TYPE_KILLED:
                    event.instigator.kills += 1
                elif event.type == event.TYPE_FLAG_PICKEDUP:
                    event.instigator.flag_pickedup += 1
                elif event.type == event.TYPE_FLAG_DROPPED:
                    event.instigator.flag_dropped += 1
                elif event.type == event.TYPE_FLAG_CAPTURED:
                    event.instigator.flag_captured += 1
            else:
                if event.type == event.TYPE_KILLED:
                    event.subject.deaths += 1

    """
    This method is executed every game tick. It updates the statistics, it
    can switch the team composition and it gives new orders to the bots
    (based on their own scripts).
    """
    def tick(self):
       # self.log.info("Tick at time " + str(self.game.match.timePassed) + "!")
        
        self.statistics.tick() # Update statistics
        self.knowledge.tick() # Update knowledge base

        # update bot stats
        self.updateBotStats()
        
        # should the commander issue a new strategy?
        # TODO:
        # 1. make distributeRoles take the current distribution into account
        # 2. Let initializeRoles take currently good performing scripts for roles into account
        
        roleList = self.metaScript.runDynamicScript([len(self.game.team.members),self.statistics])
        if roleList != None:
            self.log.info("Switching tactics and adjusting weights")
            self.updateWeights()
            self.distributeRoles(roleList)
            self.initializeRoles()
            # Generate a new script based on latest weights for the next time.
            self.metaScript = DynamicScriptingInstance(DynamicScriptingClass(self.metaRuleBase,"metaRules"))
            self.metaScript.generateScript(3)
        
		# a bot that has the flag needs to be a catcher
        for bot in self.game.team.members:
            if bot.flag is not None and bot.role != "catcher":
                bot.role = "catcher"
                bot.script = DynamicScriptingInstance(self.dsclassCatcher,botRole = bot.role,botNumber = bot.id)
                bot.script.generateScript(4)
                bot.script.insertInScript(Rule(catcherRules.default_catcher_rule))
    
        # give the bots new commands
        for bot in self.game.bots_available:
            bot.script.runDynamicScript([bot,self,self.knowledge])
            self.timeSinceLastCommand[bot.id] = self.game.match.timePassed     

        # detect bots that are stuck    
        for bot in self.game.team.members:
            # more than 30 seconds (90 ticks) since bot has been available
            # it is probably stuck doing something silly, so reactivate it.
            if self.game.match.timePassed - self.timeSinceLastCommand[bot.id] > 30:
                print "REACTIVATING BOT", bot.id
                bot.script.runDynamicScript([bot,self,self.knowledge])
                self.timeSinceLastCommand[bot.id] = self.game.match.timePassed     
    
    """
    This method is executed at the end of the game.
    """
    def shutdown(self):
        self.updateWeights()
        self.saveWeights()
        print "statistics:",self.statistics.ourScore,"-",self.statistics.theirScore
Exemplo n.º 2
0
class Sugarscape():

#---------------------------------------------------------------------------#
#                                Tile Data                                  #
#---------------------------------------------------------------------------#
    class Tile():
        def __init__(self, sugar, spice):
            self.sugar = float(sugar)
            self.spice = float(spice)
            
            self.max_sugar = self.sugar
            self.max_spice = self.spice

            self.pollution = 0.0

            self.agent = None
            self.region = None


    class Season():
        # Seasons is an array of dictionaries where each dictionary where each
        # dictionary defines a season. A season defines growth rates for a 
        # number of ticks. Each dictionary should contain:
        # ["ticks"] the number of ticks for the rates to be active
        # ["sugar_growth"] the sugar growth
        # ["spice_growth"] the spice growth
        #
        # When a season has been active for its defined number of ticks, the
        # current season is set to the next season in the array, wrapping back
        # to the start if necessary.
        def __init__(self, ticks, sugar_growth, spice_growth):
            self.ticks = ticks
            self.sugar_growth = sugar_growth
            self.spice_growth = spice_growth


    class Region():
        # x1, y1, x2, y2 specify a rectangular region of tiles that make up the
        # region.
        #
        # Tiles are inclusive.
        # x1 < x2 and y1 < y2
        #
        # seasons is an array of Seasons. Initially the current_season is set to
        # the first Season in the array.
        # When a season has been active for its defined number of ticks, the
        # current season is set to the next season in the array, wrapping back
        # to the start if necessary.
        def __init__(self, seasons):
            #self.x1, self.y1 = x1, y1
            #self.x2, self.y2 = x2, y2

            if seasons:
                self.seasons = seasons
            else:
                self.seasons = [Sugarscape.Season(1000, 1, 1)]

            self.time = 0
            self.current_index = 0
            self.current_season = self.seasons[0]


        def tick(self):
            if self.time >= self.current_season.ticks:
                self.time = 0
                self.current_index += 1

                if self.current_index >= len(self.seasons):
                    self.current_index = 0

                self.current_season = self.seasons[self.current_index]

            self.time += 1





#---------------------------------------------------------------------------#
#                                                                           #
#                            initialization/shutdown                        #
#                                                                           #
#---------------------------------------------------------------------------#
    def __init__(self):
        self.window = sf.RenderWindow()


    def init(self):
        self.rand = Random()

        # tilemap
        self.loadData("sugarscape.yaml")
        data = self.data_file

        # agent properties
        self.num_agents             = data["num_agents"]
        self.vision_range           = data["vision_range"]
        self.sugar_metabolism_range = data["sugar_metabolism_range"]
        self.spice_metabolism_range = data["spice_metabolism_range"]
        self.initial_sugar_range    = data["initial_sugar_range"]
        self.initial_spice_range    = data["initial_spice_range"]
        self.max_age_range          = data["max_age_range"]
        self.m_fertile_start_range  = data["m_fertile_start_range"]
        self.m_fertile_end_range    = data["m_fertile_end_range"]
        self.f_fertile_start_range  = data["f_fertile_start_range"]
        self.f_fertile_end_range    = data["f_fertile_end_range"]
        self.num_culture_tags       = data["num_culture_tags"]

        # agent mutations
        self.vision_mutation_chance           = data["vision_mutation_chance"]
        self.global_vision_range              = data["global_vision_range"]
        self.vision_mutation_range            = data["vision_mutation_range"]

        self.sugar_metabolism_mutation_chance = data["sugar_metabolism_mutation_chance"]
        self.global_sugar_metabolism_range    = data["global_sugar_metabolism_range"]
        self.sugar_metabolism_mutation_range  = data["sugar_metabolism_mutation_range"]

        self.spice_metabolism_mutation_chance = data["spice_metabolism_mutation_chance"]
        self.global_spice_metabolism_range    = data["global_spice_metabolism_range"]
        self.spice_metabolism_mutation_range  = data["spice_metabolism_mutation_range"]

        self.age_mutation_chance              = data["age_mutation_chance"]
        self.global_age_range                 = data["global_age_range"]
        self.age_mutation_range               = data["age_mutation_range"]

        # pollution properties
        self.pollution_ticks            = data["pollution_ticks"]
        self.pollution_sugar_metabolism = data["pollution_sugar_metabolism"]
        self.pollution_spice_metabolism = data["pollution_spice_metabolism"]
        self.pollution_harvest          = data["pollution_harvest"]
        self.pollution_decay            = data["pollution_decay"]

        # diseases
        self.diseases = []
        self.num_initial_diseases        = data["num_initial_diseases"]
        self.disease_string_length_range = data["disease_string_length_range"]
        self.disease_extra_sugar_range   = data["disease_extra_sugar_range"]
        self.disease_extra_spice_range   = data["disease_extra_spice_range"]

        self.disease_infliction_ticks     = data["disease_infliction_ticks"]
        self.disease_infliction_agents    = data["disease_infliction_agents"]
        self.next_disease_infliction_tick = self.disease_infliction_ticks

        for i in range(self.num_initial_diseases):
            d = Disease(self.rand,
                self.rand.randint(self.disease_string_length_range[0], self.disease_string_length_range[1]),
                self.rand.randint(self.disease_extra_sugar_range[0], self.disease_extra_sugar_range[1]),
                self.rand.randint(self.disease_extra_spice_range[0], self.disease_extra_spice_range[1]))
            self.diseases.append(d)

        # simulation variables
        self.current_tick = 0
        self.next_pollution_tick = self.pollution_ticks - 1
        self.num_births = 0
        self.num_deaths = 0
        self.death_causes = {}
        self.paused = False

        # agents
        self.agents = []
        for i in range(min(self.tiles_x * self.tiles_y, self.num_agents)):
            args = {
                "sugar_metabolism"  : self.rand.randint(self.sugar_metabolism_range[0], self.sugar_metabolism_range[1]),
                "spice_metabolism"  : self.rand.randint(self.spice_metabolism_range[0], self.spice_metabolism_range[1]),
                "vision"            : self.rand.randint(self.vision_range[0], self.vision_range[1]),
                "sugar"             : self.rand.randint(self.initial_sugar_range[0], self.initial_sugar_range[1]),
                "spice"             : self.rand.randint(self.initial_spice_range[0], self.initial_spice_range[1]),
                "max_age"           : self.rand.randint(self.max_age_range[0], self.max_age_range[1]),
                "gender"            : self.rand.choice(("male", "female")),
            }

            if args["gender"] == "male":
                fertile_start_range = self.m_fertile_start_range
                fertile_end_range   = self.m_fertile_end_range
            else:
                fertile_start_range = self.f_fertile_start_range
                fertile_end_range   = self.f_fertile_end_range

            args["fertile_age_start"] = self.rand.randint(fertile_start_range[0], fertile_start_range[1])
            args["fertile_age_end"] = self.rand.randint(fertile_end_range[0], fertile_end_range[1])

            args["culture_tags"] = [self.rand.choice((0, 1)) for _ in range(0, self.num_culture_tags)]
            args["culture"] = "BasicCulture"

            self.rand.shuffle(self.diseases)
            args["diseases"] = self.diseases[:data["num_agent_diseases"]]
            args["immune_sys"] = [self.rand.choice((0, 1)) for _ in range(0, 50)]

            # find an unoccupied tile
            location_taken = True
            while location_taken:
                x = self.rand.randint(0, self.tiles_x-1)
                y = self.rand.randint(0, self.tiles_y-1)
                location_taken = self.isTileOccupied(x, y)

            args["x"], args["y"] = x, y

            self.spawnAgent(args)

        # rendering
        self.window.Create(sf.VideoMode(800, 600), "sugarscape")
        self.window.SetActive(True)
        self.window.PreserveOpenGLStates(True)
        self.window.SetFramerateLimit(60)
        self.cam = Camera(0, 0, 800, 600)
        self.resized(800, 600)
        self.resetCamera()
        glDisable(GL_DEPTH_TEST)

        self.tile_size = 10
        self.agent_size = 6

        self.draw_tiles_func = self.drawSugarTiles
        self.draw_agents_func = self.drawAgentsGender

        # statistics
        self.stats = Statistics(self)
        self.data_vis = DataVis(self.stats)

        # Gather stats for the initial simulation state (tick 0).
        # Do this here since the simulation is ticked, then stats are gathered
        # for it and displayed.
        self.stats.tick()


    def deinit(self):
        self.window.Close()


#---------------------------------------------------------------------------#
#                                                                           #
#                               main loop/ticking                           #
#                                                                           #
#---------------------------------------------------------------------------#
    def run(self):
        self.init()
        self.running = True

        while self.running:
            self.doEvents()

            if not self.paused:
                self.tick()
                self.stats.tick()
                self.data_vis.tick()

            self.draw()
            self.data_vis.draw()

        self.deinit()


    def doEvents(self):
        event = sf.Event()
        while self.window.GetEvent(event):
            if event.Type in [sf.Event.KeyPressed, sf.Event.KeyReleased]:
                self.keyEvent(event)
            elif event.Type == sf.Event.Closed:
                self.running = False
            elif event.Type == sf.Event.Resized:
                self.resized(event.Size.Width, event.Size.Height)

    
#---------------------------------------------------------------------------#
#                               input events                                #
#---------------------------------------------------------------------------#
    def keyEvent(self, evt):
        c = evt.Key.Code
        shift, ctrl, alt = evt.Key.Shift, evt.Key.Control, evt.Key.Alt

        if evt.Type == sf.Event.KeyPressed:
            if c == sf.Key.V:
                self.data_vis.setRunning(not self.data_vis.running)
            elif c == sf.Key.Space:
                self.paused = not self.paused
            elif c == sf.Key.Escape:
                self.running = False

            # tile drawing modes
            elif c == sf.Key.Q:
                print "draw sugar"
                self.draw_tiles_func = self.drawSugarTiles
            elif c == sf.Key.W:
                print "draw spice"
                self.draw_tiles_func = self.drawSpiceTiles
            elif c == sf.Key.E:
                print "draw occupied"
                self.draw_tiles_func = self.drawOccupiedTiles
            elif c == sf.Key.R:
                print "draw pollution"
                self.draw_tiles_func = self.drawPollution

            # agent drawing modes
            elif c == sf.Key.Num1:
                print "draw agents"
                self.draw_agents_func = self.drawAgents
            elif c == sf.Key.Num2:
                print "draw agents gender"
                self.draw_agents_func = self.drawAgentsGender
            elif c == sf.Key.Num3:
                print "draw agents culture"
                self.draw_agents_func = self.drawAgentsCulture
            elif c == sf.Key.Num4:
                print "draw agents sugar"
                self.draw_agents_func = self.drawAgentsSugar
            elif c == sf.Key.Num5:
                print "draw agents spice"
                self.draw_agents_func = self.drawAgentsSpice
            elif c == sf.Key.Num6:
                print "draw agents health"
                self.draw_agents_func = self.drawAgentsHealth

            elif c == sf.Key.F1:
                img = self.window.Capture()
                num = 0
                name = "screenshots/" + str(num).zfill(6) + ".png"
                while os.path.exists(name):
                    num = num + 1
                    name = "screenshots/" + str(num).zfill(6) + ".png"
                print "saving screenshot '{0}'".format(name)
                img.SaveToFile(name)

                if(self.data_vis.running):
                    img = self.data_vis.window.Capture()
                    name = "screenshots/" + str(num).zfill(6) + "_data.png"
                    img.SaveToFile(name)



#---------------------------------------------------------------------------#
#                                   ticking                                 #
#---------------------------------------------------------------------------#
    def tick(self):
        self.num_births = 0
        self.num_deaths = 0
        self.death_causes.clear()

        for r in self.regions.itervalues():
            r.tick()

        self.growResourcesRegional()

        if self.next_pollution_tick <= self.current_tick:
            self.pollutionDecay()
            self.pollutionDiffusion()
            self.next_pollution_tick = self.current_tick + self.pollution_ticks

        if self.current_tick >= self.next_disease_infliction_tick:
            self.inflictDiseaseOnAgents()

        for a in self.agents:
            a.tick()

            if a.dead:
                self.setAgentAt(None, a.x, a.y)
                self.num_deaths += 1
                self.death_causes.setdefault(a.death_cause, 0)
                self.death_causes[a.death_cause] += 1
         
        self.agents = [a for a in self.agents if not a.dead]
        self.rand.shuffle(self.agents)

        self.current_tick += 1

        #print "births={0} deaths={1}".format(self.num_births, self.num_deaths)
        if self.death_causes:
            print self.death_causes

        
#---------------------------------------------------------------------------#
#                                                                           #
#                           Tilemap functions                               #
#                                                                           #
#---------------------------------------------------------------------------#
    def loadData(self, data_file_path):
        self.data_file = yaml.load(file(data_file_path, 'r'))

        if "random_seed" in self.data_file:
            seed = self.data_file["random_seed"]
        else:
            seed = self.rand.randint(0, sys.maxint)

        print "seeding RNG with {0}".format(seed)
        self.rand.seed(seed)

        sugar_img, spice_img, region_img = sf.Image(), sf.Image(), sf.Image()
        sugar_img.LoadFromFile(self.data_file["sugar_file"])
        spice_img.LoadFromFile(self.data_file["spice_file"])
        region_img.LoadFromFile(self.data_file["regions_file"])

        self.tiles_x = sugar_img.GetWidth()
        self.tiles_y = spice_img.GetHeight()

        self.tiles = []
        self.max_sugar_level = 1.0
        self.max_spice_level = 1.0

        for x in range(self.tiles_x):
            self.tiles.append([])
            for y in range(self.tiles_y):
                c_sugar = sugar_img.GetPixel(x, sugar_img.GetHeight()-y-1)
                c_spice = spice_img.GetPixel(x, spice_img.GetHeight()-y-1)
                t = Sugarscape.Tile(c_sugar.r, c_spice.r)
                self.tiles[x].append(t)
                self.max_sugar_level = float(max(self.max_sugar_level, t.max_sugar))
                self.max_spice_level = float(max(self.max_spice_level, t.max_spice))

        #s1 = Sugarscape.Season(50, 1.0, 1.0)
        #s2 = Sugarscape.Season(50, 1.0, 1.0)
        #r1 = Sugarscape.Region(0,0,self.tiles_x-1,(self.tiles_y-1)/2, [s1, s2])
        #r2 = Sugarscape.Region(0,(self.tiles_y-1)/2,self.tiles_x-1,self.tiles_y-1, [s2, s1])
        #self.regions = []
        #self.regions.append(r1)
        #self.regions.append(r2)

        #for r in self.regions:
        #    for x in range(r.x1, r.x2+1):
        #        for y in range(r.y1, r.y2+1):
        #            self.tiles[x][y].region = r

        seasons = {}
        for s_data in self.data_file["seasons"]:
            seasons[s_data["name"]] = Sugarscape.Season(s_data["ticks"], s_data["sugar_growth"], s_data["spice_growth"])

        self.regions = {}
        for r_data in self.data_file["regions"]:
            S = []
            for s_name in r_data["season_names"]:
                if s_name in seasons:
                    S.append(seasons[s_name])

            if not S:
                print "region {0} has no associated seasons!".format(r_data["region_id"])

            self.regions[r_data["region_id"]] = Sugarscape.Region(S)

        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                r_id = region_img.GetPixel(x, region_img.GetHeight()-y-1).r
                if r_id in self.regions:
                    self.tiles[x][y].region = self.regions[r_id]
                else:
                    print "tile[{0}][{1}] has no associated region!".format(x, y)


    def growResourcesGlobal(self, growth):
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                t = self.tiles[x][y]
                t.sugar = min(t.sugar + growth, t.max_sugar)
                t.spice = min(t.spice + growth, t.max_spice)


    def growResourcesRegional(self):
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                t = self.tiles[x][y]
                g_sugar = t.region.current_season.sugar_growth
                g_spice = t.region.current_season.spice_growth
                t.sugar = min(t.sugar + g_sugar, t.max_sugar)
                t.spice = min(t.spice + g_spice, t.max_spice)
        

    def pollutionDiffusion(self):
        averages = util.create2DArray(self.tiles_x, self.tiles_y, 0.0)
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                avg  = self.getTileAt(x-1, y).pollution
                avg += self.getTileAt(x+1, y).pollution
                avg += self.getTileAt(x, y-1).pollution
                avg += self.getTileAt(x, y+1).pollution
                averages[x][y] = avg / 4.0

        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                self.getTileAt(x, y).pollution = averages[x][y]


    def pollutionDecay(self):
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                p = self.tiles[x][y].pollution
                self.tiles[x][y].pollution = max(p - self.pollution_decay, 0)


    def harvestSugarAt(self, x, y):
        t = self.getTileAt(x, y)
        s = t.sugar
        t.sugar = 0
        self.addPollution(x, y, self.pollution_harvest * s)
        return s


    def harvestSpiceAt(self, x, y):
        t = self.getTileAt(x, y)
        s = t.spice
        t.spice = 0
        self.addPollution(x, y, self.pollution_harvest * s)
        return s


    def getTileAt(self, x, y):
        return self.tiles[x % self.tiles_x][y % self.tiles_y]


    def getSugarAt(self, x, y):
        return self.getTileAt(x, y).sugar


    def getSpiceAt(self, x, y):
        return self.getTileAt(x, y).spice


    def getPollutionAt(self, x, y):
        return self.getTileAt(x, y).pollution


    def addPollution(self, x, y, amount):
        self.getTileAt(x, y).pollution += amount


    def addSugarMetabolismPollution(self, x, y, sugar):
        self.addPollution(x, y, self.pollution_sugar_metabolism * sugar)

    
    def addSpiceMetabolismPollution(self, x, y, spice):
        self.addPollution(x, y, self.pollution_spice_metabolism * spice)

    
    def spawnAgent(self, args):
        a = Agent(self, **args)
        self.agents.append(a)
        self.setAgentAt(a, a.x, a.y)
        self.num_births += 1
        return a


    def setAgentAt(self, agent, x, y):
        self.getTileAt(x, y).agent = agent
        return x % self.tiles_x, y % self.tiles_y


    def getAgentAt(self, x, y):
        return self.getTileAt(x, y).agent


    def getNeighbourAgents(self, x, y):
        ns = [
            self.getAgentAt(x-1, y),
            self.getAgentAt(x+1, y),
            self.getAgentAt(x, y-1),
            self.getAgentAt(x, y+1),
        ]

        return [a for a in ns if a is not None]

        
    def updateAgentPosition(self, agent, old_x, old_y, new_x, new_y):
        self.setAgentAt(None, old_x, old_y)
        return self.setAgentAt(agent, new_x, new_y)


    def isTileOccupied(self, x, y):
        return self.getAgentAt(x, y) is not None


    def tileHasEmptyNeighbour(self, x, y):
        return (self.getAgentAt(x-1, y) is None
             or self.getAgentAt(x+1, y) is None
             or self.getAgentAt(x, y-1) is None
             or self.getAgentAt(x, y+1) is None)


    def getEmptyNeighbourTiles(self, x, y):
        n_locs = [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
        return [l for l in n_locs if not self.isTileOccupied(l[0], l[1])]


    def getWorldSize(self):
        return self.tiles_x, self.tiles_y

    

#---------------------------------------------------------------------------#
#                                                                           #
#                           Agent functions                                 #
#                                                                           #
#---------------------------------------------------------------------------#
    def getNumAgents(self):
        return len(self.agents)


    def inflictDiseaseOnAgents(self):
        self.next_disease_infliction_tick = self.current_tick + self.disease_infliction_ticks

        if not self.agents:
            return

        for i in range(self.disease_infliction_agents):
            a = self.rand.choice(self.agents)
            d = self.rand.choice(self.diseases)
            a.infect(d)
        

#---------------------------------------------------------------------------#
#                                                                           #
#                            drawing/window functions                       #
#                                                                           #
#---------------------------------------------------------------------------#
    def draw(self):
        self.window.SetActive(True)
        glLoadIdentity()
        glClear(GL_COLOR_BUFFER_BIT)

        # center the world if smaller than the screen
        win_w, win_h = self.window.GetWidth(), self.window.GetHeight()
        world_w, world_h = self.getWorldSize()
        world_w *= self.tile_size
        world_h *= self.tile_size

        if world_w < win_w:
            glTranslatef((win_w - world_w) / 2, 0.0, 0.0)
        if world_h < win_h:
            glTranslatef(0.0, (win_h - world_h) / 2, 0.0)

        # apply camera transform
        glTranslatef(-self.cam.x, -self.cam.y, 0.0)

        # draw the world
        self.draw_tiles_func()
        self.draw_agents_func()

        # swap buffers
        self.window.Display()
        #print self.window.GetFrameTime()


    def drawSugarTiles(self):
        s = self.tile_size
        glBegin(GL_QUADS)
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                t = self.tiles[x][y]
                glColor3f(t.sugar/self.max_sugar_level, t.sugar/self.max_sugar_level, 0.0)
                glVertex2f(x*s,     y*s)
                glVertex2f((x+1)*s, y*s)
                glVertex2f((x+1)*s, (y+1)*s)
                glVertex2f(x*s,     (y+1)*s)
        glEnd()


    def drawSpiceTiles(self):
        s = self.tile_size
        glBegin(GL_QUADS)
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                t = self.tiles[x][y]
                glColor3f(t.spice/self.max_spice_level, t.spice/self.max_spice_level, 0.0)
                glVertex2f(x*s,     y*s)
                glVertex2f((x+1)*s, y*s)
                glVertex2f((x+1)*s, (y+1)*s)
                glVertex2f(x*s,     (y+1)*s)
        glEnd()


    def drawOccupiedTiles(self):
        s = self.tile_size
        glColor3ub(255, 0, 0)
        glBegin(GL_QUADS)
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                t = self.tiles[x][y]
                if self.isTileOccupied(x, y):
                    glVertex2f(x*s,     y*s)
                    glVertex2f((x+1)*s, y*s)
                    glVertex2f((x+1)*s, (y+1)*s)
                    glVertex2f(x*s,     (y+1)*s)
        glEnd()


    def drawPollution(self):
        s = self.tile_size
        glBegin(GL_QUADS)
        for x in range(self.tiles_x):
            for y in range(self.tiles_y):
                t = self.tiles[x][y]
                c = t.pollution/255.0
                glColor3f(c,c,c)
                glVertex2f(x*s,     y*s)
                glVertex2f((x+1)*s, y*s)
                glVertex2f((x+1)*s, (y+1)*s)
                glVertex2f(x*s,     (y+1)*s)
        glEnd()
    

    def drawAgents(self):
        t_s = self.tile_size
        a_s = self.agent_size
        off = (t_s - a_s) / 2

        glBegin(GL_QUADS)
        for a in self.agents:
            glColor3ub(0, 0, 255)
            x,y = a.x*t_s+off, a.y*t_s+off
            glVertex2f(x,     y)
            glVertex2f(x+a_s, y)
            glVertex2f(x+a_s, y+a_s)
            glVertex2f(x,     y+a_s)
        glEnd()


    def drawAgentsGender(self):
        t_s = self.tile_size
        a_s = self.agent_size
        off = (t_s - a_s) / 2

        glBegin(GL_QUADS)
        for a in self.agents:
            if a.gender == "male":
                glColor3ub(24, 116, 205)
            else:
                glColor3ub(255, 20, 147)
            x,y = a.x*t_s+off, a.y*t_s+off
            glVertex2f(x,     y)
            glVertex2f(x+a_s, y)
            glVertex2f(x+a_s, y+a_s)
            glVertex2f(x,     y+a_s)
        glEnd()


    def drawAgentsCulture(self):
        t_s = self.tile_size
        a_s = self.agent_size
        off = (t_s - a_s) / 2

        glBegin(GL_QUADS)
        for a in self.agents:
            cul = a.culture.getGroup()
            col = a.culture.COLORS.get(cul, (1.0, 1.0, 1.0, 1.0))
            glColor4f(col[0], col[1], col[2], col[3])

            x,y = a.x*t_s+off, a.y*t_s+off
            glVertex2f(x,     y)
            glVertex2f(x+a_s, y)
            glVertex2f(x+a_s, y+a_s)
            glVertex2f(x,     y+a_s)
        glEnd()


    def drawAgentsSugar(self):
        t_s = self.tile_size
        a_s = self.agent_size
        off = (t_s - a_s) / 2

        glBegin(GL_QUADS)
        for a in self.agents:
            max_s = 100.0
            s = max(min(a.sugar, max_s), 0.0)
            s /= max_s
            r, g = (1.0 - s), s

            glColor4f(r, g, 0.0, 1.0)

            x,y = a.x*t_s+off, a.y*t_s+off
            glVertex2f(x,     y)
            glVertex2f(x+a_s, y)
            glVertex2f(x+a_s, y+a_s)
            glVertex2f(x,     y+a_s)
        glEnd()


    def drawAgentsSpice(self):
        t_s = self.tile_size
        a_s = self.agent_size
        off = (t_s - a_s) / 2

        glBegin(GL_QUADS)
        for a in self.agents:
            max_s = 100.0
            s = max(min(a.spice, max_s), 0.0)
            s /= max_s
            r, g = (1.0 - s), s

            glColor4f(r, g, 0.0, 1.0)

            x,y = a.x*t_s+off, a.y*t_s+off
            glVertex2f(x,     y)
            glVertex2f(x+a_s, y)
            glVertex2f(x+a_s, y+a_s)
            glVertex2f(x,     y+a_s)
        glEnd()


    def drawAgentsHealth(self):
        t_s = self.tile_size
        a_s = self.agent_size
        off = (t_s - a_s) / 2

        glBegin(GL_QUADS)
        for a in self.agents:
            if a.diseases:
                glColor4f(1.0, 0.0, 0.0, 1.0)
            else:
                glColor4f(0.0, 1.0, 0.0, 1.0)

            x,y = a.x*t_s+off, a.y*t_s+off
            glVertex2f(x,     y)
            glVertex2f(x+a_s, y)
            glVertex2f(x+a_s, y+a_s)
            glVertex2f(x,     y+a_s)
        glEnd()


    def resetCamera(self):
        win_w, win_h = self.window.GetWidth(), self.window.GetHeight()
        world_w, world_h = self.getWorldSize()
        self.cam.width = min(win_w, world_w)
        self.cam.height = min(win_h, world_h)


    def resized(self, width, height):
        self.window.SetView(sf.View(sf.FloatRect(0, 0, width, height)))

        self.window.SetActive(True)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(0, width, 0, height, -1, 1)
        glViewport(0, 0, width, height)
        glMatrixMode(GL_MODELVIEW)