Guess the next price of BTC in USD... with a twist – it is a multiplayer game!
The price of the BTC in USD refreshes every 60 seconds or so.
-
pnpm version
8.7.6or higher -
node version
18.18.0or higher -
(Optional) volta version
1.1.1or higher
Volta is a great tool to manage different versions of package managers and Node. I use it as an alternative for nvm which I found unreliable in the past. To use Volta with pnpm, please see these docs.
- An AWS account and the ability to get the role/user credentials.
- Ensure that your shell has the necessary AWS-related environment variables set. These are
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY_IDandAWS_SESSION_TOKEN. To check which variables are set, please type the following:
printenv | grep "AWS_"For local development & deployment, I was using a role with AdministratorAccess policy.
- Install dependencies
pnpm i- Bootstrap the backend
pnpm run bootstrapNote: The deploy and deploy:without-hosting command use a non-default qualifier. This is done to prevent any collisions with existing resources. This makes the bootstrap a required step, even if there already exists a "default" CDK boostrap in a given AWS account.
- Deploy the backend
pnpm run deployNote: If the role/user used to deploy the backend cannot create publicly-accessible buckets, the deployment might fail. To deploy the backend without a "website hosting" bucket, type the following:
pnpm run deploy:without-hosting- (Optional) Upload the frontend artifacts to the "website hosting" bucket. The game is a multiplayer game, so it might be worth trying it out with others!
pnpm run deploy:websiteThe output of this command will contain the link to the website.
Note: This step will fail if you deployed the backend without hosting.
Note: It can take up to a minute for the game state to populate right after deployment.
-
Deploy the backend, either with or without the hosting
-
Run the application locally
pnpm run dev- Access the application at http://localhost:5173/
Note: It can take up to a minute for the game state to populate right after deployment.
-
Deploy the backend, either with or without the hosting
-
Run the tests
pnpm run test- Run the e2e tests
pnpm run test:e2eThe backend architecture could be split in several components.
This component is responsible for marking the user as "CONNECTED" or "DISCONNECTED". "DISCONNECTED" users are not shown in the player list.
The client subscribes to the IoT Core MQTT topic called game. From there, AWS Lambda Functions listen to the IoT Core lifecycle events and update the user state accordingly.
IoT Core requires signed requests. Cognito was added to handle all things related to user credentials and authorization.
To update the value of the BTC in USD every 60 seconds or so, I'm utilizing an EventBridge rule that runs on a schedule. This rule invokes an AWS Lambda which fetches the latest BTC price and populates the application state.
When the "game ticker" runs, it creates a "game result" item in DynamoDB. The role of the score distributor component is to listen to "game result" item creations and update the user scores accordingly. The scores for each user are persisted in the DynamoDB.
The role of the notifier component is to stream domain events (UserPresenceEvent, GameEvent and PredictionEvent) to the frontend application. The frontend application has a live connection with the IoT Core MQTT topic.
This component utilizes EventBridge Pipes. The Normalizer (transformation/enrichment step) transforms the stream data from DynamoDB into domain events. The Notifier pushes the domain events into the MQTT topic.
This component exposes a REST interface to the frontend application.
The GET /game returns the information about the game, as well as the connected players and the predictions they have made regarding the next BTC price.
Here is how the response shape looks like:
{
game: {
id: string;
value: number;
room: string;
createdAtMs: number;
},
users: {
id: string;
status: "CONNECTED" | "DISCONNECTED";
name: string;
score: number;
prediction: "UP" | "DOWN" | null;
}[]
}The POST /{gameId}/predict is responsible for saving the users predictions regarding the next BTC price.
Here is how the payload looks like:
{
prediction: "UP" | "DOWN",
userId: string
}And here is how the response looks like:
{}-
Surface the concept of "game rooms" to the client. The concept of a "game room" is only used on the backend for the purpose of making the testing easier. It enables the creation of isolated data entities which do not influence the running application.
-
Introduce a new index to expose leaderboards. The DynamoDB already contains all the users and their scores.





