What is more or less?
“More or Less” is a guessing game where you guess which item has a higher value for a specific attribute. For example, in the “Commit Clash: Which GitHub repo has more commits?” mode, you see one GitHub repository’s commit count and another repository. You have to decide if the repository has more or less commits. If you guess correctly, you see the next pair. If you’re wrong, you lose. Players who score high earn trophies. If they guess every pair correctly, they win a special trophy.
Different Game Types
Players can challenge themselves with modes like determining which country is bigger, who might have more money, or which website gets more visits. Plus, there are even more modes to explore and enjoy.
Players have a profile where they can view their high scores per game and various statistics, such as total playtime, overall high score, trophies earned, and average score.
What did we did this semester?
At the beginning of this semester “More or Less” was using a Jamstack approach with React and a static JSON backend. This is cost-effective and easy to develop. High scores were stored in the browser so there was no need for server-side profiles. We kept our game data in file-based databases on a static server and managed this data on our own.
This semester, we updated our game to allow user-created content and server-side profiles adding a dynamic backend part to our application.
Features added this semester
User-Created Game Modes
The main update was letting users create their own game modes. This introduced user-generated content to the platform. Users can now design new modes to share with everyone, or create personalized modes just for their friends or themselves.
Now, users can sign in using oAuth or email and password, instead of the old local storage method. This allows game modes to be shared with the community and highscore saved in your account instead of your browser storage. We also introduced email verification and password reset features.
With the move to server-side profiles, we updated the profile design to allow users to share their highscores and added more account settings options. Now, users can include social links and have the ability to change their email, modify their password, and delete their account if necessary.
Software is not an end in itself
By looking back at what projects we did the last semesters we found out that most of them couldn’t make it to the market.
We discovered that it’s mostly because they were not really designed to be for the market. Trying out new things like a new technique or a jump into a new design pattern was a lot of fun, and a great learning experience, but the primary goal of a software “Solving a functional problem” was never really successful.
In this semester we wanted to change that and focused on building an app which really covers the user’s needs. We started by focusing on the functional requirements instead of the technical ones.
To achieve this goal we started to define which user groups we have and what task they would like to achieve. Tasks are actions or goals these users want to do or reach in your app. Based on this definition we build our whole API and logic around these tasks.
Players are users who only want to play the game modes.
Contributors are users who want to create game modes.
- Wants to play game modes.
- Wants to find and get suggested game modes.
- Wants to share game mode high scores.
- Wants to comment on game modes.
- Wants to share game modes.
- Wants to report game modes.
- Wants to see profile and progress.
- Wants to rate game modes.
- Wants to favorite game modes.
- Wants to manage profile.
- Wants to create game modes.
- Wants to modify game modes.
- Wants to delete game modes.
- Wants to share game modes (private and public).
- Wants to see game mode statistics.
When working with a task-oriented design it can solve the domain problem quite well. However one problem we encourage is if you try to follow all tasks as best as possible, that your technical implementation gets very complicated.
Looking at our first definition of how the game editor service should save a game:
You will end up with a functional correct implementation but technical complex solution. That’s the reason, we opted for a more straightforward approach which is not only easier to implement and maintain but also more understandable for the end user, enhancing the user experience.
To focus on these functional requirements we were looking for an architecture and infrastructure which already includes many of the technical common challenges like Authentication, Controller logic, security, scalability and deployment so that we can focus on building our business problem. While evaluating using a traditional backend, or other cloud services we finally decided to go with Firebase. Firebase seems to offer a more streamlined and integrated approach, enabling rapid development, built-in scalability, and a suite of tools that fits perfectly to our project’s specific needs.
Our final architecture looks like this:
- Github: With Github Actions we generate our Static API, build our React frontend and deploy both using Cloudflare Pages. We also run our Cypress end-to-end tests, component tests and unit tests. Lastly we test and deploy our Firebase app. You can read more about Testing and CI/CD below.
- Static API: Our static API delivers our own created game modes. Read more about it here.
- React Frontend: Our whole game is created by using React with Typescript, TailwindCSS, Formik, Luxon, Firebase and TanStack.
- Firebase Firestore: The user contributes data like game modes, profiles and highscores are stored in Firebase Firestore. There we use the centralized security approach from Firestore to protect our data using the Rules Language.
- Firebase Cloud Storage: Is used for all the image assets users upload.
- Firebase Authentication: Is used for user authentication of accounts.
While developing our guessing game “More or Less”, we encountered a common challenge many developers face: determining the structure of our data model[…]
While developing our guessing game, “More or Less”, we found a method to significantly reduce traffic on our serverless API, leading to cost savings and an improved content creation experience[…]
Workflow: How to develop a serverless app local?
One of the biggest challenges when working with serverless apps is the local development process. Working with one staging online environment has several drawbacks.
Luckily and another reason why we picked Firebase is that Firebase created their own Local Emulator services which allows you to run a local instance of Firebase on your own machine.
Some advantages we discovered using the local emulator:
- Fast Changes: Immediate testing without deploying.
- Risk-Free Trials: No worry about impacting real users or data.
- Offline Work: Development without needing internet.
- Reduced Costs: No charges for local database operations.
Why Did We Not Use AWS or a Traditional Backend?
Having had experience with traditional backends in our previous projects, we were already familiar with both their strengths and drawbacks. Here’s what we noticed:
- Time-Consuming Setup: Establishing servers, databases, and other components often take much time, delaying the actual development of functional features.
- Maintenance Overhead: Being responsible for server maintenance, software updates, and security patches often requires a lot of work.
- Scalability Concerns: Building an architecture and infrastructure which is able to handle high traffic is difficult.
- Security Challenges: Ensuring end-to-end security is an additional time consuming and difficult task .
With this knowledge we were sure to use a serverless solution and took a look at AWS.
We found out that AWS is a robust and feature-rich platform, but working with it brought up several problems:
- Complexity: AWS’s huge range of services, while very powerful, introduces a steep learning curve. Setting up and configuring multiple services for our solution seemed time consuming and often unnecessarily complex, especially when the primary goal is rapid feature deployment.
- Cost Management: AWS’s granular pricing model is, when used right, probably great but the complexity in deciding which service to use to get a good price and the technical goal was difficult for us.
- Integration Efforts: To achieve what Firebase offers out of the box, such as authentication or real-time databases, AWS often requires integrating several services.
- Development Speed: The time taken to learn, set up, deploy, and get started with AWS seemed longer for us.
Given these insights, while both traditional backends and AWS have their good fits for projects, they seemed less fitting with our project’s goals this semester.
Continuous Integration and Continuous Deployment (CI/CD)
In our development process for “More or Less,” CI/CD plays an important role in ensuring code quality and seamless deployment. Here’s a brief overview:
- Running Tests: Our CI/CD pipeline is designed to run both our Firebase and frontend tests automatically.
- Deployment: Beyond testing, our CI/CD process handles the deployment of our entire application stack. Once the tests pass, the pipeline initiates the deployment sequence, deploying the latest software into our staging environment or production environment.
- Staging Environment: To catch potential issues or test the app before it reaches our users, we maintain a separate staging environment. This environment, achieved by setting up an additional Firebase project, mirrors our production setup. It offers us a sandbox to validate changes and ensure a good user experience before rolling out updates to our main user base.
We use Cypress and the Firebase Emulator for frontend testing.
- End-to-end tests: The main user stories, like creating an account, creating a game, playing the game and using the profile are tested end to end.
- Component tests: The most important components, like buttons, form inputs, modals and so on are tested isolated with the Cypress mount feature.
- Unit tests: Additionally, our service logic is covered by unit tests.
The most important security aspect is working with the data from our users. While Firebase offers a great centralized way to define security rules for our data, it is very important for us that they are probably tested.
We decided to use the @firebase/rules-unit-testing package provided by Firebase, in combination with Jest to unit test all possible database operations. We utilize seeders to set up initial data and cleanup functions to ensure tests are independent and don’t interfere with one another.
Our frontend tests are also using the security rules and therefore testing our security rules in an End-to-end environment.
Conclusion + Thank you
This semester, we focused on an upgrade for our “More or Less” game and chose Firebase for its benefits. Through exploring the basic serverless approach, transitioning to AWS, and ultimately settling on Firebase, we learned a lot from our choices. Thanks to everyone who helped.
Now put your skills to the test: Dive into our game and guess which repository has more commits.
Our highscore was 23, are you able to beat that? 😉
PS: The update allowing user contributions is still in development. We plan to try out this version with a select community and aim to launch it by the end of the year.