View on Github
Python Beginner
Generate math equations to play NERDLE
Original Game: Nerdle
Overview
NERDLE is a math version of the viral word game WORDLE. I was trying to build myself a Nerdle program to play just for fun, but then I realized there was no dataset available to download online. Therefore, I decided to write a simple Python program for creating a list of mathematical equations that can be utilised in the game. It turned out to be more fun than I had expected. So in this post, I am going to walk through the principle logics and the technical steps of building a program to generate a math equation database for the Nerdle game.
Background
Image from The Nerdle Game
1. The 8 Spaces
In the original Nerdle game, every time you make a guess you have to fill up all the 8 spaces in the line. So you can imagine your equation dataset consist of strings with fixed length of 8. For example, the string 10 + 20 = 30 is 8-character long so it’s valid. The string 5 + 25 * 2 = 55 is NOT valid because it has 9 characters.
2. A Valid Equation
The major part of this program is to define what is a valid equation.
An equation is consist of 3 parts: the left-hand side (LHS), the equal sign ( = ), and the right-hand side (RHS).
In this program some constraints have to be put into especially on the LHS and RHS.
For example, the first digits in LHS and RHS cannot be 0, because math problems like 03 + 4 = 7 does not make sense to the game players.
Also, two operators should not appear consecutively like 7 +* 2 or 4 - 6 = -2 for the sake of simplicity for both the programmer and users.
There are a few more constraints which will be discussed in a later part: Step 1 - Generate a LHS
Get Started
Once you understand the background information of generating a list of valid 8-character equations, you can start working on the logic. The macroscopic logic is as simple as followed:
- Make the LHS of the equation by randomly generating some numbers and operators (+-*/) , with constraints. See Step 1 - Generate a LHS
- Try to compute the answer, which will be the RHS of the equation. See Step 2 - Calculate
- Check if the full equation meets some specific conditions. See Step 3 - Conditions. If it does, it’s a successful generation for the game! If it doesn’t, just forget it and start over from step 1 again.
- Repeat step 1 to 4 enough times and you will eventually have a list of playable equations. From my experience I loop 1000 times and often get 80 - 100 playable equations (Around 10%).
If you have passed the first two steps, you already have a valid mathematical equation, like 2 / 60 + 3 = 3.03 , or 128 - 52 = 76. (But NEITHER of these 2 equations can be used as Nerdle database, because the first one involves non-integer, and the second one takes up 9 spaces instead of 8.)
When a LHS gives a RHS (like 2 / 60 + 3 gives 3.03), LHS and RHS with the equal sign ( = ) become the full equation .
Implementation
Step 1: Generate a LHS
def generateEquations(equalSignPosition):
'''
Generate a list from digits and operators (+-*/)
Example:
[‘5’, ‘1’, ‘/’, ‘7’, ‘9’] or
[‘2’, ‘1’, ‘1’, ‘*’, ‘3’] or
[‘1’, ‘6’, ‘+’, ‘1’, ‘-’, ‘9’]
'''
A: It actually depends on the position of the equal sign (equalSignPosition), which generally can be 5 or 6 or 7. Since the TOTALSPACE is 8, equalSignPosition being in the 7th space would expect the answer to be a single digit number, like 3. Similarly, it being in the 6th space would expect the answer to be a double digit number, like 24, and so on.
In this project I let the computer to pick randomly among [5, 6, 7] for each equation in order to obtain a more dynamic set of data.
Increase the chance of getting a reasonable LHS by adding constraints
While randomly picking numbers and operators in hopes for getting a reasonable LHS may seem inefficient as you can imagine things like 8827 - % or 93 - 2 + % will often show up, certain constraints can be applied to prevent these from happening:
- Don’t start the equation with 0 or operators(+-*/)
- Don’t end with operators (+-*/)
- If the previous space is an operator, don’t continue with 0 or operators (+-*/)
Code
With these constraints helping you would easily generate a reasonable LHS like 7 + 813, 3 * 7 - 8, etc.
The last minor step is to convert the list into String to save effort for the next part.
Example: [‘7’, ‘+’, ‘8’, ‘1’, ‘3’] => ‘7+813’
It can be done as simple as below:
Step 2: Calculate
def Calculate(LHS):
'''
Take LHS as a string input, then use try and except to calculate the float of RHS
Example Input: ‘2/17+9’
Example Output: 9.1176470588
Example Input: ‘64-19’
Example Output: 45.0
Example Input: ‘97/0’
Example Output: None
Example Input: ‘14594’
Example Output: None
Example Input: ‘1/89/9’
Example Output: 0.001248
Example Input: ‘2-49’
Example Output: -47.0
Example Input: ‘3-14+3’
Example Output: 14.0
'''
This part is very much like a typical calculator program, where input is a string of LHS, and output is a float number.
Above are some possible inputs and their corresponding answer outputs.
The goal of this part is to try to calculate the given LHS and provide a mathematical valid answer (RHS).
For example, if the input is (the string form of) 64 - 19, the function should return an answer of float 45.0.
And 2 / 17 + 9 should give 9.1176470588.
If the input is incalculable like 97 / 0 or 14594, the function would also catch it and return None
.
Steps
-
Separate (by regular expression, regex) the numbers and operators into a list
Example: '2/ 1 7 + 9' => ['2', '/', '17', '+', '9']
Example: '97 / 0' => ['97', '/', '0'] -
If the number of items in the list is less than or equal to 2, return
None
and this part has ended, since it is certain that it is not calculable.
Example: ['14594'] => return None
Example: [ '17', '+' ] => return None -
For the remaining case, keep looping in the list like below to modify the list until there is only 1 item, specifically a float.
Example:
['2', '/', '17', '+', '9']
=> [0.117647, '+', '9']
=> [9.117647]-
Find all the * and / in the list.
If it is a * (or /) , multiply (or divide if it’s / )the number before the * with the number after the *.
Remove those 3 items (which is the number before the *, the *, and the number after the *) from the list, and insert the newly obtained float product (or quotient) into the position where the 3 belong.
Repeat until there’s no more * or / in the list.
Example: ['2', '/', '17', '+', '9']
There’s a / in the second position, so calculate 2/ 17 = 0.117647, remove 2, / and 17 from the list, insert 0.117647 in the same position and the list becomes [0.117647, '+', '9'] - Handle + and - after getting rid all * and /. Do the same thing as above, but just replace * and / with + and -.
- Eventually the list is left with one float number, which is going to be the returned answer, a.k.a the RHS!
-
Find all the * and / in the list.
Note: Handling ZeroDivisionError:
Technically, we don’t have to handle this case because the
function generateEquations
in step 1 has guaranteed that there won’t be any number 0s right after any operators, therefore any cases of numbers divided by zero will not happen.
However, since this calculate function can be helpfully reused when handling input of game users, I still added a try and except in the division part in case the user types something like 5/0.
Step 3: Conditions
The final step is relatively simple. Just check if the equation meets all the requirements of being in the database. If it does, include it in a list! (If it doesn’t, just don’t do anything and start over from step 1 to generate a new LHS.)
Requirements that must be met:
-
The RHS from step 2 is not None
isEquationValid = rightHandSide is not None
-
The RHS from step 2 is an integer, to prevent answers with decimal places
isInteger = rightHandSide == int(rightHandSide or 0)
-
The RHS is not a negative number or not 0, to prevent negative numbers and extremely small float eg. 0.000052
isNotSmallNum = rightHandSide >= 1
Note:
How Python handles small numbers is out of scope of this project, so we just assume we won't have equations = 0 for the sake of simplicity -
All 3 of the LHS, the equal sign and the RHS take up 8 spaces in total
isSpaceValid = len(str(int(rightHandSide or 0))) == TOTALSPACE - equalSignPosition
If all the conditions are met, the full equation can be appended to the list and can be written into a database file!
You've just finished reading: Generate math equations to play NERDLE