source: gamelib/gameboard.py @ 264:812bd4cda8b8

Last change on this file since 264:812bd4cda8b8 was 264:812bd4cda8b8, checked in by Neil Muller <drnlmuller@…>, 11 years ago

evict button

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