Back

Activity 4.8 Mini-Project: Escape Room Mini-Game

divider

Introduction

Activity 4.8

Escape Room Mini-Game

What you're building

  • A one-room escape game with a locked door
  • One hidden item (a key)
  • One inventory slot (held item)
  • A menu loop for actions

OOP Goals

  • Write multiple classes across files
  • Use composition (has-a relationships)
  • Handle null safely
  • Use encapsulation + methods with logic
  • Use an AP-style sentinel value method

Composition in this project

Your Room contains (has-a) a Lock and may contain (has-a) a hidden Item.

public class Room
{
private Lock doorLock; // Room has-a Lock
private Item hiddenItem; // Room has-a Item (may become null after pickup)
}

Sentinel values (AP style)

When data is "missing," an AP-style approach is to return a special value (a sentinel). In this project, the player uses "none" when no item is held.

public class Player
{
private Item heldItem; // One-slot inventory (may be null)
public String getHeldItemName()
{
if (heldItem == null)
{
return "none"; // sentinel value
}
return heldItem.getName();
}
}

Rules

  • Keep it simple: one room, one lock, one hidden item, one inventory slot

'F' → Fullscreen

Objectives

  • Design a small program using multiple interacting classes
  • Use composition to model a system
  • Write safe code reminded by object references and null
  • Build a game loop with meaningful choices
divider

Activity Tasks

This mini-project is written using stepwise refinement. Complete each task in order. After each task, run the checkpoint program to verify your progress.

  • Create a new project named 4-8-Escape-Room.
  • Create these files: Item.java, Lock.java, Room.java, Player.java, Program.java.
  • Complete the tasks below in order. Don't skip checkpoints.

Task 1: Item (Build the smallest "thing" first)

  • Create the Item class.
  • Use a keyCode where -1 means "not a key."
  • Add getDescription() for debugging and player feedback.
Item.java
public class Item
{
private String name;
private int keyCode; // -1 means "not a key" (sentinel value)
public Item(String name, int keyCode)
{
this.name = name;
this.keyCode = keyCode;
}
public String getName()
{
return name;
}
public boolean isKey()
{
return keyCode != -1;
}
public int getKeyCode()
{
return keyCode;
}
public String getDescription()
{
if (isKey())
{
return name + " (Key)";
}
return name + " (Not a key)";
}
}

Checkpoint: Run a tiny Main program to verify Items work.

Program.java (Checkpoint 1)
public class Program
{
public static void main(String[] args)
{
Item key = new Item("Brass Key", 1234);
Item note = new Item("Crumpled Note", -1);
System.out.println(key.getDescription());
System.out.println(note.getDescription());
}
}

Task 2: Lock (Write the "win condition" logic early)

  • Create the Lock class.
  • The lock starts locked.
  • tryUnlock(item) returns true only for the correct key.
  • Unlock attempts with null must fail safely.
Lock.java
public class Lock
{
private boolean locked;
private int requiredKeyCode;
public Lock(int requiredKeyCode)
{
locked = true;
this.requiredKeyCode = requiredKeyCode;
}
public boolean isLocked()
{
return locked;
}
public boolean tryUnlock(Item item)
{
if (item == null)
{
return false;
}
if (!item.isKey())
{
return false;
}
if (item.getKeyCode() != requiredKeyCode)
{
return false;
}
locked = false;
return true;
}
}

Checkpoint: Test null, wrong key, and correct key.

Program.java (Checkpoint 2)
public class Program
{
public static void main(String[] args)
{
Lock doorLock = new Lock(1234);
Item wrongKey = new Item("Fake Key", 9999);
Item rightKey = new Item("Brass Key", 1234);
System.out.println("Locked? " + doorLock.isLocked());
boolean unlocked = doorLock.tryUnlock(null);
System.out.println("Unlock with null: " + unlocked);
System.out.println("Locked? " + doorLock.isLocked());
unlocked = doorLock.tryUnlock(wrongKey);
System.out.println("Unlock with wrong key: " + unlocked);
System.out.println("Locked? " + doorLock.isLocked());
unlocked = doorLock.tryUnlock(rightKey);
System.out.println("Unlock with correct key: " + unlocked);
System.out.println("Locked? " + doorLock.isLocked());
}
}

Task 3: Room (Composition begins here)

  • Create the Room class.
  • The Room has-a Lock and has-a hidden Item.
  • The player must search before taking the item.
  • After the item is taken, the hidden item becomes null.
Room.java
public class Room
{
private String name;
private String clue;
private Lock doorLock;
private Item hiddenItem;
private boolean searched;
public Room(String name, String clue, Lock doorLock, Item hiddenItem)
{
this.name = name;
this.clue = clue;
this.doorLock = doorLock;
this.hiddenItem = hiddenItem;
searched = false;
}
public String getName()
{
return name;
}
public String search()
{
if (!searched)
{
searched = true;
return "You search the room... " + clue;
}
return "You already searched. Nothing new stands out.";
}
public Item takeItem()
{
if (!searched)
{
return null;
}
if (hiddenItem == null)
{
return null;
}
Item found = hiddenItem;
hiddenItem = null;
return found;
}
public boolean tryUnlockDoor(Item item)
{
return doorLock.tryUnlock(item);
}
public boolean isDoorLocked()
{
return doorLock.isLocked();
}
}

Checkpoint: Search → take item → print what you found.

Program.java (Checkpoint 3)
public class Program
{
public static void main(String[] args)
{
Lock doorLock = new Lock(1234);
Item key = new Item("Brass Key", 1234);
Room room = new Room(
"Locked Lab",
"You notice scratch marks near a loose floor tile.",
doorLock,
key
);
System.out.println(room.search());
Item found = room.takeItem();
if (found == null)
{
System.out.println("No item found.");
}
else
{
System.out.println("Found: " + found.getDescription());
}
}
}

Task 4: Player (One-slot inventory + sentinel method)

  • Create the Player class.
  • The player can hold exactly one item.
  • Add getHeldItemName() that returns "none" if nothing is held.
  • Add pickUp(item), dropItem(), and inspectHeldItem().
Player.java
public class Player
{
private Item heldItem;
public Player()
{
heldItem = null;
}
// AP-style sentinel method (String sentinel)
public String getHeldItemName()
{
if (heldItem == null)
{
return "none";
}
return heldItem.getName();
}
public boolean hasItem()
{
return heldItem != null;
}
public Item getHeldItem()
{
return heldItem;
}
public boolean pickUp(Item item)
{
if (item == null)
{
return false;
}
if (heldItem != null)
{
return false;
}
heldItem = item;
return true;
}
public Item dropItem()
{
Item dropped = heldItem;
heldItem = null;
return dropped;
}
public String inspectHeldItem()
{
if (heldItem == null)
{
return "You are holding nothing.";
}
return "Held item: " + heldItem.getDescription();
}
}

Checkpoint: Pick up an item, inspect it, drop it, and confirm the sentinel value.

Program.java (Checkpoint 4)
public class Program
{
public static void main(String[] args)
{
Player player = new Player();
System.out.println("Held: " + player.getHeldItemName());
Item note = new Item("Crumpled Note", -1);
boolean pickedUp = player.pickUp(note);
System.out.println("Picked up? " + pickedUp);
System.out.println(player.inspectHeldItem());
Item dropped = player.dropItem();
System.out.println("Dropped: " + dropped.getName());
System.out.println("Held: " + player.getHeldItemName());
}
}

Task 5: Main Game Loop (Put it all together)

  • Create fixed objects: one Room, one Lock, one hidden key Item, one Player.
  • Build a menu loop with the required actions.
  • Win condition: unlock the door.
  • Make sure you can attempt unlock with no item (null) safely.
Program.java
import java.util.Scanner;
public class Program
{
public static void main(String[] args)
{
Scanner input = new Scanner(System.in);
int requiredKeyCode = 1234;
Lock doorLock = new Lock(requiredKeyCode);
// Change these two lines if you want a different theme.
String roomName = "Locked Lab";
String clue = "You notice scratch marks near a loose floor tile.";
// Hidden item (the key). You can swap this to a decoy to test failure cases.
Item hiddenKey = new Item("Brass Key", requiredKeyCode);
Room room = new Room(roomName, clue, doorLock, hiddenKey);
Player player = new Player();
System.out.println("--- ESCAPE ROOM ---");
System.out.println("You wake up in a locked room. The door is sealed.");
System.out.println("Room: " + room.getName());
boolean running = true;
while (running)
{
System.out.println();
System.out.println("Held item: " + player.getHeldItemName());
System.out.println("1) Search room");
System.out.println("2) Take item");
System.out.println("3) Inspect held item");
System.out.println("4) Try unlock door");
System.out.println("5) Drop held item");
System.out.println("6) Quit");
System.out.print("> ");
String choice = input.nextLine();
if (choice.equals("1"))
{
System.out.println(room.search());
}
else if (choice.equals("2"))
{
Item found = room.takeItem();
if (found == null)
{
System.out.println("You don't find anything you can take.");
}
else
{
boolean pickedUp = player.pickUp(found);
if (pickedUp)
{
System.out.println("You picked up: " + found.getName());
}
else
{
// Player already holding something; put it back by "re-hiding" in the simplest way:
// For this mini-project, we will just tell the player to drop first.
System.out.println("Your hands are full. Drop your item first.");
}
}
}
else if (choice.equals("3"))
{
System.out.println(player.inspectHeldItem());
}
else if (choice.equals("4"))
{
boolean unlocked = room.tryUnlockDoor(player.getHeldItem());
if (unlocked)
{
System.out.println("Click! The lock opens.");
System.out.println("YOU ESCAPED!");
running = false;
}
else
{
if (player.hasItem())
{
System.out.println("That didn't work. The door remains locked.");
}
else
{
System.out.println("You try the door... It won't budge. (You have no key.)");
}
}
}
else if (choice.equals("5"))
{
Item dropped = player.dropItem();
if (dropped == null)
{
System.out.println("You have nothing to drop.");
}
else
{
System.out.println("You dropped: " + dropped.getName());
}
}
else if (choice.equals("6"))
{
running = false;
}
else
{
System.out.println("Invalid choice.");
}
// Optional: if the door is unlocked for any reason, end the game.
if (!room.isDoorLocked())
{
running = false;
}
}
System.out.println("\nGame over.");
}
}

Required Tests (Before you submit)

  • Try unlocking the door before picking up anything (must not crash).
  • Search, then take the item.
  • Inspect held item (should display item description).
  • Unlock the door and win.
  • Verify the hidden item cannot be taken twice (should fail after it becomes null).
divider

Sample Output

Your program output should look similar to the sample below (your story text can differ).

Sample Output
--- ESCAPE ROOM ---
You wake up in a locked room. The door is sealed.
Room: Locked Lab
Held item: none
1) Search room
2) Take item
3) Inspect held item
4) Try unlock door
5) Drop held item
6) Quit
> 4
You try the door... It won't budge. (You have no key.)
Held item: none
> 1
You search the room... You notice scratch marks near a loose floor tile.
Held item: none
> 2
You picked up: Brass Key
Held item: Brass Key
> 4
Click! The lock opens.
YOU ESCAPED!
Game over.
divider

Reflection Questions

You may write your reflection answers as comments at the bottom of Program.java.

  1. Where did you use composition (has-a relationships)? List at least two examples from your classes.
  2. In this game, where can null occur? Give at least two situations.
  3. What sentinel value did you use in getHeldItemName()? Why is that useful in a menu program?
  4. Which class "owns" the unlocking logic? Why is that a good design?
  5. If you were allowed to use arrays to store multiple items later, what part of this game could you expand first? (Answer conceptually—do not implement.)
divider

Submission

  • Submit: Item.java, Lock.java, Room.java, Player.java, Program.java
  • Include your reflection answers as comments at the bottom of Program.java

Activity Complete