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.
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.
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.
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 {}
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.
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.
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.
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.
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.
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.
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.
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.
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.
We did the setup we needed, now it's finally time to pick the key names we'll use.
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.
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.
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.
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.
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.
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.
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?
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.
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.
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!