Math Tournament


Every year, from 1974, the School of Computing and Mathematics at Georgia Southwestern State University (GSW) has hosted a tournament for high school students from throughout the State of Georgia. This tournament has brought hundreds of students to the campus in the early spring each year to compete with one another. The need to host hundreds of students at the same time while giving them opportunity to individually work on the same set of problems, and mainly important, to process their answers in quickest possible manner enforced us to run the tournament with the help of Scantron technology.

Before I joined the project the following workflow was used:

Preliminary Development Phases

Year 1

I joined the project in 2015 and reworked the registration form to support front-end validation. It was the time when I just started learning web technology so I implemented the task using jQuery UI. I ended up with constructing a landing page with modal dialog that was collecting form data and pushing it to PHP backend for processing. Our processing was as simple as it can be: (i) calculate the total cost; (ii) send out two customized e-mails: one to team sponsor and one to organizers.

Year 2

I was told that the old Windows 98 PC that we use to control Scantron machine is about to die. I proposed replacing it by a RasPi board that would mimic proprietary Scantron protocol. Since Scantron and PC were connected by means of RS-232 interface I decided to try to sniff on their exchange. We’ve purchased a SerialGhost Pro dongle and grabbed a few sessions where PC was controlling Scantron to handle a number of typical scanning scenarios including

The exchange protocol happend to be entirely text based, so I identified the bootstraping protocol, negotiation of form parameters, and the main part allowing to scan one or more sheets and recover in case of problems. At that time I started learning Node.js and decided to try SerialPort package to control serial port exchange of the RasPi board. I ended up with a JavaScript class implementing basic logic of the underlying finite state machine. That class was then integrated into a Node.js console application.

The implementation worked as expected and my next move was to build a web interface running off of RasPi, so we could control the Scantron in a bit more user friendlier manner. Since Node.js was already under the hood of the project, I added Express to serve static HTML5 bootstrap flavored frontend and used to support asynchronous communication between the frontend (web UI) and the backend (Scantron controller class). After all programming improvements I packaged RasPi board into a touchscreen enabled case and that was a complete product… I thought it was.

The last think to do was to replace the C++ program that was previously used to process scan data by a web facing solution, so I ended up with jQuery + Bootstrap HTML5 document, capable of merging and processing raw registration data along with scan data to generate persistent reports.

Both RasPi scanner and report generator were tested during 2017 Math Tournament event and worked pretty well.

The HMT Project

After working with different parts of the Math Tournament registration portal it was a pretty obvious move to integrate them under umbrella of a single web application capable of addressing the following tasks


I started the HMT project in the beginning of Fall 2017, and at that time, I already had some experience with full stack development using Node.js + MongoDB on the backend, and jQuery + Bootstrap on the frontend. I decided to make one step further and started learning Vue.js frontend framework in parallel. The following list shows technological skeleton of the HMT Project:

URLs and deployment

The live version of the HMT Project website is avaiulable at The registration is currently closed but tournament results can be observed at

The GitHub project page is available at

Deployment of the application should not face any bumps, but there are a number of issues that have to be carefully addressed:

Normal user workflow

A normal user (team sponsor) visits the landing page of the portal, i.e. and, if the tournament date is set at least 1 days ahead, can proceed with creating a new or revising the previously made registration. Team sponsor is identified by e-mail address that has to be entered into the corresponding input field. Entering of an unrecognized e-mail address leads to displaying of an empty registration form. But if the entered e-mail address is found matching existing registration, the form would be populated with previously entered registration data.

Form validation routines require clearing up all errors in order for the submission button to become enabled. The Invisible reCAPTCHA guards the form from spammers and other automated fillings/submissions.

Once the form is completed and submitted, the registration is treated as unconfirmed or pending until after the sponsor would follow confirmation link from the automated e-mail. Should this happen within the grace period, the same registration info will become available for the last moment editing and final submission.

Important note: The registration process allows for unrestricted submission of registration info. All unconfirmed (pending) registrations expire in 1 day. The registration process is password-free.

Admin user workflow

Admin role

The admin role is hardcoded to map selected e-mail addresses and is defined in ./backend/lib/admins.js file. The admin role is associated with tournament organizer(s).

Access of the admin interface

The admin interface will be exposed to users listed as admins (see above) and accessing the registration portal at /start URL. Such users will be prompted to follow /admin link to access the actual admin interface. Alternatively, they can play the role of a normal user and continue with normal user workflow as discussed above.

Authentication mechanism

Access to the admin interface is restricted to authenticated users only. The admin membership defined in the ./backend/lib/admins.js file sets a necessary but not a sufficient condition. The admin page available at /admin exposes two variants of the content matching authorized, and non-authorized sessions. The non-authorized variant is present by default until after valid admin credentials, i.e. e-mail and password are entered. There is no Submit button that can be clicked to initiate credential validation, instead, a mechanism relying on lodash.debounce() with a delay of 500 ms is used to counteract possible bruteforce attack.

Having specified valid admin credentials a user exposed to the authorized version of the admin interface that is discussed in details in the next section. The prompt for entering email/password is no longer shown, and is replaced by the real content of the page.

This application does not rely on HTTP sessions for authentication purposes. It is done on purpose, to explore the paradigm of the stateless backend — a better fit for scalable implementations. Every request sent from the admin interface to the admin API includes previously entered credentials that are validated prior performing requested admin task. This approach mimics JWT way of sending authentication data along with every request, but does not facilitate digital signatures for data integrity validation.

It is worth stating that since credential data are passed to the backend with no encryption, the app has to be served using HTTPs protocol configured at the Nginx level.

Admin interface features

The admin interface features 5 tabs, each responsible for a particular set of tasks:

Responsive design

The portal is designed to respect various devices ranging from a tiny screen of iPhone 5 to extra large screen of a modern desktop computer. All heavy lifting is implemented by utilizing various layout classes of Bootstrap 4. Here are a few examples of how admin UI is modeled to look on iPhone 7:

Known issues

And last but not least, this project is known to have a number of issues summarized below:

piTron — Scantron driven by RasPi

The HMT Project wouldn’t be complete without ability to automate scanning process. While working on HMT portal I hoped to re-use the previously developed Scantron controller (see above) after minor adjustment… but that was not going to happen. I couldn’t accept my own code styling, classes organization, pretty much everything except the core implementation of the controller logic. Moreover, the serialport project has evolved from v4 to v6 and many things that my implementation relied upon became non-functional.

I decided to rework the entire piTron project to have

The renewed piTron also featured Scantron Simulator — yet another SerialPort based JS class mimicing lifecycles of a real Scantron. Having both piTron and Scantron Simulator connected on the hardware level (GND-GND, Tx-Rx, Rx-Tx) I managed to debug piTron logic and its UI without running actual scans.

The piTron project has its own GitHub repo available at

The following video demonstrates piTron + HMT bundle in action.