In my last post, I walked through how the player’s operations get applied to the equation. The first step in that was to validate that the operations applied were actually valid. Today, I’m going to go through that validation method: ValidatateOperationsBeingApplied.
The method is passed a list of the operations to apply, and a boolean called ShouldForceOperationsToMatchTerms. The general design is that it makes a single pass through the list of operations, calculating statistics as it goes, and then does a series of comparisons to verify everything is in order.
it begins by performing a couple of sanitation checks on the list:
if (OperationsToApply == null) { throw new OperationsValidityException(OperationsValidityException.InvalidStates.ListIsNull); }
if (OperationsToApply.Count != this.Terms.Count) { throw new OperationsValidityException(OperationsValidityException.InvalidStates.ListCountMismatch); }
In theory, the application should not allow either of these cases to occur, but #BugsHappen.
Next, the method calculates a couple of quick statistics on the terms. These will be used as the baseline that the operations will be compared to.
NumberOfTermsOnLeft = 0;
NumberOfTermsOnRight = 0;
FoundEqualSign = false;
foreach (IAmATerm CurrentTerm in this.Terms)
{
if (CurrentTerm is EqualSign) { FoundEqualSign = true; }
else if (FoundEqualSign) { NumberOfTermsOnRight++; }
else { NumberOfTermsOnLeft++; }
}
Next is the main loop:
NumberOfOperationsOnLeft = 0;
ListContainsEqualSign = false;
FirstOperation = null;
NumberASsOnLeft = 0;
NumberASsOnRight = 0;
NumberMDsOnLeft = 0;
NumberMDsOnRight = 0;
TermIndex = 0;
foreach (IAmAnOperation CurrentOperation in OperationsToApply)
{
…
}
The variables that begin "NumberAS" refer to the number of Addition/Subtraction operations. The variables that begin "NumberMD" refer to the number of Multiplication/Division operations. The loop-logic begins by checking whether the equation was configured to require the variable operations to line up with the variable terms, and constant operations with constant terms:
if(ShouldForceOperationsToMatchTerms)
{
if (this.Terms[TermIndex] is Variable &&
CurrentOperation is ConstantOperation &&
(CurrentOperation.Operand == Operands.Add || CurrentOperation.Operand == Operands.Subtract))
{
throw new TermOperationMismatchException();
}
if (this.Terms[TermIndex] is Constant
&& CurrentOperation is VariableOperation)
{
throw new TermOperationMismatchException();
}
}
The first conditional checks to see if a constant operation is being applied to a variable one. That’s ok when the player is multiplying or dividing by that constant, but not when they’re trying to add or subtract it. The second conditional checks to see if a variable operation is being applied to a constant one, which is never allowed. In both cases, a TermOperationMismatchException is thrown.
If the equation is configured to allow mismatches, then the player can try it, but ApplyOperations will simply carry both the original term and the new operation forward into the next equation state. For the current iteration of the game, all of the levels are configured to allow mismatches to occur, so this logic is not actually executed during regular gameplay.
Next, the logic looks to see if it’s encountered the equal sign yet in the list of operations. If not, then it’s still going through the terms on the left side of the equal sign, and it increments a counter to that effect:
if (CurrentOperation is EqualSignOperation) { ListContainsEqualSign = true; }
else if (!ListContainsEqualSign) { NumberOfOperationsOnLeft++; }
Next it looks at what kind of operations (addition, division, etc.) is being applied.
if (!(CurrentOperation is EqualSignOperation) && !(CurrentOperation is NoOperation))
{
if (FirstOperation == null) { FirstOperation = CurrentOperation; }
else if (!FirstOperation.Equals(CurrentOperation)) { throw new OperationsValidityException(OperationsValidityException.InvalidStates.ListContainsMoreThanOneOperation); }
if (FirstOperation.Operand == Operands.Add || FirstOperation.Operand == Operands.Subtract)
{
NumberASsOnLeft += (!ListContainsEqualSign ? 1 : 0);
NumberASsOnRight += (ListContainsEqualSign ? 1 : 0);
}
if (FirstOperation.Operand == Operands.Multiply || FirstOperation.Operand == Operands.Divide)
{
NumberMDsOnLeft += (!ListContainsEqualSign ? 1 : 0);
NumberMDsOnRight += (ListContainsEqualSign ? 1 : 0);
}
}
Recall from "Applying Operations” that the player is only allowed to apply a single operation to the equation at a time. The if-then-else-if verifies that that is the case. It looks for the "first" operation being applied, and makes sure that all of the other ones match it.
Then it increments 2 of 4 counters, depending on what kind of operation is being applied, and whether or not it’s encountered the equal sign already.
- NumberASsOnLeft
- NumberASsOnRight
- NumberMDsOnLeft
- NumberMDsOnRight
Finally, it increments the index it uses to keep track of which term it is inspecting:
TermIndex++;
After the loop, it evaluates the statistics that it’s been collecting:
if (!ListContainsEqualSign)
{
throw new OperationsValidityException(OperationsValidityException.InvalidStates.ListLacksEqualSign);
}
First, if the list of operations didn’t even include an equal sign, it’s clearly invalid.
if (NumberOfOperationsOnLeft != NumberOfTermsOnLeft)
{
throw new OperationsValidityException(OperationsValidityException.InvalidStates.ListIsUnbalanced);
}
This verifies that the number of operations on the left matches the number of terms on the left. I only have to verify that the left sides match in number. At this point in the logic, I can safely assume that the right sides also match since a) I’ve already verified that the total number of operations matches the total number of terms, and b) I’ve verified that the former contains an equal sign.
if (NumberASsOnLeft == 0 && NumberASsOnRight == 0 && NumberMDsOnLeft == 0 && NumberMDsOnRight == 0)
{
throw new NoOperationException();
}
Now it verifies that the player applied SOMETHING to the equation – at least one of these statistics (and ideally 2) should be non-0.
if (NumberASsOnLeft != NumberASsOnRight) { throw new AddSubtractOperationException(); }
This confirms that the number of addition/subtraction operations applied on the left match those on the right…
if (NumberASsOnLeft > 1) { throw new AddSubtractOperationException(); }
…and that there was at most 1 operation applied on each side (again, if the left matches the right, I only have to check the count on the left).
if (NumberASsOnLeft == 0)
{
if (NumberMDsOnLeft != NumberOfTermsOnLeft) { throw new MultiplyDivideOperationException(); }
if (NumberMDsOnRight != NumberOfTermsOnRight) { throw new MultiplyDivideOperationException(); }
}
Finally, if the player didn’t apply an addition/subtraction operation, then they must have applied a multiplication/division operation. Check that the number of multiplication operations applied on the left matches the ones on the right. Then check that the number of division operations match left and right.
If all of these checks pass, then the operations being applied are valid, and the player can proceed.
In my next post, I’ll go through the logic involved in displaying an operation in the UI. As it turns out, it’s not as simple as "is the number negative or not?" It was surprising how many scenarios had to be accounted for to get it right.