source: gamelib/gameboard.py @ 194:5ec222ca07cd

Last change on this file since 194:5ec222ca07cd was 194:5ec222ca07cd, checked in by Neil Muller <drnlmuller@…>, 11 years ago

Selling eggs at the right price

File size: 27.1 KB
Line 
1import random
2
3import pygame
4from pygame.locals import MOUSEBUTTONDOWN, MOUSEMOTION, KEYDOWN, K_UP, K_DOWN, K_LEFT, K_RIGHT
5from pgu import gui
6
7import data
8import tiles
9import icons
10import constants
11import buildings
12import animal
13import equipment
14import sound
15import cursors
16import sprite_cursor
17
18class OpaqueLabel(gui.Label):
19    def __init__(self, value, **params):
20        gui.Label.__init__(self, value, **params)
21        if 'width' in params:
22            self._width = params['width']
23        if 'height' in params:
24            self._height = params['height']
25        self._set_size()
26
27    def _set_size(self):
28        width, height = self.font.size(self.value)
29        width = getattr(self, '_width', width)
30        height = getattr(self, '_height', height)
31        self.style.width, self.style.height = width, height
32
33    def paint(self, s):
34        s.fill(self.style.background)
35        if self.style.align > 0:
36            r = s.get_rect()
37            w, _ = self.font.size(self.value)
38            s = s.subsurface(r.move((r.w-w, 0)).clip(r))
39        gui.Label.paint(self, s)
40
41    def update_value(self, value):
42        self.value = value
43        self._set_size()
44        self.repaint()
45
46def mklabel(text="", **params):
47    params.setdefault('color', constants.FG_COLOR)
48    params.setdefault('width', GameBoard.TOOLBAR_WIDTH/2)
49    return OpaqueLabel(text, **params)
50
51def mkcountupdate(counter):
52    def update_counter(self, value):
53        getattr(self, counter).update_value("%s  " % value)
54        self.repaint()
55    return update_counter
56
57class ToolBar(gui.Table):
58    def __init__(self, gameboard, **params):
59        gui.Table.__init__(self, **params)
60        self.gameboard = gameboard
61        self.cash_counter = mklabel(align=1)
62        self.chicken_counter = mklabel(align=1)
63        self.egg_counter = mklabel(align=1)
64        self.day_counter = mklabel(align=1)
65        self.killed_foxes = mklabel(align=1)
66
67        self.tr()
68        self.td(gui.Spacer(self.rect.w/2, 0))
69        self.td(gui.Spacer(self.rect.w/2, 0))
70        self.add_counter(mklabel("Day:"), self.day_counter)
71        self.add_counter(mklabel("Groats:"), self.cash_counter)
72        self.add_counter(mklabel("Eggs:"), self.egg_counter)
73        self.add_counter(icons.CHKN_ICON, self.chicken_counter)
74        self.add_counter(icons.KILLED_FOX, self.killed_foxes)
75        self.add_spacer(20)
76
77        self.add_tool_button("Move Hen", constants.TOOL_PLACE_ANIMALS,
78                cursors.cursors['select'])
79        self.add_tool_button("Cut Trees", constants.TOOL_LOGGING)
80        self.add_spacer(20)
81
82        self.add_heading("Sell ...")
83        self.add_tool_button("Chicken", constants.TOOL_SELL_CHICKEN,
84                cursors.cursors['select'])
85        self.add_tool_button("Egg", constants.TOOL_SELL_EGG,
86                cursors.cursors['select'])
87        self.add_tool_button("Building", constants.TOOL_SELL_BUILDING,
88                cursors.cursors['select'])
89        self.add_tool_button("Equipment", constants.TOOL_SELL_EQUIPMENT)
90        self.add_spacer(20)
91
92        self.add_heading("Buy ...")
93        self.add_tool_button("Fence", constants.TOOL_BUY_FENCE)
94        for building_cls in buildings.BUILDINGS:
95            self.add_tool_button(building_cls.NAME.title(), building_cls,
96                    cursors.cursors.get('build', None))
97        for equipment_cls in equipment.EQUIPMENT:
98            self.add_tool_button(equipment_cls.NAME.title(), equipment_cls,
99                    cursors.cursors.get(equipment_cls.NAME, None))
100        self.add_spacer(30)
101
102        self.add_button("Finished Day", self.day_done)
103
104    def day_done(self):
105        import engine
106        pygame.event.post(engine.START_NIGHT)
107
108    update_cash_counter = mkcountupdate('cash_counter')
109    update_fox_counter = mkcountupdate('killed_foxes')
110    update_chicken_counter = mkcountupdate('chicken_counter')
111    update_egg_counter = mkcountupdate('egg_counter')
112    update_day_counter = mkcountupdate('day_counter')
113
114    def add_spacer(self, height):
115        self.tr()
116        self.td(gui.Spacer(0, height), colspan=2)
117
118    def add_heading(self, text):
119        self.tr()
120        self.td(mklabel(text), colspan=2)
121
122    def add_tool_button(self, text, tool, cursor=None):
123        self.add_button(text, lambda: self.gameboard.set_selected_tool(tool,
124            cursor))
125
126    def add_button(self, text, func):
127        button = gui.Button(text, width=self.rect.w, style={"padding_left": 0})
128        button.connect(gui.CLICK, func)
129        self.tr()
130        self.td(button, align=-1, colspan=2)
131
132    def add_counter(self, icon, label):
133        self.tr()
134        self.td(icon, width=self.rect.w/2)
135        self.td(label, width=self.rect.w/2)
136
137    def resize(self, width=None, height=None):
138        width, height = gui.Table.resize(self, width, height)
139        width = GameBoard.TOOLBAR_WIDTH
140        return width, height
141
142
143class VidWidget(gui.Widget):
144    def __init__(self, gameboard, vid, **params):
145        gui.Widget.__init__(self, **params)
146        self.gameboard = gameboard
147        self.vid = vid
148        self.vid.bounds = pygame.Rect((0, 0), vid.tile_to_view(vid.size))
149
150    def paint(self, surface):
151        self.vid.paint(surface)
152
153    def update(self, surface):
154        return self.vid.update(surface)
155
156    def move_view(self, x, y):
157        self.vid.view.move_ip((x, y))
158
159    def event(self, e):
160        if e.type == MOUSEBUTTONDOWN:
161            self.gameboard.use_tool(e)
162        elif e.type == MOUSEMOTION and self.gameboard.sprite_cursor:
163            self.gameboard.update_sprite_cursor(e)
164
165
166class GameBoard(object):
167    TILE_DIMENSIONS = (20, 20)
168    TOOLBAR_WIDTH = 140
169
170    GRASSLAND = tiles.REVERSE_TILE_MAP['grassland']
171    FENCE = tiles.REVERSE_TILE_MAP['fence']
172    WOODLAND = tiles.REVERSE_TILE_MAP['woodland']
173    BROKEN_FENCE = tiles.REVERSE_TILE_MAP['broken fence']
174
175    def __init__(self, main_app):
176        self.disp = main_app
177        self.tv = tiles.FarmVid()
178        self.tv.tga_load_tiles(data.filepath('tiles.tga'), self.TILE_DIMENSIONS)
179        self.tv.png_folder_load_tiles(data.filepath('tiles'))
180        self.tv.tga_load_level(data.filepath('level1.tga'))
181        self.create_display()
182
183        self.selected_tool = None
184        self.animal_to_place = None
185        self.sprite_cursor = None
186        self.chickens = set()
187        self.foxes = set()
188        self.buildings = []
189        self.cash = 0
190        self.eggs = 0
191        self.days = 0
192        self.killed_foxes = 0
193        self.add_cash(constants.STARTING_CASH)
194
195        self.fix_buildings()
196
197        self.add_some_chickens()
198
199    def get_top_widget(self):
200        return self.top_widget
201
202    def create_display(self):
203        width, height = self.disp.rect.w, self.disp.rect.h
204        tbl = gui.Table()
205        tbl.tr()
206        self.toolbar = ToolBar(self, width=self.TOOLBAR_WIDTH)
207        tbl.td(self.toolbar, valign=-1)
208        self.tvw = VidWidget(self, self.tv, width=width-self.TOOLBAR_WIDTH, height=height)
209        tbl.td(self.tvw)
210        self.top_widget = tbl
211
212    def update(self):
213        self.tvw.reupdate()
214
215    def loop(self):
216        self.tv.loop()
217
218    def set_selected_tool(self, tool, cursor):
219        self.selected_tool = tool
220        self.animal_to_place = None
221        if cursor:
222            pygame.mouse.set_cursor(*cursor)
223        else:
224            pygame.mouse.set_cursor(*cursors.cursors['arrow'])
225        if self.sprite_cursor:
226            self.tv.sprites.remove(self.sprite_cursor)
227            self.sprite_cursor = None
228        if buildings.is_building(tool):
229            self.sprite_cursor = sprite_cursor.SpriteCursor(tool.IMAGE, self.tv)
230            self.tv.sprites.append(self.sprite_cursor)
231
232    def reset_cursor(self):
233        pygame.mouse.set_cursor(*cursors.cursors['arrow'])
234
235    def update_sprite_cursor(self, e):
236        tile_pos = self.tv.screen_to_tile(e.pos)
237        self.sprite_cursor.set_pos(tile_pos)
238
239    def in_bounds(self, pos):
240        """Check if a position is within the game boundaries"""
241        if pos.x < 0 or pos.y < 0:
242            return False
243        width, height = self.tv.size
244        if pos.x >= width or pos.y >= height:
245            return False
246        return True
247
248    def use_tool(self, e):
249        if e.button == 3: # Right button
250            self.selected_tool = None
251            self.reset_cursor()
252        elif e.button != 1: # Left button
253            return
254        if self.selected_tool == constants.TOOL_SELL_CHICKEN:
255            self.sell_chicken(self.tv.screen_to_tile(e.pos))
256        elif self.selected_tool == constants.TOOL_SELL_EGG:
257            self.sell_egg(self.tv.screen_to_tile(e.pos))
258        elif self.selected_tool == constants.TOOL_PLACE_ANIMALS:
259            self.place_animal(self.tv.screen_to_tile(e.pos))
260        elif self.selected_tool == constants.TOOL_BUY_FENCE:
261            self.buy_fence(self.tv.screen_to_tile(e.pos))
262        elif self.selected_tool == constants.TOOL_SELL_BUILDING:
263            self.sell_building(self.tv.screen_to_tile(e.pos))
264        elif self.selected_tool == constants.TOOL_SELL_EQUIPMENT:
265            self.sell_equipment(self.tv.screen_to_tile(e.pos))
266        elif self.selected_tool == constants.TOOL_LOGGING:
267            self.logging_forest(self.tv.screen_to_tile(e.pos))
268        elif buildings.is_building(self.selected_tool):
269            self.buy_building(self.tv.screen_to_tile(e.pos), self.selected_tool)
270        elif equipment.is_equipment(self.selected_tool):
271            self.buy_equipment(self.tv.screen_to_tile(e.pos), self.selected_tool)
272
273    def get_outside_chicken(self, tile_pos):
274        for chick in self.chickens:
275            if chick.covers(tile_pos) and chick.outside():
276                return chick
277        return None
278
279    def get_building(self, tile_pos):
280        for building in self.buildings:
281            if building.covers(tile_pos):
282                return building
283        return None
284
285    def sell_chicken(self, tile_pos):
286
287        def do_sell(chicken):
288            if not chicken:
289                return False # sanity check
290            if len(self.chickens) == 1:
291                print "You can't sell your last chicken!"
292                return False
293            self.add_cash(constants.SELL_PRICE_CHICKEN)
294            sound.play_sound("sell-chicken.ogg")
295            self.remove_chicken(chicken)
296            return True
297
298        chick = self.get_outside_chicken(tile_pos)
299        if chick is None:
300            building = self.get_building(tile_pos)
301            if building and building.NAME in buildings.HENHOUSES:
302                self.open_building_dialog(building, do_sell)
303            return
304        do_sell(chick)
305
306
307    def sell_egg(self, tile_pos):
308        def do_sell(chicken):
309            if chicken.egg:
310                # We sell the egg
311                self.add_cash(constants.SELL_PRICE_EGG)
312                sound.play_sound("sell-chicken.ogg")
313                chicken.egg = None
314                self.eggs -= 1
315                self.toolbar.update_egg_counter(self.eggs)
316                # Force update
317                self.toolbar.chsize()
318            return False
319
320        building = self.get_building(tile_pos)
321        if building and building.NAME in buildings.HENHOUSES:
322            self.open_building_dialog(building, do_sell)
323
324    def place_animal(self, tile_pos):
325        """Handle an TOOL_PLACE_ANIMALS click.
326
327           This will either select an animal or
328           place a selected animal in a building.
329           """
330        chicken = self.get_outside_chicken(tile_pos)
331        if chicken:
332            if chicken is self.animal_to_place:
333                self.animal_to_place = None
334                pygame.mouse.set_cursor(*cursors.cursors['select'])
335            else:
336                self.animal_to_place = chicken
337                pygame.mouse.set_cursor(*cursors.cursors['chicken'])
338            return
339        building = self.get_building(tile_pos)
340        if building:
341            self.open_building_dialog(building)
342            return
343        if self.tv.get(tile_pos) == self.GRASSLAND:
344            if self.animal_to_place is not None:
345                occupant = self.animal_to_place
346                if occupant.abode is not None:
347                    occupant.abode.clear_occupant()
348                occupant.set_pos(tile_pos)
349                self.set_visibility(occupant)
350
351    def set_visibility(self, chicken):
352        if chicken.outside():
353            if chicken not in self.tv.sprites:
354                self.tv.sprites.append(chicken)
355        else:
356            if chicken in self.tv.sprites:
357                self.tv.sprites.remove(chicken)
358
359    def open_dialog(self, widget, close_callback=None):
360        """Open a dialog for the given widget. Add close button."""
361        tbl = gui.Table()
362
363        def close_dialog():
364            self.disp.close(tbl)
365            if close_callback is not None:
366                close_callback()
367
368        close_button = gui.Button("Close")
369        close_button.connect(gui.CLICK, close_dialog)
370
371        tbl = gui.Table()
372        tbl.tr()
373        tbl.td(widget, colspan=2)
374        tbl.tr()
375        tbl.td(gui.Spacer(100, 0))
376        tbl.td(close_button, align=1)
377
378        self.disp.open(tbl)
379        return tbl
380
381    def open_building_dialog(self, building, sell_callback=None):
382        """Create dialog for manipulating the contents of a building."""
383        def select_occupant(place, button, sell_callback):
384            """Select occupant in place."""
385            # sell_callback should return true if we need to remove the
386            # occupant
387            self.animal_to_place = place.occupant
388            if not sell_callback:
389                pygame.mouse.set_cursor(*cursors.cursors['chicken'])
390            else:
391                # Attempt to sell the occupant
392                self.animal_to_place = None
393                if sell_callback(place.occupant):
394                    button.value = icons.EMPTY_NEST_ICON
395                    button.disconnect(gui.CLICK, select_occupant)
396                    button.connect(gui.CLICK, set_occupant, place, button,
397                            sell_callback)
398
399        def set_occupant(place, button, sell_callback):
400            """Set occupant of a given place."""
401            if self.animal_to_place is not None:
402                button.value = icons.CHKN_NEST_ICON
403                button.disconnect(gui.CLICK, set_occupant)
404                button.connect(gui.CLICK, select_occupant, place, button,
405                        sell_callback)
406
407                old_abode = self.animal_to_place.abode
408                if old_abode is not None:
409                    old_abode.clear_occupant()
410                if id(old_abode) in place_button_map:
411                    old_button = place_button_map[id(old_abode)]
412                    old_button.value = icons.EMPTY_NEST_ICON
413                    old_button.disconnect(gui.CLICK, select_occupant)
414                    old_button.connect(gui.CLICK, set_occupant, place, button,
415                            sell_callback)
416
417                chicken = self.animal_to_place
418                place.set_occupant(chicken)
419                chicken.set_pos(place.get_pos())
420                self.set_visibility(self.animal_to_place)
421
422        place_button_map = {}
423
424        tbl = gui.Table()
425        columns = building.max_floor_width()
426        kwargs = { 'style': { 'padding_left': 10, 'padding_bottom': 10 }}
427        for floor in building.floors():
428            tbl.tr()
429            tbl.td(gui.Button(floor.title), colspan=columns, align=-1, **kwargs)
430            tbl.tr()
431            for row in floor.rows():
432                tbl.tr()
433                for place in row:
434                    if place.occupant is None:
435                        button = gui.Button(icons.EMPTY_NEST_ICON)
436                        button.connect(gui.CLICK, set_occupant, place, button,
437                                sell_callback)
438                    else:
439                        button = gui.Button(icons.CHKN_NEST_ICON)
440                        button.connect(gui.CLICK, select_occupant, place, button,
441                                sell_callback)
442                    place_button_map[id(place)] = button
443                    tbl.td(button, **kwargs)
444
445        building.selected(True)
446        def close_callback():
447            building.selected(False)
448
449        self.open_dialog(tbl, close_callback=close_callback)
450
451    def buy_fence(self, tile_pos):
452        this_tile = self.tv.get(tile_pos)
453        if this_tile not in [self.GRASSLAND, self.BROKEN_FENCE]:
454            return
455        if this_tile == self.GRASSLAND:
456            cost = constants.BUY_PRICE_FENCE
457        else:
458            cost = constants.REPAIR_PRICE_FENCE
459        if any((chicken.pos.x, chicken.pos.y) == tile_pos for chicken in self.chickens):
460            return
461
462        if self.cash < cost:
463            print "You can't afford a fence."
464            return
465        self.add_cash(-cost)
466        self.tv.set(tile_pos, self.FENCE)
467
468    def sell_fence(self, tile_pos):
469        if self.tv.get(tile_pos) != self.FENCE:
470            return
471        self.add_cash(constants.SELL_PRICE_FENCE)
472        self.tv.set(tile_pos, self.GRASSLAND)
473
474    def logging_forest(self, tile_pos):
475        if self.tv.get(tile_pos) != self.WOODLAND:
476            return
477        if self.cash < constants.LOGGING_PRICE:
478            return
479        self.add_cash(-constants.LOGGING_PRICE)
480        self.tv.set(tile_pos, self.GRASSLAND)
481
482    def buy_building(self, tile_pos, building_cls):
483        building = building_cls(tile_pos)
484        if self.cash < building.buy_price():
485            return
486        if any(building.covers((chicken.pos.x, chicken.pos.y)) for chicken in self.chickens):
487            return
488        if building.place(self.tv):
489            self.add_cash(-building.buy_price())
490            self.add_building(building)
491
492    def buy_equipment(self, tile_pos, equipment_cls):
493        chicken = self.get_outside_chicken(tile_pos)
494        equipment = equipment_cls()
495        if chicken is None or self.cash < equipment.buy_price():
496            return
497        if equipment.place(chicken):
498            self.add_cash(-equipment.buy_price())
499            chicken.equip(equipment)
500
501    def sell_building(self, tile_pos):
502        if self.tv.get(tile_pos) == self.FENCE:
503            return self.sell_fence(tile_pos)
504        building = self.get_building(tile_pos)
505        if building is None:
506            return
507        if list(building.occupants()):
508            warning = gui.Button("Occupied buildings may not be sold.")
509            self.open_dialog(warning)
510            return
511        self.add_cash(building.sell_price())
512        building.remove(self.tv)
513        self.remove_building(building)
514
515    def sell_equipment(self, tile_pos):
516        chicken = self.get_outside_chicken(tile_pos)
517        if chicken is None or not chicken.equipment:
518            return
519        if len(chicken.equipment) == 1:
520            item = chicken.equipment[0]
521            self.add_cash(item.sell_price())
522            chicken.unequip(item)
523        else:
524            self.open_equipment_dialog(chicken)
525
526    def open_equipment_dialog(self, chicken):
527        tbl = gui.Table()
528
529        def sell_item(item, button):
530            """Select item of equipment."""
531            self.add_cash(item.sell_price())
532            chicken.unequip(item)
533            self.disp.close(dialog)
534
535        kwargs = { 'style': { 'padding_left': 10, 'padding_bottom': 10 }}
536
537        tbl.tr()
538        tbl.td(gui.Button("Sell ...     "), align=-1, **kwargs)
539
540        for item in chicken.equipment:
541            tbl.tr()
542            button = gui.Button(item.name().title())
543            button.connect(gui.CLICK, sell_item, item, button)
544            tbl.td(button, align=1, **kwargs)
545
546        dialog = self.open_dialog(tbl)
547
548    def event(self, e):
549        if e.type == KEYDOWN:
550            if e.key == K_UP:
551                self.tvw.move_view(0, -self.TILE_DIMENSIONS[1])
552            if e.key == K_DOWN:
553                self.tvw.move_view(0, self.TILE_DIMENSIONS[1])
554            if e.key == K_LEFT:
555                self.tvw.move_view(-self.TILE_DIMENSIONS[0], 0)
556            if e.key == K_RIGHT:
557                self.tvw.move_view(self.TILE_DIMENSIONS[0], 0)
558        else:
559            self.disp.event(e)
560
561    def advance_day(self):
562        self.days += 1
563        self.toolbar.update_day_counter(self.days)
564
565    def clear_foxes(self):
566        for fox in self.foxes.copy():
567            # Any foxes that didn't make it to the woods are automatically
568            # killed
569            if self.in_bounds(fox.pos) and self.tv.get(fox.pos.to_tuple()) \
570                    != self.WOODLAND:
571                self.kill_fox(fox)
572            else:
573                self.tv.sprites.remove(fox)
574        self.foxes = set() # Remove all the foxes
575
576    def move_foxes(self):
577        """Move the foxes.
578       
579           We return True if there are no more foxes to move or all the
580           foxes are safely back. This end's the night"""
581        if not self.foxes:
582            return True
583        over = True
584        for fox in self.foxes:
585            fox.move(self)
586            if not fox.safe:
587                over = False
588        for chicken in self.chickens:
589            chicken.attack(self)
590        return over
591
592    def add_chicken(self, chicken):
593        self.chickens.add(chicken)
594        if chicken.outside():
595            self.tv.sprites.append(chicken)
596        self.toolbar.update_chicken_counter(len(self.chickens))
597
598    def add_fox(self, fox):
599        self.foxes.add(fox)
600        self.tv.sprites.append(fox)
601
602    def add_building(self, building):
603        self.buildings.append(building)
604        self.tv.sprites.append(building)
605
606    def lay_eggs(self):
607        self.eggs = 0
608        for building in self.buildings:
609            if building.NAME in buildings.HENHOUSES:
610                for chicken in building.occupants():
611                    chicken.lay()
612                    if chicken.egg:
613                        self.eggs += 1
614        self.toolbar.update_egg_counter(self.eggs)
615
616    def hatch_eggs(self):
617        for building in self.buildings:
618            if building.NAME in buildings.HENHOUSES:
619                for chicken in building.occupants():
620                    new_chick = chicken.hatch()
621                    if new_chick:
622                        self.eggs -= 1
623                        try:
624                            building.add_occupant(new_chick)
625                            self.add_chicken(new_chick)
626                        except buildings.BuildingFullError:
627                            print "Building full."
628        self.toolbar.update_egg_counter(self.eggs)
629
630    def kill_fox(self, fox):
631        if fox in self.foxes:
632            if not fox.survive_damage():
633                self.killed_foxes += 1
634                self.toolbar.update_fox_counter(self.killed_foxes)
635                self.add_cash(constants.SELL_PRICE_DEAD_FOX)
636                self.remove_fox(fox)
637
638    def remove_fox(self, fox):
639        self.foxes.discard(fox)
640        if fox in self.tv.sprites:
641            self.tv.sprites.remove(fox)
642
643    def remove_chicken(self, chick):
644        self.chickens.discard(chick)
645        if chick.egg:
646            self.eggs -= 1
647            self.toolbar.update_egg_counter(self.eggs)
648        if chick.abode:
649            chick.abode.clear_occupant()
650        self.toolbar.update_chicken_counter(len(self.chickens))
651        if chick in self.tv.sprites and chick.outside():
652            self.tv.sprites.remove(chick)
653
654    def remove_building(self, building):
655        if building in self.buildings:
656            self.buildings.remove(building)
657            self.tv.sprites.remove(building)
658
659    def add_cash(self, amount):
660        self.cash += amount
661        self.toolbar.update_cash_counter(self.cash)
662
663    def add_some_chickens(self):
664        """Add some random chickens to start the game"""
665        x, y = 0, 0
666        width, height = self.tv.size
667        while len(self.chickens) < 10:
668            if x < width:
669                tile = self.tv.get((x, y))
670            else:
671                y += 1
672                if y >= height:
673                    break
674                x = 0
675                continue
676            # See if we place a chicken
677            if 'grassland' == tiles.TILE_MAP[tile]:
678                # Farmland
679                roll = random.randint(1, 20)
680                # We don't place within a tile of the fence, this is to make things
681                # easier
682                for xx in range(x-1, x+2):
683                    if xx >= width or xx < 0:
684                        continue
685                    for yy in range(y-1, y+2):
686                        if yy >= height or yy < 0:
687                            continue
688                        neighbour = self.tv.get((xx, yy))
689                        if 'fence' == tiles.TILE_MAP[neighbour]:
690                            # Fence
691                            roll = 10
692                if roll == 1:
693                    # Create a chicken
694                    chick = animal.Chicken((x, y))
695                    self.add_chicken(chick)
696            x += 1
697
698    def spawn_foxes(self):
699        """The foxes come at night, and this is where they come from."""
700        # Foxes spawn just outside the map
701        x, y = 0, 0
702        width, height = self.tv.size
703        new_foxes = random.randint(3, 7)
704        while len(self.foxes) < new_foxes:
705            side = random.randint(0, 3)
706            if side == 0:
707                # top
708                y = -1
709                x = random.randint(-1, width)
710            elif side == 1:
711                # bottom
712                y = height
713                x = random.randint(-1, width)
714            elif side == 2:
715                # left
716                x = -1
717                y = random.randint(-1, height)
718            else:
719                x = width
720                y = random.randint(-1, height)
721            skip = False
722            for other_fox in self.foxes:
723                if other_fox.pos.x == x and other_fox.pos.y == y:
724                    skip = True # Choose a new position
725                    break
726            if not skip:
727                roll = random.randint(0, 10)
728                if roll < 8:
729                    fox = animal.Fox((x, y))
730                elif roll < 9:
731                    fox = animal.NinjaFox((x, y))
732                else:
733                    fox = animal.GreedyFox((x, y))
734                self.add_fox(fox)
735
736    def fix_buildings(self):
737        """Go through the level map looking for buildings that haven't
738           been added to self.buildings and adding them.
739
740           Where partial buildings exist (i.e. places where the building
741           cannot fit on the available tiles) the building is added anyway
742           to the top left corner.
743
744           Could be a lot faster.
745           """
746        tile_to_building = dict((b.TILE_NO, b) for b in buildings.BUILDINGS)
747
748        w, h = self.tv.size
749        for x in xrange(w):
750            for y in xrange(h):
751                tile_pos = (x, y)
752                tile_no = self.tv.get(tile_pos)
753                if tile_no not in tile_to_building:
754                    continue
755
756                covered = False
757                for building in self.buildings:
758                    if building.covers(tile_pos):
759                        covered = True
760                        break
761
762                if covered:
763                    continue
764
765                building_cls = tile_to_building[tile_no]
766                building = building_cls(tile_pos)
767                building.remove(self.tv)
768                building.place(self.tv)
769                self.add_building(building)
770
771    def is_game_over(self):
772        """Return true if we're complete"""
773        if self.days > constants.TURN_LIMIT:
774            return True
775        if len(self.chickens) == 0:
776            return True
Note: See TracBrowser for help on using the repository browser.