Mercurial > rinkhals
comparison gamelib/animal.py @ 70:d92a2f973cc4
Make foxes move 'better' and break fences
author | Neil Muller <drnlmuller@gmail.com> |
---|---|
date | Mon, 31 Aug 2009 22:14:41 +0000 |
parents | f20dd3dcb118 |
children | 00cf9d7f22dc |
comparison
equal
deleted
inserted
replaced
69:18db99fda6bd | 70:d92a2f973cc4 |
---|---|
1 """Class for the various animals in the game""" | 1 """Class for the various animals in the game""" |
2 | 2 |
3 from pgu.vid import Sprite | 3 from pgu.vid import Sprite |
4 from pgu.algo import getline | |
4 | 5 |
5 import imagecache | 6 import imagecache |
7 import tiles | |
8 from misc import Position | |
6 | 9 |
7 class Animal(Sprite): | 10 class Animal(Sprite): |
8 """Base class for animals""" | 11 """Base class for animals""" |
9 | 12 |
10 def __init__(self, image_left, image_right, pos): | 13 def __init__(self, image_left, image_right, tile_pos): |
11 # Create the animal somewhere far off screen | 14 # Create the animal somewhere far off screen |
12 Sprite.__init__(self, image_left, (-1000, -1000)) | 15 Sprite.__init__(self, image_left, (-1000, -1000)) |
13 self.image_left = image_left | 16 self.image_left = image_left |
14 self.image_right = image_right | 17 self.image_right = image_right |
15 self.pos = pos | 18 self.pos = Position(tile_pos[0], tile_pos[1]) |
16 | 19 |
17 def loop(self, tv, _sprite): | 20 def loop(self, tv, _sprite): |
18 ppos = tv.tile_to_view(self.pos) | 21 ppos = tv.tile_to_view(self.pos.to_tuple()) |
19 self.rect.x = ppos[0] | 22 self.rect.x = ppos[0] |
20 self.rect.y = ppos[1] | 23 self.rect.y = ppos[1] |
21 | 24 |
22 def move(self, state): | 25 def move(self, state): |
23 """Given the game state, return a new position for the object""" | 26 """Given the game state, return a new position for the object""" |
24 # Default is not to move | 27 # 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) | |
26 | 36 |
27 class Chicken(Animal): | 37 class Chicken(Animal): |
28 """A chicken""" | 38 """A chicken""" |
29 | 39 |
30 def __init__(self, pos): | 40 def __init__(self, pos): |
31 image_left = imagecache.load_image('sprites/chkn.png') | 41 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",)) | |
33 Animal.__init__(self, image_left, image_right, pos) | 44 Animal.__init__(self, image_left, image_right, pos) |
34 | 45 |
35 def move(self, gameboard): | 46 def move(self, gameboard): |
36 """A free chicken will move away from other free chickens""" | 47 """A free chicken will move away from other free chickens""" |
37 return self.pos | 48 pass |
38 | 49 |
39 class Egg(Animal): | 50 class Egg(Animal): |
40 """An egg""" | 51 """An egg""" |
41 | 52 |
42 def __init__(self, pos): | 53 def __init__(self, pos): |
46 # Eggs don't move | 57 # Eggs don't move |
47 | 58 |
48 class Fox(Animal): | 59 class Fox(Animal): |
49 """A fox""" | 60 """A fox""" |
50 | 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 | |
51 def __init__(self, pos): | 72 def __init__(self, pos): |
52 image_left = imagecache.load_image('sprites/fox.png') | 73 image_left = imagecache.load_image('sprites/fox.png') |
53 image_right = imagecache.load_image('sprites/fox.png', ("right_facing",)) | 74 image_right = imagecache.load_image('sprites/fox.png', |
54 self.full = False | 75 ("right_facing",)) |
55 Animal.__init__(self, image_left, image_right, pos) | 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 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 | |
56 | 195 |
57 def move(self, gameboard): | 196 def move(self, gameboard): |
58 """Foxes will aim to move towards the closest henhouse or free | 197 """Foxes will aim to move towards the closest henhouse or free |
59 chicken""" | 198 chicken""" |
60 if self.full: | 199 if self.dig_pos: |
61 return | 200 if self.tick: |
62 # Find the closest chicken | 201 # We're digging through the fence |
63 min_dist = 999 | 202 self.tick -= 1 |
64 min_vec = None | 203 # Check the another fox hasn't dug a hole for us |
65 closest = None | 204 # We're top busy digging to notice if a hole appears nearby, |
66 for chicken in gameboard.chickens: | 205 # but we'll notice if the fence we're digging vanishes |
67 vec = (chicken.pos[0] - self.pos[0], chicken.pos[1] - self.pos[1]) | 206 this_tile = gameboard.tv.get(self.dig_pos.to_tuple()) |
68 dist = abs(vec[0]) + abs(vec[1]) | 207 if tiles.TILE_MAP[this_tile] == 'broken fence': |
69 if dist < min_dist: | 208 self.tick = 0 |
70 min_dist = dist | 209 else: |
71 min_vec = vec | 210 # We've dug through the fence, so make a hole |
72 closest = chicken | 211 gameboard.tv.set(self.dig_pos.to_tuple(), |
73 xpos, ypos = self.pos | 212 tiles.REVERSE_TILE_MAP['broken fence']) |
74 if min_vec[0] < 0: | 213 self.dig_pos = None |
75 xpos -= 1 | 214 return |
76 self.setimage(self.image_left) | 215 if self.hunting: |
77 elif min_vec[0] > 0: | 216 desired_pos = self._find_path_to_chicken(gameboard) |
78 xpos += 1 | 217 else: |
79 self.setimage(self.image_right) | 218 desired_pos = self._find_path_to_woodland(gameboard) |
80 if min_vec[1] < 0: | 219 final_pos = self._update_pos(gameboard, desired_pos) |
81 ypos -= 1 | 220 self._fix_face(final_pos) |
82 elif min_vec[1] > 0: | 221 self.pos = final_pos |
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) | |
97 | 222 |
98 | 223 |