Files
Archipelago/worlds/apquest/client/game_manager.py
NewSoupVi 3c4af8f432 APQuest: Tap to move (#6082)
* Tap to move

* inputs

* cleanup

* oops
2026-03-31 20:55:52 +02:00

226 lines
7.7 KiB
Python

from __future__ import annotations
# isort: off
from kvui import GameManager, MDNavigationItemBase
# isort: on
from typing import TYPE_CHECKING, Any
from kivy._clock import ClockEvent
from kivy.clock import Clock
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image
from kivy.uix.layout import Layout
from kivymd.uix.recycleview import MDRecycleView
from ..game.game import Game
from ..game.graphics import Graphic
from .custom_views import (
APQuestControlsView,
APQuestGameView,
APQuestGrid,
ConfettiView,
TapIfConfettiCannonImage,
TapImage,
VolumeSliderView,
)
from .graphics import PlayerSprite, get_texture
from .sounds import SoundManager
if TYPE_CHECKING:
from .ap_quest_client import APQuestContext
class APQuestManager(GameManager):
base_title = "APQuest for AP version"
ctx: APQuestContext
lower_game_grid: GridLayout
upper_game_grid: GridLayout
game_view: MDRecycleView
game_view_tab: MDNavigationItemBase
sound_manager: SoundManager
bottom_image_grid: list[list[Image]]
top_image_grid: list[list[TapImage]]
confetti_view: ConfettiView
move_event: ClockEvent | None
bottom_grid_is_grass: bool
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.sound_manager = SoundManager()
self.sound_manager.allow_intro_to_play = not self.ctx.delay_intro_song
self.top_image_grid = []
self.bottom_image_grid = []
self.move_event = None
self.bottom_grid_is_grass = False
def allow_intro_song(self) -> None:
self.sound_manager.allow_intro_to_play = True
def add_confetti(self, position: tuple[float, float], amount: int) -> None:
self.confetti_view.add_confetti(position, amount)
def play_jingle(self, audio_filename: str) -> None:
self.sound_manager.play_jingle(audio_filename)
def switch_to_tab(self, desired_tab: MDNavigationItemBase) -> None:
if self.screens.current_tab == desired_tab:
return
self.screens.current_tab.active = False
self.screens.switch_screens(desired_tab)
desired_tab.active = True
def switch_to_game_tab(self) -> None:
self.switch_to_tab(self.game_view_tab)
def switch_to_regular_tab(self) -> None:
self.switch_to_tab(self.tabs.children[-1])
def game_started(self) -> None:
self.switch_to_game_tab()
self.sound_manager.game_started = True
def render(self, game: Game, player_sprite: PlayerSprite) -> None:
self.setup_game_grid_if_not_setup(game)
# This calls game.render(), which needs to happen to update the state of math traps
self.render_gameboard(game, player_sprite)
# Only now can we check whether a math problem is active
self.render_background_game_grid(game.gameboard.size, game.active_math_problem is None)
self.sound_manager.math_trap_active = game.active_math_problem is not None
self.render_item_column(game)
def render_gameboard(self, game: Game, player_sprite: PlayerSprite) -> None:
rendered_gameboard = game.render()
for gameboard_row, image_row in zip(rendered_gameboard, self.top_image_grid, strict=False):
for graphic, image in zip(gameboard_row, image_row[:11], strict=False):
texture = get_texture(graphic, player_sprite)
if texture is None:
image.opacity = 0
image.texture = None
continue
image.texture = texture
image.opacity = 1
def render_item_column(self, game: Game) -> None:
rendered_item_column = game.render_health_and_inventory(vertical=True)
for item_graphic, image_row in zip(rendered_item_column, self.top_image_grid, strict=False):
image = image_row[-1]
image.is_confetti_cannon = item_graphic == Graphic.CONFETTI_CANNON
texture = get_texture(item_graphic)
if texture is None:
image.opacity = 0
image.texture = None
continue
image.texture = texture
image.opacity = 1
def render_background_game_grid(self, size: tuple[int, int], grass: bool) -> None:
if grass == self.bottom_grid_is_grass:
return
for row in range(size[1]):
for column in range(size[0]):
image = self.bottom_image_grid[row][column]
if not grass:
image.color = (0.3, 0.3, 0.3)
image.texture = None
continue
boss_room = (row in (0, 1, 2) and (size[1] - column) in (1, 2, 3)) or (row, column) == (3, size[1] - 2)
if boss_room:
image.color = (0.45, 0.35, 0.1)
image.texture = None
continue
image.texture = get_texture("Grass")
image.color = (1.0, 1.0, 1.0)
self.bottom_grid_is_grass = grass
def setup_game_grid_if_not_setup(self, game: Game) -> None:
if self.upper_game_grid.children:
return
self.top_image_grid = []
self.bottom_image_grid = []
size = game.gameboard.size
for row in range(size[1]):
self.top_image_grid.append([])
self.bottom_image_grid.append([])
for column in range(size[0]):
bottom_image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
self.lower_game_grid.add_widget(bottom_image)
self.bottom_image_grid[-1].append(bottom_image)
top_image = TapImage(lambda y=row, x=column: self.ctx.queue_auto_move(x, y), fit_mode="fill")
self.upper_game_grid.add_widget(top_image)
self.top_image_grid[-1].append(top_image)
# Right side: Inventory
image = Image(fit_mode="fill", color=(0.3, 0.3, 0.3))
self.lower_game_grid.add_widget(image)
image2 = TapIfConfettiCannonImage(lambda: self.ctx.confetti_and_rerender(), fit_mode="fill", opacity=0)
self.upper_game_grid.add_widget(image2)
self.top_image_grid[-1].append(image2)
def start_auto_move(self) -> None:
if self.move_event is not None:
self.move_event.cancel()
self.ctx.do_auto_move_and_rerender()
self.move_event = Clock.schedule_interval(lambda _: self.ctx.do_auto_move_and_rerender(), 0.10)
def build(self) -> Layout:
container = super().build()
self.game_view = APQuestGameView(self.ctx.input_and_rerender)
self.game_view_tab = self.add_client_tab("APQuest", self.game_view)
controls = APQuestControlsView()
self.add_client_tab("Controls", controls)
game_container = self.game_view.ids["game_container"]
self.lower_game_grid = APQuestGrid()
self.upper_game_grid = APQuestGrid()
self.confetti_view = ConfettiView()
game_container.add_widget(self.lower_game_grid)
game_container.add_widget(self.upper_game_grid)
game_container.add_widget(self.confetti_view)
game_container.bind(size=self.lower_game_grid.check_resize)
game_container.bind(size=self.upper_game_grid.check_resize)
game_container.bind(size=self.confetti_view.check_resize)
volume_slider_container = VolumeSliderView()
volume_slider = volume_slider_container.ids["volume_slider"]
volume_slider.value = self.sound_manager.volume_percentage
volume_slider.bind(value=lambda _, new_volume: self.sound_manager.set_volume_percentage(new_volume))
self.grid.add_widget(volume_slider_container, index=3)
Clock.schedule_interval(lambda dt: self.confetti_view.redraw_confetti(dt), 1 / 60)
return container