Mark Gilbert's Blog

Science and technology, served light and fluffy.

The Last Word

Yesterday, I installed Office 365, and retired some of the oldest pieces of software I ran on my home machine – Office 2007:

10

That particular suite spanned:

  • 13 years
  • 2 different machines
  • 3 versions of Windows
  • 3 jobs
  • 1.5 kids (my younger daughter hadn’t even been born when I first installed it)

Word and Excel were by far the most used components of the suite, but PowerPoint was sprinkled in there as well.  Word ran like a champ all that time.  Excel, though, developed a tic in the last couple of years.  Somewhere along the way, it decided it needed to try to install itself again every time I booted it up.  The first couple of times I tried to let it finish, thinking that something had gotten corrupted and it was trying to run a Repair.  What I found, though, is that it didn’t matter if I let it finish or not – it would run through it again the next time I started it.  So, I took to cancelling the installation, which required 3-4 extra clicks (depending on whether I caught it soon enough after it started), and several additional seconds.  I dealt with it, but I’m not sorry to be finally rid of that extra boot-up sequence.

With Office 2007 gone, I think the title of "oldest piece of software" falls to Visio 2007.  I’ll get around to that one eventually.

March 5, 2020 Posted by | General | Comments Off on The Last Word

Pumping iron to keep the headache at bay

Back in October 2017 I ran an experiment where I determined that not staying hydrated throughout the day aggravated my headache.  This past month, I tested another couple of variables.  Just as before, for the several days leading up to the beginning of Week 1, I stopped taking all Excedrin, and didn’t take it at all during the experiment so it wouldn’t interfere with my results.

Week 1 – Establishing Baseline
I tracked my headache for this first week, striving for a pain reading every 15 minutes.  My scale runs from 0-10, where 0 is no headache at all, and 10 is "curled up in a ball, whimpering".

Week 2 – Windows Nightlight Mode
For the second week, I switched Windows Nightlight Mode on, to cut down on the amount of blue light I was seeing on my monitors during the evenings.  I configured it to be enabled on my laptop and my phone from 7:30 until midnight.  Since I am very frequently on my computer until just before I go to bed, I was curious to see if the blue light from my screens was contributing to my headache at all.

As it turns out, I felt no perceivable difference in my headache pain with Nightlight on or off.

10

After Week 2, I turned this mode off.

Week 3 – Iron Supplements
A friend of mine mentioned that an iron deficiency can contribute/cause headaches.  I did a bit of research into that angle, and sure enough, there are ample references to the link between these two (for example, see this article or this article).  So, for Week 3, I started taking a daily iron supplement.

The results here were much more interesting. 

20

On average, I saw a 0.4 drop in my headache rating when I was on the iron supplement (compared to Week 1’s baseline).

Week 4 – Blood pressure
On more than one occasion, I’ve noticed that bending over to tie my shoes, or doing something very strenuous aggravates my headache.  I began to wonder if there was a correlation between my blood pressure and my headaches.  I had an automatic home blood pressure monitor, so during Week 4 I intended to record my blood pressure alongside my headaches, and see if there was a relationship.

Unfortunately, I stopped this part of the way through the week, for two reasons.  First, I was getting consistently higher readings for my blood pressure than any of the times I’ve had it measured by a professional.  I wondered if a) I was either not placing the cuff on my arm correctly (too high/too low on the arm, too tight around it, etc.), or b) the machine needed to be recalibrated.

Second, and more seriously, I didn’t want to take readings every 15 minutes because it would have driven my co-workers nuts.  So, I tried taking a couple of readings in the mornings before I went to work and a couple in the evenings after I got home.  I was initially concerned that only getting 3-4 readings a day wouldn’t be enough to really determined correlation.  That thought continued to nag me until I decided to stop the experiment.  Perhaps I’ll revisit this later.

Summary
So, to date, I’ve found that the following aggravate my headaches:

  • Not staying sufficiently hydrated throughout the day
  • Too much sodium
  • Not enough iron in my diet

That’s progress.

March 1, 2020 Posted by | Science | Comments Off on Pumping iron to keep the headache at bay

What am I doing on Twitter?

What can you expect from me on Twitter?  I start by posing questions, every day.  Some will be silly.  Most will be about science fiction, science, or technology.  Hopefully they will all inspire some thought, and perhaps a conversation.

A good question, crafted from a place of curiosity, can cut through the noise and lead to the signal.  Let’s learn to ask better ones.  Follow me @NessoAsks.

December 17, 2018 Posted by | Nesso | Comments Off on What am I doing on Twitter?

Tweet, tweet?

I’ve written on my personal blog about a project I started called "Nesso" (https://markofquality.wordpress.com/category/nesso/ ).  That project has grown into something much larger than just my headaches.  At its core, Project Nesso is about asking better questions.  For the last two years I’ve been developing ways to ask questions about a given dataset.

This weekend I started a new one.  You can now follow me on Twitter @NessoAsks.  My goal is to tweet at least one question a day, which forces me to be more mindful of what is going on around me every day.  I’m curious to see how my questions improve over time, and if those questions can spur some interesting discussion.

Join the conversation!

December 10, 2018 Posted by | Nesso | Comments Off on Tweet, tweet?

Formatting Terms and Operations

I mentioned in my last post that the logic for displaying terms and operations was much more complicated than I originally anticipated.  The logic evolved as the game development progressed, and CJ and I identified new and interesting edge cases to be addressed.

I’ll begin with the logic behind the operations.  Both ConstantOperation and VariableOperation have their ToString() methods overridden.  ConstantOperation is the simpler of the two:

public override string ToString()
{
    return Utility.ToConstantOperationString(this.Numerator, this.Denominator, this.Operand);
}

The ToConstantOperationString method does all of the heaving lifting here:

public static string ToConstantOperationString(int Numerator, int Denominator, Operands Operand)
{
    string OperandSymbol, Sign;
    OperandSymbol = "";
    Sign = "";
    switch (Operand)
    {
        case Operands.Add:
            OperandSymbol = ((float)Numerator / (float)Denominator < 0 ? "-" : "+");
            Sign = "";
            break;
        case Operands.Subtract:
            OperandSymbol = ((float)Numerator / (float)Denominator < 0 ? "+" : "-");
            Sign = "";
             break;
        case Operands.Multiply:
            OperandSymbol = "*";
            Sign = ((float)Numerator / (float)Denominator < 0 ? "-" : "");
            break;
        case Operands.Divide:
            OperandSymbol = "/";
            Sign = ((float)Numerator / (float)Denominator < 0 ? "-" : "");
            break;
    }

    return string.Format("{0}{1}{2}{3}{4}{5}{6}",
                  OperandSymbol,
                 Sign,
                  (Math.Abs(Denominator) == 1 ? "" : "("),
                 Math.Abs(Numerator),
                 (Math.Abs(Denominator) == 1 ? "" : "/"),
                 (Math.Abs(Denominator) == 1 ? "" : Math.Abs(Denominator).ToString()),
                 (Math.Abs(Denominator) == 1 ? "" : ")"));
}

The switch statement sorts out which operand and which sign will be displayed.  The latter is dependent on the former.  For adds & subtracts, the operand and the sign end up effectively merged with each other.

  • Adding a positive 4: "+4"
  • Adding a negative 4: "-4"
  • Subtracting a positive 4: "-4"
  • Subtracting a negative 4: "+4"

Multiplication and division, however, preserve both the operand and the sign (at least when the number is negative):

  • Multiplying a positive 4: "*4"
  • Multiplying a negative 4: "*-4"
  • Dividing by a positive 4: "/4"
  • Dividing by a negative 4: "/-4"

Then we need to format the final value, which includes the operand, the sign, and the number itself.  Whole numbers like the above examples are straightforward.  Fractional numbers present an additional challenge.  I made the decision to surround fractional operations in parentheses, to set the absolute value apart from the sign, operand, and (as we’ll see later this post), the variable letter:

  • +(4/3)
  • -(4/3)
  • /-(4/3)
  • *-(4/3)

As the Format() function at the end suggests, each constant is made up of 7 pieces:

  • The operand itself (+, -, *, or /)
  • The sign of the constant
  • An open parenthesis, if this is a fraction (denoted by a non-1 denominator)
  • The numerator
  • A slash, if this is a fraction
  • The denominator, if this is a fraction
  • A closing parenthesis, if this is a fraction

It is also important to note that the numerator, the denominator, or both, may be negative.  However, it all cases, the presentation of that value will never be something like "-4/-3".  The signs of the two components effective get evaluated in the expression:

((float)Numerator / (float)Denominator < 0 ? … : … )

VariableOperation.ToString() use the exact same logic for coming up with the coefficient, but includes two additional pieces of logic:

public override string ToString()
{
    String InitialCoefficientString;

    InitialCoefficientString = Utility.ToConstantOperationString(this.Numerator, this.Denominator, this.Operand);
    if(Math.Abs(this.Numerator) == 1 && Math.Abs(this.Denominator) == 1) { InitialCoefficientString = InitialCoefficientString.Replace("1", ""); }

    return string.Format("{0}{1}", InitialCoefficientString, this.Var);
}

First, it appends the variable letter itself, in the Format() call.  Second, it evaluates the coefficient to see if it is exact "1".  If so, it drops the coefficient completely, so that the player sees "+x" rather than "+1x", by simply replacing the "1" with an empty string.  (However, since this version of the game does not allow you to "multiply by x" or "divide by x", the player wouldn’t see something like "*x" or "/x" anyway, so this bit of logic is really just future-proofing.)

That’s all there is for rendering operations.  Let’s move on to how terms are presented. 

***

Terms have a slightly different set of concerns than operations.  The operations have to show one of four operands, every time, while terms only have to worry about rendering "+" and "-".  In some cases, an operand is not shown at all, as in the case of leading, positive values:

4x – 1 = 15

This concept of "leading term" crops up a couple of times in the other methods we’ve looked at.  The signs for the first term on either side of the equal sign get treated a little differently.

As with the operations, the Constant and Variable classes have their ToString() methods overridden.  Let’s start with Constant:

public override string ToString()
{
    if(this.Numerator == 0) { return "0"; }

    return string.Format("{0}{1}{2}{3}",
                         (((float)this.Numerator / (float)this.Denominator) > 0f ? (this.IsFirstOnThisSide ? "" : "+ ") : (this.IsFirstOnThisSide ? "-" : "- ")),
                         Math.Abs(this.Numerator),
                          (Math.Abs(this.Denominator) == 1 ? "" : "/"),
                         (Math.Abs(this.Denominator) == 1 ? "" : Math.Abs(this.Denominator).ToString()));
}

Much of this should look familiar from ConstantOperation.  The notable exceptions being the lack of parentheses, and the presentation of the sign.  The latter gets subtly modified depending on whether this is the first term in the equation or not.  First-term additions show no sign:

4 + 3x = 2

While other additions show a "+" followed by a space.

3x + 4 = 2

The space improves the look of the equation.  Without it, it would look like

3x +4 = 2

First-term subtractions show the minus sign with no intervening space (the "-3" here), but other subtractions (the "- 9" here) include the space:

4x = -3 – 9

****

Now let’s look at Variable terms.  The Variable version of ToString() combines these the approaches:

public override string ToString()
{
    return string.Format("{0}{1}{2}{3}{4}{5}{6}",
                         (((float)this.Numerator / (float)this.Denominator) > 0f ? (this.IsFirstOnThisSide ? "" : "+ ") : (this.IsFirstOnThisSide ? "-" : "- ")),
                          (Math.Abs(this.Denominator) == 1 ? "" : "("),
                          (Math.Abs(this.Numerator) == 1 && Math.Abs(this.Denominator) == 1 ? "" : Math.Abs(this.Numerator).ToString()),
                          (Math.Abs(this.Denominator) == 1 ? "" : "/"),
                         (Math.Abs(this.Denominator) == 1 ? "" : Math.Abs(this.Denominator).ToString()),
                          (Math.Abs(this.Denominator) == 1 ? "" : ")"),
                          this.Var);
}

Parentheses are included (to distinguish the sign, coefficient, and variable more clearly), as is the "first term" logic.

***

At this point, you might be asking why I ended up with three different approaches to formatting the values.  The requirements for operations, constant terms, and variable terms have overlap, but none of them were complete subsets of the others.  That makes it trickier to factor anything out.  I think you can make the argument, though, that showing a constant operation of "-(3/4)", and a constant term of "-3/4" (as the game currently does) is inconsistent and should be normalized.  Doing so would go a long way to making it easier to refactor the logic together.  Another improvement for a later version.

This concludes the series on the algebra game.  There are still plenty of things that we’d like to do with the game before we consider submitting it to one or both app stores: replace the temporary graphics, add usage tracking, add error logging, and so on.  I’ve had a blast building it to this point and (for the most part) loved the discussions with CJ about its direction.  (We won’t speak of the "I’m sorry dear; I really think you need to represent numbers as fractions" conversation again.)

November 20, 2017 Posted by | Game - Algebra, Unity | Comments Off on Formatting Terms and Operations

Validating the Operations Applied

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.

November 13, 2017 Posted by | Game - Algebra, Unity | Comments Off on Validating the Operations Applied

Applying Operations

Last time, we walked through how the operations in the tray get generated, based on the current equations.  Now we’ll see how those get applied to the equation to solve it.

Let’s square away some UI-terminology first.  The equation to be solved is arranged in a series of UI "dominos".

10

Each domino has two parts.  The upper portion is a "term" of the equation, and the lower portion is the "operation" to be applied to it.

20

The process begins when the player drags his or her first operation off the tray onto a domino.  As soon as they select an operation, all of the others in the operations tray are disabled, preventing them from trying to apply two different operations in the same pass.

30

Once the user clicks the "Go" button, the system pulls together the list of operations to be applied.  For the dominos that didn’t get an operation, a special "NoOperation" object is used as a placeholder.

The first thing ApplyOperations does is call ValidateOperationsBeingApplied, passing it both the list of Terms and the list of Operations to apply.  The method does just what you think it would – makes sure the user hasn’t done anything invalid like trying to apply an operation to only one side of the equation.  The logic for this is not trivial, so I’ll cover that in my next blog post.  If the operations being applied are not valid, an error is returned to the UI, and the player can then correct it.  If the operations are valid, then it moves on to actually applying them.

The majority of ApplyOperations is a loop that walks through each Term/Operation pair, to evaluate what the "new" equation state should be.  With each term-operation pair, the code determines whether to carry forward 0, 1, or 2 terms into the new equation.

NewEquationState = new List<IAmATerm>();
for (int i = 0; i < this.Terms.Count; i++)
{
    IAmATerm CurrentTerm;
    IAmAnOperation CurrentOperation;

    CurrentTerm = this.Terms[i];
    CurrentOperation = OperationsToApply[i];

    // Check the possible scenarios
}

Let’s get into the scenarios, one at a time:

Current term is the equal sign.  Simply add an EqualSign to NewEquationState:

if (CurrentTerm is EqualSign)
{
    NewEquationState.Add(new EqualSign());
}

The current operation is a NoOperation.  The user didn’t apply anything to this term, so we’ll carry that term forward into NewEquationState, unchanged:

else if (CurrentOperation is NoOperation)
{
    NewEquationState.Add(CurrentTerm.Clone());
}

The current operation is a constant, and it’s being applied to a constant term.  Apply that operation to the term.  If the resulting value is 0, then don’t add anything to the NewEquationState; otherwise, include the newly-calculated constant:

else if (CurrentTerm is Constant && CurrentOperation is ConstantOperation)
{
    NewCoefficient = this.CalculateNewCoefficient(CurrentTerm.Numerator, CurrentTerm.Denominator, CurrentOperation);
    if (NewCoefficient.Numerator != 0) { NewEquationState.Add(NewCoefficient.Clone()); }
}

The current operation is a variable, and it’s being applied to a variable term.  Apply that operation to the term.  If the resulting value is 0, then don’t add anything to the NewEquationState; otherwise, include the newly-calculated variable:

else if (CurrentTerm is Variable && CurrentOperation is VariableOperation)
{
    NewCoefficient = this.CalculateNewCoefficient(CurrentTerm.Numerator, CurrentTerm.Denominator, CurrentOperation);
    if (NewCoefficient.Numerator != 0) { NewEquationState.Add(new Variable(NewCoefficient.Numerator, NewCoefficient.Denominator, ((Variable)CurrentTerm).Var)); }
}

Those were the easy cases.  Now comes the fun ones.  What happens if the player tries to apply a constant operation to a variable?

else if (CurrentTerm is Variable && CurrentOperation is ConstantOperation)
{
    switch (CurrentOperation.Operand)
     {
        case Operands.Add:
            NewEquationState.Add(CurrentTerm.Clone());
            NewEquationState.Add(new Constant(CurrentOperation.Numerator, CurrentOperation.Denominator));
            break;

        case Operands.Subtract:
            NewEquationState.Add(CurrentTerm.Clone());
            NewEquationState.Add(new Constant(-1 * CurrentOperation.Numerator, CurrentOperation.Denominator));
            break;

        case Operands.Multiply:
            NewCoefficient = new Constant(CurrentTerm.Numerator * CurrentOperation.Numerator, CurrentTerm.Denominator * CurrentOperation.Denominator);
            NewEquationState.Add(new Variable(NewCoefficient.Numerator, NewCoefficient.Denominator, ((Variable)CurrentTerm).Var));
            break;

        case Operands.Divide:
            NewCoefficient = new Constant(CurrentTerm.Numerator * CurrentOperation.Denominator, CurrentTerm.Denominator * CurrentOperation.Numerator);
            NewEquationState.Add(new Variable(NewCoefficient.Numerator, NewCoefficient.Denominator, ((Variable)CurrentTerm).Var));
            break;

        default:
            throw new UnsupportedOperandException(CurrentOperation.Operand.ToString());
    }
}

In the cases of addition and subtraction, both the term and the operation are carried forward into the NewEquationState, since these really don’t combine.  In the cases of multiplication and division, the constant does get applied to the variable, and the resulting variable is included in the NewEquationState.

And then, what about the case where the user applies a variable operation to a constant?

else if (CurrentTerm is Constant && CurrentOperation is VariableOperation)
{
    switch (CurrentOperation.Operand)
    {
        case Operands.Add:
            // When adding a variable operation to a constant terms, since the Term and Operation don’t line up, just carry them
            // both forward to the next equation state.  The exception is when the term is 0.  That shouldn’t be carried forward.
            if (CurrentTerm.Numerator != 0) { NewEquationState.Add(CurrentTerm.Clone()); }
             NewEquationState.Add(new Variable(CurrentOperation.Numerator, CurrentOperation.Denominator, ((VariableOperation)CurrentOperation).Var));
            break;

        case Operands.Subtract:
            // When adding a variable operation to a constant terms, since the Term and Operation don’t line up, just carry them
            // both forward to the next equation state, but flip the sign of the coefficient.  The exception is when the term is
            // 0.  That shouldn’t be carried forward.
             if (CurrentTerm.Numerator != 0) { NewEquationState.Add(CurrentTerm.Clone()); }
            NewEquationState.Add(new Variable(-1 * CurrentOperation.Numerator, CurrentOperation.Denominator, ((VariableOperation)CurrentOperation).Var));
            break;

        default:
            // Multiplying and Dividing variable operations is not supported at this time
            throw new UnsupportedOperandException(CurrentOperation.Operand.ToString());
    }

}

In this case, if the constant term is non-0, addition and subtraction work the same as above – these don’t combine, so both the term and the operation are carried forward. 

If the term is 0, however, only carry the operation forward in its place.  This addresses the scenario where you have something like:

2x – 1 = 0

And you apply a "-2x" to both sides to get

-1 = -2x

Finally, trying to multiply or divide by a variable is not allowed in this version of the game, so those just throw errors.  The player doesn’t even have the option to apply a variable operations involving multiplication or division (GenerateOptions method doesn’t currently generate these), but I included the check here for completeness and safety.

At this point, we have our tentative new equation state.  The last pieces of ApplyOperations tidies things up a bit:


// Check the possible scenarios

// If the equation is left with no terms on the right, add a 0
if (NewEquationState.Last() is EqualSign) { NewEquationState.Add(new Constant(0, 1)); }
// If the equation is left with no terms on the left, add a 0
if (NewEquationState.First() is EqualSign) { NewEquationState.Insert(0, new Constant(0, 1)); }

IsFirstTermOnThisSideOfEquation = true;
for (int i=0; i<NewEquationState.Count; i++)
{
    if (NewEquationState[i] is EqualSign) { IsFirstTermOnThisSideOfEquation = true; }
    else if(IsFirstTermOnThisSideOfEquation)
    {
        NewEquationState[i].SetAsFirst();
        IsFirstTermOnThisSideOfEquation = false;
    }
}

this._Terms.Clear();
this._Terms.AddRange(NewEquationState);

this.EvaluateEquationToSeeIfItHasBeenSolved();

First, it makes sure there is at least something on both sides of the equals sign.  If not, it adds a constant term of 0.  Then it runs through the new terms, and flags the first one on each side as officially "first" (needed so the UI can render the signs correctly).  Finally, it updates _Terms with the new equation state, and checks to see if it has, in fact, been solved.

In my next post, I’ll return to how the operations are validated before being applied.

November 6, 2017 Posted by | Game - Algebra, Unity | 1 Comment

Generating Operations

In my last post, I walked through how a new equation gets generated.  The game also generates a list of operations that the player could apply to the equation to simplify it.  An equation starts out with coefficients and constants that are randomly generated.  Then, as the player works through the problem, those values shift.  As a result, the game needs to refresh the list of possible operations each time a new equation is generated, the player applies an operation to the equation, and so on.

The Equation class has a method called GenerateOperations() that generates a list of IAmAnOperation objects based on the current state of the equation.  (IAmAnOperation objects are similar to IAmATerm objects, but they also include the operand to apply – add this, multiply this, etc.).  This method is used at the beginning when the equation is first generated and every time the equation is updated as the result of a player’s action.

***

Before I get into how the operations are generated, though, I need to discuss how the coefficients/constants are stored.  The initial version of the game stored these values as floats.  As the development progressed and I played the game more, I began to realize that that was a poor design decision.

First, there were the problems with the math.  During one of my tests, I managed to get an equation looking something like this:

1.1x = 5

I divided by 1.1 to get rid of the coefficient, but ended up with this instead:

1.0x = 4.5

“1.0x”?  There was code in the game specifically designed to drop the “1” for variables, but what I found in this case was that the coefficient on the variable was not “1”, but “1.0000000001”, or something similarly silly.  I started down the road of implementing logic that would treat something “very close to 1” as 1, but hit a lot of problems getting the rounding to work.

Then there was the UI itself.  If you solve equations like I do, you reduce the equation until there is a single variable term on one side and a single constant term on the other, and then apply a division operation to get rid of the variable’s coefficient.  That means you have to divide at most once, which means any fractional values will be remain relatively simple as you work through the equation.  However, the game doesn’t prevent you from going off the rails by applying division operations over and over again, which will quickly lead to crazy-fractional numbers.  With the floating-point approach, those numbers became much smaller than 1, and required a lot of space on the screen to display, e.g., “0.0012”.  I couldn’t increase the size of the UI elements, and I could only shrink the font so far before it became unreadable.

Finally, CJ made the argument that keeping the values in fractional form would make it easier to read and simplify the equation.  If you’re presented with “0.328”, it may or may not be obvious that multiplying that by 125 will leave you with a simple “41”.  However, if you were presented with “(41/125)x”, your next steps become a lot easier to see.

For all of these reasons, I refactored the system to store everything in fractions rather than floating-point decimals.  That means storing separate integers for numerators and denominators, and having to deal with applying fraction operations to fractional terms.  As we’ll see next time, it ends up being a little more work, but not nearly what I had put in trying to get the floating-point approach operational.  (See what I did there?)

This change would also have a direct impact to how I generated potential operations for the player to apply.

***

GenerateOperations generates one or more operations for each term in the current equation:

public List<IAmAnOperation> GenerateOperations()
{
List<IAmAnOperation> Operations, DeDupedSortedOperations;
int CurrentNumerator, CurrentDenominator;
string CurrentVariable;

    Operations = new List<IAmAnOperation>();

    foreach (IAmATerm CurrentTerm in this.Terms)
{
CurrentNumerator = CurrentTerm.Numerator;
CurrentDenominator = CurrentTerm.Denominator;
if (CurrentNumerator == 0) { continue; }

        if (CurrentTerm is Variable && this._AreMultiplicationAndDivisionNeeded)
{
CurrentVariable = ((Variable)CurrentTerm).Var;

            Operations.Add(new VariableOperation(new Variable(CurrentNumerator, CurrentDenominator, CurrentVariable), Operands.Add));
Operations.Add(new VariableOperation(new Variable(CurrentNumerator, CurrentDenominator, CurrentVariable), Operands.Subtract));
}
else if (CurrentTerm is Constant)
{
Operations.Add(new ConstantOperation(new Constant(CurrentNumerator, CurrentDenominator), Operands.Add));
Operations.Add(new ConstantOperation(new Constant(CurrentNumerator, CurrentDenominator), Operands.Subtract));
}

        if (CurrentNumerator != 1 && this._AreMultiplicationAndDivisionNeeded)
{
Operations.Add(new ConstantOperation(new Constant(CurrentNumerator, CurrentDenominator), Operands.Multiply));
Operations.Add(new ConstantOperation(new Constant(CurrentNumerator, CurrentDenominator), Operands.Divide));
}

        if (CurrentDenominator != 1 && this._AreMultiplicationAndDivisionNeeded)
{
Operations.Add(new ConstantOperation(new Constant(CurrentDenominator, 1), Operands.Multiply));
}

    }

    // Return a sorted, de-duped list
DeDupedSortedOperations = Enumerable.ToList(Enumerable.Distinct(Operations));
DeDupedSortedOperations.Sort();
return DeDupedSortedOperations;
}

For each term in the list:

  • If the numerator is 0, skip this term and move on.
  • If the term is a variable, and multiplication/division are needed, append two operations to the list – one that adds the current term, and a second that subtracts it.  Why append *add/subtract* operations if multiplication/division are needed?  Because if the latter are needed, it means the possibility of multiple variable terms in the equation.  If those terms happen to be on the same side of the equation, the player can directly combine them.  However, if there are variable terms on both sides, then the player will need to add or subtract one to simplify it.
  • If the term is a constant, append two operations to the list – one that adds the current term, and a second that subtracts it.
  • If the numerator is not 1, and multiplication/division are needed, append two operations to the list – one that multiplies by the coefficient, and another that divides by it.
  • Finally, if the denominator is not 1, and multiplication/division are needed, append an operation to the list that multiplies by that denominator.  This gives the player a way to clear out the fractional denominators.

It’s quite possible that there would be duplicate operations added as a result of all of these rules, so one of GenerateOperations’ final step is to dedup the list.

In my next post, I’ll look at the ApplyOperations() method, which does the work of validating that the player has applied a given operation correctly, and if so, does the work of transforming the equation as a result of that operation.

October 30, 2017 Posted by | Game - Algebra, Unity | 1 Comment

My headaches are now driving me to drink

For the first three weeks in October, I conducted an experiment – on myself.  I’ve suspected for a while that at least a contributing factor for my daily headaches was not drinking enough throughout the day.  There had been days where I got so busy at work that I went the entire workday drinking only 8oz.  By the time I got home on those days, my headache was raging.

My original experiment was to start on October 2, and look something like this:

  • Week 1: Establish baseline for liquid consumption and record headache-pain
  • Week 2: Increase liquid consumption by 50% of baseline
  • Week 3: Increase liquid consumption by 100% of baseline

For several days leading up to the start of Week 1, I took no Excedrin.  I wanted to get that completely out of my system, so it wouldn’t interfere.  I also vowed to not take it during the three weeks, for the same reason (as I’ll discuss towards the end, I broke that rule once).

I decided to follow my previous self-survey template, and recorded these two data points every 15 minutes (or as close to it as I could manage).  I thought about reworking iSelfSurvey to record just these two points, but that would require a fair amount of work (for starters, I would have to get Android Studio reloaded on my computer), and I just didn’t have the time at the end of September to do that.  So, I went went low-tech.  I created 21 custom PocketMods to record the time, my headache pain rating, and the amount of liquid (in ounces) I had consumed since the last reading:

10

Each evening, I would transfer the data to a spreadsheet so I could run the analysis.  I think the results were pretty clear:

20

The days I averaged only 56oz, my average daily headache was 5.7.  When I increased that amount to 50% more (which worked out to about 84oz of liquid per day), my average daily pain level dropped more than a full point to 4.5.  In fact, the effect was so pronounced that I modified Week 3 to merely be a duplicate of Week 2 (mostly to demonstrate Week 2 hadn’t been a fluke).

This is a breakthrough. 

I don’t normally notice myself getting thirsty during the day, but apparently I was – and have been for years.  In fact, for the first few days of Week 2, I learned to start drinking immediately after taking a reading, just so I would remember to drink more!

Since the end of Week 3, I’ve kept up my regimen of drinking 84+ ounces a day, and my headache has pretty consistently stayed lower.  I’m chalking that up as a win.

***

I mentioned at the beginning that I vowed I wouldn’t take Excedrin during this experiment, because I didn’t want it tainting the numbers.  At the end of Week 2, I woke up Sunday morning with an extremely nasty headache.  The baseline pain was a 6 (out of 10), but then periodically I would get a sharp stabbing pain behind my right ear, pushing my the pain up to an 8 or a 9.  I had been doing great all week – what happened?  The day before I had consumed over 100oz of liquid, so it’s not like I fell back into old habits on that front.

Oh – the chicken.  We had take out fried chicken the night before for dinner – something that I know is crazy high in sodium: 2 chicken strips, a regular side of potato wedges and a biscuit come to 2,470mg of sodium – more in one meal than I’m supposed to be getting in the entire day.  CJ has reported getting headaches pretty consistently after eating that meal in the past (especially if we had the leftovers the second night).  Perhaps sodium – or more correctly, too much sodium – is another trigger?  I’ve never tracked how much sodium I consume in a given day.  I wonder what would happen if I decreased that?

And THAT will become my next experiment.  Stay tuned.

October 27, 2017 Posted by | Science | 3 Comments

Um, progress?

“Progress” is changing code that results in a new error message.

October 25, 2017 Posted by | Software Process | Comments Off on Um, progress?