use std::rc::Rc; use dungeon::{ Dungeon, UpdateResult, entity::Item, map::Tile, player::PLAYER_INVENTORY_SIZE_USIZE, player_input::PlayerInput, pos::Direction, }; use graphics::{Key, Window}; use crate::music::Music; mod music; macro_rules! play_sound { ($self:expr, $sound:expr) => { $self.window.audio().schedule( Rc::clone(&$sound.track), $sound.priority, $sound.looping, ); }; } /// The `Error` type used within this crate pub type Error = Box; /// The `Result` type used witin this crate pub type Result = std::result::Result; pub struct Game { window: Window, dungeon: Dungeon, music: Music, // to ensure the most recently-pressed direction key is used: current_dir: Option<(Direction, Key)>, } impl Game { pub fn new(window: Window, seed: Option) -> Result { let music = Music::load()?; let dungeon = match seed { Some(s) => Dungeon::new(s), None => Dungeon::random(), }; let game = Self { window, dungeon, music, current_dir: None, }; Ok(game) } fn player_dir(&mut self) -> Option { const MOVE_KEYS: [(Direction, [Key; 2]); 4] = [ (Direction::North, [Key::Up, Key::W]), (Direction::West, [Key::Left, Key::A]), (Direction::South, [Key::Down, Key::S]), (Direction::East, [Key::Right, Key::D]), ]; // if a key was just pressed, use it for the new direction to move in for (dir, keys) in MOVE_KEYS { for key in keys { if self.window.is_key_pressed(key) { self.current_dir = Some((dir, key)); return Some(dir); } } } // otherwise, use existing direction, so long as the key is still down match self.current_dir { Some((dir, key)) if self.window.is_key_down(key) => return Some(dir), _ => self.current_dir = None, } // otherwise, use any key that is already down for (dir, keys) in MOVE_KEYS { for key in keys { if self.window.is_key_down(key) { self.current_dir = Some((dir, key)); return Some(dir); } } } // otherwise, no direction key is pressed, so return None None } fn get_player_input(&mut self) -> PlayerInput { let direction = self.player_dir(); let interact = self.window.is_key_pressed(Key::Return); let use_item = self.window.is_key_pressed(Key::E); let attack = self.window.is_key_pressed(Key::F); let drop = self.window.is_key_pressed(Key::Q); let inv_slot = (0..PLAYER_INVENTORY_SIZE_USIZE) .find(|n| self.window.is_key_pressed(Key::Number(n + 1))); PlayerInput { direction, interact, use_item, attack, drop, inv_slot, } } pub fn run(&mut self) { play_sound!(self, &self.music.background); // Main game loop while self.window.is_open() { // Handle debug keys if self.window.is_key_pressed(Key::F3) { self.window.toggle_debug(); } if self.window.is_key_pressed(Key::F4) { *self.dungeon.floor.get_mut(self.dungeon.player.entity.pos) = Tile::Chest(Some(Item::Bomb)); } // Update game state let inputs = self.get_player_input(); let result = self .dungeon .update(inputs, self.window.delta_time().as_secs_f32()); match result { UpdateResult::Default(action) => { // items if action.bomb { play_sound!(self, &self.music.use_bomb); } else if action.potion { play_sound!(self, &self.music.use_potion); } else if action.drop_item { play_sound!(self, &self.music.drop_item); } else if action.pickup_item { play_sound!(self, &self.music.pickup_item); } // actions if action.attack { play_sound!(self, &self.music.attack); } else if action.walk { // TODO: do we want walking sounds? (were a ghost) } } UpdateResult::NextFloor => { play_sound!(self, &self.music.discover); } UpdateResult::MessageUpdated(changed) => { if changed { play_sound!(self, &self.music.speak); } } UpdateResult::GameOver => { self.window.audio().clear(); play_sound!(self, &self.music.game_over); } UpdateResult::Nothing => {} } // Draw a single frame self.window.draw_frame(&self.dungeon); // Update audio self.window.audio().update(); } } }