]> git.seodisparate.com - LudumDare47_StuckInALoop/commitdiff
Add puzzle
authorStephen Seo <seo.disparate@gmail.com>
Sun, 4 Oct 2020 08:28:08 +0000 (17:28 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sun, 4 Oct 2020 08:28:08 +0000 (17:28 +0900)
src/interactable.rs
src/main.rs
src/puzzle.rs [new file with mode: 0644]
src/scenes/mainscene.rs

index aa9a324716dd118e8c1602d03a6c7c1f5013c408..b0549fe262fff26f7c195e50dca71f2fe03685f9 100644 (file)
@@ -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;
+        }
+    }
 }
index 795c423ccf75ba54f0ac5fe5f696679b046e87e9..a589848112b8c813f3916715a4c545747fa50c4b 100644 (file)
@@ -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 (file)
index 0000000..2670da6
--- /dev/null
@@ -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<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
+    }
+}
index 767648b0f2c5aab29bf28f360843435519454b17..6771e709be0db9c387537372492e2b85b2e0f0b9 100644 (file)
@@ -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<RefCell<Player>>,
@@ -87,7 +95,7 @@ pub struct MainScene {
     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,
@@ -95,6 +103,8 @@ pub struct MainScene {
     saw_earth: bool,
     window_image: Image,
     error_sfx: Source,
+    puzzle_states: HashMap<PuzzleID, bool>,
+    puzzle: Option<Puzzle>,
 }
 
 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(_) => {}
         }
     }
 }