More debugging output.
[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
14 DEFAULT_HEALTH = 50
15 REPRODUCE_HEALTH = 100
16 DIE_HEALTH = 0
17 MAX_AGE = 100
18
19 DEBUG = True
20
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)
29
30 def Debug (f):
31         def g (*args):
32                 if DEBUG:
33                         print f.__name__, args[1].__class__.__name__, args[1].GetID ()
34                 return f (*args)
35         return g
36
37 class BaseAgent:
38         def __init__ (self):
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
45                 self.__age = 0
46
47         def GetCurrentIndex (self):
48                 return self.__currentIndex
49         
50         def SetCurrentIndex (self, index):
51                 self.__currentIndex = index
52
53         def GetID (self):
54                 return self.id
55         
56         def GetPoints (self):
57                 return self.__points
58
59         def SetPoints (self, points):
60                 self.__points = points
61
62         def Defend (self, foe, bluff):
63                 return Rock
64         
65         def Attack (self, foe):
66                 return Rock
67
68         def IsDead (self):
69                 return self.__points <= DIE_HEALTH
70
71         def Reproduced (self):
72                 self.__points = DEFAULT_HEALTH
73                 self.__reproduced = True
74
75         def HasReproduced (self):
76                 return self.__reproduced
77
78         def SetReproduced (self, repro):
79                 self.__reproduced = repro
80
81         def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta):
82                 self.__points += pointDelta
83                 self.__age += 1
84                 if self.__age > MAX_AGE: self.__points = DIE_HEALTH
85
86 class LearningAgent (BaseAgent):
87         def __init__ (self):
88                 BaseAgent.__init__ (self)
89                 self.winHistory = {}
90         
91         def Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta):
92                 BaseAgent.Results (self, foeName, wasAttacker, winner, attItem, defItem, bluffItem, pointDelta)
93                 if wasAttacker:
94                         if winner == Attacker: result = Win
95                         elif winner == Tie: result = Tie
96                         else: result = Loss
97                 else:
98                         if winner == Attacker: result = Loss
99                         elif winner == Tie: result = Tie
100                         else: result = Win
101                         
102                 if foeName in self.winHistory: self.winHistory [foeName].append (result)
103                 else: self.winHistory [foeName] = [result]
104
105         def GetWinHistory (self, foeName):
106                 if foeName in self.winHistory: return self.winHistory [foeName]
107                 else: return []
108
109 class Supervisor:
110         def __init__ (self):
111                 # The full list of living agents
112                 self.population = []
113                 # A list of classes for each agent type
114                 self.agentTypes = []
115                 # The current iteration
116                 self.iteration = 0
117                 self.agentStats = {}
118                 self.pendingDeaths = []
119
120         def RegisterAgent (self, agent):
121                 self.agentTypes.append (agent)
122
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]
127                         if DEBUG: print "Created " + str(nAgentsPerClass) + " instances of " + Agent.__name__
128
129         def Iterate (self):
130                 self.ClearStats ()
131                 self.UpdateIndexes ()
132                 self.iteration += 1
133                 for attacker, defender in self.Select ():
134                         attack, bluff = attacker.Attack (defender.GetID ())
135                         defense = defender.Defend (attacker.GetID (), bluff)
136                         winner = resultTable [attack] [defense]
137                         attPoints, defPoints = pointsTable [winner][attack == bluff]
138                         attacker.Results (defender.GetID (), True, winner, attack, defense, bluff, attPoints)
139                         defender.Results (attacker.GetID (), False, winner, attack, defense, bluff, defPoints)
140                         if attacker.IsDead (): self.KillAgent (attacker)
141                         elif attacker.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (attacker)
142                         if defender.IsDead (): self.KillAgent (defender)
143                         elif defender.GetPoints () >= REPRODUCE_HEALTH: self.SpawnAgent (defender)
144
145         def IsGameOver (self):
146                 if self.population == []: return True
147                 liveAgents = [id for id,stats in self.agentStats.iteritems () if stats[0] > 0]
148                 print liveAgents
149                 if len(liveAgents) < 2: return True
150                 return False
151         
152         # This is needed because when we pick the players we also need a way of identifying them in the
153         # population list without manually searching each time. O(n) each iteration is better than O(n)
154         # each death. It also resets the check for if the agent has reproduced this round.
155         def UpdateIndexes (self):
156                 for agentID in reversed(sorted(self.pendingDeaths)): del self.population [agentID]
157                 for index, agent in enumerate(self.population): 
158                         agent.SetCurrentIndex (index)
159                         agent.SetReproduced (False)
160                 self.pendingDeaths = []
161
162         @Debug
163         def KillAgent (self, agent):
164                 self.pendingDeaths.append (agent.GetCurrentIndex ())
165                 stat = self.agentStats [str(agent.__class__)]
166                 stat [0] -= 1
167                 stat [2] += 1
168
169         @Debug
170         def SpawnAgent (self, agent):
171                 child = agent.__class__ ()
172                 self.population.append (child)
173                 agent.Reproduced ()
174                 stat = self.agentStats [str(agent.__class__)]
175                 stat [0] += 1
176                 stat [1] += 1 
177
178         def Select (self):
179                 # This approach causes agents to keep fighting until they've either died or reproduced.
180                 remaining = self.population[:]
181                 attackerID = defenderID = random.randint (0,len(remaining)-1)
182                 attacker = defender = remaining [attackerID]
183                 while len (remaining) >= 2:
184                         # Check to see if the attacker from last round needs to be dropped.
185                         if attacker.IsDead () or attacker.HasReproduced ():
186                                 remaining.pop (attackerID)
187                                 if not len(remaining) >= 2: continue
188                                 if defenderID > attackerID: defenderID -= 1
189                         # Check to see if the defender from last round is up for some attacking.
190                         if defender.IsDead () or defender.HasReproduced ():
191                                 remaining.pop (defenderID)
192                                 if not len(remaining) >= 2: continue
193                                 attackerID = random.randint (0,len(remaining)-1)
194                                 attacker = remaining [attackerID]
195                         else:
196                                 attacker = defender
197                                 attackerID = defenderID
198                         defender = None
199                         defenderID = random.randint (0,len(remaining)-2)
200                         if defenderID >= attackerID: defenderID += 1
201                         defender = remaining [defenderID]
202
203                         yield attacker, defender
204
205         def GetStats (self):
206                 return self.agentStats
207
208         def ClearStats (self):
209                 for agent in self.agentTypes: self.agentStats [str(agent)] = self.agentStats [str(agent)] [:1] + [0,0]
210
211 def RandomAttack ():
212         return random.randint (0,2)

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