From 77a94a1ab3a827f1c61ac2b7f255510a24a56e51 Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Sun, 4 Oct 2020 17:28:08 +0900 Subject: [PATCH] Add puzzle --- src/interactable.rs | 28 +++++ src/main.rs | 1 + src/puzzle.rs | 273 ++++++++++++++++++++++++++++++++++++++++ src/scenes/mainscene.rs | 127 +++++++++++++++++-- 4 files changed, 422 insertions(+), 7 deletions(-) create mode 100644 src/puzzle.rs diff --git a/src/interactable.rs b/src/interactable.rs index aa9a324..b0549fe 100644 --- a/src/interactable.rs +++ b/src/interactable.rs @@ -1,12 +1,15 @@ use ggez::graphics::{self, Color, DrawMode, DrawParam, Mesh, Rect}; use ggez::{Context, GameResult}; +use crate::scenes::mainscene::PuzzleID; + const DEFAULT_RADIUS: f32 = 70f32; #[derive(Copy, Clone, PartialEq)] pub enum InteractableType { Door(usize), LockedDoor(usize, bool), + Puzzle(PuzzleID, bool), } pub struct Interactable { @@ -86,6 +89,25 @@ impl Interactable { DrawParam::new().dest([self.x - 7f32, self.y - 8f32]), )?; } + InteractableType::Puzzle(_, cleared) => { + let color; + if cleared { + color = Color::from_rgb(0x3f, 0xf8, 0x4c); + } else { + color = Color::from_rgb(0xef, 0, 0); + } + let panel_mesh = Mesh::new_rectangle( + ctx, + DrawMode::fill(), + Rect::new(0f32, 0f32, 40f32, 30f32), + color, + )?; + graphics::draw( + ctx, + &panel_mesh, + DrawParam::new().dest([self.x - 20f32, self.y - 15f32]), + )?; + } } Ok(()) @@ -96,4 +118,10 @@ impl Interactable { *is_unlocked = unlocked; } } + + pub fn set_puzzle_cleared(&mut self, cleared: bool) { + if let InteractableType::Puzzle(_, is_cleared) = &mut self.itype { + *is_cleared = cleared; + } + } } diff --git a/src/main.rs b/src/main.rs index 795c423..a589848 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod door; mod game; mod interactable; mod player; +mod puzzle; mod scenes; use ggez::conf::WindowSetup; diff --git a/src/puzzle.rs b/src/puzzle.rs new file mode 100644 index 0000000..2670da6 --- /dev/null +++ b/src/puzzle.rs @@ -0,0 +1,273 @@ +use ggez::graphics::{self, Color, DrawMode, DrawParam, Font, Mesh, Rect, Scale, Text}; +use ggez::input::keyboard::KeyCode; +use ggez::{Context, GameResult}; + +use crate::scenes::mainscene::PuzzleID; + +const INFO_TEXT_POS: [f32; 2] = [400f32, 80f32]; +const RESET_TEXT_POS: [f32; 2] = [100f32, 500f32]; +const SKIP_TEXT_POS: [f32; 2] = [700f32, 500f32]; + +pub struct Puzzle { + ptype: PuzzleID, + tiles: Vec, + key_pos: usize, + key_pressed: bool, + abort: bool, + info_text: Text, + reset_text: Text, + skip_text: Text, + force_solve: bool, +} + +impl Puzzle { + pub fn new(ptype: PuzzleID, font: Font) -> Self { + let mut info_text = Text::new("Make all tiles green"); + info_text.set_font(font, Scale::uniform(30f32)); + let mut reset_text = Text::new("Reset"); + reset_text.set_font(font, Scale::uniform(20f32)); + let mut skip_text = Text::new("Skip"); + skip_text.set_font(font, Scale::uniform(20f32)); + + let mut puzzle = Self { + ptype, + tiles: Vec::new(), + key_pos: 0, + key_pressed: true, + abort: false, + info_text, + reset_text, + skip_text, + force_solve: false, + }; + + puzzle.reset(); + + puzzle + } + + pub fn reset(&mut self) { + match self.ptype { + PuzzleID::FarRightHall => { + self.tiles.clear(); + self.tiles.push(true); + self.tiles.push(false); + self.tiles.push(true); + + self.tiles.push(false); + self.tiles.push(false); + self.tiles.push(false); + + self.tiles.push(false); + self.tiles.push(false); + self.tiles.push(true); + } + } + } + + pub fn update(&mut self, _ctx: &mut Context) -> GameResult<()> { + match self.ptype { + PuzzleID::FarRightHall => {} + } + Ok(()) + } + + pub fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { + { + let bg_mesh = Mesh::new_rectangle( + ctx, + DrawMode::fill(), + Rect::new(50f32, 50f32, 700f32, 500f32), + Color::from_rgb(0x29, 0x8d, 0xff), + )?; + graphics::draw(ctx, &bg_mesh, DrawParam::new())?; + } + match self.ptype { + PuzzleID::FarRightHall => { + let rect = Mesh::new_rectangle( + ctx, + DrawMode::fill(), + Rect::new(0f32, 0f32, 90f32, 90f32), + graphics::WHITE, + )?; + for i in 0..9usize { + if self.tiles[i] { + graphics::draw( + ctx, + &rect, + DrawParam::new() + .dest([ + 400f32 + (i % 3) as f32 * 100f32 - 150f32 + 5f32, + 300f32 + (i / 3) as f32 * 100f32 - 150f32 + 5f32, + ]) + .color(Color::from_rgb(0, 0xff, 0)), + )?; + } else { + graphics::draw( + ctx, + &rect, + DrawParam::new() + .dest([ + 400f32 + (i % 3) as f32 * 100f32 - 150f32 + 5f32, + 300f32 + (i / 3) as f32 * 100f32 - 150f32 + 5f32, + ]) + .color(Color::from_rgb(0xff, 0, 0)), + )?; + } + } + if self.key_pressed { + let pointer = Mesh::from_triangles( + ctx, + &[[0f32, 0f32], [32f32, 0f32], [0f32, 32f32]], + graphics::WHITE, + )?; + graphics::draw( + ctx, + &pointer, + DrawParam::new().dest([ + 400f32 + (self.key_pos % 3) as f32 * 100f32 - 100f32, + 300f32 + (self.key_pos / 3) as f32 * 100f32 - 100f32, + ]), + )?; + } + } + } + let info_text_width = self.info_text.width(ctx); + graphics::draw( + ctx, + &self.info_text, + DrawParam::new().dest([INFO_TEXT_POS[0] - info_text_width as f32, INFO_TEXT_POS[1]]), + )?; + graphics::draw(ctx, &self.reset_text, DrawParam::new().dest(RESET_TEXT_POS))?; + graphics::draw(ctx, &self.skip_text, DrawParam::new().dest(SKIP_TEXT_POS))?; + Ok(()) + } + + pub fn handle_click(&mut self, ctx: &mut Context, x: f32, y: f32) { + self.key_pressed = false; + match self.ptype { + PuzzleID::FarRightHall => { + if y > 150f32 && y < 250f32 { + if x > 250f32 && x < 350f32 { + self.handle_puzzle_input(0); + } else if x > 350f32 && x < 450f32 { + self.handle_puzzle_input(1); + } else if x > 450f32 && x < 550f32 { + self.handle_puzzle_input(2); + } + } else if y > 250f32 && y < 350f32 { + if x > 250f32 && x < 350f32 { + self.handle_puzzle_input(3); + } else if x > 350f32 && x < 450f32 { + self.handle_puzzle_input(4); + } else if x > 450f32 && x < 550f32 { + self.handle_puzzle_input(5); + } + } else if y > 350f32 && y < 450f32 { + if x > 250f32 && x < 350f32 { + self.handle_puzzle_input(6); + } else if x > 350f32 && x < 450f32 { + self.handle_puzzle_input(7); + } else if x > 450f32 && x < 550f32 { + self.handle_puzzle_input(8); + } + } + } + } + let reset_width = self.reset_text.width(ctx); + let skip_width = self.skip_text.width(ctx); + if y > 490f32 && y < 530f32 { + if x > 100f32 && x < 100f32 + reset_width as f32 { + self.reset(); + } else if x > 700f32 && x < 700f32 + skip_width as f32 { + self.force_solve = true; + } + } + } + + pub fn handle_key(&mut self, _ctx: &mut Context, keycode: KeyCode) { + match self.ptype { + PuzzleID::FarRightHall => { + if keycode == KeyCode::A || keycode == KeyCode::Left { + if self.key_pos % 3 == 0 { + self.key_pos += 2; + } else { + self.key_pos -= 1; + } + self.key_pressed = true; + } else if keycode == KeyCode::D || keycode == KeyCode::Right { + if self.key_pos % 3 == 2 { + self.key_pos -= 2; + } else { + self.key_pos += 1; + } + self.key_pressed = true; + } else if keycode == KeyCode::W || keycode == KeyCode::Up { + if self.key_pos / 3 == 0 { + self.key_pos += 6; + } else { + self.key_pos -= 3; + } + self.key_pressed = true; + } else if keycode == KeyCode::S || keycode == KeyCode::Down { + if self.key_pos / 3 == 2 { + self.key_pos -= 6; + } else { + self.key_pos += 3; + } + self.key_pressed = true; + } else if keycode == KeyCode::E + || keycode == KeyCode::Space + || keycode == KeyCode::Return + { + self.handle_puzzle_input(self.key_pos); + self.key_pressed = true; + } else if keycode == KeyCode::Escape { + self.abort = true; + } + } + } + } + + fn handle_puzzle_input(&mut self, idx: usize) { + match self.ptype { + PuzzleID::FarRightHall => { + self.tiles[idx] = !self.tiles[idx]; + if idx % 3 > 0 { + self.tiles[idx - 1] = !self.tiles[idx - 1]; + } + if idx % 3 < 2 { + self.tiles[idx + 1] = !self.tiles[idx + 1]; + } + if idx / 3 > 0 { + self.tiles[idx - 3] = !self.tiles[idx - 3]; + } + if idx / 3 < 2 { + self.tiles[idx + 3] = !self.tiles[idx + 3]; + } + } + } + } + + pub fn is_solved(&self) -> bool { + if self.force_solve { + return true; + } + match self.ptype { + PuzzleID::FarRightHall => { + let mut solved = true; + for tile in &self.tiles { + if !tile { + solved = false; + break; + } + } + solved + } + } + } + + pub fn is_abort(&self) -> bool { + self.abort + } +} diff --git a/src/scenes/mainscene.rs b/src/scenes/mainscene.rs index 767648b..6771e70 100644 --- a/src/scenes/mainscene.rs +++ b/src/scenes/mainscene.rs @@ -14,6 +14,7 @@ use super::Scene; use crate::door::Door; use crate::interactable::{Interactable, InteractableType}; use crate::player::Player; +use crate::puzzle::Puzzle; const DARKNESS_PAN_RATE: f32 = 40f32; const FLICKER_TIME: [f32; 6] = [1f32, 0.1f32, 0.85f32, 0.07f32, 0.12f32, 0.09f32]; @@ -33,6 +34,7 @@ enum State { Investigate, EnterDoor(Room), ExitDoor, + InPuzzle(PuzzleID), } #[derive(Copy, Clone, PartialEq)] @@ -42,6 +44,7 @@ enum Room { MainHallFrontOfPod, WindowRightHall, LeftHall, + FarRightHall, } enum WalkingState { @@ -62,6 +65,11 @@ enum DiscoveryState { Discovery, } +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub enum PuzzleID { + FarRightHall, +} + pub struct MainScene { font: Font, player: Rc>, @@ -87,7 +95,7 @@ pub struct MainScene { doors: Vec, door_text: Text, door_sfx: Source, - // (is_open, is_locked) + // (is_open, is_unlocked) door_states: HashMap, earth_image: Image, discovery_state: DiscoveryState, @@ -95,6 +103,8 @@ pub struct MainScene { saw_earth: bool, window_image: Image, error_sfx: Source, + puzzle_states: HashMap, + puzzle: Option, } impl MainScene { @@ -109,7 +119,6 @@ impl MainScene { door_text.set_font(font, Scale::uniform(20f32)); let door_states = HashMap::new(); - // door_states.insert(DoorIDs::LeftOfPod, false); Self { font, @@ -143,6 +152,8 @@ impl MainScene { saw_earth: false, window_image: Image::new(ctx, "/window.png").unwrap(), error_sfx: Source::new(ctx, "/error_sfx.ogg").unwrap(), + puzzle_states: HashMap::new(), + puzzle: None, } } @@ -221,13 +232,13 @@ impl MainScene { self.doors.clear(); self.interactables.clear(); self.doors - .push(Door::new(false, 100f32, 600f32 - 160f32 - 50f32, 0)); + .push(Door::new(false, 150f32, 600f32 - 160f32 - 50f32, 0)); if let Some((true, _)) = self.door_states.get(&DoorIDs::LeftHall) { self.doors[0].set_open(true); } self.interactables.push(Interactable::new( InteractableType::LockedDoor(0, false), - 70f32, + 120f32, 450f32, )); if let Some((_, true)) = self.door_states.get(&DoorIDs::LeftHall) { @@ -235,6 +246,21 @@ impl MainScene { } self.darkness_yoffset = -250f32; } + Room::FarRightHall => { + self.doors.clear(); + self.interactables.clear(); + self.interactables.push(Interactable::new( + InteractableType::Puzzle(PuzzleID::FarRightHall, false), + 400f32, + 500f32, + )); + if self.puzzle_states.contains_key(&PuzzleID::FarRightHall) { + if let Some(true) = self.puzzle_states.get(&PuzzleID::FarRightHall) { + self.interactables[0].set_puzzle_cleared(true); + } + } + self.darkness_yoffset = -450f32; + } } } @@ -254,10 +280,14 @@ impl MainScene { } Room::WindowRightHall => { draw_left = true; + draw_right = true; } Room::LeftHall => { draw_right = true; } + Room::FarRightHall => { + draw_left = true; + } } if draw_left { @@ -299,6 +329,11 @@ impl MainScene { self.init_room(); } Room::LeftHall => (), + Room::FarRightHall => { + self.room = Room::WindowRightHall; + self.player.borrow_mut().x = 800f32 - 70f32 - 64f32; + self.init_room(); + } } } @@ -315,12 +350,17 @@ impl MainScene { self.player.borrow_mut().x = 70f32; self.init_room(); } - Room::WindowRightHall => {} + Room::WindowRightHall => { + self.room = Room::FarRightHall; + self.player.borrow_mut().x = 70f32; + self.init_room(); + } Room::LeftHall => { self.room = Room::MainHallFrontOfPod; self.player.borrow_mut().x = 70f32; self.init_room(); } + Room::FarRightHall => (), } } @@ -346,6 +386,7 @@ impl MainScene { Room::LeftHall => { // TODO } + Room::FarRightHall => (), } } } @@ -375,6 +416,7 @@ impl MainScene { } Room::WindowRightHall => (), Room::LeftHall => (), + Room::FarRightHall => (), } self.door_sfx.play()?; } @@ -408,6 +450,20 @@ impl MainScene { self.error_sfx.play()?; } } + Room::FarRightHall => (), + }, + InteractableType::Puzzle(id, cleared) => match self.room { + Room::StasisPod + | Room::LeftOfPod + | Room::MainHallFrontOfPod + | Room::LeftHall + | Room::WindowRightHall => (), + Room::FarRightHall => { + if !cleared { + self.state = State::InPuzzle(id); + self.puzzle = Some(Puzzle::new(id, self.font)); + } + } }, } Ok(()) @@ -439,6 +495,7 @@ impl MainScene { )?; } Room::LeftHall => {} + Room::FarRightHall => {} } for door in &self.doors { door.draw(ctx, &self.door_image)?; @@ -448,6 +505,27 @@ impl MainScene { } Ok(()) } + + fn handle_solved_puzzle(&mut self, _ctx: &mut Context) -> GameResult<()> { + match self.state { + State::InPodInDarkness + | State::InPodWakeupText + | State::GetOutOfPod + | State::Investigate + | State::EnterDoor(_) + | State::ExitDoor => unreachable!("Cannot solve puzzle from invalid state"), + State::InPuzzle(id) => match id { + PuzzleID::FarRightHall => { + self.puzzle_states.insert(id, true); + self.puzzle = None; + self.interactables[0].set_puzzle_cleared(true); + self.door_states.insert(DoorIDs::LeftHall, (false, true)); + self.door_states.insert(DoorIDs::LeftOfPod, (false, false)); + } + }, + } + Ok(()) + } } impl EventHandler for MainScene { @@ -561,6 +639,20 @@ impl EventHandler for MainScene { self.player.borrow_mut().color.a = 1f32 - self.timer / DOOR_EXIT_ENTER_TIME; } } + State::InPuzzle(_) => { + if let Some(puzzle) = &mut self.puzzle { + if puzzle.is_solved() { + self.handle_solved_puzzle(ctx)?; + } else if puzzle.is_abort() { + self.puzzle = None; + self.state = State::Investigate; + } else { + puzzle.update(ctx)?; + } + } else { + self.state = State::Investigate; + } + } } self.player.borrow_mut().update(ctx)?; if self.discovery_state == DiscoveryState::Discovery && self.discovery_music.stopped() { @@ -613,6 +705,7 @@ impl EventHandler for MainScene { State::ExitDoor => { self.draw_room(ctx)?; } + State::InPuzzle(_) => (), } self.player.borrow_mut().draw(ctx)?; @@ -648,6 +741,7 @@ impl EventHandler for MainScene { Room::MainHallFrontOfPod => {} Room::WindowRightHall => (), Room::LeftHall => (), + Room::FarRightHall => (), } for interactable in &self.interactables { @@ -685,12 +779,17 @@ impl EventHandler for MainScene { self.draw_room_arrows(ctx)?; } State::EnterDoor(_) | State::ExitDoor => (), + State::InPuzzle(_) => { + if let Some(puzzle) = &mut self.puzzle { + puzzle.draw(ctx)?; + } + } } Ok(()) } - fn mouse_button_down_event(&mut self, _ctx: &mut Context, button: MouseButton, x: f32, y: f32) { + fn mouse_button_down_event(&mut self, ctx: &mut Context, button: MouseButton, x: f32, y: f32) { match self.state { State::InPodInDarkness => (), State::InPodWakeupText => { @@ -742,6 +841,13 @@ impl EventHandler for MainScene { } } State::EnterDoor(_) | State::ExitDoor => (), + State::InPuzzle(_) => { + if let Some(puzzle) = &mut self.puzzle { + if button == MouseButton::Left { + puzzle.handle_click(ctx, x, y); + } + } + } } } @@ -756,12 +862,13 @@ impl EventHandler for MainScene { } } State::EnterDoor(_) | State::ExitDoor => (), + State::InPuzzle(_) => {} } } fn key_down_event( &mut self, - _ctx: &mut Context, + ctx: &mut Context, keycode: KeyCode, _keymods: KeyMods, _repeat: bool, @@ -820,6 +927,11 @@ impl EventHandler for MainScene { } } State::EnterDoor(_) | State::ExitDoor => (), + State::InPuzzle(_) => { + if let Some(puzzle) = &mut self.puzzle { + puzzle.handle_key(ctx, keycode); + } + } } } @@ -836,6 +948,7 @@ impl EventHandler for MainScene { } } State::EnterDoor(_) | State::ExitDoor => (), + State::InPuzzle(_) => {} } } } -- 2.49.0