From the beginning, CJ and I wanted the algebra game to have a series of levels. The first level would show the player the simplest technique – combining like terms. For example:
x = 3 + 4
Combining the “3” and the “4” into 7 would be enough to solve this equation. The second and subsequent levels would teach the player additional techniques that he or she may need to solve more complicated equations, and then, of course, generate a series of problems that required that technique.
What this meant for the app itself was that it would need to be able to not only generate random equations, but equations that met certain, configurable parameters. I created a class called “EquationConfig”:
public class EquationConfig
this.AreMultiplicationAndDivisionNeeded = true;
this.AreVariableCoefficientsOf1Allowed = true;
this.VariableLetterPool = VariableLetters.X;
public class VariableLetters
public const string X = “x”;
public const string AnyValidLetterExceptX = “abcdfghjkmnpqrstuvwyz”;
public const string AnyValidLetter = “abcdfghjkmnpqrstuvwxyz”;
public int NumVarsLeft;
public int NumVarsRight;
public int MaxAbsCoefficient;
public int NumConstsLeft;
public int NumConstsRight;
public int MaxAbsConstant;
public bool AreMultiplicationAndDivisionNeeded;
public string VariableLetterPool;
public bool AreVariableCoefficientsOf1Allowed;
An EquationConfig object gets passed into the “Equation” class (which does the work of actually generating the new equation, based on the configuration passed in; I’ll go through this class in depth in my next post).
The core of EquationConfig are these properties:
- NumVarsLeft – the number of variables to generate on the left side
- NumVarsRight – the number of variables to generate on the right side
- MaxAbsCoefficient – the maximum absolute value of the coefficients on each variable
- NumConstsLeft – the number of constants to generate on the left side
- NumConstsRight – the number of constants to generate on the right side
- MaxAbsConstant – the maximum absolute value of each constant
Each equation will consist of some number of variables and constants on the left side of the equal side, and some number of each on the right side. Each variable will have a randomly generated coefficient in the range [-MaxAbsCoefficient, MaxAbsCoefficient], except for 0. Each constant will have a randomly generated value in the range [-MaxAbsConstant, MaxAbsConstant]. Constants of 0 are sometimes allowed (as we’ll see in the next post).
In addition to these, there are three more specialized properties of an EquationConfig:
- AreMultiplicationAndDivisionNeeded: If this is true, then non-1 variable coefficients are allowed and operations involving multiplication and division are included in the tray; otherwise they aren’t. These are needed to generate the lower levels where we want players to focus on learning to use addition and subtraction operations to solve equations. Multiplication and division will come later.
- VariableLetterPool: A string that determines what will be allowed for the variable – just “x”, most letters of the alphabet, or most letters of the alphabet except for “x”. I say “most” letters of the alphabet because we initially left out the letters “o” and “i” – those looked too much like the numbers “0” and “1”. Once we started playing, though, we quickly realized that the letters “e” and “i” should be left out as well, since those have special meaning in math (Euler’s number, and the square root of -1, respectively). This is needed with the level that involves solving equations that have something other than “x” for the variable.
- AreVariableCoefficientsOf1Allowed: If this is false, then all variables are required to have a non-1 coefficient. This is used on the level that requires the player to use multiplication and division to simplify the variable. Having something like “x = 2” right out of the gate would defeat that purpose.
There are combinations of settings in EquationConfig that are not valid, so Equation needs to vet the config passed in before using it. For example, if multiplication/division are not needed, allowing more than one variable term on the left could lead to situations like this:
x + x = 2
2x = 2
Without a “divide by 2” operation, the player couldn’t solve this.
Here is the core validation logic, currently contained in the Equation constructor:
this._AreMultiplicationAndDivisionNeeded = CurrentConfig.AreMultiplicationAndDivisionNeeded;
if (!this._AreMultiplicationAndDivisionNeeded && CurrentConfig.NumVarsLeft > 1)
throw new EquationConfigurationException(EquationConfigurationException.InvalidStates.TooManyVariableTermsRequestedOnLeft);
if (!this._AreMultiplicationAndDivisionNeeded && CurrentConfig.NumVarsRight > 1)
throw new EquationConfigurationException(EquationConfigurationException.InvalidStates.TooManyVariableTermsRequestedOnRight);
if (!this._AreMultiplicationAndDivisionNeeded && CurrentConfig.NumVarsLeft > 0 && CurrentConfig.NumVarsRight > 0)
throw new EquationConfigurationException(EquationConfigurationException.InvalidStates.TooManyVariableTermsRequested);
this._VariableLetter = this.PickALetter(CurrentConfig.VariableLetterPool);
// Step 2. Build the equation here (covered in next blog post)
The invalid states are all around variables. If multiplication/division are not needed:
- make sure there is at most 1 variable on the left.
- make sure there is at most 1 variable on the right.
- make sure there is only 1 variable in the entire equation (don’t allow variables on both sides of the equal sign).
In most ways, the last check covers the first two. However, having all three allows me to record a more specific error.
Next, we need to pick a letter to use as the variable in the new equation.
Next, build the left and right sides of the equation, based on the newly vetted configuration. (I’ll cover this later.)
Finally, make sure the variables in the equation are NOT balanced. One of the earlier iterations of the game generated an equation that looked like this:
x = x – 9
There’s no valid answer for the value of “x”. The final step of the validation, then, is to ensure that the sum of the variable coefficients on the left does NOT equal the sum on the right. If Equation random generates an equation where left and right are equal, it flips the sign of the last variable in the equation, so the above would become:
x = -x – 9
Which does have a solution.
In retrospect, this validation logic probably makes more sense as a method on EquationConfig (VerifyThyself, or something like it) than in the Equation constructor – a refactoring for the future.
These configuration options allowed CJ and I to define the initial five levels of the game as follows:
- Level 1: Combining constants on the same side of the equal sign. Shows the user that they can drag constants to combine them. Requires a single variable with a coefficient of 1 on one side, and two constants on the other.
- Level 2: Moving all of the constants to one side of the equal sign. Introduces addition and subtraction operations, and teaches the player to apply those operations to both sides equally. Requires a single variable with a coefficient of 1 on one side, and constants on both sides.
- Level 3: Reducing the variable’s coefficient to 1. Introduces multiplication and division operations, and teaches the player to apply those operations to every term on both sides. Requires a variable on one side with a coefficient of something other than 1, and a constant on the other side.
- Level 4: Reducing the number of constants, and reducing the variable’s coefficient. Combines the previous lessons, and teaches the player that they sometimes need to perform multiple steps to solve the equation.
- Level 5: Solving for variables other than “x”. Similar to Level 4, except that now the variable in use is something other than “x”.
These level-configs are currently stored in a JSON file that gets loaded with the game, but the groundwork is laid for future versions to call a web service to pull them down.
In my next post, I’ll go through the logic that uses the configuration to actually generate an equation, including the edge cases it handles.