Example #1
0
class Game:
    def __init__(self, surface):
        self.surface = surface
        self.state = const.MENU
        self.level = 1
        self.dashboard = Dashboard(self.surface)
        self.ship = Ship(self.surface, LEVEL_DATA[self.level]['start_fire'])
        self.active_text_box = None # can only have one at a time

        self.f_tick_time = 5   # seconds between fire ticks
        self.f_anim_time = 0.4 # seconds between fire animation frames
        self.last_f_tick = time.time()
        self.last_f_anim = time.time()

        self.s_tick_time = 2   # seconds between sprinkler ticks
        self.last_s_tick = time.time() - 0.5 # offset so they don't happen simultaneously
        self.event_room = None
        self.event_target_flvl = 0
        self.event_time = 20
        self.event_start_time = 0

        self.repair_room = None
        self.repair_time = 7
        self.repair_start_time = 0

        self.r_tick_time = 2   # seconds between radar ticks
        self.last_r_tick = time.time() - 1.5 # offset so they don't happen simultaneously

        self.lightyear_length = 10 # seconds it takes to travel 1 lightyear
        self.last_lightyear_tick = time.time()
        self.lightyears_left = 9

        self.damage_anim_start = 0

        self.is_paused = False
        self.time_paused = None

        self.shield_room_id = 0

        self.tut_progress = 0

    def begin(self):
        """Resets game state and begins a new game.
        """
        self.__init__(self.surface)

    def begin_level(self):
        if self.level == 1:
            self.state = const.TUTORIAL
            self.active_text_box = tutorial.get_text(self.tut_progress)
            self.pause()
        elif self.level == 2:
            self.state = const.INSTALLING
            self.pause()
            install_text = 'Seeing as there is an asteroid field between you and the next spaceport, you decide to purchase an ' + \
                'asteroid shield that will protect you from them as long as it\'s not burning. (Click on a room to place it there)'
            self.active_text_box = TextBox(install_text, const.MED, 'NEW SYSTEM')
            self.active_text_box.add_button("Resume", const.GREEN)
        elif self.level == 3:
            self.ship.room_list[self.shield_room_id].type = const.SHIELD
            self.state = const.INSTALLING
            self.pause()
            install_text = 'Antiipating that your systems will soon start breaking down from the fire, you buy a repair system. ' + \
                '(Click on a room to place it there)'
            self.active_text_box = TextBox(install_text, const.MED, 'NEW SYSTEM')
            self.active_text_box.add_button("Resume", const.GREEN)

    def level_up(self):
        self.level += 1
        self.state = const.PLAYING
        self.dashboard = Dashboard(self.surface) # reset dash
        self.ship = Ship(self.surface, LEVEL_DATA[self.level]['start_fire']) # reset ship
        self.active_text_box = None # clear text box

        self.last_f_tick = time.time()
        self.last_s_tick = time.time() - 0.5 # offset so they don't happen simultaneously

        self.lightyears_left = 9
        self.last_lightyear_tick = time.time()

        self.is_paused = False
        self.time_paused = None

        self.begin_level()

    def press_key(self, key):
        if key == pygame.K_SPACE: # spacebar
            if self.state == const.MENU:
                self.begin_level()
        if key == pygame.K_UP:
            if self.state == const.PLAYING and not self.ship.is_disabled(const.LASER_PORT):
                self.dashboard.radar.fire_laser(const.NORTH)
        if key == pygame.K_DOWN:
            if self.state == const.PLAYING and not self.ship.is_disabled(const.LASER_STBD):
                self.dashboard.radar.fire_laser(const.SOUTH)

    def move_mouse(self, mouse_x, mouse_y):
        for room in self.ship.room_list:
            if mouse_x > room.x_pos and mouse_x < room.x_pos + room.size and \
                mouse_y > room.y_pos and mouse_y < room.y_pos + room.size:
                room.moused_over = True
            else:
                room.moused_over = False
        if self.active_text_box and len(self.active_text_box.buttons) > 0:
            for btn in self.active_text_box.buttons:
                btn.check_mouse_hover(mouse_x, mouse_y)
        # dashboard buttons must be checked individually
        self.dashboard.laser_button_n.check_mouse_hover(mouse_x, mouse_y)
        self.dashboard.laser_button_s.check_mouse_hover(mouse_x, mouse_y)
        self.dashboard.cactus_button.check_mouse_hover(mouse_x, mouse_y)
        self.dashboard.repair_switch.check_mouse_hover(mouse_x, mouse_y)

    def click_mouse(self):
        # This function currently does not need mouse_x or mouse_y, but
        # it might need it in the future depending on what we use it for.
        if self.state == const.MENU:
            self.begin_level()
            return

        if self.state == const.PLAYING or self.state == const.REPAIRING:
            for room in self.ship.room_list:
                if room.moused_over:
                    if self.state == const.PLAYING:
                        if room.sprinkling:
                            room.sprinkling = False
                            self.ship.num_sprinkling -= 1
                            SOUNDS['press'].play()
                        elif not room.sprinkling and self.ship.num_sprinkling < SPRINKLER_LIMIT:
                            room.sprinkling = True
                            self.ship.num_sprinkling += 1
                            SOUNDS['press'].play()
                        elif not room.sprinkling: # sprinkler limit reached
                            SOUNDS['invalid'].play()
                    elif self.state == const.REPAIRING:
                        if room.is_breaking:
                            self.repair_room.is_breaking = False
                            self.repair_room = None
                            SOUNDS['press'].play()
                            # Switch back to sprinkler mode for convenience
                            self.state = const.PLAYING
                        else:
                            SOUNDS['invalid'].play()

            # dashboard buttons must be checked individually
            if self.dashboard.laser_button_n.moused_over and \
                self.ship.is_working(const.LASER_PORT):
                self.dashboard.radar.fire_laser(const.NORTH)
            if self.dashboard.laser_button_s.moused_over and \
                self.ship.is_working(const.LASER_STBD):
                self.dashboard.radar.fire_laser(const.SOUTH)
            if self.dashboard.cactus_button.moused_over:
                self.pause()
                self.tut_progress = 0
                self.state = const.TUTORIAL
                self.active_text_box = tutorial.get_text(self.tut_progress)
            if self.dashboard.repair_switch.moused_over and \
                self.ship.is_working(const.REPAIR):
                if self.level == 3:
                    if self.state == const.PLAYING:
                        self.state = const.REPAIRING
                    elif self.state == const.REPAIRING:
                        self.state = const.PLAYING
                    SOUNDS['press'].play()

        if self.state == const.INSTALLING:
            for room_id in range(len(self.ship.room_list)):
                if self.ship.room_list[room_id].moused_over and self.ship.room_list[room_id].type == const.EMPTY:
                    if self.level == 2:
                        self.ship.room_list[room_id].type = const.SHIELD
                        self.shield_room_id = room_id
                    elif self.level == 3:
                        self.ship.room_list[room_id].type = const.REPAIR
                    self.state = const.PLAYING
                    SOUNDS['press'].play()
                    self.unpause()
                elif self.ship.room_list[room_id].moused_over:
                    SOUNDS['invalid'].play()

        if self.active_text_box and len(self.active_text_box.buttons) > 0:
            # Here is where I'll have to figure out how to add functionality
            # to the buttons. I might just have to do this on a case-by-case basis.
            button_list = self.active_text_box.buttons
            # I know the first button of the GAME OVER text goes to the title screen.
            if button_list[0].moused_over and (self.state == const.FIRE_OUT or \
                self.state == const.HULL_OUT or self.state == const.BRIDGE_OUT):
                self.begin()
                return
            # The first button of the WIN_LEVEL text advances to the next level.
            if button_list[0].moused_over and self.state == const.WIN_LEVEL:
                self.level_up()
                return
            # The first button of the WIN_GAME text brings you back to the main menu
            if button_list[0].moused_over and self.state == const.WIN_GAME:
                self.begin()
                return
            # The first button of the event text returns to normal gameplay
            if button_list[0].moused_over and self.state == const.EVENT:
                self.unpause()
                self.state = const.PLAYING
                self.active_text_box = None
                return
            if button_list[0].moused_over and self.state == const.INSTALLING:
                self.active_text_box = None
            # The first button of tutorial text advances the tutorial, unless tutorial is finished.
            if button_list[0].moused_over and self.state == const.TUTORIAL:
                if self.tut_progress >= 10: # last tutorial text
                    self.unpause()
                    self.state = const.PLAYING
                    self.active_text_box = None
                    return
                else:
                    self.tut_progress += 1
                    self.active_text_box = tutorial.get_text(self.tut_progress)
                    return
            # The second button of tutorial text skips the tutorial.
            if len(button_list) >= 2 and button_list[1].moused_over and self.state == const.TUTORIAL:
                self.unpause()
                self.state = const.PLAYING
                self.active_text_box = None
                return

    def pause(self):
        self.is_paused = True
        self.time_paused = time.time()

    def unpause(self):
        self.is_paused = False
        time_missed = time.time() - self.time_paused
        self.last_lightyear_tick += time_missed
        self.last_f_tick += time_missed
        self.last_f_anim += time_missed
        self.last_s_tick += time_missed
        self.last_r_tick += time_missed
        self.event_start_time += time_missed
        self.repair_start_time += time_missed

    def start_event(self):
        if self.level == 1:
            num_systems = 4
        elif self.level == 2:
            num_systems = 5
        elif self.level == 3:
            num_systems = 6
        event_room_id = random.randint(2, num_systems)
        for i in self.ship.room_list:
            if i.type == event_room_id:
                self.event_room = i
                self.event_room.is_event = True
        if self.event_room.fire_level <= 1:
            self.event_target_flvl = 2
        elif self.event_room.fire_level == 2:
            self.event_target_flvl = 0
        self.event_start_time = time.time()
        self.state = const.EVENT
        self.pause()
        if self.event_target_flvl == 0:
            event_text = 'The fire in your ship\'s ' + const.ROOM_NAMES[event_room_id] + ' room is starting to wear down the hull. ' + \
                'If you don\'t extinguish it soon, the ship will take damage.'
        else:
            event_text = 'A particularly annoying species of space termites has infested the ' + const.ROOM_NAMES[event_room_id] + ' room! ' + \
                'You can exterminate them by setting the room on fire, but if you take too long they\'ll damage your hull. '
        self.active_text_box = TextBox(event_text, const.MED, 'EVENT')
        self.active_text_box.add_button('Resume', const.GREEN)

    def start_repair(self, room_id):
        for i in self.ship.room_list:
            if i.type == room_id:
                self.repair_room = i
                self.repair_room.is_breaking = True
        self.repair_start_time = time.time()
        self.state = const.EVENT
        self.pause()
        repair_text = 'The ' + const.ROOM_NAMES[room_id] + ' system is breaking! If it isn\'t fixed soon,' + \
            'it will become permanently disabled. (To repair it, click on the repair button and then ' + \
            'on the room you want to repair)'
        self.active_text_box = TextBox(repair_text, const.MED, 'EVENT')
        self.active_text_box.add_button("Resume", const.GREEN)

    def tick(self):
        """Checks if fire and sprinklers need to activate, checks if you've won or lost,
        and does whatever other things need to happen each frame.
        """
        if self.state == const.PLAYING or self.state == const.REPAIRING:
            # Check if you've lost the game
            if self.ship.num_onfire == 0:
                self.state = const.FIRE_OUT
                self.pause()
                game_over_text = 'The fire went out! You\'re relieved for a moment, but then ' + \
                    'you feel your ship begin to stall. Your engines can no longer be powered. ' + \
                    'You and your cactus are screwed.'
                self.active_text_box = TextBox(game_over_text, const.MED, 'GAME OVER')
                self.active_text_box.add_button('Back to Title', const.RED)
            elif self.ship.is_bridge_burning():
                self.state = const.BRIDGE_OUT
                self.pause()
                game_over_text = 'The bridge has burned up, and you along with it! You were ' + \
                    'unable to control the flames, and they consumed you. Not even your brave ' + \
                    'cactus survived.'
                self.active_text_box = TextBox(game_over_text, const.MED, 'GAME OVER')
                self.active_text_box.add_button('Back to Title', const.RED)
            elif self.dashboard.get_health() <= 0:
                self.state = const.HULL_OUT
                self.pause()
                game_over_text = 'The hull is breached! The air in your space ship rushes out into ' + \
                    'the vacuum of space, sucking you out with it. Luckily, by some miracle, your ' + \
                    'cactus manages to survive, and lives to tell your tale.'
                self.active_text_box = TextBox(game_over_text, const.MED, 'GAME OVER')
                self.active_text_box.add_button('Back to Title', const.RED)

            # Check if you've won the level or game
            if self.lightyears_left <= 0:
                if self.level < 3:
                    self.state = const.WIN_LEVEL
                    self.pause()
                    win_text = 'Your ship manages to reach the spaceport intact! You get a much-needed ' + \
                        'chance to refill your water, repair your hull, and catch your breath. But you are ' + \
                        'still a long way from home, so you must keep going!'
                    self.active_text_box = TextBox(win_text, const.MED, 'SPACEPORT REACHED')
                    self.active_text_box.add_button('Continue to level ' + str(self.level + 1), const.GREEN)
                else:
                    self.state = const.WIN_GAME
                    self.pause()
                    win_text = 'You glance away from the hectic state of your ship for a moment only ' + \
                        'to be greeted by the blue and green planet you call home. You\'re finally back ' + \
                        'to Earth! You and your cactus will remember this journey forever.'
                    self.active_text_box = TextBox(win_text, const.MED, 'EARTH REACHED')
                    self.active_text_box.add_button('Woohoo!', const.GREEN)

            # Check if you've travelled another lightyear
            current_time = time.time()
            if current_time - self.last_lightyear_tick >= self.lightyear_length:
                self.lightyears_left -= 1
                self.last_lightyear_tick = time.time()
                level_str = LEVEL_DATA[self.level][str(self.lightyears_left)]
                if 'event' in level_str:
                    self.start_event()
                if 'ship_n' in level_str:
                    self.dashboard.radar.add_alien(const.NORTH)
                if 'ship_s' in level_str:
                    self.dashboard.radar.add_alien(const.SOUTH)
                if 'ast_nw' in level_str:
                    self.dashboard.radar.add_asteroid(const.NORTHWEST)
                if 'ast_ne' in level_str:
                    self.dashboard.radar.add_asteroid(const.NORTHEAST)
                if 'ast_sw' in level_str:
                    self.dashboard.radar.add_asteroid(const.SOUTHWEST)
                if 'ast_se' in level_str:
                    self.dashboard.radar.add_asteroid(const.SOUTHEAST)
                if 'repair' in level_str:
                    if 'repair_sn' in level_str:
                        room_id = const.SENSORS
                    elif 'repair_rd' in level_str:
                        room_id = const.RADAR
                    elif 'repair_sh' in level_str:
                        room_id = const.SHIELD
                    # make a module break and require repairs
                    self.start_repair(room_id)
            if self.state == const.REPAIRING and self.ship.disabled_systems[7]:
                self.state = const.PLAYING

            # Check fire
            if current_time - self.last_f_tick >= self.f_tick_time:
                self.ship.fire_tick()
                self.last_f_tick = current_time
                # Update which systems are disabled
                self.ship.check_systems()
                self.dashboard.sensors.disabled = self.ship.disabled_systems[2]
                self.dashboard.sensors.broken = self.ship.broken_systems[2]
                self.dashboard.radar.disabled = self.ship.disabled_systems[3]
                self.dashboard.radar.broken = self.ship.broken_systems[3]
                self.dashboard.laser_n_disabled = self.ship.disabled_systems[4]
                self.dashboard.laser_s_disabled = self.ship.disabled_systems[5]
                self.dashboard.repair_disabled = self.ship.disabled_systems[7]
                # Checks event goal
                if self.event_room is not None and self.event_target_flvl == 2 and self.event_room.fire_level == 2:
                    self.event_room.is_event = False
                    self.event_room = None
            if current_time - self.last_f_anim >= self.f_anim_time:
                for i in self.ship.room_list:
                    if i.fire_anim_state >= 2:
                        i.fire_anim_state = 0
                    else:
                        i.fire_anim_state += 1
                    self.last_f_anim = current_time

            # Check sprinklers
            if current_time - self.last_s_tick >= self.s_tick_time:
                if self.ship.num_sprinkling <= self.dashboard.get_water():
                    self.ship.sprinkler_tick()
                    self.dashboard.lose_water(self.ship.num_sprinkling)
                    self.last_s_tick = current_time
                else:
                    self.ship.sprinkler_tick(self.dashboard.get_water())
                    self.dashboard.lose_water(self.dashboard.get_water())
                    self.last_s_tick = current_time
                # Update which systems are disabled
                self.ship.check_systems()
                self.dashboard.sensors.disabled = self.ship.disabled_systems[2]
                self.dashboard.sensors.broken = self.ship.broken_systems[2]
                self.dashboard.radar.disabled = self.ship.disabled_systems[3]
                self.dashboard.radar.broken = self.ship.broken_systems[3]
                self.dashboard.laser_n_disabled = self.ship.disabled_systems[4]
                self.dashboard.laser_s_disabled = self.ship.disabled_systems[5]
                self.dashboard.repair_disabled = self.ship.disabled_systems[7]
                # Checks event goal
                if self.event_room is not None and self.event_target_flvl == 0 and self.event_room.fire_level <= 1:
                    self.event_room.is_event = False
                    self.event_room = None
            # Check radar
            if current_time - self.last_r_tick >= self.r_tick_time:
                shields_up = self.ship.is_working(const.SHIELD)
                damage_taken = self.dashboard.radar.radar_tick(shields_up)
                self.dashboard.take_damage(damage_taken)
                if damage_taken > 0:
                    self.damage_anim_start = time.time()
                self.last_r_tick = current_time

            # Check events
            if self.event_room is not None and current_time - self.event_start_time >= self.event_time:
                if self.event_target_flvl == 0 and self.event_room.fire_level == 2 or \
                    self.event_target_flvl == 2 and self.event_room.fire_level <= 1:
                    self.dashboard.take_damage(3)
                    self.damage_anim_start = time.time()
                self.event_room.is_event = False
                self.event_room = None

            # Check repair events
            if self.repair_room is not None and current_time - self.repair_start_time >= self.repair_time:
                self.repair_room.is_broken = True
                self.repair_room.is_breaking = False
                self.repair_room = None

    def draw(self):
        if self.state == const.MENU:
            draw_menu(self.surface)

        else:
            self.ship.draw()
            self.dashboard.draw(SPRINKLER_LIMIT - self.ship.num_sprinkling, self.lightyears_left, \
                self.state == const.REPAIRING)
            if self.active_text_box:
                self.active_text_box.draw(self.surface)
        # red flash when you take damage
        current_time = time.time()
        # from 0 to 0.1, transparency goes from 0 to 100, and then from 0.1 to 0.2 it goes back down
        if current_time - self.damage_anim_start <= 0.1:
            transparency = (current_time - self.damage_anim_start) * 1000
        else:
            transparency = (self.damage_anim_start - current_time + 0.2) * 1000

        if current_time - self.damage_anim_start <= 0.2:
            overlay_surface = pygame.Surface((const.WIN_LENGTH, const.WIN_HEIGHT), pygame.HWSURFACE)
            overlay_surface.set_alpha(transparency) # the surface is now semi-transparent
            util.bevelled_rect(overlay_surface, (255, 0, 0), (0, 0, const.WIN_LENGTH, const.WIN_HEIGHT), \
                15)
            self.surface.blit(overlay_surface, (0, 0))