view gamelib/engine.py @ 469:e0b11d684ee8

Reference gameboard.level rather than game.level where appropriate. Saving and restoring games appears to largely work now (even if the level of the restored game is different to the current one).
author Simon Cross <hodgestar@gmail.com>
date Tue, 24 Nov 2009 22:19:53 +0000
parents 2b4cd86dfcaf
children 393e30ea0165
line wrap: on
line source

"""Game engine and states."""
from pgu.engine import Game, State, Quit
import pygame
from pygame.locals import USEREVENT, QUIT, KEYDOWN, K_ESCAPE, K_n, K_d, K_s, K_i

import gameboard
import gameover
import sound
import constants
import mainmenu
import helpscreen
import loadlevel
import level
from misc import check_exit

class Engine(Game):
    def __init__(self, main_app, level_name):
        self.main_app = main_app
        self.clock = pygame.time.Clock()
        self.level = level.Level(level_name)
        self._open_window = None
        self.gameboard = None
        self.level_loader = None

    def tick(self):
        """Tic toc."""
        pygame.time.wait(10)

    def load_new_level(self):
        self.level = self.level_loader.cur_level

    def open_window(self, window):
        """Open a widget as the main window."""
        if self._open_window is not None:
            self.main_app.close(self._open_window)
        self.main_app.open(window)
        self._open_window = window

    def create_game_board(self):
        """Create and open a gameboard window."""
        self.gameboard = gameboard.GameBoard(self.main_app,
                self.level)
        self.open_window(self.gameboard.get_top_widget())

    def set_main_menu(self):
        """Open the main menu"""
        self.scoreboard = gameover.ScoreTable(self.level)
        self.level_loader = None
        main_menu = mainmenu.make_main_menu(self.level)
        self.open_window(main_menu)

    def set_help_screen(self):
        """Open the main menu"""
        help_screen = helpscreen.make_help_screen(self.level)
        self.open_window(help_screen)

    def set_level_screen(self):
        """Open the Load Level screen"""
        level_screen, self.level_loader = loadlevel.make_load_screen(self.level)
        self.open_window(level_screen)

    def create_game_over(self):
        """Create and open the Game Over window"""
        level = self.gameboard.level
        game_over = gameover.create_game_over(self.gameboard,
                self.scoreboard[level.level_name], level)
        self.gameboard = None
        self.open_window(game_over)

    def event(self, e):
        if not Game.event(self, e):
            if self.gameboard:
                return self.gameboard.event(e)
            return False
        return True


class MainMenuState(State):
    def init(self):
        sound.stop_background_music()
        self.game.set_main_menu()

    def event(self, e):
        if events_equal(e, START_DAY):
            self.game.create_game_board()
            return DayState(self.game)
        elif events_equal(e, GO_HELP_SCREEN):
            return HelpScreenState(self.game)
        elif events_equal(e, GO_LEVEL_SCREEN):
            return LevelScreenState(self.game)
        elif e.type is KEYDOWN:
            if e.key == K_ESCAPE:
                return Quit(self.game)
            elif e.key == K_s:
                self.game.create_game_board()
                return DayState(self.game)
            elif e.key == K_i:
                return HelpScreenState(self.game)
        elif e.type is not QUIT:
            self.game.main_app.event(e)

    def paint(self, screen):
        screen.fill((0,0,0))
        self.game.main_app.paint(screen)
        pygame.display.flip()

    def update(self, screen):
        update = self.game.main_app.update(screen)
        pygame.display.update(update)

class HelpScreenState(State):
    def init(self):
        sound.stop_background_music()
        self.game.set_help_screen()

    def event(self, e):
        if e.type is KEYDOWN and e.key == K_ESCAPE:
            return MainMenuState(self.game)
        elif events_equal(e, GO_MAIN_MENU):
            return MainMenuState(self.game)
        elif e.type is not QUIT:
            self.game.main_app.event(e)

    def paint(self, screen):
        screen.fill((0,0,0))
        self.game.main_app.paint(screen)
        pygame.display.flip()

    def update(self, screen):
        update = self.game.main_app.update(screen)
        pygame.display.update(update)


class LevelScreenState(State):
    def init(self):
        sound.stop_background_music()
        self.game.set_level_screen()

    def event(self, e):
        if e.type is KEYDOWN and e.key == K_ESCAPE:
            return MainMenuState(self.game)
        elif events_equal(e, GO_MAIN_MENU):
            return MainMenuState(self.game)
        elif events_equal(e, DO_LOAD_LEVEL):
            self.game.load_new_level()
            return MainMenuState(self.game)
        elif e.type is not QUIT:
            self.game.main_app.event(e)

    def paint(self, screen):
        screen.fill((0,0,0))
        self.game.main_app.paint(screen)
        pygame.display.flip()

    def update(self, screen):
        update = self.game.main_app.update(screen)
        pygame.display.update(update)


class DayState(State):
    def init(self):
        """Add some chickens to the farm"""
        sound.stop_background_music()
        self.game.gameboard.start_day()

        sound.play_sound("daybreak.ogg")
        # disable timer
        pygame.time.set_timer(MOVE_FOX_ID, 0)
        sound.background_music("daytime.ogg")
        self.dialog = None

    def event(self, e):
        if self.dialog and self.dialog.running:
            if self.dialog.event(e):
                return
        elif self.dialog:
            if self.dialog.do_quit:
                self.dialog = None
                self.game.gameboard.reset_states()
                return GameOver(self.game)
            self.dialog=None
            return
        if events_equal(e, START_NIGHT):
            self.game.gameboard.reset_states()
            return NightState(self.game)
        elif e.type is KEYDOWN and e.key == K_ESCAPE:
            self.dialog = check_exit()
        elif e.type is KEYDOWN and e.key == K_n:
            return pygame.event.post(START_NIGHT)
        elif events_equal(e, GO_MAIN_MENU):
            return MainMenuState(self.game)
        elif e.type is not QUIT:
            self.game.main_app.event(e)

    def paint(self, screen):
        self.game.main_app.paint(screen)
        pygame.display.flip()

    def update(self, screen):
        self.game.gameboard.update()
        update = self.game.main_app.update(screen)
        pygame.display.update(update)

    def loop(self):
        self.game.gameboard.loop()

class NightState(State):
    def init(self):
        """Add some foxes to the farm"""
        sound.stop_background_music()
        self.game.gameboard.start_night()

        sound.play_sound("nightfall.ogg")

        self.game.gameboard.chickens_scatter()
        self.game.gameboard.chickens_chop_wood()
        # Add a timer to the event queue
        self.cycle_count = 0
        self.cycle_time = SLOW__SPEED
        pygame.time.set_timer(MOVE_FOX_ID, self.cycle_time)
        sound.background_music("nighttime.ogg")

        self.dialog = None

    def event(self, e):
        if self.dialog and self.dialog.running:
            if self.dialog.event(e):
                return
        elif self.dialog:
            if self.dialog.do_quit:
                self.dialog = None
                self.game.gameboard.reset_states()
                return GameOver(self.game)
            self.dialog=None
            return
        if events_equal(e, START_DAY):
            if self.game.gameboard.level.is_game_over(self.game.gameboard):
                return GameOver(self.game)
            return DayState(self.game)
        elif (e.type is KEYDOWN and e.key == K_d) or \
                events_equal(e, FAST_FORWARD):
            if self.cycle_time > FAST__SPEED:
                self.cycle_time = FAST__SPEED
            else:
                self.cycle_time = SLOW__SPEED
            pygame.time.set_timer(MOVE_FOX_ID, self.cycle_time)
        elif e.type is KEYDOWN and e.key == K_ESCAPE:
            self.dialog = check_exit()
        elif e.type is MOVE_FOX_ID:
            # ensure no timers trigger while we're running
            pygame.time.set_timer(MOVE_FOX_ID, 0)
            cur_time = pygame.time.get_ticks()
            # Clear any queued timer events, so we don't full the queue
            pygame.event.clear(MOVE_FOX_ID)
            # Ensure any outstanding animitions get cleaned up
            self.cycle_count += 1
            if self.cycle_count > constants.NIGHT_LENGTH:
                return pygame.event.post(START_DAY)
            if self.game.gameboard.do_night_step():
                # All foxes are gone/safe, so dawn happens
                return pygame.event.post(START_DAY)
            # Re-enable timers
            diff = pygame.time.get_ticks() - cur_time
            time_left = self.cycle_time - diff
            if time_left < 0:
                time_left = self.cycle_time
            pygame.time.set_timer(MOVE_FOX_ID, time_left)
        elif e.type is not QUIT:
            self.game.main_app.event(e)

    def loop(self):
        self.game.gameboard.loop()

    def paint(self, screen):
        self.game.main_app.paint(screen)
        pygame.display.flip()

    def update(self, screen):
        self.game.gameboard.update()
        update = self.game.main_app.update(screen)
        pygame.display.update(update)

class GameOver(State):
    def init(self):
        """Setup everything"""
        sound.stop_background_music()
        self.game.create_game_over()
        pygame.time.set_timer(MOVE_FOX_ID, 0)

    def event(self, e):
        if e.type is KEYDOWN:
            if e.key == K_ESCAPE:
                return MainMenuState(self.game)
        elif events_equal(e, GO_MAIN_MENU):
            return MainMenuState(self.game)
        elif e.type is not QUIT:
            self.game.main_app.event(e)

    def paint(self, screen):
        screen.fill((0,0,0))
        self.game.main_app.paint(screen)
        pygame.display.flip()

    def update(self, screen):
        update = self.game.main_app.update(screen)
        pygame.display.update(update)

# pygame events

def events_equal(e1, e2):
    """Compare two user events."""
    return (e1.type is e2.type and e1.name == e2.name)

START_DAY = pygame.event.Event(USEREVENT, name="START_DAY")
START_NIGHT = pygame.event.Event(USEREVENT, name="START_NIGHT")
GO_MAIN_MENU = pygame.event.Event(USEREVENT, name="GO_MAIN_MENU")
GO_HELP_SCREEN = pygame.event.Event(USEREVENT, name="GO_HELP_SCREEN")
GO_LEVEL_SCREEN = pygame.event.Event(USEREVENT, name="GO_LEVEL_SCREEN")
DO_LOAD_LEVEL = pygame.event.Event(USEREVENT, name="DO_LEVEL_SCREEN")
FAST_FORWARD = pygame.event.Event(USEREVENT, name="FAST_FORWARD")
MOVE_FOX_ID = USEREVENT + 1
MOVE_FOXES = pygame.event.Event(MOVE_FOX_ID, name="MOVE_FOXES")
QUIT = pygame.event.Event(QUIT)

# Due to the way pgu's loop timing works, these will only get proceesed
# at intervals of 10ms, so there's no point in them not being multiples of 10
FAST__SPEED=80
SLOW__SPEED=200