cleanup
[progcomp10.git] / src / uccProgComp.py
diff --git a/src/uccProgComp.py b/src/uccProgComp.py
new file mode 100644 (file)
index 0000000..0eee563
--- /dev/null
@@ -0,0 +1,211 @@
+'''uccProgComp.py - A supervisor candidate for Rock Paper Scissors.
+Written by Luke Williams <[email protected]> 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)

UCC git Repository :: git.ucc.asn.au