X-Git-Url: https://git.ucc.asn.au/?a=blobdiff_plain;f=src%2FuccProgComp.py;fp=src%2FuccProgComp.py;h=0eee563848df3e4d2213591caf577fffc5c8a37f;hb=e9a8105a8f22404f4ac550d79954eaa6b7f5d8ff;hp=0000000000000000000000000000000000000000;hpb=4597b27a44522a5c2785cc23029435f44f60ea55;p=progcomp10.git diff --git a/src/uccProgComp.py b/src/uccProgComp.py new file mode 100644 index 0000000..0eee563 --- /dev/null +++ b/src/uccProgComp.py @@ -0,0 +1,211 @@ +'''uccProgComp.py - A supervisor candidate for Rock Paper Scissors. +Written by Luke Williams for the UCC Programming Competition in 2008. +Requires Python 2.5. + +Licensed under an MIT-style license: see the LICENSE file for details. +''' + + +import random, uuid +random.seed () + +from rpsconst import * + +DEFAULT_HEALTH = 50 +REPRODUCE_HEALTH = 100 +DIE_HEALTH = 0 +MAX_AGE = 100 + +DEBUG_MODE = False + +# Game dynamics - these are not final: +# WINNER TRUTH ATTPoints, DEFPoints +pointsTable [Attacker] [False] = (2, -2) +pointsTable [Attacker] [True] = (2, -2) +pointsTable [Defender] [False] = (-2, 2) +pointsTable [Defender] [True] = (-2, 2) +pointsTable [Tie] [False] = (0, 0) +pointsTable [Tie] [True] = (1, 1) + +def Debug (f): + def g (*args): + if DEBUG_MODE: + print f.__name__, args[1].__class__.__name__, args[1].GetID () + return f (*args) + return g + +class BaseAgent: + def __init__ (self): + self.id = uuid.uuid4().int + self.__points = DEFAULT_HEALTH + # The index will be changing all the time. It can go stale as soon as something dies. + # So use it cautiously. + self.__currentIndex = 0 + self.__reproduced = False + self.__age = 0 + + def GetCurrentIndex (self): + return self.__currentIndex + + def SetCurrentIndex (self, index): + self.__currentIndex = index + + def GetID (self): + return self.id + + def GetPoints (self): + return self.__points + + def SetPoints (self, points): + self.__points = points + + def Defend (self, foe, bluff): + return Rock + + def Attack (self, foe): + return Rock + + def IsDead (self): + return self.__points <= DIE_HEALTH + + def Reproduced (self): + self.__points = DEFAULT_HEALTH + self.__reproduced = True + + def HasReproduced (self): + return self.__reproduced + + def SetReproduced (self, repro): + self.__reproduced = repro + + def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta): + self.__points += pointDelta + self.__age += 1 + if self.__age > MAX_AGE: self.__points = DIE_HEALTH + +class LearningAgent (BaseAgent): + def __init__ (self): + BaseAgent.__init__ (self) + self.winHistory = {} + + def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta): + BaseAgent.Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta) + if wasAttacker: + if winner == Attacker: result = Win + elif winner == Tie: result = Tie + else: result = Loss + else: + if winner == Attacker: result = Loss + elif winner == Tie: result = Tie + else: result = Win + + if foeName in self.winHistory: self.winHistory [foeName].append (result) + else: self.winHistory [foeName] = [result] + + def GetWinHistory (self, foeName): + if foeName in self.winHistory: return self.winHistory [foeName] + else: return [] + +class Supervisor: + def __init__ (self): + # The full list of living agents + self.population = [] + # A list of classes for each agent type + self.agentTypes = [] + # The current iteration + self.iteration = 0 + self.agentStats = {} + self.pendingDeaths = [] + + def RegisterAgent (self, agent): + self.agentTypes.append (agent) + + def GeneratePopulation (self, nAgentsPerClass): + for Agent in self.agentTypes: + for i in range (0,nAgentsPerClass): self.population.append (Agent ()) + self.agentStats [str(Agent)] = [nAgentsPerClass,0,0] + + def Iterate (self): + self.ClearStats () + self.UpdateIndexes () + self.iteration += 1 + for attacker, defender in self.Select (): + attack, bluff = attacker.Attack (defender.GetID ()) + defense = defender.Defend (attacker.GetID (), bluff) + winner = resultTable [attack] [defense] + attPoints, defPoints = pointsTable [winner][attack == bluff] + attacker.Results (defender.GetID (), True, winner, attack, defense, bluff, attPoints) + defender.Results (attacker.GetID (), False, winner, attack, defense, bluff, defPoints) + if attacker.IsDead (): self.KillAgent (attacker) + elif attacker.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (attacker) + if defender.IsDead (): self.KillAgent (defender) + elif defender.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (defender) + + def IsGameOver (self): + if self.population == []: return True + liveAgents = [id for id,stats in self.agentStats.iteritems () if stats[0] > 0] + print liveAgents + if len(liveAgents) < 2: return True + return False + + # This is needed because when we pick the players we also need a way of identifying them in the + # population list without manually searching each time. O(n) each iteration is better than O(n) + # each death. It also resets the check for if the agent has reproduced this round. + def UpdateIndexes (self): + for agentID in reversed(sorted(self.pendingDeaths)): del self.population [agentID] + for index, agent in enumerate(self.population): + agent.SetCurrentIndex (index) + agent.SetReproduced (False) + self.pendingDeaths = [] + + @Debug + def KillAgent (self, agent): + self.pendingDeaths.append (agent.GetCurrentIndex ()) + stat = self.agentStats [str(agent.__class__)] + stat [0] -= 1 + stat [2] += 1 + + @Debug + def SpawnAgent (self, agent): + child = agent.__class__ () + self.population.append (child) + agent.Reproduced () + stat = self.agentStats [str(agent.__class__)] + stat [0] += 1 + stat [1] += 1 + + def Select (self): + # This approach causes agents to keep fighting until they've either died or reproduced. + remaining = self.population[:] + attackerID = defenderID = random.randint (0,len(remaining)-1) + attacker = defender = remaining [attackerID] + while len (remaining) >= 2: + # Check to see if the attacker from last round needs to be dropped. + if attacker.IsDead () or attacker.HasReproduced (): + remaining.pop (attackerID) + if not len(remaining) >= 2: continue + if defenderID > attackerID: defenderID -= 1 + # Check to see if the defender from last round is up for some attacking. + if defender.IsDead () or defender.HasReproduced (): + remaining.pop (defenderID) + if not len(remaining) >= 2: continue + attackerID = random.randint (0,len(remaining)-1) + attacker = remaining [attackerID] + else: + attacker = defender + attackerID = defenderID + defender = None + defenderID = random.randint (0,len(remaining)-2) + if defenderID >= attackerID: defenderID += 1 + defender = remaining [defenderID] + + yield attacker, defender + + def GetStats (self): + return self.agentStats + + def ClearStats (self): + for agent in self.agentTypes: self.agentStats [str(agent)] = self.agentStats [str(agent)] [:1] + [0,0] + +def RandomAttack (): + return random.randint (0,2)