In today’s post, I step through how I developed a statistics dashboard for an online maths game Arithmetic Zetamac. Zetamac is a minimalist online speed maths game which, although serves its purpose very well, does not have a native statistics dashboard or way to store your previous scores. As someone interested in tracking progress over time, I would track my scores on a google doc- a very labour intensive task. Today we will see how to automate local data storage using Selenium and produce a dashboard showing statistics using PySimpleGui.
The Zetamac Class
Let us commence our development process by defining a class named Zetamac which will encapsulate all of our program’s logic. We need to make sure that we parse the url of the game-type we want, and the length of that game into the constructer when initialising the class.
It is also important to check if the necessary infrastructure exists before diving into our code. Let’s make sure we check that our data storage csv exists in the program directory. Let’s also define our web driver- I used Chrome for no good reason.
Now that we have the base of our class established, let’s start building in some of the features. For the purposes of this blog post, I am going to omit some of the less interesting methods like init_browser() and init_game(), but rather discuss which methods they point to. The complete code will be listed at the bottom of this post for those that are interested.
Data Scraping and End of Game GUI
The init_game() method is called at the beginning of every game. This method simply executes time.sleep() for the duration of the game, and then calls the store_data() method followed by self.end_of_game(). Let’s take a look these methods.
I am confident there is a more elegant way to deal with waiting the duration of the game, by checking for final establishment of the score for example, but I opted for a very quick and dirty method- terrible programming practice. This method uses Selenium to scrape the score from the website via its xpath. It is typical that, depending on site loading times, the script is executed early and an IndexError is thrown. This is handled with an exception allowing more time. After we get the score, it is timestamped and stored in the local csv.
Let’s take a look at how the end_of_game popup is implemented in PySimpleGui:
The physical appearance of the GUI is organised within the layout array in rows. We can define columns too, which we will see how to do a little later in the stats dashboard implementation.
A lot of button clicking between games can get a little tedious too so I implemented an auto-restart feature 5 seconds after a game is completed. Countdown is a separate class defined as follows:
In our case, we are using a 5 second countdown which is called within our event loop:
The window.update() call is what allows us to change the countdown on the screen for the user to see. This is what the end of game GUI with the specified theme looks like!
Statistics Dashboard
The main event of the program is the statistics dashboard. The calculation of these statistics is relatively boring, so we will instead focus on the implementation of the GUI. It is worth noting that the stats_calculation method outputs a tuple containing all of the statistics calculated from the data csv.
One of the stand-out features of the dashboard is the plot. This is made in matplotlib with scores from the user’s previous ten games. The line is smoothed using scipy’s interpolate.make_interp_spline. Disclaimer: I am aware that this sometimes results in scores that are negative or inflated in an effort to fit a nice curve. This is a purely aesthetic choice and there is nothing mathematical about it! :)
The stats dashboard is implemented as follows:
You can also see my incredibly messy column implementation here (please reach out if you have any suggestions for tidying up the code!). I have also implemented two buttons at the bottom which allow the user to exit or restart the game. The dashboard ended up looking like this:
Here you can notice the wiggly effect of the fitted line.
Concluding Thoughts and Full Code
Overall, in terms of maintainability, the code base is pretty poor. There are a lot of static parts that can break the code if the website is ever updated, and more generally a lot of places where things can go wrong. However, for my use case I think that is fine- this was a quick and dirty script to solve a simple problem after all.
If you are interested in checking out the full code, I have a public repo linked here. I would love your feedback and ideas for improvements too. Contact details in the side bar!