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 {
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(())
*is_unlocked = unlocked;
}
}
+
+ pub fn set_puzzle_cleared(&mut self, cleared: bool) {
+ if let InteractableType::Puzzle(_, is_cleared) = &mut self.itype {
+ *is_cleared = cleared;
+ }
+ }
}
mod game;
mod interactable;
mod player;
+mod puzzle;
mod scenes;
use ggez::conf::WindowSetup;
--- /dev/null
+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<bool>,
+ 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
+ }
+}
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];
Investigate,
EnterDoor(Room),
ExitDoor,
+ InPuzzle(PuzzleID),
}
#[derive(Copy, Clone, PartialEq)]
MainHallFrontOfPod,
WindowRightHall,
LeftHall,
+ FarRightHall,
}
enum WalkingState {
Discovery,
}
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+pub enum PuzzleID {
+ FarRightHall,
+}
+
pub struct MainScene {
font: Font,
player: Rc<RefCell<Player>>,
doors: Vec<Door>,
door_text: Text,
door_sfx: Source,
- // (is_open, is_locked)
+ // (is_open, is_unlocked)
door_states: HashMap<DoorIDs, (bool, bool)>,
earth_image: Image,
discovery_state: DiscoveryState,
saw_earth: bool,
window_image: Image,
error_sfx: Source,
+ puzzle_states: HashMap<PuzzleID, bool>,
+ puzzle: Option<Puzzle>,
}
impl MainScene {
door_text.set_font(font, Scale::uniform(20f32));
let door_states = HashMap::new();
- // door_states.insert(DoorIDs::LeftOfPod, false);
Self {
font,
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,
}
}
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) {
}
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;
+ }
}
}
}
Room::WindowRightHall => {
draw_left = true;
+ draw_right = true;
}
Room::LeftHall => {
draw_right = true;
}
+ Room::FarRightHall => {
+ draw_left = true;
+ }
}
if draw_left {
self.init_room();
}
Room::LeftHall => (),
+ Room::FarRightHall => {
+ self.room = Room::WindowRightHall;
+ self.player.borrow_mut().x = 800f32 - 70f32 - 64f32;
+ self.init_room();
+ }
}
}
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 => (),
}
}
Room::LeftHall => {
// TODO
}
+ Room::FarRightHall => (),
}
}
}
}
Room::WindowRightHall => (),
Room::LeftHall => (),
+ Room::FarRightHall => (),
}
self.door_sfx.play()?;
}
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(())
)?;
}
Room::LeftHall => {}
+ Room::FarRightHall => {}
}
for door in &self.doors {
door.draw(ctx, &self.door_image)?;
}
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 {
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() {
State::ExitDoor => {
self.draw_room(ctx)?;
}
+ State::InPuzzle(_) => (),
}
self.player.borrow_mut().draw(ctx)?;
Room::MainHallFrontOfPod => {}
Room::WindowRightHall => (),
Room::LeftHall => (),
+ Room::FarRightHall => (),
}
for interactable in &self.interactables {
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 => {
}
}
State::EnterDoor(_) | State::ExitDoor => (),
+ State::InPuzzle(_) => {
+ if let Some(puzzle) = &mut self.puzzle {
+ if button == MouseButton::Left {
+ puzzle.handle_click(ctx, x, y);
+ }
+ }
+ }
}
}
}
}
State::EnterDoor(_) | State::ExitDoor => (),
+ State::InPuzzle(_) => {}
}
}
fn key_down_event(
&mut self,
- _ctx: &mut Context,
+ ctx: &mut Context,
keycode: KeyCode,
_keymods: KeyMods,
_repeat: bool,
}
}
State::EnterDoor(_) | State::ExitDoor => (),
+ State::InPuzzle(_) => {
+ if let Some(puzzle) = &mut self.puzzle {
+ puzzle.handle_key(ctx, keycode);
+ }
+ }
}
}
}
}
State::EnterDoor(_) | State::ExitDoor => (),
+ State::InPuzzle(_) => {}
}
}
}