In September, we held Michigan Software Labs’ first Hackathon! We invited college students and people new to their software development career to participate and work together in teams on a problem. We planned this Hackathon throughout the year, and the event was a blast to execute. This article is intended to fulfill our commitment to the participants and the developer community by sharing our process for planning the event under various constraints — and a portion of the source code.
Planning process #
We started work at the beginning of the year with a single goal: running a Hackathon. We invited any MichiganLabs team members interested in volunteering their time to join our group and take part in this effort. We then began with a clear purpose: A fun, approachable problem that a team can solve in one day.
We knew that the event had to fit into a single day, and it had to be interesting enough to work on. So we started brainstorming ideas and problem sets. We looked at other hackathons that have been offered to college students, exploring what made them successful.
We landed on a rat maze problem, where participants would control their own “rat” and navigate it through various mazes blind. We had a couple, simple technical goals in mind to keep the problem accessible:
- The participant’s code needs to be platform agnostic
- The participant must interact with the maze through REST APIs
- Clear, objective scoring with minimal chance for ties
But the rat maze problem itself isn’t unique, so we needed to add a spin on it — which was “optional” goals for participants. We landed on backtracking.
By adding cheese throughout each maze, the rats had the ability to “smell” where nearby cheese might be. Eating the cheese increased the score a little, but bringing the cheese to the end of the maze one-by-one was worth a lot more. This made the participants’ objectives the following:
- Finishing the maze is worth the most points
- Ferrying cheese to the end of the maze is worth a good amount of points
- Eating cheese along the way is worth a small amount of points
When considering these objectives, we wanted to rule out a couple easy approaches. One being brute-force, where it would be possible to find all cheese and the end of the maze given enough time. So we implemented score penalties:
- Moving the rat reduced the teams’ score by a little, incentivizing smarter backtracking and navigation
- “Sniffing” for cheese reduced the score by a little, incentivizing more intelligent routing to locate cheese
- If the rat never reached the end of a maze, they wouldn’t get any points, incentivizing finding the end as the primary goal for everyone, and making cheese more of a side objective
- There was a maximum amount of API calls a team would make per maze, adding resource scarcity
At first, everything seemed fine, but something was missing: the fun factor we wanted to emphasize. Visualizing the maze one move at a time made it hard to get a complete picture, and troubleshooting how well the algorithm was performing could be challenging, especially for visual thinkers.
We added a couple new features:
- We would provide a visual troubleshooting tool so that participants could run their rats through sample mazes while they develop their algorithm
- We would provide a swagger page for our API to help people who were new to REST calls
- We would simulate all of the teams running through each maze in the grand finale, using Unity3D
To bring our Hackathon to life, we needed to create several components:
- A backend to serve as the main API infrastructure
- A frontend troubleshooting tool to visually test mazes (competition mazes remained hidden)
- The mazes themselves
- A Unity3D simulator
- Load testing tools
- Beta testing processes
- A hackathon schedule, including the agenda, catering, prizes, and swag
While this list might have seemed daunting at first, careful planning and delegation made it manageable.
We tackled it in our spare time, using Jira to track the overall effort. Treating this like a billable project, we tracked hours, allocated time thoughtfully, and maintained diligence with our estimates to stay on schedule.
Below is a screenshot of our Jira board mid-project, showing the energy and activity invested across multiple areas:
The Backend #
As with all aspects of the project, we aimed to make this a valuable learning experience for our team.
For the backend, we chose ExpressJS, which gave us an opportunity to deepen our knowledge of the platform. We discovered innovative ways to set up authentication and middleware, implemented rate limiting, conducted load testing, and effectively managed API keys. One feature that stood out was the automatic Swagger documentation the team successfully implemented. This was an impressive addition, making it easy for anyone to explore and understand the capabilities of the new API.
The Frontend #
Our frontend testing website was built using NextJS and React. The goal was to create it as efficiently as possible while providing maximum value for participants. The website allowed users to enter their API key and view the test mazes, track their moves, and reset their rat as needed.
Participants had unlimited access to test mazes throughout the day, but the competition mazes were locked until later and couldn’t be viewed through the website tool. The website also doubled as an admin dashboard for us, so we could easily keep track of participants and their progress throughout the day.
Mazes #
Because we didn’t have unlimited time to work on this project, we wanted maze generation to be easy, visual, and immediately usable. We chose Google Sheets and used a clever Apps Script, where we could enter a piece of cheese, and it would automatically place “smell” values in a radius around the cheese. This let us review maze designs collaboratively, without having to develop a tool to do it.
The source code of our action script:
function onEdit(e) {
var range = e.range;
var sheet = range.getSheet();
var startValue = range.getValue();
console.log(`Start Value: "${startValue}"`)
console.log(`Old Value ${e.oldValue}`)
if (startValue != "C" && e.oldValue != "C") {
return;
}
var writeValues = startValue == "C";
var startRow = range.getRow();
var startCol = range.getColumn();
var maxDistance = 5;
var queue = [[startRow, startCol, 0]];
var visited = new Set();
visited.add(`${startRow},${startCol}`);
console.log("Starting loop.");
while (queue.length > 0) {
var [row, col, distance] = queue.shift();
console.log("Acting on queue...")
if (distance <= maxDistance) {
console.log("Distance is in range...")
if (distance > 0){
if (writeValues) {
// Write to sheet
sheet.getRange(row, col).setValue(distance);
} else if (sheet.getRange(row, col).getValue() == distance) {
sheet.getRange(row, col).setValue("");
}
}
var neighbors = [
[row - 1, col], // up
[row + 1, col], // down
[row, col - 1], // left
[row, col + 1], // right
];
for (var i = 0; i < neighbors.length; i++) {
var [newRow, newCol] = neighbors[i];
console.log(`Iterating over the neighbor ${newRow}, ${newCol}`);
if (newRow > 0 && newCol > 0 && newRow <= sheet.getMaxRows() && newCol <= sheet.getMaxColumns() && !visited.has(`${newRow},${newCol}`)) {
var neighborValue = sheet.getRange(newRow, newCol).getValue();
if(!isNaN(neighborValue) || neighborValue === "") {
console.log(`Adding ${newRow}, $`)
queue.push([newRow, newCol, distance + 1]);
visited.add(`${newRow},${newCol}`);
}
}
}
}
}
}
Whenever we made an update to the spreadsheet, we could run a command on the backend to pull the cell data and regenerate the table in our PostgreSQL database.
Unity3D simulation #
We had a couple team members with experience in Unity, and we wanted to use their skills to make something fun for the grand finale. We chose to keep track of every single move a rat makes. Then the simulation would “play” the moves back, so we could watch all the teams together on the big screen.
This was a great learning experience for our team members who were new to Unity, as they learned a lot about Canvas UI, animations, LOD, and general game design principles. The simulation would stagger the participants, since each team was isolated to their own maze and there was no resource scarcity, and reveal their score at the end. We could control the simulation speed and select mazes and start/pause if we needed. This was easily one of the highlights of the day, where teams were able to see their rat’s performance live!
A closeup of some of the animations:
One thing to note is that while we’re sharing the source code of this Unity project, there are some third party assets we can’t share due to licensing. But the basics are there.
Load testing #
We used Artillery to load test our backend, to make sure that it could handle rate limiting and a large number of participants. Here’s an example artillery script that would run through a couple movements and reset the rat repeatedly:
config:
target: "http://localhost:8080/v1"
phases:
- duration: 1
arrivalRate: 1
defaults:
headers:
Content-Type: "application/json"
X-API-KEY: "<API-KEY>"
scenarios:
- name: "Rat reset"
flow:
- post:
url: "/rat/reset"
json:
mazeId: "oneTurn"
- post:
url: "/rat/smell"
json:
mazeId: "oneTurn"
- post:
url: "/rat/move"
json:
mazeId: "oneTurn"
direction: "NORTH"
- post:
url: "/rat/move"
json:
mazeId: "oneTurn"
direction: "NORTH"
- post:
url: "/rat/eat"
json:
mazeId: "oneTurn"
Beta testing #
It’s easy to get biased results when the same team that creates the project is also solving it. To address this, we involved people across our company with varying levels of software development experience. A couple of our developers tackled the problem, and we paired them with non-technical employees to work through it together. This approach solved two key challenges:
- We needed more entry-level attempts at solving the problem.
- We’re always looking for ways to help non-technical team members at MichiganLabs gain deeper insight into the software development process.
The beta testing proved to be highly successful. Team members from delivery leads, business operations, and even our executive assistant participated in the challenge — helping us identify issues with documentation and other potential problems. This process provided valuable user feedback and ensured that the event was well-validated by a diverse range of expertise before going live.
The agenda #
We carefully planned the entire day, setting hard stops and clear timings for each phase to keep everything on track. To make the event even more memorable, one of our designers created a custom t‑shirt logo for the event, which turned out great and added a nice touch to the swag.
We also gave out trophies to our winners, which were 3D printed. The winning team — Adam Byle and Nick Roberts from Calvin University — each received $200, and the second place team got $50 each.
Adam and Nick from the winning team also open-sourced their solution to our problem, which you can check out here
Participants spent the entire day with us, collaborating with our team members to work through the problem set. A big thank you to all the participants for dedicating your time, and to the MichiganLabs team for volunteering your expertise to make this event a success!
We learned so much about running community events and thoroughly enjoyed the process of designing and hosting the challenge. As promised, here is the open-sourced code:
Thank you also to Madison Coleman, our student photographer!
Looking for more like this?
Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.
When to “shape up” versus be more “agile”
May 30, 2024Should you develop your new software using the Agile or Shape Up method? Here’s how to decide.
Read moreInnovation in healthcare
December 16, 2024Healthcare innovation is transforming patient care and productivity. From voice-to-text tools that save doctors' time to groundbreaking gene therapy restoring hearing, these advancements enhance efficiency while focusing on life-changing outcomes.
Read more