agb/examples/the-dungeon-puzzlers-lament/src/game.rs

427 lines
12 KiB
Rust
Raw Normal View History

2023-07-20 02:41:17 +10:00
use agb::{
display::{
object::{
OamIterator, ObjectTextRender, ObjectUnmanaged, PaletteVram, Size, SpriteLoader,
SpriteVram, TextAlignment,
},
palette16::Palette16,
tiled::{MapLoan, RegularMap, TiledMap, VRamManager},
HEIGHT,
},
fixnum::Vector2D,
input::{Button, ButtonController, Tri},
};
use crate::{
resources::{ARROW_RIGHT, FONT},
sfx::Sfx,
};
use self::{
game_state::{GameState, PLAY_AREA_HEIGHT, PLAY_AREA_WIDTH},
simulation::Simulation,
};
pub use simulation::Direction;
use core::{cell::RefCell, fmt::Write};
mod game_state;
mod simulation;
mod numbers;
struct Game<'a, 'b> {
phase: GamePhase<'a, 'b>,
}
struct Lament<'a, 'b> {
level: usize,
writer: RefCell<ObjectTextRender<'static>>,
background: &'a mut MapLoan<'b, RegularMap>,
}
fn generate_text_palette() -> PaletteVram {
let mut palette = [0x0; 16];
palette[1] = 0xFF_FF;
let palette = Palette16::new(palette);
PaletteVram::new(&palette).unwrap()
}
impl<'a, 'b> Lament<'a, 'b> {
fn new(level: usize, background: &'a mut MapLoan<'b, RegularMap>) -> Self {
let palette = generate_text_palette();
let mut writer = ObjectTextRender::new(&super::resources::FONT, Size::S16x16, palette);
let _ = writeln!(
writer,
"{}\n\n{}",
numbers::NUMBERS[level],
crate::level::Level::get_level(level).name
);
writer.layout(
Vector2D::new(
PLAY_AREA_WIDTH as i32 * 16 - 32,
PLAY_AREA_HEIGHT as i32 * 16,
),
TextAlignment::Center,
0,
);
Self {
level,
writer: RefCell::new(writer),
background,
}
}
fn update(self, input: &ButtonController, vram_manager: &mut VRamManager) -> GamePhase<'a, 'b> {
{
let mut writer = self.writer.borrow_mut();
writer.next_letter_group();
writer.update(Vector2D::new(16, HEIGHT / 4));
}
if input.is_just_pressed(Button::A) {
GamePhase::Construction(Construction::new(self.level, self.background, vram_manager))
} else {
GamePhase::Lament(self)
}
}
fn render(&self, oam: &mut OamIterator) {
self.writer.borrow_mut().commit(oam);
}
}
struct Construction<'a, 'b> {
game: GameState,
background: &'a mut MapLoan<'b, RegularMap>,
}
impl<'a, 'b> Drop for Construction<'a, 'b> {
fn drop(&mut self) {
self.background.set_visible(false);
2023-07-20 02:41:17 +10:00
}
}
impl<'a, 'b> Construction<'a, 'b> {
fn new(
level: usize,
background: &'a mut MapLoan<'b, RegularMap>,
vram_manager: &mut VRamManager,
) -> Self {
let game = GameState::new(level);
game.load_level_background(background, vram_manager);
background.commit(vram_manager);
background.set_visible(true);
2023-07-20 02:41:17 +10:00
Self { background, game }
}
fn update(
mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
) -> GamePhase<'a, 'b> {
self.game.step(input, sfx);
if input.is_just_pressed(Button::START) {
self.game.force_place();
GamePhase::Execute(Execute::new(self, sfx, loader))
} else {
GamePhase::Construction(self)
}
}
fn render(&self, oam: &mut OamIterator, loader: &mut SpriteLoader) {
self.game.render(loader, oam);
}
}
impl<'a, 'b> Execute<'a, 'b> {
fn new(construction: Construction<'a, 'b>, sfx: &mut Sfx, loader: &mut SpriteLoader) -> Self {
Self {
simulation: construction.game.create_simulation(sfx, loader),
construction,
}
}
fn update(
mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
) -> GamePhase<'a, 'b> {
if input.is_just_pressed(Button::START) {
return GamePhase::Construction(self.construction);
}
match self.simulation.update(loader, sfx) {
simulation::Outcome::Continue => GamePhase::Execute(self),
simulation::Outcome::Loss => GamePhase::Construction(self.construction),
simulation::Outcome::Win => GamePhase::NextLevel,
}
}
fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
self.simulation.render(oam);
self.construction
.game
.render_arrows(loader, oam, Some(self.simulation.current_turn()));
}
}
struct Execute<'a, 'b> {
simulation: Simulation,
construction: Construction<'a, 'b>,
}
#[derive(Default)]
enum GamePhase<'a, 'b> {
#[default]
Empty,
Lament(Lament<'a, 'b>),
Construction(Construction<'a, 'b>),
Execute(Execute<'a, 'b>),
NextLevel,
}
impl GamePhase<'_, '_> {
fn update(
&mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
vram_manger: &mut VRamManager,
) {
*self = match core::mem::take(self) {
GamePhase::Lament(lament) => lament.update(input, vram_manger),
GamePhase::Construction(construction) => construction.update(input, sfx, loader),
GamePhase::Execute(execute) => execute.update(input, sfx, loader),
GamePhase::NextLevel => GamePhase::NextLevel,
GamePhase::Empty => panic!("bad state"),
}
}
fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
match self {
GamePhase::Empty => panic!("bad state"),
GamePhase::Lament(lament) => lament.render(oam),
GamePhase::Construction(construction) => construction.render(oam, loader),
GamePhase::Execute(execute) => execute.render(loader, oam),
GamePhase::NextLevel => {}
}
}
}
impl<'a, 'b> Game<'a, 'b> {
pub fn new(level: usize, background: &'a mut MapLoan<'b, RegularMap>) -> Self {
Self {
phase: GamePhase::Lament(Lament::new(level, background)),
}
}
pub fn update(
&mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
vram_manager: &mut VRamManager,
) -> bool {
self.phase.update(input, sfx, loader, vram_manager);
matches!(self.phase, GamePhase::NextLevel)
}
pub fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
self.phase.render(loader, oam)
}
pub fn set_background_visibility(&mut self, visible: bool) {
2023-07-20 02:41:17 +10:00
match &mut self.phase {
GamePhase::Construction(construction) => construction.background.set_visible(visible),
GamePhase::Execute(execute) => execute.construction.background.set_visible(visible),
2023-07-20 02:41:17 +10:00
_ => {}
}
}
}
pub struct Pausable<'a, 'b> {
paused: Paused,
menu: PauseMenu,
game: Game<'a, 'b>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Paused {
Paused,
Playing,
}
impl Paused {
fn change(self) -> Paused {
match self {
Paused::Paused => Paused::Playing,
Paused::Playing => Paused::Paused,
}
}
}
#[derive(Clone, Copy)]
pub enum PauseSelection {
Restart,
LevelSelect(usize),
}
enum PauseSelectionInner {
Restart,
LevelSelect,
}
struct PauseMenu {
option_text: RefCell<[ObjectTextRender<'static>; 2]>,
selection: PauseSelectionInner,
indicator_sprite: SpriteVram,
selected_level: usize,
maximum_level: usize,
}
impl PauseMenu {
fn text_at_position(
text: core::fmt::Arguments,
position: Vector2D<i32>,
) -> ObjectTextRender<'static> {
let mut t = ObjectTextRender::new(&FONT, Size::S32x16, generate_text_palette());
let _ = writeln!(t, "{}", text);
t.layout(Vector2D::new(i32::MAX, i32::MAX), TextAlignment::Left, 0);
t.next_line();
t.update(position);
t
}
fn new(loader: &mut SpriteLoader, maximum_level: usize, current_level: usize) -> Self {
PauseMenu {
option_text: RefCell::new([
Self::text_at_position(format_args!("Restart"), Vector2D::new(32, HEIGHT / 4)),
Self::text_at_position(
format_args!("Go to level: {}", current_level + 1),
Vector2D::new(32, HEIGHT / 4 + 20),
),
]),
selection: PauseSelectionInner::Restart,
indicator_sprite: loader.get_vram_sprite(ARROW_RIGHT.sprite(0)),
selected_level: current_level,
maximum_level,
}
}
fn update(&mut self, input: &ButtonController) -> Option<PauseSelection> {
if input.is_just_pressed(Button::UP) | input.is_just_pressed(Button::DOWN) {
self.selection = match self.selection {
PauseSelectionInner::Restart => PauseSelectionInner::LevelSelect,
PauseSelectionInner::LevelSelect => PauseSelectionInner::Restart,
};
}
let lr = Tri::from((
input.is_just_pressed(Button::LEFT),
input.is_just_pressed(Button::RIGHT),
));
if matches!(self.selection, PauseSelectionInner::LevelSelect) && lr != Tri::Zero {
let selected_level = self.selected_level as i32;
let selected_level =
(selected_level + lr as i32).rem_euclid(self.maximum_level as i32 + 1);
self.selected_level = selected_level as usize;
self.option_text.borrow_mut()[1] = Self::text_at_position(
format_args!("Go to level: {}", selected_level + 1),
Vector2D::new(32, HEIGHT / 4 + 20),
)
}
if input.is_just_pressed(Button::A) | input.is_just_pressed(Button::START) {
Some(match self.selection {
PauseSelectionInner::Restart => PauseSelection::Restart,
PauseSelectionInner::LevelSelect => {
PauseSelection::LevelSelect(self.selected_level)
}
})
} else {
None
}
}
fn render(&self, oam: &mut OamIterator) {
for text in self.option_text.borrow_mut().iter_mut() {
text.commit(oam);
}
let mut indicator = ObjectUnmanaged::new(self.indicator_sprite.clone());
indicator.show();
match self.selection {
PauseSelectionInner::Restart => indicator.set_position(Vector2D::new(16, HEIGHT / 4)),
PauseSelectionInner::LevelSelect => {
indicator.set_position(Vector2D::new(16, HEIGHT / 4 + 20))
}
};
if let Some(slot) = oam.next() {
slot.set(&indicator);
}
}
}
pub enum UpdateResult {
MenuSelection(PauseSelection),
NextLevel,
}
impl<'a, 'b> Pausable<'a, 'b> {
pub fn new(
level: usize,
maximum_level: usize,
background: &'a mut MapLoan<'b, RegularMap>,
loader: &mut SpriteLoader,
) -> Self {
Self {
paused: Paused::Playing,
game: Game::new(level, background),
menu: PauseMenu::new(loader, maximum_level, level),
}
}
pub fn update(
&mut self,
input: &ButtonController,
sfx: &mut Sfx,
loader: &mut SpriteLoader,
vram_manager: &mut VRamManager,
) -> Option<UpdateResult> {
if input.is_just_pressed(Button::SELECT)
|| (matches!(self.paused, Paused::Paused) && input.is_just_pressed(Button::B))
{
self.paused = self.paused.change();
match self.paused {
Paused::Paused => self.game.set_background_visibility(false),
Paused::Playing => self.game.set_background_visibility(true),
2023-07-20 02:41:17 +10:00
}
}
if !matches!(self.paused, Paused::Paused) {
if self.game.update(input, sfx, loader, vram_manager) {
Some(UpdateResult::NextLevel)
} else {
None
}
} else {
self.menu.update(input).map(UpdateResult::MenuSelection)
}
}
pub fn render(&self, loader: &mut SpriteLoader, oam: &mut OamIterator) {
if matches!(self.paused, Paused::Paused) {
self.menu.render(oam);
} else {
self.game.render(loader, oam);
}
}
}