Module player
[hide private]
[frames] | no frames]

Source Code for Module player

  1  import time 
  2  import threading 
  3  import thread 
  4  import random 
  5   
  6  from pyglet.event import EventDispatcher 
  7  from cocos.layer import * 
  8  from cocos.text import * \ 
  9      # Delete later. Just using for now to get a text version of cpu count 
 10  from cocos.scene import Scene 
 11   
 12  from constants import * 
 13  from research import RESEARCH 
 14  from models import * 
 15  from game_layers import TransTimer 
 16  from utils import * 
 17  from imageLayer import * 
 18  from controller import * 
 19   
 20  from utils import * 
 21   
 22   
23 -class Player(EventDispatcher, Layer):
24
25 - def __init__(self, ID, gameMap, colman, color, special=None):
26 super(Player, self).__init__() 27 self.map = gameMap 28 self.cm = colman 29 30 self.ID = ID 31 self.color = PLAYER_COLOR[self.ID] 32 self.special = None 33 34 self.troops = [] 35 # contains all sorts of buildings (including research!) 36 self.buildings = [] 37 38 # the buildings that are currently under construction 39 self.underConstruction = [] 40 41 #TODO: integrate research with player default actions list 42 # number ot indicate completed research 43 self.completedResearch = 1 44 # research we haven't completed but is avialable 45 self.availableResearch = [] 46 # all the research available for this level 47 self.allResearch = [] 48 49 self.startingLevel = 1 # starting level of research 50 51 self.visibleAS = set() # set of ASes that are visible 52 # troops that become available after research 53 self.availableTroops = [] 54 self.idleCPUs = [] 55 self.busyCPUs = [] 56 57 self.unitActionLists = DEFAULT_ACTIONS #in constants 58 59 self.has_server = True 60 61 self.numServers = 1 62 63 self.aiPlayerIDs = self.map.AIPlayers
64
65 - def step(self, dt):
66 # self.check_loss() 67 toRemove = [] 68 for unit in self.underConstruction: 69 unit.health += unit.maxHealth * (dt / unit.buildTime) 70 if not self.ID in self.aiPlayerIDs: 71 try: 72 unit.opacity = (unit.health / unit.maxHealth) * ( 73 255 - UNIT_STARTING_OPACITY) + 255 74 except ZeroDivisionError: 75 unit.opacity = 255 # Silly fix for encyrpt units for now. 76 if unit.health >= unit.maxHealth: 77 if not self.ID in self.aiPlayerIDs: 78 unit.opacity = 255 79 if issubclass(type(unit), Troop): 80 self.troops.append(unit) 81 elif issubclass(type(unit), Building): 82 self.buildings.append(unit) 83 self.cm.add(unit) 84 toRemove.append(unit) 85 for unit in toRemove: 86 self.underConstruction.remove(unit) 87 if not self.ID in self.aiPlayerIDs: 88 unit.curVertex.set_visibility_old(1, self.cm, self.map.minimap) 89 90 pass
91
92 - def setup(self):
93 # Add starting info that was loaded from the map file 94 for r in self.map.startingResearch[self.ID]: # ? 95 self.completedResearch *= RESEARCH[r].uid 96 for r in self.map.availResearch: 97 if self.completedResearch % RESEARCH[r].dependencies == 0: 98 self.availableResearch.append(r) 99 self.allResearch = self.map.availResearch 100 for t in self.map.startingUnits[self.ID]: 101 self.availableTroops.append(t) 102 for vertID in self.map.startingUnits[self.ID]: 103 curVert = self.map.get_vertex(vertID) 104 unitsInVert = self.map.startingUnits[self.ID][vertID] 105 for strUnitType in unitsInVert: 106 unitType = eval(strUnitType) 107 if issubclass(unitType, Building): 108 self.add_building(unitType, vertID) 109 elif issubclass(unitType, Troop): 110 self.add_troop(unitType, vertID) 111 if type(curVert) == Vertex: 112 self.visibleAS.add(curVert.asID) 113 if self.ID not in self.aiPlayerIDs: 114 self.setup_visibility()
115 116 # in its own function for clarity
117 - def setup_visibility(self):
118 for v in self.map.vertices.values(): 119 if v.asID in self.visibleAS: 120 v.set_visibility_old(HALF_VISIBLE, self.cm,self.map.minimap)
121
122 - def add_troop(self, troopType, vid, oldTroop = None):
123 curVertex = self.map.get_vertex(vid) 124 if oldTroop != None: 125 # Encrypting a troop. 126 newTroop = EncryptedTroop(curVertex = oldTroop.curVertex, position = oldTroop.curVertex.position, health = oldTroop.health, pid = self.ID, speed = oldTroop.speed, originalType = type(oldTroop)) 127 else: 128 # Just adding a new troop. 129 newTroop = troopType(curVertex,pid=self.ID) 130 if not self.ID in self.aiPlayerIDs: 131 newTroop.opacity = UNIT_STARTING_OPACITY 132 else: 133 newTroop.opacity = 0 134 newTroop.color = self.color 135 newTroop.pid = self.ID 136 newTroop.push_handlers(self.on_destruction) 137 self.map.add(newTroop, TROOP_Z) 138 self.underConstruction.append(newTroop) 139 self.dispatch_event("player_add_troop", troopType) 140 return newTroop
141
142 - def add_building(self, buildingType, vid):
143 curVertex = self.map.get_vertex(vid) 144 if curVertex.building is None or buildingType == CPU: 145 if buildingType == RSA: 146 curVertex.color = (0,0,0) 147 self.map.remove(curVertex) 148 self.map.add(curVertex, z = RSA_Z) 149 newBuilding = buildingType(curVertex,player=self,pid=self.ID) 150 else: 151 newBuilding = buildingType(curVertex,pid=self.ID) 152 153 if not self.ID in self.aiPlayerIDs: 154 newBuilding.opacity = UNIT_STARTING_OPACITY 155 else: 156 newBuilding.opacity = 0 157 newBuilding.color = self.color 158 newBuilding.pid = self.ID 159 self.map.add(newBuilding, z=BUILDING_Z) 160 newBuilding.push_handlers(self.on_destruction) 161 self.underConstruction.append(newBuilding) 162 if buildingType == CPU: 163 self.idleCPUs.append(newBuilding) 164 return newBuilding
165 166 # def change_troop_to_type(self, oldTroop, newType): 167 # action = CallFunc(self.player.on_destruction, troopToEncrypt) 168 # action += CallFunc(self.player.add_troop, type(oldTroop), oldTroop.curVertex, oldTroop) # this call is a bit silly but it lets us avoid making two versions of add troop 169 # self.do(action) 170 171
172 - def execute_build_cpu(self, vertex, builder):
173 builder.opacity = 200 174 cores = vertex.adjacentCores 175 if cores: 176 curCore = cores.pop() 177 newCPU = self.add_building(CPU, curCore.vid) 178 timer, action = self.start_cpu(newCPU.buildTime, newCPU) 179 action += CallFunc(self.busyCPUs.remove, newCPU) 180 action += CallFunc(self.on_destruction, builder) 181 timer.do(action) 182 else: 183 pass
184 # DEBUG 185 # print "no cores available" 186
187 - def execute_build_building(self, buildingName, unit, selectedUnits=None):
188 if buildingName == "CPU": 189 # unit.oldActionList = unit.actionList # Store the old action list so we can restore it if we choose to cancel. 190 # unit.actionList = ["CANCEL"] 191 self.execute_build_cpu(unit.curVertex, unit) 192 # self.on_destruction(unit) 193 194 if len(self.idleCPUs) > 0 and unit.curVertex.building == None: 195 # Determine building type to build 196 unit.opacity = 200 197 # unit.oldActionList = unit.actionList # Store the old action list so we can restore it if we choose to cancel. 198 # unit.actionList = ["CANCEL"] 199 200 buildingType = ALL_UNITS[buildingName] 201 cpu = self.idleCPUs.pop() 202 building = self.add_building(buildingType, unit.curVertex.vid) 203 204 timer, action = self.start_cpu(building.buildTime, cpu) 205 action += CallFunc(self.stop_cpu, cpu) 206 action += CallFunc(self.on_destruction, unit) # After the building is finished, destroy the builder. We should also add a cancel button to the action menu. 207 self.dispatch_event("player_add_building", buildingType) 208 cpu.action = action 209 timer.do(action) 210 return True # Successfully built. 211 else: 212 # print "Building already in vertex OR no idle CPUs available" 213 return False # Failed build.
214
215 - def execute_build_troop(self, troopName, unit, selectedUnits=None):
216 # Build a Troop 217 if len(self.idleCPUs) > 0 and None in unit.curVertex.troops: 218 troopType = ALL_UNITS[troopName] 219 cpu = self.idleCPUs.pop() 220 troop = self.add_troop(troopType, unit.curVertex.vid) 221 timer, action = self.start_cpu(troop.buildTime, cpu) 222 action += CallFunc(self.stop_cpu, cpu) 223 timer.do(action) 224 return True 225 else: 226 print "Building already in vertex OR no idle CPUs available" 227 return False
228
229 - def perform_research(self, researchChoice, researchFacility, cm):
230 if len(self.idleCPUs) > 0: 231 cpu = self.idleCPUs.pop() 232 researchType = RESEARCH[researchChoice] 233 timer, action = self.start_cpu(researchType.buildTime, cpu) 234 action += CallFunc(self.stop_cpu, cpu) 235 action += CallFunc(self.finish_research, researchChoice) 236 action += CallFunc(self.update_research_fac_menu, researchFacility, self.map, cm) 237 cpu.action = action 238 timer.do(action) 239 return True 240 return False
241
242 - def update_research_fac_menu(self, researchFacility, gameMap, cm):
243 # Clears and displays the research factory menu so that it reflects the changes made my research (otherwise can double execute research which crashes) 244 if researchFacility.isSelected != False: 245 menuUpdate = CallFunc(researchFacility.clear_action_menu, self.map, cm, self) 246 menuUpdate += CallFunc(researchFacility.display_action_menu, self.map, cm, self) 247 self.do(menuUpdate)
248
249 - def start_cpu(self, sec, cpu, action=None):
250 self.busyCPUs.append(cpu) 251 transTimer = TransTimer(sec, pos=cpu.position) 252 cpu.transTimer = transTimer 253 moveAction = transTimer.get_move_action(action) 254 self.map.add(transTimer, z=TIMER_Z) 255 self.unitActionLists["CPU"].append("CANCEL") 256 return transTimer, moveAction
257
258 - def cancel_cpu(self, cpu):
259 if cpu in self.busyCPUs: 260 cpu.action.stop() 261 cpu.transTimer.kill() 262 self.stop_cpu(cpu)
263
264 - def stop_cpu(self, cpu):
265 if cpu in self.busyCPUs: 266 self.unitActionLists["CPU"].remove("CANCEL") 267 self.busyCPUs.remove(cpu) 268 self.idleCPUs.append(cpu)
269
270 - def unit_attack(self, attackers, targets):
271 # TODO: need a for loop for multiple attackers and targets 272 attacker = attackers.pop() 273 target = targets.pop() 274 if issubclass(type(attacker), Unit) and issubclass(type(target), Unit): 275 path = self.map.get_path(attacker.curVertex, target.curVertex) 276 if len(path) <= attacker.range: 277 attacker.attack(target, path, self.map) 278 self.dispath_event("player_unit_attack", attacker) 279 return True 280 else: 281 print "path too long" 282 return False
283
284 - def on_destruction(self, unit, selectedUnits=None, cmRemove = True):
285 # Deletes this unit from the game. Note: assume the menu has been 286 # cleared aleady. 287 if not unit.isDestroyed: 288 if issubclass(type(unit), Troop): 289 unit.curVertex.remove_troop(unit) 290 self.troops.remove(unit) 291 else: 292 unit.curVertex.building = None 293 self.buildings.remove(unit) 294 self.map.remove(unit) 295 296 if cmRemove: 297 self.cm.remove_tricky(unit) 298 unit.isDestroyed = True 299 300 if issubclass(type(unit), Server): 301 self.numServers -= 1
302 # Every time a unit is destroyed, check if I have lost 303
304 - def check_loss(self):
305 if len(self.troops) == 0 and len(self.buildings) == 0: 306 self.dispatch_event("on_loss", self)
307
308 - def switch_units_select_state(self, units, selectedUnits):
309 # Switches the select state of <units> 310 for unit in units: 311 if unit is not None and ((issubclass(type(unit), Troop) and not unit.are_actions_running()) or issubclass(type(unit), Building)) and unit.pid == self.ID: 312 unit.set_is_selected(not unit.isSelected, self.map, self.cm, self) 313 if unit.isSelected: 314 selectedUnits.append(unit) 315 else: 316 selectedUnits.remove(unit)
317 318 '''MARK: Research Methods''' 319
320 - def update_research_action_button(self, researchFactory):
321 researchFactory.actionList = ["DEL"] 322 researchFactory.actionList += self.availableResearch
323
324 - def update_troop_action_button(self, algorithmFactory):
325 algorithmFactory.actionList = ["DEL"] 326 algorithmFactory.actionList += self.availableTroops
327
328 - def finish_research(self, newR):
329 newResearch = RESEARCH[newR] 330 newResearch.on_completion(self) 331 self.completedResearch *= newResearch.uid 332 # self.availableTroops += newResearch.units make this more generic 333 # self.availableResearch.remove(newR) # REMOVE FOR NOW 334 for s in self.allResearch: 335 research = RESEARCH[s] 336 if (self.completedResearch % research.dependencies) == 0 and (self.completedResearch % research.uid != 0) and (s not in self.availableResearch): 337 # first cond ensures we have met dependencies 338 # second cond ensures we haven't done the research already 339 self.availableResearch.append(s)
340
341 - def building_complete(self, building):
342 self.dispatch_event('on_building_complete')
343 344 Player.register_event_type('on_loss') 345 Player.register_event_type('player_add_troop') 346 Player.register_event_type('player_unit_attack') 347 Player.register_event_type('player_add_building') 348 349
350 -class ComputerPlayer(Player):
351 ''' 352 The premise of this AI is to use a FINITE STATE MACHINE to react "intelligently" to a human player. 353 The states of this machine are defined below. An "N-Gram" will be used to collect data about player moves. 354 The "A-Star" pathfinding algorithm will be employed to find the most efficient way to attack. 355 '''
356 - def __init__(self, ID, gameMap, cm, color, enemy):
357 super(ComputerPlayer, self).__init__(ID, gameMap, cm, color) 358 self.aiRunning = False 359 self.theMap = gameMap 360 self.aiID = ID 361 self.ai_levels = ["Easy", "Medium", "Hard", "Godlike"] # I wish there was an enumerator in Python. 362 self.ai_level = "Easy" 363 self.ai_states = ["Initial", "Waiting", "Scanning", "Researching", "Attacking", "Building", "Defending"] # Finite State Machine States. 364 self.max_move_per_move = 4 # This could be better named. 365 self.max_attack_per_move = 5 # Same here. 366 self.enemy = enemy # That's the human. 367 self.color = (70, 0, 180) # Feel free to change. 368 self.ai_cur_state = "" # Always set to blank on new instance of AI! 369 self.ai_troops = 0 # The number of troops available to the AI. 370 self.human_troops = 0 # Number of troops available to human player. 371 self.all_troops = 0 # All troops on the board. 372 self.human_cpus = 1 # Will always be at least one, else AI wins. 373 self.board_research_level = 1 # The level of research currently allowed on the board. 374 self.discovered = False # Set to true when humans make first contact (discover) AI troops. 375 self.health = 1000 # Set to what Kat decides for the top menu. 376 self.human_health = 1000 # Self explanatory. 377 self.adjacent_free_slots = 0
378
379 - def scan(self):
380 # Brute Force Information Gathering. 381 pass
382 # print self.troops 383 # print self.enemy.availableTroops 384
385 - def scan_all_vertices(self):
386 # DEBUG 387 # print len(self.enemy.availableTroops) 388 for vertex in self.theMap.vertices.values(): 389 for troop in vertex.troopSlots.values(): 390 self.all_troops += 1 391 # print "Number of Human Troops:", len(self.units) # was breaking 392 # print "Number of All Troops:", self.all_troops 393 #print "Number of AI Troops:", 394 self.human_troops = 0 395 self.ai_troops = 0
396 #print self.enemy.availableTroops 397 398 #print self.troops 399 #for vertex in self.enemy.availableTroops[0].curVertex.adjacentVertices: 400 #print vertex.troopSlots 401
402 - def run_basic_ai(self):
403 thread.start_new_thread(self.ai_loop, ())
404
405 - def ai_loop(self):
406 # FST (Yes, it is a bunch of if statements) 407 while True: 408 if self.ai_cur_state == "": 409 # print "[ai] cur_state is blank, setting to initial." 410 self.ai_cur_state = "Initial" 411 if self.ai_cur_state == "Initial": 412 # print "[ai] state: Initial" 413 self.scan_all_vertices() 414 else: 415 pass 416 # DEBUG 417 # print "unknown case." 418 time.sleep(4) 419 ''' 420 while True: 421 time.sleep(3) 422 units_left = self.max_move_per_move 423 attack_left = self.max_attack_per_move 424 for unit in self.troops: 425 if unit.power > 0: 426 target = self.select_target(unit) 427 if target is not None: 428 if attack_left == 0: 429 break 430 path = self.map.get_path( 431 unit.curVertex, target.curVertex) 432 unit.attack(target, path, self.map) 433 attack_left -= 1 434 else: 435 if units_left == 0: 436 break 437 units_left -= 1 438 if not self.enemy.troops and not self.enemy.buildings: 439 return 440 target = random.choice( 441 self.enemy.troops + self.enemy.buildings) 442 unit.move(target.curVertex, self.map, self.cm) 443 '''
444 - def select_target(self, unit):
445 for vertex in unit.curVertex.adjacentVertices + [unit.curVertex]: 446 for troop in vertex.troops: 447 # have to incorporate range somehow, can't just look at adjacent vertices 448 # implement something like objs_within_range 449 if troop is not None and troop.pid != self.ID: 450 return troop 451 if vertex.building != None: 452 return vertex.building 453 return None
454