1 '''uccProgComp.py - A supervisor candidate for Rock Paper Scissors.
2 Written by Luke Williams <
[email protected]> for the UCC Programming Competition in 2008.
5 Licensed under an MIT-style license: see the LICENSE file for details.
12 from rpsconst import *
15 REPRODUCE_HEALTH = 100
21 # Game dynamics - these are not final:
22 # WINNER TRUTH ATTPoints, DEFPoints
23 pointsTable [Attacker] [False] = (2, -2)
24 pointsTable [Attacker] [True] = (2, -2)
25 pointsTable [Defender] [False] = (-2, 2)
26 pointsTable [Defender] [True] = (-2, 2)
27 pointsTable [Tie] [False] = (0, 0)
28 pointsTable [Tie] [True] = (1, 1)
33 print f.__name__, args[1].__class__.__name__, args[1].GetID ()
39 self.id = uuid.uuid4().int
40 self.__points = DEFAULT_HEALTH
41 # The index will be changing all the time. It can go stale as soon as something dies.
42 # So use it cautiously.
43 self.__currentIndex = 0
44 self.__reproduced = False
47 def GetCurrentIndex (self):
48 return self.__currentIndex
50 def SetCurrentIndex (self, index):
51 self.__currentIndex = index
59 def SetPoints (self, points):
60 self.__points = points
62 def Defend (self, foe, bluff):
65 def Attack (self, foe):
69 return self.__points <= DIE_HEALTH
71 def Reproduced (self):
72 self.__points = DEFAULT_HEALTH
73 self.__reproduced = True
75 def HasReproduced (self):
76 return self.__reproduced
78 def SetReproduced (self, repro):
79 self.__reproduced = repro
81 def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta):
82 self.__points += pointDelta
84 if self.__age > MAX_AGE: self.__points = DIE_HEALTH
86 class LearningAgent (BaseAgent):
88 BaseAgent.__init__ (self)
91 def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta):
92 BaseAgent.Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta)
94 if winner == Attacker: result = Win
95 elif winner == Tie: result = Tie
98 if winner == Attacker: result = Loss
99 elif winner == Tie: result = Tie
102 if foeName in self.winHistory: self.winHistory [foeName].append (result)
103 else: self.winHistory [foeName] = [result]
105 def GetWinHistory (self, foeName):
106 if foeName in self.winHistory: return self.winHistory [foeName]
111 # The full list of living agents
113 # A list of classes for each agent type
115 # The current iteration
118 self.pendingDeaths = []
120 def RegisterAgent (self, agent):
121 self.agentTypes.append (agent)
123 def GeneratePopulation (self, nAgentsPerClass):
124 for Agent in self.agentTypes:
125 for i in range (0,nAgentsPerClass): self.population.append (Agent ())
126 self.agentStats [str(Agent)] = [nAgentsPerClass,0,0]
130 self.UpdateIndexes ()
132 for attacker, defender in self.Select ():
133 attack, bluff = attacker.Attack (defender.GetID ())
134 defense = defender.Defend (attacker.GetID (), bluff)
135 winner = resultTable [attack] [defense]
136 attPoints, defPoints = pointsTable [winner][attack == bluff]
137 attacker.Results (defender.GetID (), True, winner, attack, defense, bluff, attPoints)
138 defender.Results (attacker.GetID (), False, winner, attack, defense, bluff, defPoints)
139 if attacker.IsDead (): self.KillAgent (attacker)
140 elif attacker.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (attacker)
141 if defender.IsDead (): self.KillAgent (defender)
142 elif defender.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (defender)
144 def IsGameOver (self):
145 if self.population == []: return True
146 liveAgents = [id for id,stats in self.agentStats.iteritems () if stats[0] > 0]
148 if len(liveAgents) < 2: return True
151 # This is needed because when we pick the players we also need a way of identifying them in the
152 # population list without manually searching each time. O(n) each iteration is better than O(n)
153 # each death. It also resets the check for if the agent has reproduced this round.
154 def UpdateIndexes (self):
155 for agentID in reversed(sorted(self.pendingDeaths)): del self.population [agentID]
156 for index, agent in enumerate(self.population):
157 agent.SetCurrentIndex (index)
158 agent.SetReproduced (False)
159 self.pendingDeaths = []
162 def KillAgent (self, agent):
163 self.pendingDeaths.append (agent.GetCurrentIndex ())
164 stat = self.agentStats [str(agent.__class__)]
169 def SpawnAgent (self, agent):
170 child = agent.__class__ ()
171 self.population.append (child)
173 stat = self.agentStats [str(agent.__class__)]
178 # This approach causes agents to keep fighting until they've either died or reproduced.
179 remaining = self.population[:]
180 attackerID = defenderID = random.randint (0,len(remaining)-1)
181 attacker = defender = remaining [attackerID]
182 while len (remaining) >= 2:
183 # Check to see if the attacker from last round needs to be dropped.
184 if attacker.IsDead () or attacker.HasReproduced ():
185 remaining.pop (attackerID)
186 if not len(remaining) >= 2: continue
187 if defenderID > attackerID: defenderID -= 1
188 # Check to see if the defender from last round is up for some attacking.
189 if defender.IsDead () or defender.HasReproduced ():
190 remaining.pop (defenderID)
191 if not len(remaining) >= 2: continue
192 attackerID = random.randint (0,len(remaining)-1)
193 attacker = remaining [attackerID]
196 attackerID = defenderID
198 defenderID = random.randint (0,len(remaining)-2)
199 if defenderID >= attackerID: defenderID += 1
200 defender = remaining [defenderID]
202 yield attacker, defender
205 return self.agentStats
207 def ClearStats (self):
208 for agent in self.agentTypes: self.agentStats [str(agent)] = self.agentStats [str(agent)] [:1] + [0,0]
211 return random.randint (0,2)