Thanks for the feedback! Indeed, the lack of unique rewards was something that irked me about my approach. However, it was not an easy or clean fix to change due in large part to how I made the animation structure work. I spent a good deal of my development just getting the animation for the dots and such. Since you asked I will try to explain it as well as I can with the time that I have.
Note though, I forced this with knowledge I already had an it is by no means a simple fix. I cannot recommend this to a beginner unless they already have, like I did, some idea of the thread and locks concepts or are willing to learn a lot for an application that I don’t think it was really intended for. That said, the third point on adding a Terminal.AddToLine(); function is a useful alteration I think anyone can use.
Alright, so to explain how I did it I’m going to break down the key parts that make it all work for those who might try an add this. (Please note that I’m sure there I another approach which will make this cleaner to use but this is what worked for me with what I know and not wanting to change too much of what was given.)
There are three key aspects I found to making this work
-
Using coroutines. This is the core of what makes the waiting work. These can be complicated and feels like you are using a howitzer to kill a fly. These are necessary only because the function I used, namely “yield return new WaitForSeconds(0.1f);” can only be used within one of these coroutines. The real challenge of these is the fact that a coroutine can exist and run without stopping the body of code which called it. You might be asking, “Wait! How does that then wait for anything if they run independently?” We force it to wait.
-
A mutex/spin style lock is placed upon the code which creates the coroutine. This is a rather involved concept but the simple way to think of it is that we force our code into a loop where it’s doing nothing until the coroutine we made is finished. The part that is really messy about this is that WaitForSeconds(), the thing which forces our code to wait, can only be used within a coroutine. Thus, to use this kind of yield process you must either be within a coroutine already or you must have the code terminate when it creates the coroutines (and the coroutine must call code which continues the program once it is done). I will now step through the code to explain what the code is doing.
Below shows both styles of doing this. Within CheckPassword() I start the coroutine RefreshPasswordHint(). This calling code ends and it is all up to the coroutine. At this point, there is nothing really special about the way it is running. The original code stopped and now the coroutine is continuing on alone like any other function. However, for spin style lock, we need the next step to be within a coroutine.
So, RefreshPasswordHint is now running. This coroutine starts a coroutine called Waiting(). At this point both RefreshPasswordHint AND Waiting() functions are now running. The thing is you don’t know which is running compared to the other. Thus, you must make sure readyCheck=true; before starting the code or else you will get into a race condition. (Note I made this mistake upon rereading my code. The above program is actually completely flawed since I placed the ready check in the wrong place. It was working without issues that that was completely by chance. Make sure you place readyCheck = true; right before the while loop to prevent chance mistakes like this, even though even this is not a 100% guarantee. I’m not going to dual lock it for something small like this; at least not right now.) Since we already took care of that problem we can keep reading.
Let’s continue following RefreshPasswordHint(). It is actually stuck since the code will enter the while loop since readyCheck is set to true. Thus it runs the code inside which says, stop doing anything for 0.1 of a second before checking if readyCheck is true or not. So RefreshPassword is completely stuck in a loop until readyCheck is set to false. Now, the coroutine Waiting() is essentially the only one which is running. Looking at the code it has inside, it just executes WaitiForSeconds(1); and so the running code stops for 1 second and then sets readyCheck to false and ends. Now that readyCheck is false, RefreshPasswordHint() can run again and it continues on with ShowPasswordLevel();
So with all the details of what is happening it may not be clear what is actually happening on screen. When we are on the screen where we are guessing, we check if the password is correct or not, if it is not, we show “Access Denied” and then we wait for 1 second and then call ShowPasswordLevel() which starts over with the guess. That’s it. A lot for a little but I reused parts of this elsewhere. Probably, some shorter ways of doing this or extra lines but I wanted to be certain it was
// Confirms if password is correct and redirects accordingly.
void CheckPassword(string input)
{
if (input == password)
{
DisplayWinScreen();
}
else
{
Terminal.WriteLine("Access Denied.");
StartCoroutine(RefreshPasswordHint());
}
}
// If password is false this will refresh page after delay with a
//differnt rescrambled password and hint.
IEnumerator RefreshPasswordHint()
{
StartCoroutine(Waiting());
readyCheck = true; //Moved line below to where it belongs here.
while (readyCheck)
{
yield return new WaitForSeconds(0.1f);
}
ShowPasswordLevel();
}
// Enables the waiting after unsuccessful hint.
IEnumerator Waiting()
{
//readyCheck = true;
//DO NOT DO THIS!!! This is a race condition situation.
// It may work 99% on your system but will that all depends
// on how the lines of code are organized through the processor.
yield return new WaitForSeconds(1);
readyCheck = false;
}
As you can see, things get confusing fast and keeping code clean is a real chore, but a necessary one. This also really isn’t utilizing the true power of coroutines, but rather just forcing them to fit my own purpose to run WaitForSeconds(). If you are going to use this be very careful and incremental in how you build things up.
-
Altering the WM2000 files to enable a write function enabling you to add to the lines instead of just writing new ones. This was absolutely necessary to enable my code to have sensible structure at all without repeating myself. Required learning about lists but you can just easily use what I wrote. It’s very generic. Here is the code to do so.
For DisplayBuffer.cs within WM2000 files, add the following (preferrably below “public void WriteLine(string line)” function)
// Custom function added by Nick-88.
//Allows text to be added to last string entered using WriteLine().
public void AddToLine(string line)
{
string editedLine;
editedLine = logLines[logLines.Count - 1];
editedLine += line;
logLines[logLines.Count - 1] = editedLine;
}
For Terminal.cs within WM2000 files, add the following (preferably below “public static void WriteLine(string line)” function)
// Custom function added by Nick-88.
//Allows text to be added to last string entered using WriteLine().
public static void AddToLine(string line)
{
primaryTerminal.displayBuffer.AddToLine(line);
}
Now you can use the function Terminal.AddToLine("."); to add a dot (or any string to the last line created with Terminal.WriteLine("");