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