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>, 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.hide(); } } 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.show(); 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 hide_background(&mut self) { match &mut self.phase { GamePhase::Construction(construction) => construction.background.hide(), GamePhase::Execute(execute) => execute.construction.background.hide(), _ => {} } } pub fn show_background(&mut self) { match &mut self.phase { GamePhase::Construction(construction) => construction.background.show(), GamePhase::Execute(execute) => execute.construction.background.show(), _ => {} } } } 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, ) -> 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 { 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 { 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.hide_background(), Paused::Playing => self.game.show_background(), } } 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); } } }