Yunho Lee Final Project – Escape Room (Ver. p5js)

Inspiration:

The inspiration for the game is Escape Room, where participants have to solve several puzzles in order to escape from the room.

Content:

A player is stuck in a room where you need to solve 3 puzzles to escape through the exit. In order to find all clues, the player has to interact with the miniature of the room. The game requires both in-game actions, and real-life actions to clear.

Main Algorithms of game mechanics:

  1. Object Interactions: Each game object has a specific area where the message and the clue show up. The message shows up automatically, and the clue opens after the interaction with the object (change in the amount of light)
  2. Character Movement: The character sprite has 4 direction movements, each of them played when corresponding direction keys are pressed. The program remembers the last direction moved and stays looking in the same direction when there is no movement.
  3. UI: Its UI is made as simple as possible, but the timer does not stop when the game is paused to prevent CHEATERS.

Why Photosensors?

There are two reasons why I used photosensors for all of the interactions of the objects in the game. First, the size of the photosensor is very small that it is very easy to hide its location and to place it inside small miniature objects. Second, the photosensor is the most appropriate sensor that goes along with the concept of the game, escaping from a dark room with limited light sources.

Goals that I failed to accomplish:

  1. The collision between the player and the objects
  2. Miniature’s roof and sidewalls that have LED to change the brightness of the room according to the location of the player in the game

Game (Playable through interaction by SpaceBar instead of interacting with the miniature) :

P5js (Alert! Very Very Long)

let serial; // variable to hold an instance of the serialport library
let portName = "COM7"; // fill in your serial port name here

let Character = [];
let playerObj;

let lastkey;

let start = true;
let pause = false;
let fail = false;
let gameclear = false;

let quiz1clear = false;
let quiz2clear = false;
let quiz3clear = false;

let clueOn = true;
let hintOn = false;
let clueUI;

let answercheck = false;
let getinput = false;

let swipeCount = 0;

let roomObjects = [];

let solveNum = 0;
let timer = 300;

let exitdoor;
let userinput = "";

let carpetinput;
let bedinput;
let tableinput;
let glassinput;
let shelfinput;
let drawerinput;

function preload() {
  charSprite = loadImage("character.png");
  bg = loadImage("floor.jpg");
  table = loadImage("table.png");
  darkness = loadImage("darkness.png");
  bed = loadImage("bed.png");
  shelf = loadImage("shelf.png");
  exit = loadImage("exit.png");
  candle = loadImage("candle.png");
  desk = loadImage("desk.png");
  carpet = loadImage("carpet.png");
  wall1 = loadImage("wall1.jpg");
  pauseIcon = loadImage("pause.png");
  textbg = loadImage("textbox.png");
  x = loadImage("x.png");
  quiz1 = loadImage("quiz1.jpg");
  quiz2 = loadImage("quiz2.jpg");
  quiz3 = loadImage("quiz3.jpg");
  quiz2ans = loadImage("quiz2ans.jpg");
  quiz3ans = loadImage("quiz3ans.jpg");
  
  keypic = loadImage("key.jpg");
  quiz1hint = loadImage("quiz1hint.jpg");
  water = loadImage("water.jpg");
  
  lock = loadImage("lock.jpg");
  paper = loadImage("paper.jpg");
}

function setup() {
  createCanvas(500, 400);
  frameRate(60);
  
  //arduino serial
  serial = new p5.SerialPort(); // make a new instance of the serialport library
  serial.on("list", printList); // set a callback function for the serialport list event
  serial.on("connected", serverConnected); // callback for connecting to the server
  serial.on("open", portOpen); // callback for the port opening
  serial.on("data", serialEvent); // callback for when new data arrives
  serial.on("error", serialError); // callback for errors
  serial.on("close", portClose); // callback for the port closing

  serial.list(); // list the serial ports
  serial.open(portName); // open a serial port
  
  // create character
  let charw = charSprite.width/12;
  let charh = charSprite.height/8;
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j<4; j++) {
      Character[i+3*j] = charSprite.get((i+6)*charw, j*charh, charw, charh);
    }
  }
  
  //construct playerObj
  playerObj = new player(Character, darkness);
  
  // setup room objects
  roomObjects[0] = new roomObj (table, 180, 150, 0, 0);
  roomObjects[1] = new roomObj (bed, 0, 0, 0, 0);
  roomObjects[2] = new roomObj (shelf, 330, 0, 0, 0);
  roomObjects[3] = new roomObj (exit, 450, 200, 0, 0);
  roomObjects[4] = new roomObj (candle, 0, 100, 0, 0);
  roomObjects[5] = new roomObj (desk, 100, 0, 0, 0);
  roomObjects[6] = new roomObj (carpet, 100, 320, 0, 0);
  roomObjects[7] = new roomObj (wall1, 0, 240, 0, 0);
  roomObjects[8] = new roomObj (wall1, 225, -55, 0, 0);
  
  // setup scripts for each objects with clues
  tableUI = new scriptUI("There is a note on the table but it is very dusty... \n(Clean the table to show the hint.)", quiz1,175, 290, 130, 210);
  deskUI = new scriptUI("The drawer opened with the key! There is a note inside the drawer. \n(Pull the drawer to show the hint.)", quiz2,150, 180, 40, 60);
  shelfUI = new scriptUI("As I pour down the water, letters appeared on the paper!!!\n(Place the cup in front of the bookshelf to show the hint.)", quiz3,330, 380, 60, 85);
  
  // keys to obtain clues
  bedUI = new scriptUI("I see something below my bed... What is it? \n(Check under the bed with a flashlight to show what's there.)", keypic,0, 30, 0, 50);
  carpetUI  = new scriptUI("Found a letter on the carpet... \n(Lift the carpet to show the hint.)", quiz1hint, 120, 170, 300, 335);
  glassUI = new scriptUI("There is a glass of water. Where do I use it? \n(Take the cup to obtain the water.)", water, 0, 25, 90, 180);
  
  // UI when you don't have item to see the clue
  lockUI = new scriptUI("There is a cabinet that is hard locked. Maybe there is a key nearby to open it. \n(Press SPACE BAR to show/hide the image)", lock, 150, 180, 40, 60);
  paperUI = new scriptUI("There is a paper between books, but nothing is written on it! Maybe I can pour something to make it show? \n(Press SPACE BAR to hide/show the paper)", paper, 330, 380, 60, 85);
  // no hint but just there
  shelf2UI = new scriptUI("There are so many books on the bookshelf. I wander how many there are...", "empty", 420, 450, 75, 92);
  
  emptyUI = new scriptUI("","empty",0,0,0,0);
  
  // construct the exitdoor
  exitdoor = new exitdoorUI();
}

let count = 0;

// mouseClick interact with UI
function mousePressed(){
  // Start UI interaction
  if (start == true){
    if (mouseX >= 180 && mouseX <= 330) {
      if (mouseY >= 265 && mouseY <=305){
        start = false;  
      }
    }
  }
  // Pause UI interaction
  else{
    if (mouseX>=480 && mouseY <=20){
      pause = !pause;
    }
  }
  // unpause
  if (pause == true){
    if (mouseX >= 165 && mouseX <= 345) {
      if (mouseY >= 185 && mouseY <=225){
        pause = !pause;
      }
    }
    if (mouseX >= 165 && mouseX <= 345) {
      if (mouseY >= 265 && mouseY <=305){
        fail = true;
        pause = false;
      }
    }
  }
  // restart
  if (gameclear == true || fail == true){
    // rect(145, 250, 210, 50);
    if (mouseX >= 145 && mouseX <= 355){
      if (mouseY >= 250 && mouseY <= 300){
        quiz1clear = false;
        quiz2clear = false;
        quiz3clear = false;
        gameclear = false;
        fail = false;
        timer = 300;
        playerObj.posX = width/2+50;
        playerObj.posY = height/2+80;
        playerObj.lastkey = "Front";
        start = true;
        tableUI.isopen = false;
        deskUI.isopen = false;
        shelfUI.isopen = false;
        bedUI.isopen = false;
        glassUI.isopen = false;
        carpetUI.isopen = false;
        shelf2UI.isopen = false;
        lockUI.isopen = false;
        paperUI.isopen = false;
        userinput = "";
        swipeCount = 0;
        solveNum = 0;
      }
    }
  }
}

// gain input from keyboard and send it to the PlayerObj.
function keyPressed(){
  if (keyCode === 32){
    hintOn = !hintOn;
  }
  else if (keyCode === ENTER){
    answercheck = !answercheck;
  }
  else if (keyCode === BACKSPACE){
    userinput="";
  }
  // else if (clueOn == false){
  else{
  if (keyCode === LEFT_ARROW) {
    playerObj.direction = "Left";
    lastkey = "Left";
  } 
  else if (keyCode === RIGHT_ARROW) {
    playerObj.direction = "Right";
    lastkey = "Right";
  }
  else if (keyCode === UP_ARROW) {
    playerObj.direction = "Back";
    lastkey = "Back";
  }
  else if (keyCode === DOWN_ARROW) {
    playerObj.direction = "Front";
    lastkey = "Front";
  }
  }
}

function keyTyped() {
  if (getinput){
    let letter = key;
    if (letter.length == 1){
      userinput += letter;
    }  
  }
  
}

function draw(){
  background(220);
  image(bg, 0, 0, width, height, 700, 1200, 1000, 800);
  
  // print(carpetinput, " ", bedinput," ", tableinput);
  
  if (keyIsPressed == false){
    playerObj.direction = "Idle";
    playerObj.lastkey = lastkey;
  }
  // display room objects
  for (i=0; i<roomObjects.length; i++){
    roomObjects[i].display();
  }
  
  // display player object
  playerObj.move();
  playerObj.display();
  
  // if (frameCount%10 == 0){
  //   print(playerObj.posX+" "+playerObj.posY);
  // }
  clueUI = emptyUI;
  // player on the Object with clue => match clueUI Object's clue
  // table
  if (playerObj.posX >= 175 && playerObj.posX <= 290){
    if (playerObj.posY >= 130 && playerObj.posY <= 210){
      clueUI = tableUI;
      if (frameCount%10 == 0 && tableinput < 140){
        swipeCount++;
        // print(swipeCount);
      }
      if (swipeCount > 10) hintOn = true;
    }
  }
  //desk
  if(playerObj.posX >= 150 && playerObj.posX <= 180){
    if (playerObj.posY >= 40 && playerObj.posY <= 60){
      if (bedUI.isopen == false){
        clueUI = lockUI;
        if (frameCount%10 == 0 && deskinput > 250){
          hintOn = true;
        }
      }
      else clueUI = deskUI;
    }
    
  }
  //shelf
  if(playerObj.posX >= 330 && playerObj.posX <= 380){
    if (playerObj.posY >= 60 && playerObj.posY <= 85){
      if (glassUI.isopen == false){
        clueUI = paperUI;
        if (frameCount%10 == 0 && shelfinput < 150){
          hintOn = true;
        }
      }
      else clueUI = shelfUI;
    }
  }
  //bed
  if(playerObj.posX >= 0 && playerObj.posX <= 30){
    if (playerObj.posY >= 0 && playerObj.posY <= 50){
      clueUI = bedUI;
      if (frameCount%10 == 0 && bedinput > 250){
          hintOn = true;
        }
    }
  }
  //carpet
  if(playerObj.posX >= 120 && playerObj.posX <= 170){
    if (playerObj.posY >= 300 && playerObj.posY <= 335){
      clueUI = carpetUI;
      if (frameCount%10 == 0 && carpetinput > 170){
          hintOn = true;
        }
    }
  }
  //glass
  if(playerObj.posX >= 0 && playerObj.posX <= 25){
    if (playerObj.posY >= 90 && playerObj.posY <= 180){
      clueUI = glassUI;
      if (frameCount%10 == 0 && glassinput > 130){
          hintOn = true;
        }
    }
  }
  //shelf2
  if(playerObj.posX >= 420 && playerObj.posX <= 450){
    if (playerObj.posY >= 75 && playerObj.posY <= 92){
      clueUI = shelf2UI;
    }
  }
  //exit
  if(playerObj.posX >= 440 && playerObj.posX <= 451){
    if (playerObj.posY >= 176 && playerObj.posY <= 230){
      getinput = true;
    }
    else getinput = false;
  }else getinput = false;
  
  if(clueUI.inside == false) hintOn = false;
  
  if (tableUI.isopen && shelfUI.isopen){
    if (deskUI.isopen){
      if (!quiz1clear) exitdoor.quiz1();
      else if (!quiz2clear) {solveNum = 1; exitdoor.quiz2();}
      else if (!quiz3clear) {sdolveNum = 2; exitdoor.quiz3();}
      else gameclear = true;
    }
  }
  else exitdoor.notopen();
  
  // print(userinput);
  
  // clue UI display algorithm
  if (clueOn == true){
    clueUI.display();
    if (hintOn == true){
      clueUI.showclue();
    }
  }

  //UI font
  fill("white");
  textFont("Courier New", 50);
  
  // start UI
  if (start == true){
    image(bg, 0, 0, width, height, 700, 1200, 1000, 800);
    // rect(180, 175, 150, 50);
    if (mouseX >= 180 && mouseX <= 330) {
      if (mouseY >= 265 && mouseY <=305){
        fill("rgb(35,35,167)")  
      }
      else fill("white")
    }
    text("Start",180, 300);
    fill("white");
  }
  
  // pause UI
  if (pause == true){
    image(bg, 0, 0, width, height, 700, 1200, 1000, 800);
    // rect(165, 185, 180, 40);
    if (mouseX >= 165 && mouseX <= 345) {
      if (mouseY >= 185 && mouseY <=225){
        fill("rgb(38,38,160)");
      }
    }
    text("Resume",165, 220);
    fill("white");
    if (mouseX >= 165 && mouseX <= 345) {
      if (mouseY >= 265 && mouseY <=305){
        fill("rgb(38,38,160)");
      }
    }
    text("Restart",165, 300);
    fill("white");
  }
  image(pauseIcon,480,0);
  
  // fail UI
  if (fail){
    image(bg, 0, 0, width, height, 700, 1200, 1000, 800);
    text("Time Over!!", 90, 150);
    textFont("Courier New", 25);
    text("You found "+solveNum+" out of 3 clues!", 45, 210);
    textFont("Courier New", 50);
    if (mouseX >= 145 && mouseX <= 355){
      if (mouseY >= 250 && mouseY <= 300){
        fill("rgb(38,38,160)");
      }
    }
    text("Restart", 145, 290);
    fill("white");
    // rect(145, 250, 210, 50);
  }
  
  // Game clear UI
  if (gameclear){
    image(bg, 0, 0, width, height, 700, 1200, 1000, 800);
    text("Game Clear!!", 80, 130);
    textFont("Times New Roman", 28);
    textAlign(CENTER);
    text("You got all 3 Questions correct!! \nCongratulations!", 30, 170, 455, 300);
    textFont("Courier New", 50);
    textAlign(LEFT);
    if (mouseX >= 145 && mouseX <= 355){
      if (mouseY >= 250 && mouseY <= 300){
        fill("rgb(38,38,160)");
      }
    }
    text("Restart", 145, 290);
    fill("white");
  }
  // Timer UI
  if (timer%60 < 10){
    text(int(timer/60)+":0"+(timer%60), 200, 55);
  }
  else {
    text(int(timer/60)+":"+(timer%60), 200, 55);  
  }
  if (frameCount%75 == 0){
    if (!start && !gameclear){
      if (!fail) timer--;
    }
  }
  if (timer == 0){
    fail = true;
    timer = 300;
  }
}


//serial functions

// get the list of ports:
function printList(portList) {
  // portList is an array of serial port names
  for (let i = 0; i < portList.length; i++) {
    // Display the list the console:
    print(i + " " + portList[i]);
  }
}

function serverConnected() {
  print("connected to server.");
}

function portOpen() {
  print("the serial port opened.");
}

function serialEvent() {
  // read a string from the serial port
  // until you get carriage return and newline:
  let inString = serial.readLine();
  if (inString.length > 0) {
    let inputVals = split(inString, ","); // split the string on the commas
    if (inputVals.length == 6) {
      // if there are 6 elements
      carpetinput =  inputVals[0];
      bedinput =     inputVals[1];
      tableinput =   inputVals[2];
      glassinput =   inputVals[3];
      shelfinput =   inputVals[4];
      drawerinput =  inputVals[5];
      // print(carpetinput, bedinput, tableinput, glassinput, shelfinput, drawerinput);
    }
  }
  serial.write("0");
}

function serialError(err) {
  print("Something went wrong with the serial port. " + err);
}

function portClose() {
  print("The serial port closed.");
}

class player{
  constructor(Character, darkness){
    this.sprite = Character;
    this.darkness = darkness;
    this.posX = width/2+50;
    this.posY = height/2+80;
    this.direction = "Idle";
    this.lastkey = "Front";
    this.speed = 2;
    this.frame = 1;
  }
  
  display(){
    image(this.sprite[this.frame], this.posX, this.posY);
    image(darkness, 0, 0, width, height, 475-this.posX, 380-this.posY, 500, 400);
  }
  
  // Controlls Player's Movement according to the keyboard input
  move(){
    if (this.direction == "Idle"){
      if (this.lastkey == "Front"){
        this.frame = 1;
      }
      else if (this.lastkey == "Left"){
        this.frame = 4
      }
      else if (this.lastkey == "Right"){
        this.frame = 7
      }
      else if (this.lastkey == "Back"){
        this.frame = 10
      }
    }
    else if (this.direction == "Left"){
      if (frameCount % 9 == 0){
      if (this.frame < 3 || this.frame > 4){
        this.frame = 3;
      }
      else {
        this.frame++;
      }
    }
    if (this.posX > 10){
      this.posX -= this.speed;
    }
    }
    else if (this.direction == "Right"){
      if (frameCount % 9 == 0){
      if (this.frame < 6 || this.frame > 7){
        this.frame = 6;
      }
      else {
        this.frame++;
      }
      
      }
      if (this.posX < 450){
        this.posX += this.speed;
      }
    }
    else if (this.direction == "Front"){
      if (frameCount % 9 == 0){
      if (this.frame > 1){
        this.frame = 0;
      }
      else {
        this.frame++;
      }
      
    }
      if (this.posY < 350){
        this.posY += this.speed;
      }
    }
    else if (this.direction == "Back"){
      if (frameCount % 9 == 0){
      if (this.frame < 9 || this.frame > 10){
        this.frame = 9;
      }
      else {
        this.frame++;
      }
    }
      if (this.posY > 10){
        this.posY -= this.speed;
      }
    }
    
    this.xbox = this.posX + this.sprite[0].width;
    this.ybox = this.posY + this.sprite[0].height;
  }
  
  // collide() {
  //   if (this.lastkey == "Front"){
  //     this.frame = 1;
  //   }
  //   else if (this.lastkey == "Left"){
  //     this.frame = 4
  //   }
  //   else if (this.lastkey == "Right"){
  //     this.frame = 7
  //   }
  //   else if (this.lastkey == "Back"){
  //     this.frame = 10
  //   }
  // }
  // more functions to be added
}

// class of room Objects
class roomObj {
  constructor(image, xPos, yPos, actionX, actionY){
    this.image = image;
    this.xPos = xPos;
    this.yPos = yPos;
    // action Area for objects with specific interaction
    this.actionX = xPos + actionX;
    this.actionY = yPos + actionY;
  }
  
  display() {
    image (this.image, this.xPos, this.yPos);
  }
}

class scriptUI {
  constructor(scriptText, clueimg, x1, x2, y1, y2){
    this.scriptText = scriptText;
    this.clueimg = clueimg;
    this.isopen = false;
    this.inside = false;
    this.x1 = x1;
    this.x2 = x2;
    this.y1 = y1;
    this.y2 = y2;
  }
  
  display() {
    if (playerObj.posX >= this.x1 && playerObj.posX <= this.x2){
      if (playerObj.posY >= this.y1 && playerObj.posY <= this.y2){
        this.inside = true;
        image(textbg, 25, 250);
        textFont("Courier New", 18);
        text(this.scriptText, 35, 260, 445, 360);
      }
      else this.inside = false;
    }
  }
  
  showclue() {
    if (playerObj.posX >= this.x1 && playerObj.posX <= this.x2){
      if (playerObj.posY >= this.y1 && playerObj.posY <= this.y2){
    if (this.clueimg != "empty"){
      image(this.clueimg, 25, 60);
      this.isopen = true;
    }
  }}}
}

class exitdoorUI{
  constructor(){
    this.quizNum = 1;
    this.answer1 = 151;
    this.answer2 = 102;
    this.answer3 = 6511;
  }
  
  notopen(){
    if (playerObj.posX >= 440 && playerObj.posX <= 451){
      if (playerObj.posY >= 176 && playerObj.posY <= 230){
        image(textbg, 25, 250);
        textFont("Courier New", 20);
        text("You are locked inside the room until you solve all the problems. There are 3 clues... (Search the room for the clues!!)", 35, 260, 455, 360);
      }
    }
  }
  
  quiz1(){
    textFont("Courier New", 20);
    if (playerObj.posX >= 440 && playerObj.posX <= 451){
      if (playerObj.posY >= 176 && playerObj.posY <= 230){
        if (!answercheck){
          image(textbg, 25, 250);
          textFont("Courier New", 20);
          text("Q1: The answer is : " + userinput, 35, 280);
          image(quiz1, 25, 100);
        }
        else if (userinput != this.answer1){
          image(textbg, 25, 250);
          text("Hmm... Why don't you try again. \nPress Enter to Continue...", 35, 260, 455, 360);
          userinput = "";
        }
        else if (userinput == this.answer1){
          // image(textbg, 25, 250);
          // text("Your answer to Question 1 is correct! \nPress Enter to Continue...", 35, 260, 455, 360);
          quiz1clear = true;
          userinput = "";
          answercheck = false;
        }
      }
    }
  }
  quiz2(){
    textFont("Courier New", 20);
    if (playerObj.posX >= 440 && playerObj.posX <= 451){
      if (playerObj.posY >= 176 && playerObj.posY <= 230){
        if (!answercheck){
          image(textbg, 25, 250);
          textFont("Courier New", 20);
          text("Your answer to Question 1 is correct! \nQ2: The answer is : " + userinput, 35, 260, 455, 360);
          image(quiz2ans, 25, 100);
        }
        else if (userinput != this.answer2){
          image(textbg, 25, 250);
          text("Hmm... Why don't you try again. \nPress Enter to Continue...", 35, 260, 455, 360);
          userinput = "";
        }
        else if (userinput == this.answer2){
        //   image(textbg, 25, 250);
        //   text("Your answer to Question 2 is correct! \nPress Enter to Continue...", 35, 260, 455, 360);
          quiz2clear = true;
          userinput = "";
          answercheck = false;
        }
      }
    }
  }
  quiz3(){
    textFont("Courier New", 20);
    if (playerObj.posX >= 440 && playerObj.posX <= 451){
      if (playerObj.posY >= 176 && playerObj.posY <= 230){
        if (!answercheck){
          image(textbg, 25, 250);
          textFont("Courier New", 20);
          text("Your answer to Question 2 is correct! \nQ3: The answer is : " + userinput, 35, 260, 455, 360);
          image(quiz3ans, 25, 100);
        }
        else if (userinput != this.answer3){
          image(textbg, 25, 250);
          text("Hmm... Why don't you try again. \nPress Enter to Continue...", 35, 260, 455, 360);
          userinput = "";
        }
        else if (userinput == this.answer3){
          // image(textbg, 25, 250);
          // text("Your answer to Question 3 is correct! \nPress Enter to Continue...", 35, 260, 455, 360);
          quiz3clear = true;
          answercheck = false;          
        }
      }
    }
  }
}

 

Arduino

#include <Servo.h>

Servo servo;

int carpet, bed, glass, shelf, table, drawer;

void setup() {
  servo.attach(9);
  Serial.begin(9600);
  while (Serial.available() <= 0) {
    Serial.println("0,0,0,0,0,0"); // send a starting message
    delay(300);              // wait 1/3 second
  }
}
void loop() {
  while (Serial.available() > 0) {
    read the incoming byte:
    int lock = Serial.read();
    servo.write(lock);

    byte CarpetButton = digitalRead(2);
    
    if (CarpetButton == HIGH) carpet = 1;
    else carpet = 0;
    
    table = analogRead(A0);
    glass = analogRead(A1);
    shelf = analogRead(A2);
    drawer = analogRead(A3);
    bed = analogRead(A4);
    carpet = analogRead(A5);
    
    Serial.print(carpet);
    Serial.print(",");
    Serial.print(bed);
    Serial.print(",");
    Serial.print(table);
    Serial.print(",");
    Serial.print(glass);
    Serial.print(",");
    Serial.print(shelf);
    Serial.print(",");
    Serial.print(drawer);
    Serial.println();
  }
}

 

Leave a Reply