comparison gamelib/animal.py @ 476:3dae0fc14009

Animals have their own gameboard references instead of lugging them around in params all the time.
author Jeremy Thurgood <firxen@gmail.com>
date Wed, 25 Nov 2009 17:44:32 +0000
parents d4f04d81fe54
children c1439f6705a2
comparison
equal deleted inserted replaced
475:d4f04d81fe54 476:3dae0fc14009
30 'accoutrements', 30 'accoutrements',
31 'abode', 31 'abode',
32 'facing', 32 'facing',
33 ] 33 ]
34 34
35 def __init__(self, tile_pos): 35 def __init__(self, tile_pos, gameboard):
36 # load images 36 # load images
37 self._image_left = imagecache.load_image(self.IMAGE_FILE) 37 self._image_left = imagecache.load_image(self.IMAGE_FILE)
38 self._image_right = imagecache.load_image(self.IMAGE_FILE, ("right_facing",)) 38 self._image_right = imagecache.load_image(self.IMAGE_FILE, ("right_facing",))
39 # Create the animal somewhere far off screen 39 # Create the animal somewhere far off screen
40 Sprite.__init__(self, self._image_left, (-1000, -1000)) 40 Sprite.__init__(self, self._image_left, (-1000, -1000))
46 self.pos = Position(tile_pos[0], tile_pos[1], 0) 46 self.pos = Position(tile_pos[0], tile_pos[1], 0)
47 self.equipment = [] 47 self.equipment = []
48 self.accoutrements = [] 48 self.accoutrements = []
49 self.abode = None 49 self.abode = None
50 self.facing = 'left' 50 self.facing = 'left'
51 self.gameboard = gameboard
51 52
52 def make(cls): 53 def make(cls):
53 """Override default Simplifiable object creation.""" 54 """Override default Simplifiable object creation."""
54 return cls((0, 0)) 55 return cls((0, 0))
55 make = classmethod(make) 56 make = classmethod(make)
64 def loop(self, tv, _sprite): 65 def loop(self, tv, _sprite):
65 ppos = tv.tile_to_view(self.pos.to_tile_tuple()) 66 ppos = tv.tile_to_view(self.pos.to_tile_tuple())
66 self.rect.x = ppos[0] 67 self.rect.x = ppos[0]
67 self.rect.y = ppos[1] 68 self.rect.y = ppos[1]
68 69
69 def die(self, gameboard): 70 def die(self):
70 """Play death animation, noises, whatever.""" 71 """Play death animation, noises, whatever."""
71 if hasattr(self, 'DEATH_SOUND'): 72 if hasattr(self, 'DEATH_SOUND'):
72 sound.play_sound(self.DEATH_SOUND) 73 sound.play_sound(self.DEATH_SOUND)
73 if hasattr(self, 'DEATH_ANIMATION'): 74 if hasattr(self, 'DEATH_ANIMATION'):
74 self.DEATH_ANIMATION(gameboard.tv, self.pos.to_tile_tuple()) 75 self.DEATH_ANIMATION(self.gameboard.tv, self.pos.to_tile_tuple())
75 self._game_death(gameboard) 76 self._game_death()
76 77
77 def _game_death(self, gameboard): 78 def _game_death(self):
78 # Call appropriate gameboard cleanup here. 79 # Call appropriate gameboard cleanup here.
79 pass 80 pass
80 81
81 def move(self, state): 82 def move(self, state):
82 """Given the game state, return a new position for the object""" 83 """Given the game state, return a new position for the object"""
83 # Default is not to move 84 # Default is not to move
84 pass 85 pass
85 86
86 def attack(self, gameboard): 87 def attack(self):
87 """Given the game state, attack a suitable target""" 88 """Given the game state, attack a suitable target"""
88 # Default is not to attack 89 # Default is not to attack
89 pass 90 pass
90 91
91 def set_pos(self, tile_pos): 92 def set_pos(self, tile_pos):
157 return tile_pos[0] == self.pos.x and tile_pos[1] == self.pos.y 158 return tile_pos[0] == self.pos.x and tile_pos[1] == self.pos.y
158 159
159 def outside(self): 160 def outside(self):
160 return self.abode is None 161 return self.abode is None
161 162
162 def damage(self, gameboard): 163 def damage(self):
163 for a in self.armour(): 164 for a in self.armour():
164 if not a.survive_damage(): 165 if not a.survive_damage():
165 self.unequip(a) 166 self.unequip(a)
166 return True 167 return True
167 self.die(gameboard) 168 self.die()
168 return False 169 return False
169 170
170 class Chicken(Animal): 171 class Chicken(Animal):
171 """A chicken""" 172 """A chicken"""
172 173
175 DEATH_SOUND = 'kill-chicken.ogg' 176 DEATH_SOUND = 'kill-chicken.ogg'
176 IMAGE_FILE = 'sprites/chkn.png' 177 IMAGE_FILE = 'sprites/chkn.png'
177 178
178 SIMPLIFY = Animal.SIMPLIFY + ['eggs'] 179 SIMPLIFY = Animal.SIMPLIFY + ['eggs']
179 180
180 def __init__(self, pos): 181 def __init__(self, pos, gameboard):
181 Animal.__init__(self, pos) 182 Animal.__init__(self, pos, gameboard)
182 self.eggs = [] 183 self.eggs = []
183 184
184 def start_night(self, gameboard): 185 def start_night(self):
185 self.lay(gameboard) 186 self.lay()
186 self.reload_weapon() 187 self.reload_weapon()
187 188
188 def start_day(self, gameboard): 189 def start_day(self):
189 self.hatch(gameboard) 190 self.hatch()
190 191
191 def _game_death(self, gameboard): 192 def _game_death(self):
192 gameboard.remove_chicken(self) 193 self.gameboard.remove_chicken(self)
193 194
194 def move(self, gameboard): 195 def move(self):
195 """A free chicken will wander around aimlessly""" 196 """A free chicken will wander around aimlessly"""
196 pos_x, pos_y = self.pos.to_tile_tuple() 197 pos_x, pos_y = self.pos.to_tile_tuple()
197 surrounds = [Position(pos_x + dx, pos_y + dy) for dx in [-1, 0, 1] for dy in [-1, 0, 1]] 198 surrounds = [Position(pos_x + dx, pos_y + dy) for dx in [-1, 0, 1] for dy in [-1, 0, 1]]
198 pos_options = [pos for pos in surrounds if gameboard.in_bounds(pos) and gameboard.tv.get(pos.to_tile_tuple()) == gameboard.GRASSLAND and not gameboard.get_outside_chicken(pos.to_tile_tuple())] + [self.pos] 199 pos_options = [pos for pos in surrounds if self.gameboard.in_bounds(pos) and self.gameboard.tv.get(pos.to_tile_tuple()) == self.gameboard.GRASSLAND and not self.gameboard.get_outside_chicken(pos.to_tile_tuple())] + [self.pos]
199 self.pos = pos_options[random.randint(0, len(pos_options)-1)] 200 self.pos = pos_options[random.randint(0, len(pos_options)-1)]
200 201
201 def has_axe(self): 202 def has_axe(self):
202 return bool([e for e in self.weapons() if e.TYPE == "AXE"]) 203 return bool([e for e in self.weapons() if e.TYPE == "AXE"])
203 204
204 def chop(self, gameboard): 205 def chop(self):
205 if self.has_axe(): 206 if self.has_axe():
206 pos_x, pos_y = self.pos.to_tile_tuple() 207 pos_x, pos_y = self.pos.to_tile_tuple()
207 surrounds = [Position(pos_x + dx, pos_y + dy) for dx in [-1, 0, 1] for dy in [-1, 0, 1]] 208 surrounds = [Position(pos_x + dx, pos_y + dy) for dx in [-1, 0, 1] for dy in [-1, 0, 1]]
208 tree_options = [pos for pos in surrounds if gameboard.in_bounds(pos) and gameboard.tv.get(pos.to_tile_tuple()) == gameboard.WOODLAND] 209 tree_options = [pos for pos in surrounds if self.gameboard.in_bounds(pos) and self.gameboard.tv.get(pos.to_tile_tuple()) == self.gameboard.WOODLAND]
209 if tree_options: 210 if tree_options:
210 num_trees_to_cut = random.randint(1, len(tree_options)) 211 num_trees_to_cut = random.randint(1, len(tree_options))
211 trees_to_cut = random.sample(tree_options, num_trees_to_cut) 212 trees_to_cut = random.sample(tree_options, num_trees_to_cut)
212 for tree_pos in trees_to_cut: 213 for tree_pos in trees_to_cut:
213 gameboard.add_wood(5) 214 self.gameboard.add_wood(5)
214 gameboard.tv.set(tree_pos.to_tile_tuple(), gameboard.GRASSLAND) 215 self.gameboard.tv.set(tree_pos.to_tile_tuple(), self.gameboard.GRASSLAND)
215 216
216 def lay(self, gameboard): 217 def lay(self):
217 """See if the chicken lays an egg""" 218 """See if the chicken lays an egg"""
218 if self.abode and self.abode.building.HENHOUSE: 219 if self.abode and self.abode.building.HENHOUSE:
219 if not self.eggs: 220 if not self.eggs:
220 for x in range(random.randint(1, 4)): 221 for x in range(random.randint(1, 4)):
221 self.eggs.append(Egg(self.pos)) 222 self.eggs.append(Egg(self.pos, self.gameboard))
222 self.equip(equipment.NestEgg()) 223 self.equip(equipment.NestEgg())
223 gameboard.eggs += self.get_num_eggs() 224 self.gameboard.eggs += self.get_num_eggs()
224 225
225 def remove_eggs(self, gameboard): 226 def remove_eggs(self):
226 """Clean up the egg state""" 227 """Clean up the egg state"""
227 gameboard.remove_eggs(len(self.eggs)) 228 self.gameboard.remove_eggs(len(self.eggs))
228 self.eggs = [] 229 self.eggs = []
229 self.unequip_by_name("Nestegg") 230 self.unequip_by_name("Nestegg")
230 231
231 def remove_one_egg(self, gameboard): 232 def remove_one_egg(self):
232 """Clean up the egg state""" 233 """Clean up the egg state"""
233 self.eggs.pop() 234 self.eggs.pop()
234 gameboard.remove_eggs(1) 235 self.gameboard.remove_eggs(1)
235 if not self.eggs: 236 if not self.eggs:
236 self.unequip_by_name("Nestegg") 237 self.unequip_by_name("Nestegg")
237 238
238 def get_num_eggs(self): 239 def get_num_eggs(self):
239 return len(self.eggs) 240 return len(self.eggs)
240 241
241 def hatch(self, gameboard): 242 def hatch(self):
242 """See if we have an egg to hatch""" 243 """See if we have an egg to hatch"""
243 if self.eggs: 244 if self.eggs:
244 chick = self.eggs[0].hatch() 245 chick = self.eggs[0].hatch()
245 if chick: 246 if chick:
246 # sell the remaining eggs 247 # sell the remaining eggs
247 # Remove hatched egg 248 # Remove hatched egg
248 self.eggs.pop() 249 self.remove_one_egg()
249 gameboard.eggs -= 1
250 # Sell other eggs 250 # Sell other eggs
251 for egg in self.eggs[:]: 251 for egg in self.eggs[:]:
252 gameboard.sell_one_egg(self) 252 self.gameboard.sell_one_egg(self)
253 self.remove_eggs(gameboard) # clean up stale images, etc. 253 self.remove_eggs() # clean up stale images, etc.
254 gameboard.place_hatched_chicken(chick, self.abode.building) 254 self.gameboard.place_hatched_chicken(chick, self.abode.building)
255 255
256 def _find_killable_fox(self, weapon, gameboard): 256 def _find_killable_fox(self, weapon):
257 """Choose a random fox within range of this weapon.""" 257 """Choose a random fox within range of this weapon."""
258 killable_foxes = [] 258 killable_foxes = []
259 for fox in gameboard.foxes: 259 for fox in self.gameboard.foxes:
260 if not weapon.in_range(gameboard, self, fox): 260 if not weapon.in_range(self.gameboard, self, fox):
261 continue 261 continue
262 if visible(self, fox, gameboard): 262 if visible(self, fox, self.gameboard):
263 killable_foxes.append(fox) 263 killable_foxes.append(fox)
264 if not killable_foxes: 264 if not killable_foxes:
265 return None 265 return None
266 return random.choice(killable_foxes) 266 return random.choice(killable_foxes)
267 267
268 def attack(self, gameboard): 268 def attack(self):
269 """An armed chicken will attack a fox within range.""" 269 """An armed chicken will attack a fox within range."""
270 if not self.weapons(): 270 if not self.weapons():
271 # Not going to take on a fox bare-winged. 271 # Not going to take on a fox bare-winged.
272 return 272 return
273 # Choose the first weapon equipped. 273 # Choose the first weapon equipped.
274 weapon = self.weapons()[0] 274 weapon = self.weapons()[0]
275 fox = self._find_killable_fox(weapon, gameboard) 275 fox = self._find_killable_fox(weapon)
276 if not fox: 276 if not fox:
277 return 277 return
278 self._fix_face(fox.pos) 278 self._fix_face(fox.pos)
279 if weapon.hit(gameboard, self, fox): 279 if weapon.hit(self.gameboard, self, fox):
280 fox.damage(gameboard) 280 fox.damage()
281 281
282 def reload_weapon(self): 282 def reload_weapon(self):
283 """If we have a weapon that takes ammunition, reload it.""" 283 """If we have a weapon that takes ammunition, reload it."""
284 for weapon in self.weapons(): 284 for weapon in self.weapons():
285 weapon.refresh_ammo() 285 weapon.refresh_ammo()
289 289
290 IMAGE_FILE = 'sprites/equip_egg.png' 290 IMAGE_FILE = 'sprites/equip_egg.png'
291 291
292 SIMPLIFY = Animal.SIMPLIFY + ['timer'] 292 SIMPLIFY = Animal.SIMPLIFY + ['timer']
293 293
294 def __init__(self, pos): 294 def __init__(self, pos, gameboard):
295 Animal.__init__(self, pos) 295 Animal.__init__(self, pos, gameboard)
296 self.timer = 2 296 self.timer = 2
297 297
298 # Eggs don't move 298 # Eggs don't move
299 299
300 def hatch(self): 300 def hatch(self):
301 self.timer -= 1 301 self.timer -= 1
302 if self.timer == 0: 302 if self.timer == 0:
303 return Chicken(self.pos) 303 return Chicken(self.pos, self.gameboard)
304 return None 304 return None
305 305
306 class Fox(Animal): 306 class Fox(Animal):
307 """A fox""" 307 """A fox"""
308 308
322 'henhouse' : 30, # Don't go into a henhouse unless we're going to 322 'henhouse' : 30, # Don't go into a henhouse unless we're going to
323 # catch a chicken there 323 # catch a chicken there
324 'hendominium' : 30, 324 'hendominium' : 30,
325 } 325 }
326 326
327 def __init__(self, pos): 327 def __init__(self, pos, gameboard):
328 Animal.__init__(self, pos) 328 Animal.__init__(self, pos, gameboard)
329 self.landmarks = [self.pos] 329 self.landmarks = [self.pos]
330 self.hunting = True 330 self.hunting = True
331 self.dig_pos = None 331 self.dig_pos = None
332 self.tick = 0 332 self.tick = 0
333 self.safe = False 333 self.safe = False
338 self.building = None 338 self.building = None
339 339
340 def outside(self): 340 def outside(self):
341 return self.building is None 341 return self.building is None
342 342
343 def _game_death(self, gameboard): 343 def _game_death(self):
344 gameboard.kill_fox(self) 344 self.gameboard.kill_fox(self)
345 345
346 def _cost_tile(self, pos, gameboard): 346 def _cost_tile(self, pos):
347 if gameboard.in_bounds(pos): 347 if self.gameboard.in_bounds(pos):
348 this_tile = gameboard.tv.get(pos.to_tile_tuple()) 348 this_tile = self.gameboard.tv.get(pos.to_tile_tuple())
349 cost = self.costs.get(tiles.TILE_MAP[this_tile], 100) 349 cost = self.costs.get(tiles.TILE_MAP[this_tile], 100)
350 else: 350 else:
351 cost = 100 # Out of bounds is expensive 351 cost = 100 # Out of bounds is expensive
352 return cost 352 return cost
353 353
354 def _cost_path(self, path, gameboard): 354 def _cost_path(self, path):
355 """Calculate the cost of a path""" 355 """Calculate the cost of a path"""
356 total = 0 356 total = 0
357 for pos in path: 357 for pos in path:
358 total += self._cost_tile(pos, gameboard) 358 total += self._cost_tile(pos)
359 return total 359 return total
360 360
361 def _gen_path(self, start_pos, final_pos): 361 def _gen_path(self, start_pos, final_pos):
362 """Construct a direct path from start_pos to final_pos, 362 """Construct a direct path from start_pos to final_pos,
363 excluding start_pos""" 363 excluding start_pos"""
364 return start_pos.intermediate_positions(final_pos) 364 return start_pos.intermediate_positions(final_pos)
365 365
366 def _find_best_path_step(self, final_pos, gameboard): 366 def _find_best_path_step(self, final_pos):
367 """Find the cheapest path to final_pos, and return the next step 367 """Find the cheapest path to final_pos, and return the next step
368 along the path.""" 368 along the path."""
369 # We calculate the cost of the direct path 369 # We calculate the cost of the direct path
370 if final_pos.z < self.pos.z: 370 if final_pos.z < self.pos.z:
371 # We need to try heading down. 371 # We need to try heading down.
385 min_cost = 1000 385 min_cost = 1000
386 min_dist = cur_dist 386 min_dist = cur_dist
387 for point in neighbours: 387 for point in neighbours:
388 dist = point.dist(final_pos) 388 dist = point.dist(final_pos)
389 if dist < cur_dist: 389 if dist < cur_dist:
390 cost = self._cost_tile(point, gameboard) 390 cost = self._cost_tile(point)
391 if cost < min_cost or (min_cost == cost and dist < min_dist): 391 if cost < min_cost or (min_cost == cost and dist < min_dist):
392 # Prefer closest of equal cost points 392 # Prefer closest of equal cost points
393 min_dist = dist 393 min_dist = dist
394 min_cost = cost 394 min_cost = cost
395 best = point 395 best = point
396 if min_cost < 20 or not gameboard.in_bounds(self.pos): 396 if min_cost < 20 or not self.gameboard.in_bounds(self.pos):
397 # If we're not on the gameboard yet, there's no point in looking 397 # If we're not on the gameboard yet, there's no point in looking
398 # for an optimal path. 398 # for an optimal path.
399 return best 399 return best
400 # Else expensive step, so think further 400 # Else expensive step, so think further
401 direct_path = self._gen_path(self.pos, final_pos) 401 direct_path = self._gen_path(self.pos, final_pos)
402 min_cost = self._cost_path(direct_path, gameboard) 402 min_cost = self._cost_path(direct_path)
403 min_path = direct_path 403 min_path = direct_path
404 # is there a point nearby that gives us a cheaper direct path? 404 # is there a point nearby that gives us a cheaper direct path?
405 # This is delibrately not finding the optimal path, as I don't 405 # This is delibrately not finding the optimal path, as I don't
406 # want the foxes to be too intelligent, although the implementation 406 # want the foxes to be too intelligent, although the implementation
407 # isn't well optimised yet 407 # isn't well optimised yet
410 poss = [Position(self.pos.x + x, self.pos.y + y, self.pos.z) for 410 poss = [Position(self.pos.x + x, self.pos.y + y, self.pos.z) for
411 x in range(-3, 4) for y in range(-3, 4) if (x, y) != (0, 0)] 411 x in range(-3, 4) for y in range(-3, 4) if (x, y) != (0, 0)]
412 for start in poss: 412 for start in poss:
413 cand_path = self._gen_path(self.pos, start) + \ 413 cand_path = self._gen_path(self.pos, start) + \
414 self._gen_path(start, final_pos) 414 self._gen_path(start, final_pos)
415 cost = self._cost_path(cand_path, gameboard) 415 cost = self._cost_path(cand_path)
416 if cost < min_cost: 416 if cost < min_cost:
417 min_cost = cost 417 min_cost = cost
418 min_path = cand_path 418 min_path = cand_path
419 if not min_path: 419 if not min_path:
420 return final_pos 420 return final_pos
421 return min_path[0] 421 return min_path[0]
422 422
423 def _find_path_to_woodland(self, gameboard): 423 def _find_path_to_woodland(self):
424 """Dive back to woodland through the landmarks""" 424 """Dive back to woodland through the landmarks"""
425 # find the closest point to our current location in walked path 425 # find the closest point to our current location in walked path
426 if self.pos == self.landmarks[-1]: 426 if self.pos == self.landmarks[-1]:
427 if len(self.landmarks) > 1: 427 if len(self.landmarks) > 1:
428 self.landmarks.pop() # Moving to the next landmark 428 self.landmarks.pop() # Moving to the next landmark
429 if not gameboard.in_bounds(self.pos) and not self.hunting: 429 if not self.gameboard.in_bounds(self.pos) and not self.hunting:
430 # Safely out of sight 430 # Safely out of sight
431 self.safe = True 431 self.safe = True
432 return self.pos 432 return self.pos
433 return self._find_best_path_step(self.landmarks[-1], gameboard) 433 return self._find_best_path_step(self.landmarks[-1])
434 434
435 def _select_target(self, gameboard): 435 def _select_target(self):
436 min_dist = 999 436 min_dist = 999
437 self.closest = None 437 self.closest = None
438 for chicken in gameboard.chickens: 438 for chicken in self.gameboard.chickens:
439 dist = chicken.pos.dist(self.pos) 439 dist = chicken.pos.dist(self.pos)
440 if chicken.abode: 440 if chicken.abode:
441 dist += 5 # Prefer free-ranging chickens 441 dist += 5 # Prefer free-ranging chickens
442 if len(chicken.weapons()) > 0: 442 if len(chicken.weapons()) > 0:
443 dist += 5 # Prefer unarmed chickens 443 dist += 5 # Prefer unarmed chickens
444 if dist < min_dist: 444 if dist < min_dist:
445 min_dist = dist 445 min_dist = dist
446 self.closest = chicken 446 self.closest = chicken
447 447
448 def _find_path_to_chicken(self, gameboard): 448 def _find_path_to_chicken(self):
449 """Find the path to the closest chicken""" 449 """Find the path to the closest chicken"""
450 # Find the closest chicken 450 # Find the closest chicken
451 if self.closest not in gameboard.chickens: 451 if self.closest not in self.gameboard.chickens:
452 # Either no target, or someone ate it 452 # Either no target, or someone ate it
453 self._select_target(gameboard) 453 self._select_target()
454 if not self.closest: 454 if not self.closest:
455 # No more chickens, so leave 455 # No more chickens, so leave
456 self.hunting = False 456 self.hunting = False
457 return self.pos 457 return self.pos
458 if self.closest.pos == self.pos: 458 if self.closest.pos == self.pos:
463 if self.closest.pos.z < self.pos.z: 463 if self.closest.pos.z < self.pos.z:
464 new_z = self.pos.z - 1 464 new_z = self.pos.z - 1
465 else: 465 else:
466 new_z = self.pos.z + 1 466 new_z = self.pos.z + 1
467 return Position(self.pos.x, self.pos.y, new_z) 467 return Position(self.pos.x, self.pos.y, new_z)
468 return self._find_best_path_step(self.closest.pos, gameboard) 468 return self._find_best_path_step(self.closest.pos)
469 469
470 def attack(self, gameboard): 470 def attack(self):
471 """Attack a chicken""" 471 """Attack a chicken"""
472 chicken = gameboard.get_animal_at_pos(self.pos, 'chicken') 472 chicken = self.gameboard.get_animal_at_pos(self.pos, 'chicken')
473 if chicken: 473 if chicken:
474 # Always attack a chicken we step on, even if not hunting 474 # Always attack a chicken we step on, even if not hunting
475 self._catch_chicken(chicken, gameboard) 475 self._catch_chicken(chicken)
476 476
477 def _catch_chicken(self, chicken, gameboard): 477 def _catch_chicken(self, chicken):
478 """Catch a chicken""" 478 """Catch a chicken"""
479 chicken.damage(gameboard) 479 chicken.damage()
480 self.closest = None 480 self.closest = None
481 self.hunting = False 481 self.hunting = False
482 self.last_steps = [] # Forget history here 482 self.last_steps = [] # Forget history here
483 483
484 def _update_pos(self, gameboard, new_pos): 484 def _update_pos(self, new_pos):
485 """Update the position, making sure we don't step on other foxes""" 485 """Update the position, making sure we don't step on other foxes"""
486 if new_pos == self.pos: 486 if new_pos == self.pos:
487 # We're not moving, so we can skip all the checks 487 # We're not moving, so we can skip all the checks
488 return new_pos 488 return new_pos
489 blocked = gameboard.get_animal_at_pos(new_pos, 'fox') is not None 489 blocked = self.gameboard.get_animal_at_pos(new_pos, 'fox') is not None
490 if not blocked and new_pos.z == self.pos.z: 490 if not blocked and new_pos.z == self.pos.z:
491 # We're only worried about loops when not on a ladder 491 # We're only worried about loops when not on a ladder
492 blocked = new_pos in self.last_steps 492 blocked = new_pos in self.last_steps
493 final_pos = new_pos 493 final_pos = new_pos
494 if blocked: 494 if blocked:
504 self.pos.z == 0] 504 self.pos.z == 0]
505 # find the cheapest point in moves that's not blocked 505 # find the cheapest point in moves that's not blocked
506 final_pos = None 506 final_pos = None
507 min_cost = 1000 507 min_cost = 1000
508 for poss in moves: 508 for poss in moves:
509 if gameboard.get_animal_at_pos(poss, 'fox'): 509 if self.gameboard.get_animal_at_pos(poss, 'fox'):
510 continue # blocked 510 continue # blocked
511 cost = self._cost_tile(poss, gameboard) 511 cost = self._cost_tile(poss)
512 if cost < min_cost: 512 if cost < min_cost:
513 min_cost = cost 513 min_cost = cost
514 final_pos = poss 514 final_pos = poss
515 if cost == min_cost and random.randint(0, 1) > 0: 515 if cost == min_cost and random.randint(0, 1) > 0:
516 # Add some randomness in this case 516 # Add some randomness in this case
517 final_pos = poss 517 final_pos = poss
518 if not final_pos: 518 if not final_pos:
519 # No good choice, so stay put 519 # No good choice, so stay put
520 return self.pos 520 return self.pos
521 if gameboard.in_bounds(final_pos): 521 if self.gameboard.in_bounds(final_pos):
522 this_tile = gameboard.tv.get(final_pos.to_tile_tuple()) 522 this_tile = self.gameboard.tv.get(final_pos.to_tile_tuple())
523 else: 523 else:
524 this_tile = tiles.REVERSE_TILE_MAP['woodland'] 524 this_tile = tiles.REVERSE_TILE_MAP['woodland']
525 if tiles.TILE_MAP[this_tile] == 'broken fence' and self.hunting: 525 if tiles.TILE_MAP[this_tile] == 'broken fence' and self.hunting:
526 # We'll head back towards the holes we make/find 526 # We'll head back towards the holes we make/find
527 self.landmarks.append(final_pos) 527 self.landmarks.append(final_pos)
528 elif tiles.TILE_MAP[this_tile] == 'fence' and not self.dig_pos: 528 elif tiles.TILE_MAP[this_tile] == 'fence' and not self.dig_pos:
529 return self._dig(gameboard, final_pos) 529 return self._dig(final_pos)
530 self.last_steps.append(final_pos) 530 self.last_steps.append(final_pos)
531 if len(self.last_steps) > 3: 531 if len(self.last_steps) > 3:
532 self.last_steps.pop(0) 532 self.last_steps.pop(0)
533 return final_pos 533 return final_pos
534 534
535 def _dig(self, gameboard, dig_pos): 535 def _dig(self, dig_pos):
536 """Setup dig parameters, to be overridden if needed""" 536 """Setup dig parameters, to be overridden if needed"""
537 self.tick = 5 537 self.tick = 5
538 self.dig_pos = dig_pos 538 self.dig_pos = dig_pos
539 return self.pos 539 return self.pos
540 540
541 def _make_hole(self, gameboard): 541 def _make_hole(self):
542 """Make a hole in the fence""" 542 """Make a hole in the fence"""
543 fence = gameboard.get_building(self.dig_pos.to_tile_tuple()) 543 fence = self.gameboard.get_building(self.dig_pos.to_tile_tuple())
544 # Another fox could have made the same hole this turn 544 # Another fox could have made the same hole this turn
545 if fence: 545 if fence:
546 fence.damage(gameboard.tv) 546 fence.damage(self.gameboard.tv)
547 self.dig_pos = None 547 self.dig_pos = None
548 548
549 def move(self, gameboard): 549 def move(self):
550 """Foxes will aim to move towards the closest henhouse or free 550 """Foxes will aim to move towards the closest henhouse or free
551 chicken""" 551 chicken"""
552 if self.safe: 552 if self.safe:
553 # We're safe, so do nothing 553 # We're safe, so do nothing
554 return 554 return
557 self.tick -= 1 557 self.tick -= 1
558 # We're still digging through the fence 558 # We're still digging through the fence
559 # Check the another fox hasn't dug a hole for us 559 # Check the another fox hasn't dug a hole for us
560 # We're too busy digging to notice if a hole appears nearby, 560 # We're too busy digging to notice if a hole appears nearby,
561 # but we'll notice if the fence we're digging vanishes 561 # but we'll notice if the fence we're digging vanishes
562 this_tile = gameboard.tv.get(self.dig_pos.to_tile_tuple()) 562 this_tile = self.gameboard.tv.get(self.dig_pos.to_tile_tuple())
563 if tiles.TILE_MAP[this_tile] != 'fence': 563 if tiles.TILE_MAP[this_tile] != 'fence':
564 self.tick = 0 564 self.tick = 0
565 else: 565 else:
566 # We've dug through the fence, so make a hole 566 # We've dug through the fence, so make a hole
567 self._make_hole(gameboard) 567 self._make_hole()
568 return 568 return
569 elif self.hunting: 569 elif self.hunting:
570 desired_pos = self._find_path_to_chicken(gameboard) 570 desired_pos = self._find_path_to_chicken()
571 else: 571 else:
572 desired_pos = self._find_path_to_woodland(gameboard) 572 desired_pos = self._find_path_to_woodland()
573 final_pos = self._update_pos(gameboard, desired_pos) 573 final_pos = self._update_pos(desired_pos)
574 self._fix_face(final_pos) 574 self._fix_face(final_pos)
575 self.pos = final_pos 575 self.pos = final_pos
576 change_visible = False 576 change_visible = False
577 # See if we're entering/leaving a building 577 # See if we're entering/leaving a building
578 building = gameboard.get_building(final_pos.to_tile_tuple()) 578 building = self.gameboard.get_building(final_pos.to_tile_tuple())
579 if building and self.outside(): 579 if building and self.outside():
580 # Check if we need to enter 580 # Check if we need to enter
581 if self.closest and not self.closest.outside() and \ 581 if self.closest and not self.closest.outside() and \
582 self.closest.abode.building is building: 582 self.closest.abode.building is building:
583 building.add_predator(self) 583 building.add_predator(self)
593 else: 593 else:
594 # we've moved away from the building we were in 594 # we've moved away from the building we were in
595 self.building.remove_predator(self) 595 self.building.remove_predator(self)
596 change_visible = True 596 change_visible = True
597 if change_visible: 597 if change_visible:
598 gameboard.set_visibility(self) 598 self.gameboard.set_visibility(self)
599 599
600 600
601 class NinjaFox(Fox): 601 class NinjaFox(Fox):
602 """Ninja foxes are hard to see""" 602 """Ninja foxes are hard to see"""
603 603
613 CONFIG_NAME = 'sapper fox' 613 CONFIG_NAME = 'sapper fox'
614 614
615 costs = Fox.costs.copy() 615 costs = Fox.costs.copy()
616 costs['fence'] = 2 616 costs['fence'] = 2
617 617
618 def _dig(self, gameboard, dig_pos): 618 def _dig(self, dig_pos):
619 """Setup dig parameters, to be overridden if needed""" 619 """Setup dig parameters, to be overridden if needed"""
620 self.tick = 0 # Costs us nothing to go through a fence. 620 self.tick = 0 # Costs us nothing to go through a fence.
621 self.dig_pos = dig_pos 621 self.dig_pos = dig_pos
622 self.DIG_ANIMATION(gameboard.tv, dig_pos.to_tile_tuple()) 622 self.DIG_ANIMATION(self.gameboard.tv, dig_pos.to_tile_tuple())
623 self._make_hole(gameboard) 623 self._make_hole()
624 return self.pos 624 return self.pos
625 625
626 class GreedyFox(Fox): 626 class GreedyFox(Fox):
627 """Greedy foxes eat more chickens""" 627 """Greedy foxes eat more chickens"""
628 CONFIG_NAME = 'greedy fox' 628 CONFIG_NAME = 'greedy fox'
629 629
630 def __init__(self, pos): 630 def __init__(self, pos, gameboard):
631 Fox.__init__(self, pos) 631 Fox.__init__(self, pos, gameboard)
632 self.chickens_eaten = 0 632 self.chickens_eaten = 0
633 633
634 def _catch_chicken(self, chicken, gameboard): 634 def _catch_chicken(self, chicken):
635 chicken.damage(gameboard) 635 chicken.damage()
636 self.closest = None 636 self.closest = None
637 self.chickens_eaten += 1 637 self.chickens_eaten += 1
638 if self.chickens_eaten > 2: 638 if self.chickens_eaten > 2:
639 self.hunting = False 639 self.hunting = False
640 self.last_steps = [] 640 self.last_steps = []
643 """The Rinkhals has eclectic tastes""" 643 """The Rinkhals has eclectic tastes"""
644 STEALTH = 80 644 STEALTH = 80
645 IMAGE_FILE = 'sprites/rinkhals.png' 645 IMAGE_FILE = 'sprites/rinkhals.png'
646 CONFIG_NAME = 'rinkhals' 646 CONFIG_NAME = 'rinkhals'
647 647
648 def _select_target(self, gameboard): 648 def _select_target(self):
649 """The Rinkhals eats eggs""" 649 """The Rinkhals eats eggs"""
650 min_dist = 999 650 min_dist = 999
651 self.closest = None 651 self.closest = None
652 for chicken in gameboard.chickens: 652 for chicken in self.gameboard.chickens:
653 dist = chicken.pos.dist(self.pos) 653 dist = chicken.pos.dist(self.pos)
654 if not chicken.eggs: 654 if not chicken.eggs:
655 dist += 100 # The closest eggs have to be *far* away to be safe 655 dist += 100 # The closest eggs have to be *far* away to be safe
656 if dist < min_dist: 656 if dist < min_dist:
657 min_dist = dist 657 min_dist = dist
658 self.closest = chicken 658 self.closest = chicken
659 659
660 def _catch_chicken(self, chicken, gameboard): 660 def _catch_chicken(self, chicken):
661 """The Rinkhals eats eggs, but does not harm chickens""" 661 """The Rinkhals eats eggs, but does not harm chickens"""
662 chicken.remove_eggs(gameboard) 662 chicken.remove_eggs()
663 self.closest = None 663 self.closest = None
664 self.hunting = False 664 self.hunting = False
665 self.last_steps = [] 665 self.last_steps = []
666 666
667 def _dig(self, gameboard, dig_pos): 667 def _dig(self, dig_pos):
668 """Snakes ignore fences""" 668 """Snakes ignore fences"""
669 return dig_pos 669 return dig_pos
670 670
671 def damage(self, gameboard): 671 def damage(self):
672 """The Rinkhals is invincible!""" 672 """The Rinkhals is invincible!"""
673 return True 673 return True
674 674
675 def _get_vision_param(parameter, watcher): 675 def _get_vision_param(parameter, watcher):
676 param = getattr(watcher, parameter) 676 param = getattr(watcher, parameter)