Mercurial > rinkhals
comparison gamelib/animal.py @ 507:d3ceb9e9c48e
The fox move rewrite
author | Neil Muller <drnlmuller@gmail.com> |
---|---|
date | Thu, 26 Nov 2009 22:52:31 +0000 |
parents | 3ed6c011106d |
children | 8fbff3505d1d |
comparison
equal
deleted
inserted
replaced
506:3b5717d742b2 | 507:d3ceb9e9c48e |
---|---|
10 import sound | 10 import sound |
11 import equipment | 11 import equipment |
12 import animations | 12 import animations |
13 import serializer | 13 import serializer |
14 import constants | 14 import constants |
15 | |
16 | |
17 NEIGHBOUR_4 = [Position(-1, 0), Position(1, 0), Position(0, 1), Position(0, -1)] | |
18 | |
19 | |
20 NEIGHBOUR_8 = [Position(-1, 0), Position(1, 0), Position(0, 1), Position(0, -1), | |
21 Position(1, 1), Position(1, -1), Position(-1, 1), Position(-1, -1)] | |
22 | |
23 | |
24 TILE_FENCE = tiles.REVERSE_TILE_MAP['fence'] | |
15 | 25 |
16 class Animal(Sprite, serializer.Simplifiable): | 26 class Animal(Sprite, serializer.Simplifiable): |
17 """Base class for animals""" | 27 """Base class for animals""" |
18 | 28 |
19 STEALTH = 0 | 29 STEALTH = 0 |
78 | 88 |
79 def _game_death(self): | 89 def _game_death(self): |
80 # Call appropriate gameboard cleanup here. | 90 # Call appropriate gameboard cleanup here. |
81 pass | 91 pass |
82 | 92 |
83 def move(self, state): | 93 def move(self): |
84 """Given the game state, return a new position for the object""" | 94 """Return a new position for the object""" |
85 # Default is not to move | 95 # Default is not to move |
86 pass | 96 pass |
87 | 97 |
88 def attack(self): | 98 def attack(self): |
89 """Given the game state, attack a suitable target""" | 99 """Given the game state, attack a suitable target""" |
315 | 325 |
316 costs = { | 326 costs = { |
317 # weighting for movement calculation | 327 # weighting for movement calculation |
318 'grassland' : 2, | 328 'grassland' : 2, |
319 'woodland' : 1, # Try to keep to the woods if possible | 329 'woodland' : 1, # Try to keep to the woods if possible |
320 'broken fence' : 2, | 330 'broken fence' : 1, |
321 'fence' : 25, | 331 'fence' : 25, |
322 'guardtower' : 2, # We can pass under towers | 332 'guardtower' : 2, # We can pass under towers |
323 'henhouse' : 30, # Don't go into a henhouse unless we're going to | 333 'henhouse' : 30, # Don't go into a henhouse unless we're going to |
324 # catch a chicken there | 334 # catch a chicken there |
325 'hendominium' : 30, | 335 'hendominium' : 30, |
326 } | 336 } |
327 | 337 |
328 def __init__(self, pos, gameboard): | 338 def __init__(self, pos, gameboard): |
329 Animal.__init__(self, pos, gameboard) | 339 Animal.__init__(self, pos, gameboard) |
330 self.landmarks = [self.pos] | 340 self.start_pos = self.pos |
341 self.target = None | |
331 self.hunting = True | 342 self.hunting = True |
332 self.dig_pos = None | 343 self.dig_pos = None |
333 self.tick = 0 | 344 self.tick = 0 |
334 self.safe = False | 345 self.safe = False |
335 self.closest = None | 346 self.closest = None |
336 self.last_steps = [] | |
337 # Foxes don't occupy places in the same way chickens do, but they | 347 # Foxes don't occupy places in the same way chickens do, but they |
338 # can still be inside | 348 # can still be inside |
339 self.building = None | 349 self.building = None |
350 self._last_steps = [] | |
351 self.path = [] | |
340 | 352 |
341 def outside(self): | 353 def outside(self): |
342 return self.building is None | 354 return self.building is None |
343 | 355 |
344 def _game_death(self): | 356 def _game_death(self): |
350 cost = self.costs.get(tiles.TILE_MAP[this_tile], 100) | 362 cost = self.costs.get(tiles.TILE_MAP[this_tile], 100) |
351 else: | 363 else: |
352 cost = 100 # Out of bounds is expensive | 364 cost = 100 # Out of bounds is expensive |
353 return cost | 365 return cost |
354 | 366 |
355 def _cost_path(self, path): | 367 def _is_fence(self, pos): |
356 """Calculate the cost of a path""" | 368 if self.gameboard.in_bounds(pos): |
357 total = 0 | 369 this_tile = self.gameboard.tv.get(pos.to_tile_tuple()) |
358 for pos in path: | 370 return this_tile == TILE_FENCE |
359 total += self._cost_tile(pos) | 371 return False |
360 return total | 372 |
361 | 373 def _check_steps(self, step, border_func, end_func, max_steps): |
362 def _gen_path(self, start_pos, final_pos): | 374 steps = 0 |
363 """Construct a direct path from start_pos to final_pos, | 375 cur_pos = self.pos |
364 excluding start_pos""" | 376 path = [] |
365 return start_pos.intermediate_positions(final_pos) | 377 while steps < max_steps: |
366 | 378 if not border_func(cur_pos): |
367 def _find_best_path_step(self, final_pos): | 379 # Not walking the edge |
368 """Find the cheapest path to final_pos, and return the next step | 380 # Is there a 4-NEIGHBOUR, to path[-1], not in the path that |
369 along the path.""" | 381 # is a continue |
370 # We calculate the cost of the direct path | 382 if not path: |
371 if final_pos.z < self.pos.z: | 383 # Rest of search will cover neighbours |
372 # We need to try heading down. | 384 return None |
373 return Position(self.pos.x, self.pos.y, self.pos.z - 1) | 385 for cand in [path[-1] + x for x in NEIGHBOUR_4]: |
374 if final_pos.x == self.pos.x and final_pos.y == self.pos.y and \ | 386 if cand in path: |
375 final_pos.z > self.pos.z: | 387 continue |
376 # We try heading up | 388 if border_func(cand): |
377 return Position(self.pos.x, self.pos.y, self.pos.z + 1) | 389 cur_pos = cand |
378 cur_dist = final_pos.dist(self.pos) | 390 break |
379 if cur_dist < 2: | 391 return None |
380 # We're right ontop of our target, so just go there | 392 path.append(cur_pos) |
381 return final_pos | 393 if end_func(cur_pos): |
382 # Find the cheapest spot close to us that moves us closer to the target | 394 # is there an 8-NEIGHBOUR that also satisfies end_func and is |
383 neighbours = [Position(self.pos.x + x, self.pos.y + y, self.pos.z) for | 395 # closer to target |
384 x in range(-1, 2) for y in range(-1, 2) if (x, y) != (0, 0)] | 396 dist = self.target.dist(cur_pos) |
385 best_pos = self.pos | 397 fin_pos = None |
398 for pos in [cur_pos + x for x in NEIGHBOUR_8]: | |
399 if end_func(pos) and self.target.dist(pos) < dist: | |
400 fin_pos = pos | |
401 dist = self.target.dist(pos) | |
402 if fin_pos: | |
403 path.append(fin_pos) | |
404 return path | |
405 steps += 1 | |
406 cur_pos = cur_pos + step | |
407 return None | |
408 | |
409 def _search_for_path(self, border_func, end_func, max_steps): | |
410 paths = [None] * 4 | |
411 paths[0] = self._check_steps(Position(-1, 0), border_func, end_func, max_steps) | |
412 paths[1] = self._check_steps(Position(0, -1), border_func, end_func, max_steps) | |
413 paths[2] = self._check_steps(Position(1, 0), border_func, end_func, max_steps) | |
414 paths[3] = self._check_steps(Position(0, 1), border_func, end_func, max_steps) | |
415 cands = [x for x in paths if x is not None] | |
416 if not cands: | |
417 return None | |
418 elif len(cands) == 1: | |
419 return cands[0][1:] | |
420 # take the end point closest to our target | |
421 final_path = cands[0] | |
422 min_dist = final_path[-1].dist(self.target) | |
423 for this_path in cands[1:]: | |
424 dist = this_path[-1].dist(self.target) | |
425 if dist < min_dist: | |
426 min_dist = dist | |
427 final_path = this_path | |
428 elif dist == min_dist and random.randint(0, 1) == 0: | |
429 final_path = this_path | |
430 return final_path[1:] # path's include self.pos | |
431 | |
432 def _find_nearest_corner(self): | |
433 """Find the nearest corner of the bulding""" | |
434 COST_MARGIN = 25 | |
435 def border(pos): | |
436 cost = self._cost_tile(pos) | |
437 if cost >= COST_MARGIN: | |
438 return False # in building isn't border | |
439 for tpos in [pos + step for step in NEIGHBOUR_8]: | |
440 if self.gameboard.in_bounds(tpos): | |
441 cost = self._cost_tile(tpos) | |
442 if cost >= COST_MARGIN: | |
443 return True | |
444 return False | |
445 | |
446 def corner(pos): | |
447 # A corner is not 4-connected to a building | |
448 if not border(pos): | |
449 return False | |
450 for tpos in [pos + step for step in NEIGHBOUR_4]: | |
451 if self.gameboard.in_bounds(tpos): | |
452 cost = self._cost_tile(tpos) | |
453 if cost >= COST_MARGIN: | |
454 return False | |
455 return True | |
456 | |
457 # We check left, then right and take the shortest if both are valid | |
458 return self._search_for_path(border, corner, 6) | |
459 | |
460 def _find_fence_gap(self): | |
461 # We search for a gap in the fence | |
462 # we know we are next to fence. A gap in the fence is | |
463 # a point that borders the fence where the fence is not | |
464 # 4-connected | |
465 | |
466 COST_MARGIN = 25 | |
467 | |
468 def border(pos): | |
469 if self._is_fence(pos) or self._cost_tile(pos) >= COST_MARGIN: | |
470 return False | |
471 for tpos in [pos + step for step in NEIGHBOUR_8]: | |
472 if self._is_fence(tpos) or self._cost_tile(tpos) >= COST_MARGIN: | |
473 print 'border pos', pos, self._cost_tile(pos) | |
474 return True | |
475 return False | |
476 | |
477 def is_gap(pos): | |
478 # A gap neighbours only fence tiles which has < 2 4-neighbours | |
479 if self._is_fence(pos): | |
480 return False | |
481 fence_neighbours = [0] | |
482 for tpos in [pos + step for step in NEIGHBOUR_8]: | |
483 if self._is_fence(tpos): | |
484 connections = 0 | |
485 for fpos in [tpos + step for step in NEIGHBOUR_4]: | |
486 if self.gameboard.in_bounds(fpos): | |
487 if self._is_fence(fpos) or self._cost_tile(tpos) >= COST_MARGIN: | |
488 # Expensive building is considered fence | |
489 connections += 1 | |
490 else: | |
491 # Fence connecting to out of bounds counts as fence | |
492 connections += 1 | |
493 fence_neighbours.append(connections) | |
494 return max(fence_neighbours) < 2 | |
495 | |
496 return self._search_for_path(border, is_gap, 7) | |
497 | |
498 def _find_min_cost_neighbour(self, target): | |
499 """Find the minimum cost neighbour that's closer to target""" | |
500 cur_dist = target.dist(self.pos) | |
501 neighbours = [self.pos + step for step in NEIGHBOUR_8] | |
386 min_cost = 1000 | 502 min_cost = 1000 |
387 min_dist = cur_dist | 503 min_dist = cur_dist |
504 best = self.pos | |
388 for point in neighbours: | 505 for point in neighbours: |
389 dist = point.dist(final_pos) | 506 if point in self._last_steps: |
390 if dist < cur_dist: | 507 continue |
508 dist = point.dist(target) | |
509 if dist <= min_dist: | |
391 cost = self._cost_tile(point) | 510 cost = self._cost_tile(point) |
392 if cost < min_cost or (min_cost == cost and dist < min_dist): | 511 if cost < min_cost or (min_cost == cost and dist < min_dist): |
393 # Prefer closest of equal cost points | 512 # Prefer closest of equal cost points |
394 min_dist = dist | 513 min_dist = dist |
395 min_cost = cost | 514 min_cost = cost |
396 best = point | 515 best = point |
397 if min_cost < 20 or not self.gameboard.in_bounds(self.pos): | 516 elif min_cost == cost and random.randint(0, 1) == 0: |
517 # Be slightly non-deterministic when presented with | |
518 # equal choices | |
519 best = point | |
520 return best, min_cost | |
521 | |
522 def _find_best_path_step(self): | |
523 """Find the cheapest path to final_pos, and return the next step | |
524 along the path.""" | |
525 if self.path: | |
526 next_step = self.path.pop(0) | |
527 if next_step.dist(self.pos) < 2: | |
528 return next_step | |
529 else: | |
530 # Been bounced off the path | |
531 self.path = [] | |
532 if self.target.z < self.pos.z: | |
533 # We need to try heading down. | |
534 return Position(self.pos.x, self.pos.y, self.pos.z - 1) | |
535 if self.target.x == self.pos.x and self.target.y == self.pos.y and \ | |
536 self.target.z > self.pos.z: | |
537 # We try heading up | |
538 return Position(self.pos.x, self.pos.y, self.pos.z + 1) | |
539 cur_dist = self.target.dist(self.pos) | |
540 best, min_cost = self._find_min_cost_neighbour(self.target) | |
541 if cur_dist < 2: | |
542 # We're right ontop of our target, so just go there | |
543 return self.target | |
544 # Find the cheapest spot close to us that moves us closer to the target | |
545 if min_cost < 20 or not self.gameboard.in_bounds(self.pos) \ | |
546 or not self.gameboard.in_bounds(best): | |
398 # If we're not on the gameboard yet, there's no point in looking | 547 # If we're not on the gameboard yet, there's no point in looking |
399 # for an optimal path. | 548 # for an optimal path. |
400 return best | 549 return best |
401 # Else expensive step, so think further | 550 # Else expensive step, so think further |
402 direct_path = self._gen_path(self.pos, final_pos) | 551 if self._is_fence(best): |
403 min_cost = self._cost_path(direct_path) | 552 path = self._find_fence_gap() |
404 min_path = direct_path | 553 elif min_cost == 30: |
405 # is there a point nearby that gives us a cheaper direct path? | 554 # building |
406 # This is delibrately not finding the optimal path, as I don't | 555 path = self._find_nearest_corner() |
407 # want the foxes to be too intelligent, although the implementation | 556 else: |
408 # isn't well optimised yet | 557 # We're looping |
409 # FIXME: Currently, this introduces loops, but the memory kind | 558 self._last_steps = [] |
410 # avoids that. Fixing this is the next goal. | |
411 poss = [Position(self.pos.x + x, self.pos.y + y, self.pos.z) for | |
412 x in range(-3, 4) for y in range(-3, 4) if (x, y) != (0, 0)] | |
413 for start in poss: | |
414 cand_path = self._gen_path(self.pos, start) + \ | |
415 self._gen_path(start, final_pos) | |
416 cost = self._cost_path(cand_path) | |
417 if cost < min_cost: | |
418 min_cost = cost | |
419 min_path = cand_path | |
420 if not min_path: | |
421 return final_pos | |
422 return min_path[0] | |
423 | |
424 def _find_path_to_woodland(self): | |
425 """Dive back to woodland through the landmarks""" | |
426 # find the closest point to our current location in walked path | |
427 if self.pos == self.landmarks[-1]: | |
428 if len(self.landmarks) > 1: | |
429 self.landmarks.pop() # Moving to the next landmark | |
430 if not self.gameboard.in_bounds(self.pos) and not self.hunting: | |
431 # Safely out of sight | |
432 self.safe = True | |
433 return self.pos | 559 return self.pos |
434 return self._find_best_path_step(self.landmarks[-1]) | 560 if path: |
435 | 561 self.path = path[1:] # exclude 1st step |
436 def _select_target(self): | 562 return path[0] |
563 return best | |
564 | |
565 def _calc_next_move(self): | |
566 """Find the path to the target""" | |
567 if self.hunting: | |
568 # Check if we need to update our idea of a target | |
569 if not self.closest or self.closest not in self.gameboard.chickens: | |
570 # Either no target, or someone ate it | |
571 self._select_prey() | |
572 elif not self.target: | |
573 self.target = self.closest.pos | |
574 if not self.target: | |
575 self.target = self.start_pos | |
576 self._last_steps = [] | |
577 if self.target == self.pos: | |
578 # No need to move, but we will need to update the target | |
579 self.target = None | |
580 return self.pos | |
581 if self.target.to_tile_tuple() == self.pos.to_tile_tuple(): | |
582 # Only differ in z, so next step is in z | |
583 if self.target.z < self.pos.z: | |
584 new_z = self.pos.z - 1 | |
585 else: | |
586 new_z = self.pos.z + 1 | |
587 return Position(self.pos.x, self.pos.y, new_z) | |
588 return self._find_best_path_step() | |
589 | |
590 def _select_prey(self): | |
437 min_dist = 999 | 591 min_dist = 999 |
438 self.closest = None | 592 self.closest = None |
439 for chicken in self.gameboard.chickens: | 593 for chicken in self.gameboard.chickens: |
440 dist = chicken.pos.dist(self.pos) | 594 dist = chicken.pos.dist(self.pos) |
441 if chicken.abode: | 595 if chicken.abode: |
443 if len(chicken.weapons()) > 0: | 597 if len(chicken.weapons()) > 0: |
444 dist += 5 # Prefer unarmed chickens | 598 dist += 5 # Prefer unarmed chickens |
445 if dist < min_dist: | 599 if dist < min_dist: |
446 min_dist = dist | 600 min_dist = dist |
447 self.closest = chicken | 601 self.closest = chicken |
448 | 602 self.target = chicken.pos |
449 def _find_path_to_chicken(self): | |
450 """Find the path to the closest chicken""" | |
451 # Find the closest chicken | |
452 if self.closest not in self.gameboard.chickens: | |
453 # Either no target, or someone ate it | |
454 self._select_target() | |
455 if not self.closest: | 603 if not self.closest: |
456 # No more chickens, so leave | 604 # No more chickens, so leave |
457 self.hunting = False | 605 self.hunting = False |
606 self.target = self.start_pos | |
458 return self.pos | 607 return self.pos |
459 if self.closest.pos == self.pos: | |
460 # No need to move | |
461 return self.pos | |
462 if self.closest.pos.to_tile_tuple() == self.pos.to_tile_tuple(): | |
463 # Only differ in z, so next step is in z | |
464 if self.closest.pos.z < self.pos.z: | |
465 new_z = self.pos.z - 1 | |
466 else: | |
467 new_z = self.pos.z + 1 | |
468 return Position(self.pos.x, self.pos.y, new_z) | |
469 return self._find_best_path_step(self.closest.pos) | |
470 | 608 |
471 def attack(self): | 609 def attack(self): |
472 """Attack a chicken""" | 610 """Attack a chicken""" |
473 chicken = self.gameboard.get_animal_at_pos(self.pos, 'chicken') | 611 chicken = self.gameboard.get_animal_at_pos(self.pos, 'chicken') |
474 if chicken: | 612 if chicken: |
478 def _catch_chicken(self, chicken): | 616 def _catch_chicken(self, chicken): |
479 """Catch a chicken""" | 617 """Catch a chicken""" |
480 chicken.damage() | 618 chicken.damage() |
481 self.closest = None | 619 self.closest = None |
482 self.hunting = False | 620 self.hunting = False |
483 self.last_steps = [] # Forget history here | 621 self.target = self.start_pos |
622 self._last_steps = [] | |
484 | 623 |
485 def _update_pos(self, new_pos): | 624 def _update_pos(self, new_pos): |
486 """Update the position, making sure we don't step on other foxes""" | 625 """Update the position, making sure we don't step on other foxes""" |
626 if not self.hunting and not self.gameboard.in_bounds(self.pos): | |
627 self.safe = True | |
628 return self.pos | |
487 if new_pos == self.pos: | 629 if new_pos == self.pos: |
488 # We're not moving, so we can skip all the checks | 630 # We're not moving, so we can skip all the checks |
489 return new_pos | 631 return new_pos |
490 blocked = self.gameboard.get_animal_at_pos(new_pos, 'fox') is not None | 632 blocked = self.gameboard.get_animal_at_pos(new_pos, 'fox') is not None |
491 if not blocked and new_pos.z == self.pos.z: | |
492 # We're only worried about loops when not on a ladder | |
493 blocked = new_pos in self.last_steps | |
494 final_pos = new_pos | 633 final_pos = new_pos |
495 if blocked: | 634 if blocked: |
496 if new_pos.z != self.pos.z: | 635 if new_pos.z != self.pos.z or self.pos.z != 0: |
497 # We can only move up and down a ladder | 636 # We can only move up and down a ladder |
498 moves = [Position(self.pos.x, self.pos.y, z) for z | 637 moves = [Position(self.pos.x, self.pos.y, z) for z |
499 in range(self.pos.z-1, self.pos.z + 2) if z >= 0] | 638 in range(self.pos.z-1, self.pos.z + 2) if z >= 0] |
500 else: | 639 else: |
501 moves = [Position(x, y) for x in range(self.pos.x-1, self.pos.x + 2) | 640 moves = [self.pos + step for step in NEIGHBOUR_8] |
502 for y in range(self.pos.y-1, self.pos.y + 2) | |
503 if Position(x,y) != self.pos and | |
504 Position(x, y) not in self.last_steps and | |
505 self.pos.z == 0] | |
506 # find the cheapest point in moves that's not blocked | 641 # find the cheapest point in moves that's not blocked |
507 final_pos = None | 642 final_pos = None |
508 min_cost = 1000 | 643 min_cost = 1000 |
509 for poss in moves: | 644 for poss in moves: |
510 if self.gameboard.get_animal_at_pos(poss, 'fox'): | 645 if self.gameboard.get_animal_at_pos(poss, 'fox'): |
517 # Add some randomness in this case | 652 # Add some randomness in this case |
518 final_pos = poss | 653 final_pos = poss |
519 if not final_pos: | 654 if not final_pos: |
520 # No good choice, so stay put | 655 # No good choice, so stay put |
521 return self.pos | 656 return self.pos |
522 if self.gameboard.in_bounds(final_pos): | 657 if self._is_fence(final_pos) and not self.dig_pos: |
523 this_tile = self.gameboard.tv.get(final_pos.to_tile_tuple()) | |
524 else: | |
525 this_tile = tiles.REVERSE_TILE_MAP['woodland'] | |
526 if tiles.TILE_MAP[this_tile] == 'broken fence' and self.hunting: | |
527 # We'll head back towards the holes we make/find | |
528 self.landmarks.append(final_pos) | |
529 elif tiles.TILE_MAP[this_tile] == 'fence' and not self.dig_pos: | |
530 return self._dig(final_pos) | 658 return self._dig(final_pos) |
531 self.last_steps.append(final_pos) | 659 self._last_steps.append(final_pos) |
532 if len(self.last_steps) > 3: | 660 if len(self._last_steps) > 6: |
533 self.last_steps.pop(0) | 661 self._last_steps.pop(0) |
534 return final_pos | 662 return final_pos |
535 | 663 |
536 def _dig(self, dig_pos): | 664 def _dig(self, dig_pos): |
537 """Setup dig parameters, to be overridden if needed""" | 665 """Setup dig parameters, to be overridden if needed""" |
538 self.tick = 5 | 666 self.tick = 5 |
558 self.tick -= 1 | 686 self.tick -= 1 |
559 # We're still digging through the fence | 687 # We're still digging through the fence |
560 # Check the another fox hasn't dug a hole for us | 688 # Check the another fox hasn't dug a hole for us |
561 # We're too busy digging to notice if a hole appears nearby, | 689 # We're too busy digging to notice if a hole appears nearby, |
562 # but we'll notice if the fence we're digging vanishes | 690 # but we'll notice if the fence we're digging vanishes |
563 this_tile = self.gameboard.tv.get(self.dig_pos.to_tile_tuple()) | 691 if not self._is_fence(self.dig_pos): |
564 if tiles.TILE_MAP[this_tile] != 'fence': | |
565 self.tick = 0 | 692 self.tick = 0 |
566 else: | 693 else: |
567 # We've dug through the fence, so make a hole | 694 # We've dug through the fence, so make a hole |
568 self._make_hole() | 695 self._make_hole() |
569 return | 696 return |
570 elif self.hunting: | |
571 desired_pos = self._find_path_to_chicken() | |
572 else: | 697 else: |
573 desired_pos = self._find_path_to_woodland() | 698 desired_pos = self._calc_next_move() |
574 final_pos = self._update_pos(desired_pos) | 699 final_pos = self._update_pos(desired_pos) |
575 self._fix_face(final_pos) | 700 self._fix_face(final_pos) |
576 self.pos = final_pos | 701 self.pos = final_pos |
577 change_visible = False | 702 change_visible = False |
578 # See if we're entering/leaving a building | 703 # See if we're entering/leaving a building |
636 chicken.damage() | 761 chicken.damage() |
637 self.closest = None | 762 self.closest = None |
638 self.chickens_eaten += 1 | 763 self.chickens_eaten += 1 |
639 if self.chickens_eaten > 2: | 764 if self.chickens_eaten > 2: |
640 self.hunting = False | 765 self.hunting = False |
641 self.last_steps = [] | 766 self.target = self.start_pos |
767 self._last_steps = [] | |
642 | 768 |
643 class Rinkhals(Fox): | 769 class Rinkhals(Fox): |
644 """The Rinkhals has eclectic tastes""" | 770 """The Rinkhals has eclectic tastes""" |
645 STEALTH = 80 | 771 STEALTH = 80 |
646 IMAGE_FILE = 'sprites/rinkhals.png' | 772 IMAGE_FILE = 'sprites/rinkhals.png' |
647 CONFIG_NAME = 'rinkhals' | 773 CONFIG_NAME = 'rinkhals' |
648 | 774 |
649 def _select_target(self): | 775 costs = Fox.costs.copy() |
776 costs['fence'] = 2 | |
777 | |
778 def _select_prey(self): | |
650 """The Rinkhals eats eggs""" | 779 """The Rinkhals eats eggs""" |
651 min_dist = 999 | 780 min_dist = 999 |
652 self.closest = None | 781 self.closest = None |
653 for chicken in self.gameboard.chickens: | 782 for chicken in self.gameboard.chickens: |
654 dist = chicken.pos.dist(self.pos) | 783 dist = chicken.pos.dist(self.pos) |
655 if not chicken.eggs: | 784 if not chicken.eggs: |
656 dist += 100 # The closest eggs have to be *far* away to be safe | 785 dist += 100 # The closest eggs have to be *far* away to be safe |
657 if dist < min_dist: | 786 if dist < min_dist: |
658 min_dist = dist | 787 min_dist = dist |
659 self.closest = chicken | 788 self.closest = chicken |
789 self.target = self.closest.pos | |
660 | 790 |
661 def _catch_chicken(self, chicken): | 791 def _catch_chicken(self, chicken): |
662 """The Rinkhals eats eggs, but does not harm chickens""" | 792 """The Rinkhals eats eggs, but does not harm chickens""" |
663 chicken.remove_eggs() | 793 chicken.remove_eggs() |
664 self.closest = None | 794 self.closest = None |
665 self.hunting = False | 795 self.hunting = False |
666 self.last_steps = [] | 796 self.target = self.start_pos |
797 self._last_steps = [] | |
667 | 798 |
668 def _dig(self, dig_pos): | 799 def _dig(self, dig_pos): |
669 """Snakes ignore fences""" | 800 """Snakes ignore fences""" |
670 return dig_pos | 801 return dig_pos |
671 | 802 |