Changeset 70:d92a2f973cc4


Ignore:
Timestamp:
Aug 31, 2009, 10:14:41 PM (11 years ago)
Author:
Neil Muller <drnlmuller@…>
Branch:
default
Convert:
svn:b4e93282-eac8-4b8b-b765-0f5d36de2b68@71
Message:

Make foxes move 'better' and break fences

File:
1 edited

Legend:

Unmodified
Added
Removed
  • gamelib/animal.py

    r53 r70  
    22
    33from pgu.vid import Sprite
     4from pgu.algo import getline
    45
    56import imagecache
     7import tiles
     8from misc import Position
    69
    710class Animal(Sprite):
    811    """Base class for animals"""
    912
    10     def __init__(self, image_left, image_right, pos):
     13    def __init__(self, image_left, image_right, tile_pos):
    1114        # Create the animal somewhere far off screen
    1215        Sprite.__init__(self, image_left, (-1000, -1000))
    1316        self.image_left = image_left
    1417        self.image_right = image_right
    15         self.pos = pos
     18        self.pos = Position(tile_pos[0], tile_pos[1])
    1619
    1720    def loop(self, tv, _sprite):
    18         ppos = tv.tile_to_view(self.pos)
     21        ppos = tv.tile_to_view(self.pos.to_tuple())
    1922        self.rect.x = ppos[0]
    2023        self.rect.y = ppos[1]
     
    2326        """Given the game state, return a new position for the object"""
    2427        # Default is not to move
    25         return self.pos
     28        pass
     29
     30    def _fix_face(self, final_pos):
     31        """Set the face correctly"""
     32        if final_pos.left_of(self.pos):
     33            self.setimage(self.image_left)
     34        elif final_pos.right_of(self.pos):
     35            self.setimage(self.image_right)
    2636
    2737class Chicken(Animal):
     
    3040    def __init__(self, pos):
    3141        image_left = imagecache.load_image('sprites/chkn.png')
    32         image_right = imagecache.load_image('sprites/chkn.png', ("right_facing",))
     42        image_right = imagecache.load_image('sprites/chkn.png',
     43                ("right_facing",))
    3344        Animal.__init__(self, image_left, image_right, pos)
    3445
    3546    def move(self, gameboard):
    3647        """A free chicken will move away from other free chickens"""
    37         return self.pos
     48        pass
    3849
    3950class Egg(Animal):
     
    4960    """A fox"""
    5061
     62    costs = {
     63            # weighting for movement calculation
     64            'grassland' : 2,
     65            'woodland' : 1, # Try to keep to the woods if possible
     66            'broken fence' : 1,
     67            'fence' : 10,
     68            'guardtower' : 1, # We can pass under towers
     69            'henhouse' : 1,
     70            }
     71
    5172    def __init__(self, pos):
    5273        image_left = imagecache.load_image('sprites/fox.png')
    53         image_right = imagecache.load_image('sprites/fox.png', ("right_facing",))
    54         self.full = False
     74        image_right = imagecache.load_image('sprites/fox.png',
     75                ("right_facing",))
    5576        Animal.__init__(self, image_left, image_right, pos)
     77        self.landmarks = [self.pos]
     78        self.hunting = True
     79        self.dig_pos = None
     80        self.tick = 0
     81
     82    def _cost_path(self, path, gameboard):
     83        """Calculate the cost of a path"""
     84        total = 0
     85        for pos in path:
     86            if gameboard.in_bounds(pos):
     87                this_tile = gameboard.tv.get(pos.to_tuple())
     88                cost = self.costs.get(tiles.TILE_MAP[this_tile], 100)
     89            else:
     90                cost = 100 # Out of bounds is expensive
     91            total += cost
     92        return total
     93
     94    def _gen_path(self, start_pos, final_pos):
     95        """Construct a direct path from start_pos to final_pos,
     96           excluding start_pos"""
     97        if abs(start_pos.x - final_pos.x) < 2 and \
     98                abs(start_pos.y - final_pos.y) < 2:
     99            # pgu gets this case wrong on occasion.
     100            return [final_pos]
     101        start = start_pos.to_tuple()
     102        end = final_pos.to_tuple()
     103        points = getline(start, end)
     104        points.remove(start) # exclude start_pos
     105        if end not in points:
     106            # Rounding errors in getline cause this
     107            points.append(end)
     108        return [Position(x[0], x[1]) for x in points]
     109
     110    def _find_best_path_step(self, final_pos, gameboard):
     111        """Find the cheapest path to final_pos, and return the next step
     112           along the path."""
     113        # We calculate the cost of the direct path
     114        direct_path = self._gen_path(self.pos, final_pos)
     115        min_cost = self._cost_path(direct_path, gameboard)
     116        min_path = direct_path
     117        # is there a point nearby that gives us a cheaper direct path?
     118        poss = [Position(x, y) for x in range(self.pos.x - 2, self.pos.x + 3)
     119                for y in range(self.pos.y - 2, self.pos.y + 3)]
     120        for start in poss:
     121            if start == self.pos:
     122                continue # don't repeat work we don't need to
     123            cand_path = self._gen_path(self.pos, start) + \
     124                    self._gen_path(start, final_pos)
     125            cost = self._cost_path(cand_path, gameboard)
     126            if cost < min_cost:
     127                min_cost = cost
     128                min_path = cand_path
     129        if not min_path:
     130            return final_pos
     131        return min_path[0]
     132
     133    def _find_path_to_woodland(self, gameboard):
     134        """Dive back to woodland through the landmarks"""
     135        # find the closest point to our current location in walked path
     136        if self.pos == self.landmarks[-1]:
     137            if len(self.landmarks) > 1:
     138                self.landmarks.pop() # Moving to the next landmark
     139            else:
     140                # Safely back at the start
     141                return self.pos
     142        return self._find_best_path_step(self.landmarks[-1], gameboard)
     143
     144    def _find_path_to_chicken(self, gameboard):
     145        """Find the path to the closest chicken"""
     146        # Find the closest chicken
     147        min_dist = 999
     148        closest = None
     149        for chicken in gameboard.chickens:
     150            dist = chicken.pos.dist(self.pos)
     151            if dist < min_dist:
     152                min_dist = dist
     153                closest = chicken
     154        if not closest:
     155            # No more chickens, so leave
     156            self.hunting = False
     157            return self.pos
     158        if closest.pos == self.pos:
     159            # Caught a chicken
     160            gameboard.remove_chicken(closest)
     161            self.hunting = False
     162            return self.pos
     163        return self._find_best_path_step(closest.pos, gameboard)
     164
     165    def _update_pos(self, gameboard, new_pos):
     166        """Update the position, making sure we don't step on other foxes"""
     167        final_pos = new_pos
     168        moves = [Position(x, y) for x in range(self.pos.x-1, self.pos.x + 2)
     169                for y in range(self.pos.y-1, self.pos.y + 2)]
     170        blocked = False
     171        for fox in gameboard.foxes:
     172            if fox is not self and fox.pos == new_pos:
     173                blocked = True
     174            if fox.pos in moves:
     175                moves.remove(fox.pos)
     176        if blocked:
     177            # find the closest point in moves to new_pos that's not a fence
     178            final_pos = None
     179            dist = 10
     180            for poss in moves:
     181                this_tile = gameboard.tv.get(poss.to_tuple())
     182                new_dist = poss.dist(new_pos)
     183                if new_dist < dist:
     184                    dist = new_dist
     185                    final_pos = poss
     186        this_tile = gameboard.tv.get(final_pos.to_tuple())
     187        if tiles.TILE_MAP[this_tile] == 'broken fence' and self.hunting:
     188            # We'll head back towards the holes we make/find
     189            self.landmarks.append(final_pos)
     190        elif tiles.TILE_MAP[this_tile] == 'fence' and not self.dig_pos:
     191            self.tick = 5
     192            self.dig_pos = final_pos
     193            return self.pos
     194        return final_pos
    56195
    57196    def move(self, gameboard):
    58197        """Foxes will aim to move towards the closest henhouse or free
    59           chicken"""
    60         if self.full:
    61             return
    62         # Find the closest chicken
    63         min_dist = 999
    64         min_vec = None
    65         closest = None
    66         for chicken in gameboard.chickens:
    67             vec = (chicken.pos[0] - self.pos[0], chicken.pos[1] - self.pos[1])
    68             dist = abs(vec[0]) + abs(vec[1])
    69             if dist < min_dist:
    70                 min_dist = dist
    71                 min_vec = vec
    72                 closest = chicken
    73         xpos, ypos = self.pos
    74         if min_vec[0] < 0:
    75             xpos -= 1
    76             self.setimage(self.image_left)
    77         elif min_vec[0] > 0:
    78             xpos += 1
    79             self.setimage(self.image_right)
    80         if min_vec[1] < 0:
    81             ypos -= 1
    82         elif min_vec[1] > 0:
    83             ypos += 1
    84         if closest.pos == self.pos:
    85             gameboard.remove_chicken(closest)
    86             self.full = True
    87         for fox in gameboard.foxes:
    88             if fox is not self:
    89                 if fox.pos[0] == xpos and fox.pos[1] == ypos:
    90                     if xpos != self.pos[0]:
    91                         xpos = self.pos[0]
    92                     elif ypos != self.pos[1]:
    93                         ypos = self.pos[1]
    94                     else: # We move a step away
    95                         xpos += 1
    96         self.pos = (xpos, ypos)
     198           chicken"""
     199        if self.dig_pos:
     200            if self.tick:
     201                # We're digging through the fence
     202                self.tick -= 1
     203                # Check the another fox hasn't dug a hole for us
     204                # We're top busy digging to notice if a hole appears nearby,
     205                # but we'll notice if the fence we're digging vanishes
     206                this_tile = gameboard.tv.get(self.dig_pos.to_tuple())
     207                if tiles.TILE_MAP[this_tile] == 'broken fence':
     208                    self.tick = 0
     209            else:
     210                # We've dug through the fence, so make a hole
     211                gameboard.tv.set(self.dig_pos.to_tuple(),
     212                        tiles.REVERSE_TILE_MAP['broken fence'])
     213                self.dig_pos = None
     214            return
     215        if self.hunting:
     216            desired_pos = self._find_path_to_chicken(gameboard)
     217        else:
     218            desired_pos = self._find_path_to_woodland(gameboard)
     219        final_pos = self._update_pos(gameboard, desired_pos)
     220        self._fix_face(final_pos)
     221        self.pos = final_pos
    97222       
    98223           
Note: See TracChangeset for help on using the changeset viewer.