Mercurial > rinkhals
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) |