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:
- 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)
- 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.
- 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:
- The collision between the player and the objects
- 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(); } }