The Room Adventure: Additional Features

Locking Rooms and Needing Keys

Difficulty: ★★★☆☆

Requirement: Added Interactive Features

Note: This version of the feature will not necessarily work with the randomizeRoomLayout() function from the book. It is recommended you also create the checkKeys() function to correct this and to check your lock and key placement.

Sometimes a good challenge is making the player find a key they need to get to another location. This lets you control the flow of your game better.

There are many ways we can handle this situation. Is there only one type of key? Can a key be used more than once? Do unlocked doors get locked again?

For the purposes here, we will make this closer to real life and less like some video games.

  • Keys won't break or be consumed when they are used.
  • Keys can be used over and over.
  • Different locks will usually need different keys.
  • Some locks can share the same key.

Note: this feature isn't particularly difficult to add, but it has a lot of parts that need to be updated for it to work.

Sidenote: The randomizeRoomLayout function is an optional feature from the book that shuffles the locations of the rooms. This may lead to a key being hidden behind a locked door where the player can't get to it. If you're using this basic version of the locked door feature, you should disable the randomizeRoomLayout function by commenting it out inside startGame. Do so by putting two slashes // in front of the line, like this.
  • //rooms = randomizeRoomLayout(rooms);

O-Key-Do-Key... let's get to it.

What we need to do

  • Let's add a property to our player that holds the keys.
  • Add a property to specific rooms that are locked, with the key needed to open it.
  • Add keys to other rooms to be found.
  • Prevent player from going into locked rooms.
  • Handle finding and using keys.

We want to add some challenge by locking some rooms and making the player find the right key for it. Once again, we have many ways to achieve this. I'm making certain design decisions here; some for simplicity, and some for future plans.

Step 1: Prep the player object

By far the easiest part of this feature is to add a new keys property to the player. We're going to use an array so the player can hold more than one key at a time. I've played games where you could only hold one. It wasn't very realistic and although it added a definite strategic challenge, it was more of a frustration, and we don't want that experience for our player.

You have an idea how to do this on your own, right?

Go into your getPlayer function. Inside the player object, add the keys property as an empty array.

I added mine right after the inventory property because they're related, but you can put it anywhere in the list.

Step 2: Locking a room and placing its key

Think about the layout of your house and where you want to have a locked room. Then we're going to put its key somewhere else. Hey, it's like how we originally set up the game with our broken things! Yep, but it's an added layer on top.

A few locking rules:

Don't lock the room your player starts in.

The way we're going to handle locked rooms here, your player will be able to walk out of the room, but won't be able to get back in without a key.

You can do that if you want; people do get locked out of things all the time. But it's an awkward experience for the player. "Hey, wait, I was just in there, why can't I go back in?"

Don't lock the next room over unless you have a second exit.

For example, I start in the living room. I have a dining room to the north and a hallway to the east. If I lock my dining room, that's ok, because my player can still go east and eventually find a key that way.

On the other hand, if my player started in the pantry, and I locked the kitchen, there would be no place for the player to go to. They'd be trapped.

When placing the key, make sure the player can reach it.

This seems obvious, but it's easy to overlook, especially if you lock more than one room and use more than one kind of key. Let's say you need a red key to open the dining room. In there, I hid the blue key. I hid the red key inside the office and I need the blue key to open the office. Uh oh. There's a problem. I can't get to either key.

It pays to have a map drawn out with the doors you're locking and the keys you need.

Incidentally, this is why we can't use our randomizeRoomLayout function without some serious computer decision-making code to place locks and keys.

Step 3: Setting the lock and key

Go on and pick a room that you want to lock. We are going to add another property to that room to tell us which it needs. The fact that it needs a key will tell us that it's locked at all.

For flexibility, we're going to use an array here instead of a string. What if we later decide to have a master key that works for all bedrooms? Then two possible keys could open that room up. Using an array now will allow us to make use of that later if we want to.

Go into getRooms.

We have two things to do here. Lock a room and place the key elsewhere.

Lock a room.

Find the room you want to lock and add the locked: ["brass key"] property. (You don't have to use a brass key. Use whatever you like.)

Place the key.

Now go to another room and place the key into it. For consistency, we'll also use an array. Let's add the keys: ["brass key"] property. If you changed the name of the key, make sure you use that same name.

Step 4: Update navigation buttons

We need to let the player know if the room they want to go to is locked. We'll show this on the navigation button itself. All we need is a simple conditional statement to show it.

Go to createNavigationButtons. Depending on what features you have added, your section may look a little different, but the important line you're looking for is the one that closes the </button> tag. Your new if statement goes just above that.

Ok, now the button will show us if a room is locked, but it won't keep us out! Let's tackle that next. The HTML entity number &#128274; shows 🔒.

Step 5: Preventing the player from entering a locked room

Well if it's locked, it's locked. They can't go in without the key. So let's keep 'em out!

Again, here's a design decision. If the player has the key in their possession, do they need to specifically unlock the door first or will it open automatically on its own? I'm going to keep the player interactive by making them unlock the door first. This way, if they use the wrong key, they can't get in.

Our movePlayer function is called when the player presses a navigation button, so let's go there to handle the locked room situation.

Here's the entire function as it is. Do you know where we have to change it?

We used a lot of helper variables when we made this one. Which variable represents the room that might be locked? Yep. It's roomToGoTo.

We're going to insert a conditional statement to see if the room is locked. If so, we're going to let the player know. If not, we're going to finish the function as normal. That means we have to wrap those last two lines of code in an else block of our upcoming if statement.

This goes right after roomToGoTo is created.

Instead of just showing the information, we're interrupting the player with an OK button so they know what happened.

I also added a small penalty for jiggling the lock just to show you how to do it. You can leave that part out. If you do decide to leave out the penalty, I suggest using display.found(""); to clear out that part of the UI until they hit the OK button.

Step 6: Looking around

We already have a way for a player to pick up something in a room. Now we need them to be able to pick up a key.

I want to make a change to how we handle finding things. I want the player to purposely look around the room to see if something is there. Right now, if there is an item to pick, we automatically tell them there is something useful there. We're going to change that.

We're going to make changes to showItemInRoom, isThereAnItem, and we're going to create isThereAKey.

Updating showItemInRoom

We're really changing this. We're removing the if check, changing the text of the button, and adding a second button. Here's the new version.

I gave each button an id so we can just update that part when they're clicked.

Step 6: Updating isThereAnItem

With the new floating buttons, let's only change out the Look for an item button with information regarding the search for an item. We'll make use of the foundItem id for this. But as always, I like to be cautious so we don't have errors.

Go into isThereAnItem. We're going to start with an empty text string now. Where we have both cases of display.found, we're going to set the text string equal to what we were displaying before. Then at the bottom, we will check to make sure the floating <div> exists. If so, we'll display the text there and if not, we'll display it to the regular found box.

Here's the whole function with the new or updated parts noted with comments. It's only a few changes, really.

We're disabling the button once it has been used so the player can't keep clicking on it and losing points accidentally.

Step 7: Creating isThereAKey

We need a function that handles checking for a key in the room. You can probably imagine that this will be very similar to isThereAnItem and you'd be right. The main difference is that we're using an array for the room's keys property, so it will take a little extra work.

Copying and pasting code is not a recommended practice. It's very easy to forget to change the names of things, and that can lead to a lot of bugs in your code that can be hard to track down. You know the function works in one place so why not in the other?

Well, let's practice it anyway. Go ahead and highlight your updated isThereAnItem function, copy it, and then paste it into a new spot as if you were creating a new function from scratch. Watch carefully for changes. As you try this, you may start to see why it's not recommended to do in most cases.

If you don't want to do it that way, that's fine. You can just build this function from scratch too!

The main changes are renaming item and inventory to keys and pulling a key from the room's array of keys.

Here's the isThereAKey function

I don't want to penalize the player for searching for a key. You'll see where I used block a block comment /* ... */ to comment out the code that was there which takes away the points. I could have just deleted it, but this gives me the option of putting it back in later.

Another option, which I used in another feature (getting a passcode hint), would be to use a penalty variable. Setting it to zero would say there's no key, and having a value would use the dialogue I commented out. That would be a more flexible way to handle all the places we take points away from a player, but let's not worry about it for now.

Step 8: Displaying the keys

Now the player can pick up keys. It's time to let show them. Keys are related to inventory, right? Why don't we display them there?

Inside our showInventory function, we'll cycle through any keys the player has and display those. Clicking them will call a new function that tries to use them. Since they will behave differently than other items we use to fix things, we don't want to use our fixBrokenThing function for that.

Updating showInventory

Right now the showInventory function turns every inventory item into a clickable button by looping through the player's inventory array. Let's add a second loop to do the same for the player's new keys array. But if the player has no keys, let's not show anything for them, not even a placeholder.

Go to the bottom of the function and insert this new loop before the text string gets displayed. This loop is going to be very similar to the one that's already there for the inventory buttons, but wrapped inside an if statement, so if you want to try that copy/paste technique a second time, go ahead. Even the best programmers out there make mistakes when using copy/paste, so it may be better to avoid it, but everyone codes differently. Use what works for you.

Step 9: Unlocking a room

We've come all this way. It's time to use the key to unlock a room. In our update to the inventory box, we have the key buttons call a new function we have to create, useKey. Can you guess what function it's going to look the most like?

When we try to use the key, we need to see if there is a locked room attached to our current room. If there is, then we need to see if the key we have will work to open the lock. If so, then open it and remove the lock from that room so we don't have to unlock it again.

It sounds a lot like our fixBrokenThing function, doesn't it?

Building the useKey function

When the key button is pressed, the name of the key is sent in so we can use it to try to unlock the door.

One thing that's different from fixing broken things is that we're using arrays for locked doors and keys. In addition to that, you could be in a room that has more than one locked door around you. So even though the basic idea is the same as fixBrokenThing, we're going to handle it a little differently.

I'm going to break this down into a few parts so we can talk about it.

Starting the useKey function

Here we declare the function as well as a few variables we're going to use to track what's happening with the locks and keys.

The text variable, you're used to by now. It makes it easier to display information to the player. The others are less obvious.

Let's fill those arrays.

Finding locked doors, and trying to unlock them.

We're going to look at the exits of the room the player is currently in to see if any of them are locked. If so, they go on the locked list so we can count them. If this list is empty later, then it means the player tried to unlock a door but none of them were locked.

For any door that is locked, we see if the key being used (keyName) is one of the keys that can unlock that door. If it is, then we add that exit to the unlocked list and remove the lock from that room by setting the locked property to null.

Finally, if a door was locked but the key sent in does not unlock it, we add that exit to the notUnlocked list.

To do all this, we need to loop through all of the current room's exits.

If nothing was locked...

If the locks array is empty, then none of the exits were locked. The player tried to use a key on unlocked doors. I'm going to give a penalty for this, like I give one for using the wrong item to try to fix something. As always, you don't have to have a penalty.

Even though we're going to test other conditions after this, we're not going to use else or else if. We could wrap the next two checks in an else block, but the other two arrays don't ever get touched if the locks array is empty. In this case, our code doesn't require it. Again, I'm just showing you options. If you want to wrap the next part in an else block, go right ahead.

If something was locked...

If there was at least one locked exit, then we will have information in unlocked, notUnlocked, or both. We want to report all that to the player. Here is another design decision. How detailed do you want the text to be? We can do a very simple list along the lines of Exits unlocked: east, west or we could be very detailed in our sentence structure and grammar: The silver key opened the east, west, and south exits.

I'm going to show you both ways. We will start with the first, simpler case. It's very short because it doesn't worry about singles and plurals and all that. The second version is pretty hefty and you may not want to deal with it at this moment in time. But if you're up for it, it'll be here for you.

In either case, we'll be using the array join method, which takes the elements of the array and combines them into a string, separated by whatever we put in the parentheses.

Player report: Simple grammar version

All that's left is to display that information. If you're happy with this version of reporting the information, then skip down to the next part to finish off this function.

Player report: Complex grammar version

This version adds a bit of complexity to the dialogue that gets shown to the player. It's because we have different rules for lists. Let's use colored hats as an example.

We have the difference between "hat" and "hats" for one thing. Also, comma usage differs depending on the number of items in the list and that changes things for our text string. I also use the Oxford comma. That's the comma after the blue hat in the third example. Not everyone puts a comma before the last item in a list, but I think it's clearer when you do. So, I'm even going to control for that.

Ready for this?

Whoa, right? That's just to show what the unlocked array holds. We need to repeat all of this again for the notUnlocked array. Well, we've done the copy/paste twice here, so let's do one more. Copy that whole code block and paste it back in right below it. Then change all of the unlocked to notUnlocked. And in the two text strings where it says " unlocked the ", change it to say, " could not unlock the ".

I'll put the code here, but see if you can do it on your own based on the instructions I just gave you.

Got all that? You deserve a break. But let's polish off this function first. You're almost there.

Finishing the function

Whether you created the simple or the complete reports, these lines go next, and they should look pretty familiar.

That was a long journey, but it's finally finished. There are two more optional things you could consider doing, which I'll mention below.

Once you've got it running and tested, put different keys around and see how it all goes. I'm going to put a silver key in my dining room and that key will unlock the upstairs. But the dining room itself will be locked and you'll need the brass key to open it up! That key is going to be hiding in the office.

It is recommended you also create the checkKeys() function to check your lock and key placement.

Optional: Step 11: Styling the key buttons

We added a keyButton class to the key buttons, did you notice? That means we can style them! If we don't they will have the default <button> style, which is totally fine.

Some styling for the keys

Remember, this has to go in your CSS file!

Optional: Step 12: Passcode pieces as keys

In my opinion, passcode pieces are part of a larger key and now that we have a place to hold keys, I want to move the pieces there. Alternately, we could make a passcodePiece array and display them separately but let's keep them with the other keys for now.

We can move the passcode pieces from the inventory array into the keys array with a single word change! Go into the fixBrokenThing function where the player finds a passcode piece and we add it to the inventory. Look for this line.

See the word inventory? Change it to keys.

Update complete.

Added caution — mostly for my code

Sidenote: You may wonder why I'm bothering. The only way someone would change it to keys is if they made this update anyway, right? True. But if they're looking at my code and copy a function from it to use in their code because their function isn't working, and if they didn't get to this page, then I don't want to break their game. It's one of the reasons why I have so many extra conditionals throughout the code where it looks like we may not need them. Now you know.

I need to keep my code extra (overly?) flexible because different people may be at different parts of the game, so I'm actually going to make the following change instead, but you don't have to. I'm replacing that inventory line above with this.

You've unlocked more of your potential today!

—Dr. Wolf