v2.3.3
Prediction League is a game that started socially between friends.
Everyone in the group puts some coins into the kitty and writes down what they think the final Premier League table will look like, before a ball has even been kicked.
Whoever is closest at the end of the season wins the kitty. And sometimes gets to pick the following week’s teams for 6-a-side...
This project is a digital representation of the game with some additional "gamification" to retain engagement throughout the season (players can swap the position of two teams in their table each Match Week).
This repo implements a frontend and backend for handling new entries, payment workflows, management of predictions and scoring of points (which are now accrued on a cumulative basis throughout the season instead of just at the end).
The project is written predominantly in Go, using templates to generate dynamic HTML.
Vuejs and Bootstrap are used on the frontend.
Sass is used for pre-processing CSS and npm + Webpack are used for building assets.
It's hosted at play.taybl.app.
To read about the passive-aggressive joys of trying to scaffold this project in the first place, see docs/local-environment-setup.md
The following environment variables must be set in order to configure the usage of third-party dependencies.
You can do this by creating a new .env
file in the project root, or overriding the contents of infra/app.env
and infra/app.docker.env
as required.
Leaving the following values blank will result in the default behaviour as described.
-
PAYPAL_CLIENT_ID
- Client ID as required by PayPal's Basic Checkout Integration workflow.
- If left blank, provides a "skip payment" step during sign-up (for debugging purposes only).
-
MAILGUN_API_KEY
- API Key required by Mailgun integration for transactional emails.
- If left blank, dumps content of email to the terminal without sending.
-
FOOTBALLDATA_API_TOKEN
- API Key required by football-data.org integration for consumption of real-world football league table data.
- If left blank, no latest league table standings are retrieved and processed by the cron job.
Look out for CPU with this option. Where the asset builds are watching for changes across a network, it can become quite resource-hungry...
make app.docker.up
make test.docker.up
Requires more dependencies installed on the host machine, but results in generally quieter fans...
Make sure you're using npm version 13.10
# via nvm
nvm install 13.10
nvm use 13.10
Then start your engines:
make app.install
make app.run
make test.run
N.B. Take a look at the other make
commands in the project root Makefile
which help to automate
some of the stop/restart/kill workflows too.
To seed a demo version of the game, carry out the following:
cp service/cmd/demoseeder/.env.example service/cmd/demoseeder/.env
go run service/cmd/demoseeder/main.go
To cleanup the changes made by the demo seeder, execute the file service/cmd/demoseeder/cleanup.sql
against the database.
Players can sign-up to create an Entry and make their first Prediction before the Season begins.
They can make subsequent changes to their Prediction throughout the Season - swapping the positions of a maximum two Teams per Match Week.
Timeframes are configured independently for each Season.
For example, no more Entries can be made once the Season's EntriesAccepted
timeframe has elapsed.
Additional settings can also be configured for each Realm (an instance of the game which runs on a particular URL/sub-domain).
Each Entry requires a payment in order to be accepted into the game.
This is to fund the kitty that the winner receives at the end of the game.
For convenience and user peace-of-mind, payment is made via PayPal using their Basic Checkout Integration.
Given that the payment flow exists entirely on the Frontend, each Entry must be "approved" by an Admin in order that payment can be verified manually.
This is a single API endpoint that is protected by Basic Auth. These Basic Auth credentials can be configured via the
.env
variable named ADMIN_BASIC_AUTH
.
Payment can be skipped when running locally for debugging purposes, by leaving the .env
variable named PAYPAL_CLIENT_ID
with an empty value.
Each Season is broken down into a number of "Match Weeks", which work the same as in Fantasy Football.
i.e. For a Premier League season with 20 teams, there are 38 Match Weeks. For a Championship season with 24 teams, there are 46 Match Weeks.
Once a new Match Week starts, the previous Match Week’s score is frozen and the next Match Week begins with a fresh score.
For every Match Week, each Prediction receives a score, which produces a Scored Prediction for that Match Week.
Each player begins with 100 points every Match Week, and accrues 1 "hit point" for each position that a Team has been placed incorrectly within their Prediction.
For example, if Team A are in 3rd place but are predicted to be in 1st, 2
hit points will be accrued.
Likewise, placing Team B at the bottom (in 20th), who sit in 15th in the real table, will accrue 5
hit points.
Placing a team in the exact position they are in the real-world table will receive 0
hit points.
The total number of hit points is added up and then deducted from the player's initial 100 points, to give them their overall score for that Match Week.
The total cumulative score for all players' Scored Predictions across all Match Weeks forms the foundation of the Leaderboard. The Leaderboard is topped by the player with the highest cumulative score across all Match Weeks.
In the event of a tie on the Leaderboard, the tied players will be ordered by the maximum score that each one has gained throughout all Match Weeks so far, highest first. You can think of this as the equivalent of “goal difference” when ranking teams who are on equal points in a real-world league table.
The real-world league table data used to calculate each score is retrieved from football-data.org.
This data source is polled every 15 minutes as a cron task.
At this point, the most recently made Prediction for each Entry is used to calculate the Scored Prediction for the current Match Week. It is also used to determine if a new Match Week has begun, or if the final Match Week has ended (at which point the Season is considered to be complete and is therefore finalised).
The frontend accommodates an FAQ page which can be customised on a per-Realm basis.
For more details around core business logic and entities, see Domain Knowledge
For more details around the project's intended Roadmap, see New Features and Improvements
If you'd like to help out with this project in any way, please feel free to fork it and submit your PRs! 😁