source: gamelib/animal.py @ 71:00cf9d7f22dc

Last change on this file since 71:00cf9d7f22dc was 71:00cf9d7f22dc, checked in by Neil Muller <drnlmuller@…>, 11 years ago

Expand comment

File size: 8.3 KB
Line 
1"""Class for the various animals in the game"""
2
3from pgu.vid import Sprite
4from pgu.algo import getline
5
6import imagecache
7import tiles
8from misc import Position
9
10class Animal(Sprite):
11    """Base class for animals"""
12
13    def __init__(self, image_left, image_right, tile_pos):
14        # Create the animal somewhere far off screen
15        Sprite.__init__(self, image_left, (-1000, -1000))
16        self.image_left = image_left
17        self.image_right = image_right
18        self.pos = Position(tile_pos[0], tile_pos[1])
19
20    def loop(self, tv, _sprite):
21        ppos = tv.tile_to_view(self.pos.to_tuple())
22        self.rect.x = ppos[0]
23        self.rect.y = ppos[1]
24
25    def move(self, state):
26        """Given the game state, return a new position for the object"""
27        # Default is not to move
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)
36
37class Chicken(Animal):
38    """A chicken"""
39
40    def __init__(self, pos):
41        image_left = imagecache.load_image('sprites/chkn.png')
42        image_right = imagecache.load_image('sprites/chkn.png',
43                ("right_facing",))
44        Animal.__init__(self, image_left, image_right, pos)
45
46    def move(self, gameboard):
47        """A free chicken will move away from other free chickens"""
48        pass
49
50class Egg(Animal):
51    """An egg"""
52
53    def __init__(self, pos):
54        image = imagecache.load_image('sprites/egg.png')
55        Animal.__init__(self, image, image, pos)
56
57    # Eggs don't move
58
59class Fox(Animal):
60    """A fox"""
61
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
72    def __init__(self, pos):
73        image_left = imagecache.load_image('sprites/fox.png')
74        image_right = imagecache.load_image('sprites/fox.png',
75                ("right_facing",))
76        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        # This is delibrately not finding the optimal path, as I don't
119        # want the foxes to be too intelligent, although the implementation
120        # isn't well optimised yet
121        poss = [Position(x, y) for x in range(self.pos.x - 2, self.pos.x + 3)
122                for y in range(self.pos.y - 2, self.pos.y + 3)]
123        for start in poss:
124            if start == self.pos:
125                continue # don't repeat work we don't need to
126            cand_path = self._gen_path(self.pos, start) + \
127                    self._gen_path(start, final_pos)
128            cost = self._cost_path(cand_path, gameboard)
129            if cost < min_cost:
130                min_cost = cost
131                min_path = cand_path
132        if not min_path:
133            return final_pos
134        return min_path[0]
135
136    def _find_path_to_woodland(self, gameboard):
137        """Dive back to woodland through the landmarks"""
138        # find the closest point to our current location in walked path
139        if self.pos == self.landmarks[-1]:
140            if len(self.landmarks) > 1:
141                self.landmarks.pop() # Moving to the next landmark
142            else:
143                # Safely back at the start
144                return self.pos
145        return self._find_best_path_step(self.landmarks[-1], gameboard)
146
147    def _find_path_to_chicken(self, gameboard):
148        """Find the path to the closest chicken"""
149        # Find the closest chicken
150        min_dist = 999
151        closest = None
152        for chicken in gameboard.chickens:
153            dist = chicken.pos.dist(self.pos)
154            if dist < min_dist:
155                min_dist = dist
156                closest = chicken
157        if not closest:
158            # No more chickens, so leave
159            self.hunting = False
160            return self.pos
161        if closest.pos == self.pos:
162            # Caught a chicken
163            gameboard.remove_chicken(closest)
164            self.hunting = False
165            return self.pos
166        return self._find_best_path_step(closest.pos, gameboard)
167
168    def _update_pos(self, gameboard, new_pos):
169        """Update the position, making sure we don't step on other foxes"""
170        final_pos = new_pos
171        moves = [Position(x, y) for x in range(self.pos.x-1, self.pos.x + 2)
172                for y in range(self.pos.y-1, self.pos.y + 2)]
173        blocked = False
174        for fox in gameboard.foxes:
175            if fox is not self and fox.pos == new_pos:
176                blocked = True
177            if fox.pos in moves:
178                moves.remove(fox.pos)
179        if blocked:
180            # find the closest point in moves to new_pos that's not a fence
181            final_pos = None
182            dist = 10
183            for poss in moves:
184                this_tile = gameboard.tv.get(poss.to_tuple())
185                new_dist = poss.dist(new_pos)
186                if new_dist < dist:
187                    dist = new_dist
188                    final_pos = poss
189        this_tile = gameboard.tv.get(final_pos.to_tuple())
190        if tiles.TILE_MAP[this_tile] == 'broken fence' and self.hunting:
191            # We'll head back towards the holes we make/find
192            self.landmarks.append(final_pos)
193        elif tiles.TILE_MAP[this_tile] == 'fence' and not self.dig_pos:
194            self.tick = 5
195            self.dig_pos = final_pos
196            return self.pos
197        return final_pos
198
199    def move(self, gameboard):
200        """Foxes will aim to move towards the closest henhouse or free
201           chicken"""
202        if self.dig_pos:
203            if self.tick:
204                # We're digging through the fence
205                self.tick -= 1
206                # Check the another fox hasn't dug a hole for us
207                # We're top busy digging to notice if a hole appears nearby,
208                # but we'll notice if the fence we're digging vanishes
209                this_tile = gameboard.tv.get(self.dig_pos.to_tuple())
210                if tiles.TILE_MAP[this_tile] == 'broken fence':
211                    self.tick = 0 
212            else:
213                # We've dug through the fence, so make a hole
214                gameboard.tv.set(self.dig_pos.to_tuple(),
215                        tiles.REVERSE_TILE_MAP['broken fence'])
216                self.dig_pos = None
217            return 
218        if self.hunting:
219            desired_pos = self._find_path_to_chicken(gameboard)
220        else:
221            desired_pos = self._find_path_to_woodland(gameboard)
222        final_pos = self._update_pos(gameboard, desired_pos)
223        self._fix_face(final_pos)
224        self.pos = final_pos
225       
226           
Note: See TracBrowser for help on using the repository browser.