8 min read

Daddy Does Not Flip Flash Cards

Building a Simple, Fun Math Game for my Kids
Daddy Does Not Flip Flash Cards

Building a Simple, Fun Math Game for my Kids

Problem

It would be great if my kids achieved automaticity in the basic facts of addition sooner than later. When I was an Assistant Principal in a past life at a wonderful school, we used to say that a teacher's job is not to flip flash cards with kids, but rather to decide which flash cards go with each student.

With that in mind I programmed a game to help my kids improve their addition skills. When I was a teacher I used to create Google Form quizzes that auto-scored themselves and emailed feedback to the student (long before this became a feature of Google Forms). Automating evening math time with my kids is very on brand for me.

The automation project is not complete. It has gone through four iterations. At each iteration I took a few steps forward in design and use-ability.

Iteration 0

To start with I wrote some simple Ruby code. I love the expressiveness of the Ruby programming language. It is the language I know the best so it made sense to start a new project here.

  def run
    start_round_message

    until complete?
      round = Round.new
      result = round.play

      streak.increment if result.first_guess?
      score.increment if result.success?
      @complete = result.quit?

      print scoreboard
    end
    
    finish
  end

The goal was to randomly generate addition problems with numbers between 1 and 12, present these problems in the terminal, allow my kids to type an answer, and then provide them with feedback regarding whether or not they got the question correct. One of the earliest improvements I made at this stage was giving the user the ability to retry incorrectly answered problems. Early on I added a score and streak metric. Score is the total number of questions answered correctly. Streak is how many questions have been answered correctly in a row.

Iteration 0 had a few significant drawbacks. The terminal display is clunky. While I know there are libraries that can help improve the user interface of terminal-based applications, I did not have a strong desire to learn any of these libraries. Additionally, I use a very normal keyboard. Typing is hard enough for my soon to be seven-year-old twins. Typing on a nerd board is a non starter at this age.

Iteration 1

My wonderful wife found these really nice looking math flash cards. I used them for a bit with my kids, but as I said, daddy doesn't flip flash cards.

I decided that I could use a Google Sheet to make the game that emulated the design of the flash cards. I wrote some Javascript that randomly generates addition fact problems, writes them to the spreadsheet, and scores the answers. I am less comfortable in Javascript than I am in Ruby, so implementing these basic algorithms felt like I was learning something new.

class AdditionProblem {
  constructor(max) {
    this.leftOperand = Math.floor(Math.random() * max);
    this.rightOperand = Math.floor(Math.random() * max);
  }
}

Innovation is often the result of uniquely combining ideas and technologies that already exist. While I am not really a pet person, I am of the Tamagotchi Generation and have followed the progress of pet-care games over the years enough to appreciate the motivational force of a digital pet. I designed a special pet for each of my kids (not creepy unicorn creature & neon robot) and wouldn't you know it, these pets required math knowledge to thrive.

The kids loved the visual progress indicator that I built with Google Sheet functions. I loved that I could track their answers to each question. Instead of randomly picking two numbers between 0 and 12, in the future I could have the algorithm pick math problems with a lower than average correct percentage or ones that have taken slightly longer than average to answer.

The spreadsheet implementation also had a few fatal flaws. Keyboards are hard for six year old hands even when I bust out ol' foxy. Second, typing in a spreadsheet is surprisingly challenging. Google Sheets is my favorite software of all time, but this was a proof of concept and not a workable solution in the long run. Also reading and writing out of the Google Sheet via apps script after each answer submission is actually kind of slow.

Iteration 2

With a new set of problems in mind, I decided to prioritize the user interface. The spreadsheet ergonomics were just too unwieldy, but using the sheet to store answer data was pretty great.

I decided to serve html from Google Apps Script as a standalone web app and continue to use a Google Sheet as a database. My thinking here was that by keeping the code in Google Apps Script I would be able to reuse most of the code from iteration 1 and focus on improving the user interface.

I landed on a kid friendly login screen - just push your picture to login!

I wanted the kids to be able to input their answers using my phone's number keyboard. As a result I decided to use a number input. This had a few unfortunate consequences. First, it looks bad. My CSS is not just rusty, it never was really any good to begin with. Second, the flow for answering a question is to first press the input, then do the math, then press the number buttons, and then finally press submit. Not great.

On the plus side, iteration 2 put the game on my phone, which was the form factor I wanted (big keyboard problem solved!). But, the user interface and experience were pretty bad. I also was not particularly enjoying the developer experience in Google Apps Script world.

Iteration 3

I made the decision to ditch Google Apps Script (which I love, but just not for this) and try my hand at React because I've heard it's cool and hip. I looked through the Create React App documentation got some numbers to show up on my screen and then took off from there.

You can try it for yourself here.

There are a few things I really like about this iteration. First, it's entirely client-side code so it is snappy feeling. Second, it looks great on my phone (less great on a computer screen at the time of this writing - okay not great at all). I realized I did not need an answer input field I could just have the key presses render the answer in the math problem itself. Also, by creating my own number pad I was able to make all the elements fit the screen nicely.

Once the basic UI and speed issues were solved, I got to add some fun stuff for the kids. I have a list of fun emojis that get randomly sampled and then displayed at the top of the app when the user answers a question correctly. I added an experience bar, similar to what I had in iteration 1 in the Google sheet (and what I remember from playing Diablo as a kid). I also added sound effects for when they answer questions correctly or incorrectly and when they level up.

The snappy feel, improved user interface, gamification, and sound effects make this little math game surprisingly fun. I've literally had to pull the phone out of my daughter's hands.

I've also improved the problem selection algorithm. Rather than picking random numbers between 0 and 12, I tie the range of possible problems to the user's level. If you are level one you'll only see numbers between 0 and 5. Each level increase will result in a corresponding increase to the maximum integer you'll see in the problems for that level. In that way the game starts easy and gets more difficult over time. This is important for reluctant math gamers. Start with a wrong answer and they'll never want to play again. Make it easy at first and suddenly they think, "I only have to do a few more to get to the next level."

Future Iterations

There is still work to be done here and so I want to share my current thinking around the roadmap for this little game.

This is entirely client side code and I don't even use temporary browser storage so every time you open the game, you start from scratch. This is probably the most important thing to fix - my users are begging for this feature. Ideally I'd like to use this as an excuse to mess with Firebase. But, I may just use browser storage as a proof of concept that will allow me to iterate on the data model more quickly.

The immediate data needs are to store the user and their progress. A nice to have is what I originally built in iteration 1, which recorded every problem seen by the user. I could represent this data with something like:

{
  // a unique id for the problem
  id: <integer>,
  
  // the user that the problem belongs to
  userId: <integer> 
  
  // the problem
  leftOperand: <integer>,
  rightOperand: <integer>,

  // how many times the user got it right / wrong
  correctResponseCount: <integer>,
  incorrectResponseCount: <integer>,

  // how quickly the user submitted a response
  maxTimeToCorrectResponse: <integer - milliseconds>,
  minTimeToCorrectResponse: <integer - milliseconds>,

  // timestamp representing the DateTime when the user last saw
  // this problem
  lastSeen: <timestamp>
}

I'm pretty new to NoSQL databases, so this is very much a work in progress and is also why using temporary browser storage before jumping into Firebase seems like the right move to me.

With this data I'll be able to improve the problem selection algorithm. I'll be able to preference problems with a high incorrect response count to correct response count ratio along with questions that tend to take longer to answer.

I also want to make the leveling more interesting. Right now if you get 10 questions right in a row you level up. If you get one question wrong, you start the level over. Like any good RPG leveling should happen at the right speed and levels should get longer over time.

I'm also thinking about certain multipliers that increase the user's score and help them level up more quickly. For example, answering a question quickly should be worth more than a slow answer. Answering a harder problem should be worth more than an easier problem. On top of that, communicating these multipliers to my target audience - two six-year-olds is going to be another challenge.

Of course I've thought about how to monetize this game. I'm wondering if converting it to React Native and distributing it via Android and Apple app stores is the right move. It would certainly be a good learning opportunity!

Why not use Ruby on Rails?

A number of the subscribers to this publication know that Ruby on Rails is typically my technology of choice. The reason I decided not to use Rails, which would have been a perfectly reasonable choice for this game, is just that I want to push myself to learn technologies I'm not that familiar with. It's almost like I'm putting the idea to the test that I'm a real software developer and should just be able to pick up new technologies to the test. Also Rails is dead 😂.