option to cap number of agents
[progcomp10.git] / src / uccProgComp.py
1 '''uccProgComp.py - A supervisor candidate for Rock Paper Scissors.
2 Written by Luke Williams <[email protected]> for the UCC Programming Competition in 2008.
3 Requires Python 2.5.
4
5 Licensed under an MIT-style license: see the LICENSE file for details.
6 '''
7
8
9 import random, uuid
10 random.seed ()
11
12 from rpsconst import *
13 from conf import *
14
15 def Debug (f):
16         def g (*args):
17                 if DEBUG:
18                         print f.__name__, args[1].__class__.__name__, args[1].GetID ()
19                 return f (*args)
20         return g
21
22 class BaseAgent:
23         def __init__ (self):
24                 self.id = uuid.uuid4().int
25                 self.__points = DEFAULT_HEALTH
26                 # The index will be changing all the time. It can go stale as soon as something dies.
27                 # So use it cautiously.
28                 self.__currentIndex = 0
29                 self.__reproduced = False
30                 self.__age = 0
31
32         def GetCurrentIndex (self):
33                 return self.__currentIndex
34         
35         def SetCurrentIndex (self, index):
36                 self.__currentIndex = index
37
38         def GetID (self):
39                 return self.id
40         
41         def GetPoints (self):
42                 return self.__points
43
44         def SetPoints (self, points):
45                 self.__points = points
46
47         def Defend (self, foe, bluff):
48                 return Rock
49         
50         def Attack (self, foe):
51                 return Rock
52
53         def IsDead (self):
54                 return self.__points <= DIE_HEALTH
55
56         def Reproduced (self):
57                 self.__points = DEFAULT_HEALTH
58                 self.__reproduced = True
59
60         def HasReproduced (self):
61                 return self.__reproduced
62
63         def SetReproduced (self, repro):
64                 self.__reproduced = repro
65
66         def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta):
67                 self.__points += pointDelta
68                 self.__age += 1
69                 if self.__age > MAX_AGE: self.__points = DIE_HEALTH
70
71 class LearningAgent (BaseAgent):
72         def __init__ (self):
73                 BaseAgent.__init__ (self)
74                 self.winHistory = {}
75         
76         def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta):
77                 BaseAgent.Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta)
78                 if wasAttacker:
79                         if winner == Attacker: result = Win
80                         elif winner == Tie: result = Tie
81                         else: result = Loss
82                 else:
83                         if winner == Attacker: result = Loss
84                         elif winner == Tie: result = Tie
85                         else: result = Win
86                         
87                 if foeName in self.winHistory: self.winHistory [foeName].append (result)
88                 else: self.winHistory [foeName] = [result]
89
90         def GetWinHistory (self, foeName):
91                 if foeName in self.winHistory: return self.winHistory [foeName]
92                 else: return []
93
94 class Supervisor:
95         def __init__ (self):
96                 # The full list of living agents
97                 self.population = []
98                 # A list of classes for each agent type
99                 self.agentTypes = []
100                 # The current iteration
101                 self.iteration = 0
102                 self.agentStats = {}
103                 self.pendingDeaths = []
104
105         def RegisterAgent (self, agent):
106                 self.agentTypes.append (agent)
107
108         def GeneratePopulation (self, nAgentsPerClass):
109                 for Agent in self.agentTypes:
110                         for i in range (0,nAgentsPerClass): self.population.append (Agent ())
111                         self.agentStats [str(Agent)] = [nAgentsPerClass,0,0]
112                         if DEBUG: print "Created " + str(nAgentsPerClass) + " instances of " + Agent.__name__
113
114         def Iterate (self):
115                 self.ClearStats ()
116                 self.UpdateIndexes ()
117                 self.iteration += 1
118                 for attacker, defender in self.Select ():
119                         attack, bluff = attacker.Attack (defender.GetID ())
120                         defense = defender.Defend (attacker.GetID (), bluff)
121                         winner = resultTable [attack] [defense]
122                         attPoints, defPoints = pointsTable [winner][attack == bluff]
123                         attacker.Results (defender.GetID (), True, winner, attack, defense, bluff, attPoints)
124                         defender.Results (attacker.GetID (), False, winner, attack, defense, bluff, defPoints)
125                         if attacker.IsDead (): self.KillAgent (attacker)
126                         elif attacker.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (attacker)
127                         if defender.IsDead (): self.KillAgent (defender)
128                         elif defender.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (defender)
129
130         def IsGameOver (self):
131                 if self.population == []: return True
132                 liveAgents = [id for id,stats in self.agentStats.iteritems () if stats[0] > 0]
133                 print liveAgents
134                 if len(liveAgents) < 2: return True
135                 return False
136         
137         # This is needed because when we pick the players we also need a way of identifying them in the
138         # population list without manually searching each time. O(n) each iteration is better than O(n)
139         # each death. It also resets the check for if the agent has reproduced this round.
140         def UpdateIndexes (self):
141                 for agentID in reversed(sorted(self.pendingDeaths)): del self.population [agentID]
142                 for index, agent in enumerate(self.population): 
143                         agent.SetCurrentIndex (index)
144                         agent.SetReproduced (False)
145                 self.pendingDeaths = []
146
147         @Debug
148         def KillAgent (self, agent):
149                 self.pendingDeaths.append (agent.GetCurrentIndex ())
150                 stat = self.agentStats [str(agent.__class__)]
151                 stat [0] -= 1
152                 stat [2] += 1
153
154         @Debug
155         def SpawnAgent (self, agent):
156                 if MAX_TOTAL_AGENTS > 0 and len(self.population) - len(self.pendingDeaths) < MAX_TOTAL_AGENTS:
157                         child = agent.__class__ ()
158                         self.population.append (child)
159                         agent.Reproduced ()
160                         stat = self.agentStats [str(agent.__class__)]
161                         stat [0] += 1
162                         stat [1] += 1
163
164         def Select (self):
165                 # This approach causes agents to keep fighting until they've either died or reproduced.
166                 remaining = self.population[:]
167                 attackerID = defenderID = random.randint (0,len(remaining)-1)
168                 attacker = defender = remaining [attackerID]
169                 while len (remaining) >= 2:
170                         # Check to see if the attacker from last round needs to be dropped.
171                         if attacker.IsDead () or attacker.HasReproduced ():
172                                 remaining.pop (attackerID)
173                                 if not len(remaining) >= 2: continue
174                                 if defenderID > attackerID: defenderID -= 1
175                         # Check to see if the defender from last round is up for some attacking.
176                         if defender.IsDead () or defender.HasReproduced ():
177                                 remaining.pop (defenderID)
178                                 if not len(remaining) >= 2: continue
179                                 attackerID = random.randint (0,len(remaining)-1)
180                                 attacker = remaining [attackerID]
181                         else:
182                                 attacker = defender
183                                 attackerID = defenderID
184                         defender = None
185                         defenderID = random.randint (0,len(remaining)-2)
186                         if defenderID >= attackerID: defenderID += 1
187                         defender = remaining [defenderID]
188
189                         yield attacker, defender
190
191         def GetStats (self):
192                 return self.agentStats
193
194         def ClearStats (self):
195                 for agent in self.agentTypes: self.agentStats [str(agent)] = self.agentStats [str(agent)] [:1] + [0,0]
196
197 def RandomAttack ():
198         return random.randint (0,2)

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