view pytouhou/ui/gamerunner.pyx @ 792:11bc22bad1bf

python: Replace the image crate with png We weren’t using any of its features anyway, so the png crate is exactly what we need, without the many heavy dependencies of image. https://github.com/image-rs/image-png/pull/670 will eventually make it even faster to build.
author Link Mauve <linkmauve@linkmauve.fr>
date Sat, 17 Jan 2026 22:22:25 +0100
parents ec1e06402a97
children
line wrap: on
line source

# -*- encoding: utf-8 -*-
##
## Copyright (C) 2011 Thibaut Girka <thib@sitedethib.com>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##

cimport cython

from pytouhou.lib.gui cimport EXIT, PAUSE, SCREENSHOT, RESIZE, FULLSCREEN

from .window cimport Window, Runner
from libtouhou import Audio
from pytouhou.game.game cimport Game


class DummyAudio:
    def play(self, name):
        pass

    def play_bgm(self, track):
        pass


cdef class GameRunner(Runner):
    cdef object background, con, resource_loader, keys, replay_level, common
    cdef Game game
    cdef Window window
    cdef list save_keystates
    cdef bint skip, audio

    # Since we want to support multiple renderers, don’t specify its type.
    #TODO: find a way to still specify its interface.
    cdef object renderer

    def __init__(self, Window window, renderer, common, resource_loader,
                 bint skip=False, con=None, bint audio=True):
        self.renderer = renderer
        self.common = common
        self.resource_loader = resource_loader

        self.window = window
        self.replay_level = None
        self.skip = skip
        self.con = con
        self.audio = audio

        self.width = common.interface.width
        self.height = common.interface.height


    def load_game(self, Game game, background=None, bgms=None, replay=None,
                  save_keystates=None):
        self.game = game
        self.background = background

        if self.renderer is not None:
            self.renderer.load_textures(self.resource_loader.instanced_anms)
            self.renderer.load_background(background)

        self.set_input(replay)
        if replay and replay.levels[game.stage - 1]:
            game.players[0].lives = self.replay_level.lives
            game.players[0].power = self.replay_level.power
            game.players[0].bombs = self.replay_level.bombs
            game.difficulty = self.replay_level.difficulty

        self.save_keystates = save_keystates

        if self.audio:
            game.music = Audio(self.resource_loader, bgms)
        else:
            game.music = DummyAudio()
        if bgms is not None:
            game.music.play_bgm(0)

        game.sfx_player = game.music if not self.skip else DummyAudio()


    cdef bint set_input(self, replay=None) except True:
        if not replay or not replay.levels[self.game.stage-1]:
            self.replay_level = None
        else:
            self.replay_level = replay.levels[self.game.stage-1]
            self.keys = self.replay_level.iter_keystates()


    @cython.cdivision(True)
    cdef bint set_renderer_size(self, long width, long height) except True:
        if self.renderer is not None:
            runner_width = float(self.width)
            runner_height = float(self.height)

            scale = min(width / runner_width,
                        height / runner_height)

            new_width = <long>(runner_width * scale)
            new_height = <long>(runner_height * scale)

            x = (width - new_width) // 2
            y = (height - new_height) // 2

            self.renderer.size = x, y, new_width, new_height


    cdef bint start(self) except True:
        if self.renderer is not None:
            self.set_renderer_size(self.width, self.height)
            self.renderer.start(self.common)


    cdef bint capture(self) except True:
        if self.renderer is not None:
            filename = 'screenshot/frame%06d.ppm' % self.game.frame
            self.renderer.capture(filename, self.width, self.height)


    cpdef bint update(self, bint render) except -1:
        cdef long keystate
        capture = False

        if self.background is not None:
            self.background.update(self.game.frame)
        for event, args in self.window.get_events():
            if event == EXIT:
                return False
            elif event == PAUSE:
                return False  # TODO: implement the pause.
            elif event == FULLSCREEN:
                self.window.toggle_fullscreen()
            elif event == SCREENSHOT:
                capture = True
            elif event == RESIZE:
                width, height = args
                self.set_renderer_size(width, height)
                if self.window is not None:
                    self.window.set_size(width, height)
        if self.replay_level is None:
            keystate = self.window.get_keystate()
        else:
            try:
                keystate = self.keys.next()
            except StopIteration:
                keystate = 0
                if self.skip:
                    self.set_input()
                    self.skip = False
                    self.game.sfx_player = self.game.music

        if self.save_keystates is not None:
            self.save_keystates.append(keystate)

        if self.con is not None:
            self.con.run_iter(self.game, keystate)
        else:
            self.game.run_iter([keystate])

        labels = self.game.interface.labels
        if self.window is not None and 'framerate' in labels:
            labels['framerate'].set_text('%.2ffps' % self.window.get_fps())

        if render and not self.skip and self.renderer is not None:
            self.renderer.render(self.game)

        if capture:
            self.capture()

        return True