The Room Adventure: Additional Features

Asking the Player for a Name

Difficulty: ★★★★☆

Requirement: Lock and Key Feature

What if you want the locks and keys to be randomized? This is the function for you. It's like other randomizers, so a lot of this will be familiar.

It is very important that the player is able to complete the game. Like setting locks and keys yourself, you need to make sure the game can be completed. To this end, it is highly recommended that you create the checkKeys function first.

What we need to do

  • Makes lists for total rooms, pre-locked rooms, and stairways (if you have floors).
  • Determine the number of keys to create based on a number passed in or a random number.
  • Create the list of needed keys.
  • Lock any rooms that are not already pre-locked (if the pre-locked parameter is set).
  • Place the keys.
  • Technically optional, but extremely recommended, use the checkKeys() function to make sure the game is playable.
  • Send back the updated list of rooms.

As with the checkKeys function, I will include a number calls to the console to display variables. You are not required to include these in your code. They are just for testing the function.

Step 1: Starting the function

We are going to pass in five parameters so we can control how this function works from the outside. The first part of the function will be a series of comments explaining these parameters so you remember how to use them. I suggest not skipping this part. If you come back to this code later or if someone else looks at this function, these comments will explain how best to use it.

Step 2: Declare the starting variables

We will be using these throughout the function. Some will be used a few times and others are only used for checking the keys with the added function.

Most of these are empty arrays with square brackets [], but watch for the empty object with squiggly braces {}

Step 3: Make a list of rooms

This loop has several parts to it based on the parameters sent in. This first part starts the loop and deals with the situation of clearing out all the locks and keys from all the rooms if a zero 0 was passed in for numberOfKeys.

We're only keeping the names of the room because those are strings. This lets us call specific room details later without accidentally altering the actual rooms in the meantime.

It is extremely important to use the triple equals === sign when checking for the value of zero. The triple means you also check the data type and not just the value. If you don't send in a numberOfKeys variable, or send in null so the program chooses a random number, that is interpreted as false when it's compared. If you use a double equals, ==, then the zero is also considered false, so this if statement would incorrectly come back as being true.

Using the typeof keyword

The other way to force the issue is to use the typeof keyword. Then you can make sure that numberOfKeys is actually a number and not undefined or null and then check the zero.

This would do the same thing as the triple === equals. You should always use the triple equals sign === in JavaScript!!!

We have to run this check a second time soon, so I'll leave the typeof version there so you can see it.

Step 4:

We need to make sure that every room has an array set aside for the locked and keys properties. You may have only added them to rooms you wanted them in. For this randomizer to work, we need them all to have an array to keep other code cleaner. It also keeps the room objects consistent. If we were using constructor functions, this would have already be taken care of when we instantiated a room. (Don't worry about that!)

We make the assignment using the OR || logical operator. This sets the property as the existing one if it exits, otherwise it makes a new, empty array.

We also need to use the splice method any time we want a shallow copy of an array. If we don't, we only get a pointer (like a nickname) that points to the original array. That means we would accidentally ruin the original value, and we don't want to do that.

Step 5:

We need to remove all the keys that are out there from all the rooms. If the keepLocks flags was sent in as true then we don't want to remove those locks. Therefore, we need to keep track of what keys they need.

Step 6: Finish sorting the room information

This has a few parts to it, but it's one big conditional check. This handles all the possibilities we're looking for now.

Technically, we don't need the check for room.locked before we check room.locked.length because we took the time to purposely set all the rooms to having at least an empty array []. But it doesn't hurt and it's a good safety measure in case we ever remove that other mini reset.

Step 7: Quick console report

Again, when setting up a function like this, I like to be able to debug any problems as easily as possible. There are tools specifically designed for that, but I find just printing variable values to the console can help identify many issues quickly.

These lines are not required for your code to run and you could leave them out if you would like to.

Step 8: If no keys are being set, let's exit out

Now that the initial sorting is done, if the parameter was in for zero keys, they've all been cleared out now, so we can send back the set of rooms without their keys. This time I'm leaving in the typeof check so you can see it. Like above, this would also work with just the numberOfKeys === 0 as long as you have the triple equals signs.

Step 9: Making a list of key names we can use

In order to set the keys into rooms, we need a list of keys. We did something similar to this in the optional passcode feature from the book.

We may have some keys that are already going to be set aside if pre-locked rooms are being preserved, so we will have to remove those from the main list too.

Step 10: Figuring out exactly how many new keys we need to create

We have a parameter to tell us the numberOfKeys we need, but there are a few issues we need to control for. First, some keys may already be accounted for if we're keeping some rooms with pre-set locks. Also, what if there are 10 rooms but the parameter calls for 50 keys? Also, what if you don't have a lot of different names to pick from?

There are, of course, many different ways we could handle each of these things, and you're welcome to make changes and experiment. It's your game after all! Maybe you do want a door to have more than one lock. You would have to also make some changes to how the rooms are unlocked in order to deal with it, but it's definitely possible and easy to do. When we unlock a room, we just clear out the entire locked array. Instead, you'd have to remove just the one key. That would require you to find other keys for that door if it had more than one lock in that array. You would also have to update the checkKeys function to adjust for this difference too.

But anyway... If a number of keys was sent in to the function, we'll make use of that. Otherwise, we will pick a random number. I want one key for roughly every four rooms. That's about 25%. But I want it to be a little random, so I'm picking a number from 20-30%. I'm setting it up this way so you can see how to get a value within a range of numbers. This would make a decent helper function.

Step 11: Let's pick the key names

We did the setup we needed, now it's finally time to pick the key names we'll use.

Step 12: Wrapper for checkKeys—Part 1

It would be best for you to create and include the checkKeys() function. It's being used here to wrap around the process of setting all the keys, so if it checks for the locks and keys and finds a problem, it can reset the keys and try again.

This is the start of the wrapper. If you choose not to use the checkKeys function, then leave these few lines out. There will be a Part 2 section you'll leave out also if you're skipping this section.

The _tempkeys property is used by the checkKeys function exclusively. This allows a key to be found on one floor (for example) and be used on this floor, assuming you added the "floors" feature. This property should not be used anywhere else except to pass in to the checkKeys function either a set of keys the player already has or an empty array.

Also, to make sure we don't enter into an infinite loop situation, we're basing this loop on the number of keys we have to set around the availableRooms. The key checker will see if the keys that get placed (which is coming up) will work where they are. If not, it will try shuffling them a couple of times, testing again each time. But after that, if it still hasn't been able to solve the lock and key puzzle for the floor, then it will remove one key from the list, therefore having one less locked door. It will keep doing this until either the floor can be played successfully or it runs out of keys and therefore has no locked rooms.

Step 13: Locking things up

Here is where we start setting the locks and keys.

Since we know how many keys to put around, we also know how many rooms to lock. We're going to deal with the locks first, making sure that each room only gets locked once. When we place keys later, we will allow more than one key to be found in a room.

Step 14: Locking things up

We are already a couple of loops deep, but we have to create another one. We need to place keys, but we want to make sure we don't put a key inside the room it's meant to unlock. Though technically this is fine as long as the player starts in that room, it would be a very rare possibility and not worth leaving to chance.

We're using a do...while loop here because we want the shuffle to happen at least once. Many programmers avoid this version of a while loop and just set a Boolean ahead of time. It works either way. As always, I like to show you options.

I am also creating a shuffleCount variable. This is solely for debugging and does nothing for the actual setting of the keys. If you don't want to include it, you don't have to. If that's the case, delete the three lines that make use of this variable near the top of this code snippet.

Step 15: Starting the inner loop that actually places the keys

There is a lot going on, which is why we're taking all of this in small chunks. We've had a lot of loops going on and we're about to add yet another one that we have to do things inside. Yes, we're going yet another layer deep. You'll notice my code is getting more and more indented as we go!

But each part should be familiar to other work we have done. The challenge is just that we need to do these things in a certain order and repeatedly. And because we want to make sure things work, that means running extra loops in case something does not work. We could instead put those additional loops outside this function and have them call this function repeatedly until everything works. The reason I chose not to do that here is because we would be repeating a lot of the starting code for this function. Checking things here is just a matter of two extra wrapping-loops: the one that runs the checkKeys function and the do loop that keeps keys from being locked inside their own rooms.

It's a lot, but keep focused. Let's start up our next nested loop and get the helper variables ready.

Step 16: Setting keys into rooms they don't belong to

We have a conditional here to make sure the key and the locked door don't match. If they do, then we set the stillShuffling variable totrue and then break out of the loop. This is just like we did to keep itemFound items out of the rooms they were needed for fixWith, if you added created that add-on feature.

I am going to allow more than one key to be added to a room. So I'm not going to remove possible rooms from the list. If you only want one key per room, I included the line of code you need; you'll just have to remove the comment slashes // from the front. It's near the end of the loop here.

Step 17: Wrapper for checkKeys—Part 2

If you created Part 1 of the checkKeys wrapper, then you must be sure to include this to finish it. If you skipped that step above, skip this step too and go here to finish.

This is broken into some subsections.

Part 2-A: Did the randomization work? If yes, exit out.

Part 2-B: Remove a key to set if this has failed five times.

Part 2-C: Reset the locks and keys to where they were before all the shuffling so we can start over.

Step 18: Finish the randomizeKeys function

It's been a lot, but we made it. Let's wrap it up!

Once we've gotten to this point, all the locks and keys have been randomized. If you added the checkKeys loop, then we know the keys and locks are all going to work where they are, and if not, then the developer-made lock and key will be restored and put back in place. That's the set you created with room properties in the getRooms function.

Now that everything is set, we need to send the rooms back.

Ready?

Step 19: Using our new function

All that remains is for you make use of this new function. You either have a little bit of work to do, or just one more line of code to add. It depends on what else you've done already.

On the checkKeys function page, near the end I mentioned the other randomizers and had you create a while loop inside of the startGame. If you took care of that already, then all you need to do is add the call for our new function after you randomize the room layout but before you randomize the player's starting position. I'll put a comment below in the full code for that section.

If you're not going to use the checkKeys function at all, then you're also just going to insert that one line.

For everyone else, we need to make a loop that will run a few times until the rooms and keys and locks are all set up and ready to use. To do that, you need to alter part of your startGame function.

Inside the top of startGame...

After the full set of rooms and the player are created, you probably have a bunch of functions for randomizers.

Sort the functions into the order shown below. Some of the functions you have will go below the while loop we're creating, which you'll see.

Wrap the related randomizers in a loop

We don't want this to run forever. We want it to break out at some point no matter what. I'm going to have two checks. One is an iteration counter and the other is whether we are successful.

I'm setting up the two variable first and then wrapping the related functions in the loop. The other functions will be after the loop. I'm not deleting them.

Done!

Now, where did I leave my keys—or my locks for that matter?

—Dr. Wolf