source: gamelib/gameboard.py @ 197:d74693555b86

Last change on this file since 197:d74693555b86 was 197:d74693555b86, checked in by Simon Cross <hodgestar@…>, 11 years ago

Put chickens into first empty space in buildings to make it easier to add lots of chickens.

File size: 27.5 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            if self.animal_to_place:
342                try:
343                    place = building.first_empty_place()
344                    self.relocate_animal(self.animal_to_place, place=place)
345                    self.animal_to_place = None
346                    pygame.mouse.set_cursor(*cursors.cursors['select'])
347                except buildings.BuildingFullError:
348                    pass
349            else:
350                self.open_building_dialog(building)
351            return
352        if self.tv.get(tile_pos) == self.GRASSLAND:
353            if self.animal_to_place is not None:
354                self.relocate_animal(self.animal_to_place, tile_pos=tile_pos)
355
356    def relocate_animal(self, chicken, tile_pos=None, place=None):
357        assert((tile_pos, place) != (None, None))
358        if chicken.abode is not None:
359            chicken.abode.clear_occupant()
360        if tile_pos:
361            chicken.set_pos(tile_pos)
362        else:
363            place.set_occupant(chicken)
364            chicken.set_pos(place.get_pos())
365        self.set_visibility(chicken)
366
367    def set_visibility(self, chicken):
368        if chicken.outside():
369            if chicken not in self.tv.sprites:
370                self.tv.sprites.append(chicken)
371        else:
372            if chicken in self.tv.sprites:
373                self.tv.sprites.remove(chicken)
374
375    def open_dialog(self, widget, close_callback=None):
376        """Open a dialog for the given widget. Add close button."""
377        tbl = gui.Table()
378
379        def close_dialog():
380            self.disp.close(tbl)
381            if close_callback is not None:
382                close_callback()
383
384        close_button = gui.Button("Close")
385        close_button.connect(gui.CLICK, close_dialog)
386
387        tbl = gui.Table()
388        tbl.tr()
389        tbl.td(widget, colspan=2)
390        tbl.tr()
391        tbl.td(gui.Spacer(100, 0))
392        tbl.td(close_button, align=1)
393
394        self.disp.open(tbl)
395        return tbl
396
397    def open_building_dialog(self, building, sell_callback=None):
398        """Create dialog for manipulating the contents of a building."""
399        def select_occupant(place, button, sell_callback):
400            """Select occupant in place."""
401            # sell_callback should return true if we need to remove the
402            # occupant
403            self.animal_to_place = place.occupant
404            if not sell_callback:
405                pygame.mouse.set_cursor(*cursors.cursors['chicken'])
406            else:
407                # Attempt to sell the occupant
408                self.animal_to_place = None
409                if sell_callback(place.occupant):
410                    button.value = icons.EMPTY_NEST_ICON
411                    button.disconnect(gui.CLICK, select_occupant)
412                    button.connect(gui.CLICK, set_occupant, place, button,
413                            sell_callback)
414
415        def set_occupant(place, button, sell_callback):
416            """Set occupant of a given place."""
417            if self.animal_to_place is not None:
418                button.value = icons.CHKN_NEST_ICON
419                button.disconnect(gui.CLICK, set_occupant)
420                button.connect(gui.CLICK, select_occupant, place, button,
421                        sell_callback)
422
423                old_abode = self.animal_to_place.abode
424                if id(old_abode) in place_button_map:
425                    old_button = place_button_map[id(old_abode)]
426                    old_button.value = icons.EMPTY_NEST_ICON
427                    old_button.disconnect(gui.CLICK, select_occupant)
428                    old_button.connect(gui.CLICK, set_occupant, place, button,
429                            sell_callback)
430
431                self.relocate_animal(self.animal_to_place, place=place)
432
433        place_button_map = {}
434
435        tbl = gui.Table()
436        columns = building.max_floor_width()
437        kwargs = { 'style': { 'padding_left': 10, 'padding_bottom': 10 }}
438        for floor in building.floors():
439            tbl.tr()
440            tbl.td(gui.Button(floor.title), colspan=columns, align=-1, **kwargs)
441            tbl.tr()
442            for row in floor.rows():
443                tbl.tr()
444                for place in row:
445                    if place.occupant is None:
446                        button = gui.Button(icons.EMPTY_NEST_ICON)
447                        button.connect(gui.CLICK, set_occupant, place, button,
448                                sell_callback)
449                    else:
450                        button = gui.Button(icons.CHKN_NEST_ICON)
451                        button.connect(gui.CLICK, select_occupant, place, button,
452                                sell_callback)
453                    place_button_map[id(place)] = button
454                    tbl.td(button, **kwargs)
455
456        building.selected(True)
457        def close_callback():
458            building.selected(False)
459
460        self.open_dialog(tbl, close_callback=close_callback)
461
462    def buy_fence(self, tile_pos):
463        this_tile = self.tv.get(tile_pos)
464        if this_tile not in [self.GRASSLAND, self.BROKEN_FENCE]:
465            return
466        if this_tile == self.GRASSLAND:
467            cost = constants.BUY_PRICE_FENCE
468        else:
469            cost = constants.REPAIR_PRICE_FENCE
470        if any((chicken.pos.x, chicken.pos.y) == tile_pos for chicken in self.chickens):
471            return
472
473        if self.cash < cost:
474            print "You can't afford a fence."
475            return
476        self.add_cash(-cost)
477        self.tv.set(tile_pos, self.FENCE)
478
479    def sell_fence(self, tile_pos):
480        if self.tv.get(tile_pos) != self.FENCE:
481            return
482        self.add_cash(constants.SELL_PRICE_FENCE)
483        self.tv.set(tile_pos, self.GRASSLAND)
484
485    def logging_forest(self, tile_pos):
486        if self.tv.get(tile_pos) != self.WOODLAND:
487            return
488        if self.cash < constants.LOGGING_PRICE:
489            return
490        self.add_cash(-constants.LOGGING_PRICE)
491        self.tv.set(tile_pos, self.GRASSLAND)
492
493    def buy_building(self, tile_pos, building_cls):
494        building = building_cls(tile_pos)
495        if self.cash < building.buy_price():
496            return
497        if any(building.covers((chicken.pos.x, chicken.pos.y)) for chicken in self.chickens):
498            return
499        if building.place(self.tv):
500            self.add_cash(-building.buy_price())
501            self.add_building(building)
502
503    def buy_equipment(self, tile_pos, equipment_cls):
504        chicken = self.get_outside_chicken(tile_pos)
505        equipment = equipment_cls()
506        if chicken is None or self.cash < equipment.buy_price():
507            return
508        if equipment.place(chicken):
509            self.add_cash(-equipment.buy_price())
510            chicken.equip(equipment)
511
512    def sell_building(self, tile_pos):
513        if self.tv.get(tile_pos) == self.FENCE:
514            return self.sell_fence(tile_pos)
515        building = self.get_building(tile_pos)
516        if building is None:
517            return
518        if list(building.occupants()):
519            warning = gui.Button("Occupied buildings may not be sold.")
520            self.open_dialog(warning)
521            return
522        self.add_cash(building.sell_price())
523        building.remove(self.tv)
524        self.remove_building(building)
525
526    def sell_equipment(self, tile_pos):
527        chicken = self.get_outside_chicken(tile_pos)
528        if chicken is None or not chicken.equipment:
529            return
530        if len(chicken.equipment) == 1:
531            item = chicken.equipment[0]
532            self.add_cash(item.sell_price())
533            chicken.unequip(item)
534        else:
535            self.open_equipment_dialog(chicken)
536
537    def open_equipment_dialog(self, chicken):
538        tbl = gui.Table()
539
540        def sell_item(item, button):
541            """Select item of equipment."""
542            self.add_cash(item.sell_price())
543            chicken.unequip(item)
544            self.disp.close(dialog)
545
546        kwargs = { 'style': { 'padding_left': 10, 'padding_bottom': 10 }}
547
548        tbl.tr()
549        tbl.td(gui.Button("Sell ...     "), align=-1, **kwargs)
550
551        for item in chicken.equipment:
552            tbl.tr()
553            button = gui.Button(item.name().title())
554            button.connect(gui.CLICK, sell_item, item, button)
555            tbl.td(button, align=1, **kwargs)
556
557        dialog = self.open_dialog(tbl)
558
559    def event(self, e):
560        if e.type == KEYDOWN:
561            if e.key == K_UP:
562                self.tvw.move_view(0, -self.TILE_DIMENSIONS[1])
563            if e.key == K_DOWN:
564                self.tvw.move_view(0, self.TILE_DIMENSIONS[1])
565            if e.key == K_LEFT:
566                self.tvw.move_view(-self.TILE_DIMENSIONS[0], 0)
567            if e.key == K_RIGHT:
568                self.tvw.move_view(self.TILE_DIMENSIONS[0], 0)
569        else:
570            self.disp.event(e)
571
572    def advance_day(self):
573        self.days += 1
574        self.toolbar.update_day_counter(self.days)
575
576    def clear_foxes(self):
577        for fox in self.foxes.copy():
578            # Any foxes that didn't make it to the woods are automatically
579            # killed
580            if self.in_bounds(fox.pos) and self.tv.get(fox.pos.to_tuple()) \
581                    != self.WOODLAND:
582                self.kill_fox(fox)
583            else:
584                self.tv.sprites.remove(fox)
585        self.foxes = set() # Remove all the foxes
586
587    def move_foxes(self):
588        """Move the foxes.
589       
590           We return True if there are no more foxes to move or all the
591           foxes are safely back. This end's the night"""
592        if not self.foxes:
593            return True
594        over = True
595        for fox in self.foxes:
596            fox.move(self)
597            if not fox.safe:
598                over = False
599        for chicken in self.chickens:
600            chicken.attack(self)
601        return over
602
603    def add_chicken(self, chicken):
604        self.chickens.add(chicken)
605        if chicken.outside():
606            self.tv.sprites.append(chicken)
607        self.toolbar.update_chicken_counter(len(self.chickens))
608
609    def add_fox(self, fox):
610        self.foxes.add(fox)
611        self.tv.sprites.append(fox)
612
613    def add_building(self, building):
614        self.buildings.append(building)
615        self.tv.sprites.append(building)
616
617    def lay_eggs(self):
618        self.eggs = 0
619        for building in self.buildings:
620            if building.NAME in buildings.HENHOUSES:
621                for chicken in building.occupants():
622                    chicken.lay()
623                    if chicken.egg:
624                        self.eggs += 1
625        self.toolbar.update_egg_counter(self.eggs)
626
627    def hatch_eggs(self):
628        for building in self.buildings:
629            if building.NAME in buildings.HENHOUSES:
630                for chicken in building.occupants():
631                    new_chick = chicken.hatch()
632                    if new_chick:
633                        self.eggs -= 1
634                        try:
635                            building.add_occupant(new_chick)
636                            self.add_chicken(new_chick)
637                        except buildings.BuildingFullError:
638                            print "Building full."
639        self.toolbar.update_egg_counter(self.eggs)
640
641    def kill_fox(self, fox):
642        if fox in self.foxes:
643            if not fox.survive_damage():
644                self.killed_foxes += 1
645                self.toolbar.update_fox_counter(self.killed_foxes)
646                self.add_cash(constants.SELL_PRICE_DEAD_FOX)
647                self.remove_fox(fox)
648
649    def remove_fox(self, fox):
650        self.foxes.discard(fox)
651        if fox in self.tv.sprites:
652            self.tv.sprites.remove(fox)
653
654    def remove_chicken(self, chick):
655        self.chickens.discard(chick)
656        if chick.egg:
657            self.eggs -= 1
658            self.toolbar.update_egg_counter(self.eggs)
659        if chick.abode:
660            chick.abode.clear_occupant()
661        self.toolbar.update_chicken_counter(len(self.chickens))
662        if chick in self.tv.sprites and chick.outside():
663            self.tv.sprites.remove(chick)
664
665    def remove_building(self, building):
666        if building in self.buildings:
667            self.buildings.remove(building)
668            self.tv.sprites.remove(building)
669
670    def add_cash(self, amount):
671        self.cash += amount
672        self.toolbar.update_cash_counter(self.cash)
673
674    def add_some_chickens(self):
675        """Add some random chickens to start the game"""
676        x, y = 0, 0
677        width, height = self.tv.size
678        while len(self.chickens) < 10:
679            if x < width:
680                tile = self.tv.get((x, y))
681            else:
682                y += 1
683                if y >= height:
684                    break
685                x = 0
686                continue
687            # See if we place a chicken
688            if 'grassland' == tiles.TILE_MAP[tile]:
689                # Farmland
690                roll = random.randint(1, 20)
691                # We don't place within a tile of the fence, this is to make things
692                # easier
693                for xx in range(x-1, x+2):
694                    if xx >= width or xx < 0:
695                        continue
696                    for yy in range(y-1, y+2):
697                        if yy >= height or yy < 0:
698                            continue
699                        neighbour = self.tv.get((xx, yy))
700                        if 'fence' == tiles.TILE_MAP[neighbour]:
701                            # Fence
702                            roll = 10
703                if roll == 1:
704                    # Create a chicken
705                    chick = animal.Chicken((x, y))
706                    self.add_chicken(chick)
707            x += 1
708
709    def spawn_foxes(self):
710        """The foxes come at night, and this is where they come from."""
711        # Foxes spawn just outside the map
712        x, y = 0, 0
713        width, height = self.tv.size
714        new_foxes = random.randint(3, 7)
715        while len(self.foxes) < new_foxes:
716            side = random.randint(0, 3)
717            if side == 0:
718                # top
719                y = -1
720                x = random.randint(-1, width)
721            elif side == 1:
722                # bottom
723                y = height
724                x = random.randint(-1, width)
725            elif side == 2:
726                # left
727                x = -1
728                y = random.randint(-1, height)
729            else:
730                x = width
731                y = random.randint(-1, height)
732            skip = False
733            for other_fox in self.foxes:
734                if other_fox.pos.x == x and other_fox.pos.y == y:
735                    skip = True # Choose a new position
736                    break
737            if not skip:
738                roll = random.randint(0, 10)
739                if roll < 8:
740                    fox = animal.Fox((x, y))
741                elif roll < 9:
742                    fox = animal.NinjaFox((x, y))
743                else:
744                    fox = animal.GreedyFox((x, y))
745                self.add_fox(fox)
746
747    def fix_buildings(self):
748        """Go through the level map looking for buildings that haven't
749           been added to self.buildings and adding them.
750
751           Where partial buildings exist (i.e. places where the building
752           cannot fit on the available tiles) the building is added anyway
753           to the top left corner.
754
755           Could be a lot faster.
756           """
757        tile_to_building = dict((b.TILE_NO, b) for b in buildings.BUILDINGS)
758
759        w, h = self.tv.size
760        for x in xrange(w):
761            for y in xrange(h):
762                tile_pos = (x, y)
763                tile_no = self.tv.get(tile_pos)
764                if tile_no not in tile_to_building:
765                    continue
766
767                covered = False
768                for building in self.buildings:
769                    if building.covers(tile_pos):
770                        covered = True
771                        break
772
773                if covered:
774                    continue
775
776                building_cls = tile_to_building[tile_no]
777                building = building_cls(tile_pos)
778                building.remove(self.tv)
779                building.place(self.tv)
780                self.add_building(building)
781
782    def is_game_over(self):
783        """Return true if we're complete"""
784        if self.days > constants.TURN_LIMIT:
785            return True
786        if len(self.chickens) == 0:
787            return True
Note: See TracBrowser for help on using the repository browser.