Wait for n-seconds for input

I want to make something like qte, but stricte in code (c#).

It should work like this: if there’s a chance for this event, wait for 3 seconds while checking if there is any input. If there is correct input - continue, if input is incorrect or 3 seconds passed with wrong input, without input - break. Nothing more. That is the only mechanism my program lacks.

I’m just unity newbie, please help me with this thing.

Okay, so this is an answer with code, but it’s not an answer with working code that you can use right off the bat. Why? Well, because I wanted to give a different answer for variety. And because I want you to learn how to fish instead of being given fish.

Think about what you wrote. Your system should manage time, input and pass results depending on some logic thereof. That’s three or four jobs in one. If the problem is too complex to solve on its own, break it up into smaller problems to make it more managable.

If you have a simple class that dealt with accepting input, and input only, that problem is easier to solve on its own.

If you have a simple class that deal with timeout, that problem is easier to solve on its own.

If you have a simple class that deal with juggling the logic, well, that problem is easy to solve on its own.

Then, when you have made classes with simple interfaces to use, you can think about how you could package it all together.

Imagine if you had three classes at your disposal, in your code library:

PassOrFail passOrFail;       // Logic to flip from inconclusive to pass or fail
InputSequence inputSequence; // Given some input, checks that the input is correct
Timeout timeout;             // More or less a timer that can expire

Then imagine you could have code that look something like this to deal with the core logic of what you just wrote:

// Called each update etc
public void TimeElapsed(float time)
{
    timeout.TimeElapsed(time);
    if (timeout.expired)
        passOrFail.Fail(); 
}

// Called each time there's input
public void Input(string input)
{
    inputSequence.ProcessInput(input);
    if (!inputSequence.inputCorrectThusFar)
        passOrFail.Fail();
    else if (inputSequence.endOfString)
        passOrFail.Pass();
}

That’s the core rules of the system, written in 8 lines of code. And it would have been rather easy to verify that your system work as intended with some unit tests (preferably written just before you make any code changes).

I know you said you’re a newbie and that it may look daunting at first, but writing code that put expectations on your system isn’t that hard. Making the system meet those expectations, however, can be hard. :slight_smile: I thought at least I would throw in some code that could describe your problem. I tried to capture your requirements into these unit tests (or integration tests or whatever people want to call them - the point is that we can verify the correctness of the system one way or another). I leave it up to you to consider if you think having code that verifies that your system will do what you demand is of any value. Some of the latter tests may be a bit over the top and should probably only be written to provide defined behavior for special cases:

/*
    It should work like this: 
    if there's a chance for this event, wait for 3 seconds while checking if 
    there is any input. If there is correct input - continue, if input is 
    incorrect or 3 seconds passed with wrong input, without input - break. 
    Nothing more. That is the only mechanism my program lacks.
*/

[Test]
public void InconclusiveWithoutInput()
{
    // Right, I guess before you haven't pressed any key and before any time elapsed,
    // so it makes sense the quick time event is inconclusive (waiting for a result, 
    // maybe "pending" would be a better phrase but, hey, can't get naming right always)
    
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    Assert.AreEqual(TemporalResult.Inconclusive, quickTimeEvent.outcome);
}

[Test]
public void InconclusiveWithPartialCorrectInput()
{
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.Input("ABC"); // We're still missing D, so it's still inconclusive
    Assert.AreEqual(TemporalResult.Inconclusive, quickTimeEvent.outcome);
}

[Test]
public void PassOnCorrectInputString()
{
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.Input("ABCD"); // We typed ABCD and in the right order, the qte should pass
    Assert.AreEqual(TemporalResult.Passed, quickTimeEvent.outcome);
}

[Test]
public void PassOnCorrectInputOverTime()
{
    // Just a more involved test, simulates a button press every 0.5 seconds,
    // with a trailing accidental button press that should have no effect.
    // This is kind of the golden path for a user playing the game.
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.TimeElapsed(0.5f); quickTimeEvent.Input("A");
    quickTimeEvent.TimeElapsed(0.5f); quickTimeEvent.Input("B");
    quickTimeEvent.TimeElapsed(0.5f); quickTimeEvent.Input("C");
    quickTimeEvent.TimeElapsed(0.5f); quickTimeEvent.Input("D"); // Pass!
    quickTimeEvent.TimeElapsed(0.5f); quickTimeEvent.Input("B"); // No effect, ignore
    Assert.AreEqual(2.5f, quickTimeEvent.elapsedTime);
    Assert.AreEqual(TemporalResult.Passed, quickTimeEvent.outcome);
}

[Test]
public void FailOnTimeout()
{
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.TimeElapsed(3); // After 3 seconds have elapsed, it should fail, right?
    Assert.AreEqual(TemporalResult.Failed, quickTimeEvent.outcome);
}

[Test]
public void FailOnIncorrectInput()
{
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.Input("D"); // If the user doesn't start typing "A", well, they failed right?
    Assert.AreEqual(TemporalResult.Failed, quickTimeEvent.outcome);
}

[Test]
public void CantPassAfterFail()
{
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.Input("ABC");
    quickTimeEvent.TimeElapsed(3);
    quickTimeEvent.Input("D"); // Too slow, time is up, even if you were correct
    Assert.AreEqual(TemporalResult.Failed, quickTimeEvent.outcome);
}

[Test]
public void CantFailAfterPass()
{
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.Input("ABCD");
    quickTimeEvent.TimeElapsed(3); // Doesn't matter, I got it right before time ran out
    Assert.AreEqual(TemporalResult.Passed, quickTimeEvent.outcome);
}

And if you want to share or sell your code, you may want to do a little more testing to think about all kinds of input users may throw at it. You may feel it’s not necessary for your system, but it could be worth keeping those scenarios in mind:

[Test]
public void ResetsInputAndTime()
{
    // Require to reset and reuse the QTE object to avoid allocating a new one?
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.Input("D");      // Failed QTE
    quickTimeEvent.Reset();         // Reset to Inconclusive
    quickTimeEvent.TimeElapsed(3);  // Failed QTE
    quickTimeEvent.Reset();         // Reset to Inconclusive
    quickTimeEvent.TimeElapsed(1);  // No effect
    quickTimeEvent.TimeElapsed(1);  // No effect
    quickTimeEvent.Input("ABCD");   // Passed QTE
    quickTimeEvent.TimeElapsed(1);  // No effect, already passed
    Assert.AreEqual(TemporalResult.Passed, quickTimeEvent.outcome);
}

[Test]
public void ProvidesElapsedTime()
{
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    Assert.AreEqual(0.0f, quickTimeEvent.elapsedTime);
    Assert.AreEqual(3.0f, quickTimeEvent.remainingTime);

    quickTimeEvent.TimeElapsed(0.5f);

    Assert.AreEqual(0.5f, quickTimeEvent.elapsedTime);
    Assert.AreEqual(2.5f, quickTimeEvent.remainingTime);

    quickTimeEvent.TimeElapsed(2.5f);

    Assert.AreEqual(3.0f, quickTimeEvent.elapsedTime);
    Assert.AreEqual(0.0f, quickTimeEvent.remainingTime);
}

[TestCase(-1, ExpectedException = typeof(QTE.TimeException))]
[TestCase(float.NegativeInfinity, ExpectedException = typeof(QTE.TimeException))]
[TestCase(float.NaN, ExpectedException = typeof(QTE.TimeException))]
public void TimeElapsedThrowsUnexpectedTimeArgument(float time)
{
    // Passing negative numbers or nan is treated as an error, because 
    // it doesn't make sense to go backwards in time for this system.
    // Or at the very least, I thought of no use case when I wrote the test.
    // Either way, it's simple to remove or change the test to other criteria.
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.TimeElapsed(time);
}

[Test]
public void IgnoresNullInput()
{
    // Maybe I am being silly, but I rather the system do nothing 
    // than throw exceptions on null input. Peoples opinions differ.
    QTE quickTimeEvent = QTE.TimedInputSequence(3, "ABCD");
    quickTimeEvent.Input("AB");
    quickTimeEvent.Input(null); 
    quickTimeEvent.Input("CD");
    Assert.AreEqual(TemporalResult.Passed, quickTimeEvent.outcome);
}