Architecture
This guide contains an overview of the architecture for the application.
Shared
Some pieces of the application are shared by both the frontend and the backend. This is especially important when the frontend and backend need to use the same types variables and objects.
Types
The shared/types.ts file contains the types for the data within the application. Actually, it only has enum
and interface
declarations, but these are used to ensure the data is in the proper form everywhere.
As an example, the game data stores the number of bits collected, the amount of currency earned, the user's email, and a list of owned upgrades. This is done with the IGameData
interface.
Utility Functions
The shared/util.ts file contains some helper functions that can be utilized throughout the application:
- The
pluck
function takes an array and a predicate function, and removes one value from the array that satisfies the predicate - The
runMod
function takes a variable value and modifies it based on the value and mod function - The
calculateVariableValue
function calculates the value of a game variable (like the likelihood of a bit appearing)
Upgrades
The IUpgrade
interface represents objects that are upgrades in the game (like the one that doubles the amount of bits that appear). Each upgrade has a name, description, cost, picture, and list of effects.
Each effect is an IUpgradeEffect
. These effects define a modification to one of the GameVariable
values. They point to one of those variables, have a VariableModFunction
to determine how the variable will be updated, and have a modValue
to represent the amount of change. Here's an example of an upgrade from the shared/upgrades.ts file:
{
name: "Double Checks",
description: "Check for bits twice as often",
picture: "🔍🔍",
cost: 10,
effects: [
{
variableAffected: GameVariable.BitCheckInterval,
variableMod: VariableModFunction.Multiply,
modValue: 0.5
}
]
}
It is named "Double Checks" and it costs 10 currency. When owned, it modifies the BitCheckInterval
game variable - basically, how often the game should check for bits to appear. It uses the Multiply
mod, and 0.5
as the modValue
to split the amount in half. Effectively, this takes the normal "once per second" check and makes it "once per half second" instead.
Frontend
The frontend of the application is built using React, React Redux, and Phaser.
Starting Point
The actual starting point is the frontend/index.html file - however, that file does not do much beyond including the frontend/src/main.tsx file:
<script type="module" src="/src/main.tsx"></script>
The main.tsx file is the main entry point into the actual React application. This file does not do anything too interesting - it does have a little router that is not currently used very much. The main thing it does is point the root ("/"
) to the App.tsx file. The important thing is the ReactDOM.createRoot
- that renders the React application into the HTML. The interesting thing there is the Provider
element:
<Provider store={store}>
That store
is the React Redux store - basically, the thing that holds all of the data for the frontend.
So, from there, there are two big things: the <App>
component (from the App.tsx file), and the Redux store
(from the store.ts file).
App
In the App.tsx file lives the <App>
component. This is really the main HTML container for everything else in the game. It contains the actual <PhaserGame>
component (the part that uses the Phaser library and has the bits appearing), as well as a <BoxContainer>
that includes the left <Box>
for auth and the right <Box>
for game data.
Store
The game data is kind of magically distributed throughout the app using Redux. There is quite a bit that goes into writing Redux code - it is recommended to read through some of the essential concepts and structure.
The store.ts file stores the game data. It is in the form of the IGameState
interface. This state is both loaded from and saved to localStorage
using the loadState
and saveState
functions. That is what allows the application to persist data over time. The store sets up a subscription - basically, every time the state is updated, it's saved to localStorage
using saveState
. This happens at most once per second because of the throttle
.
The configureStore
function sets up the store with the state (loaded from localStorage
) and all the reducers from the slices (more on that next).
Slices
In Redux, a slice is a collection of Redux reducer logic and actions for a single feature in the application. A reducer is basically a function that takes the current state and updates it based on an action that is occurring. There's quite a bit of slices that are doing things that are complex, but the important ones for basic gameplay are in the gameDataSlice.ts file and upgradesSlice.ts file within the slices/ folder.
These slices tell the game how to respond to certain actions. For example, the addBits
reducer in the gameDataSlice
takes the current state, and updates the number of bits in the game data based on the action payload. The addBits
reducer is dispatched by the application at various points - these all come back to the store through the reducer, and the state is updated there.
The upgradesSlice
contains the available upgrades for purchase. Currently, it simply holds the basic starter upgrades - but as the game progresses, there may be upgrades that are revealed later.
Ultimately, slices are the tools that allow the application to update the state of the application in a "global" way using Redux.
Components
In React, a component is an isolated piece of UI. Components are a lot like HTML Elements - in fact, they are represented in the same manner, using angle brackets like <ThisComponent></ThisComponent>
. There are some components in the frontend/src/components/ folder - all of which are meant to represent one small part of the larger game. The <App>
component pulls in several of these components to make them work together.
For example, the <AutoCollector>
component is a little piece of UI to represent Al the Auto-Collector. It dispatches the addBits
reducer to add bits based on the current interval, and it also displays the current rate of bit collection. A component is just a function that returns some JSX (basically HTML with JS or TS inside).
Several components use the useEffect
hook - this allows the application to sync a component with an external system.
Game
The actual game, with the Phaser code, is connected from the <PhaserGame>
component. The connection between React and Phaser is based on this template. Using both allows for much more powerful interactions - the gameplay can be fun and interactive, but React can keep track of the state and interact with the backend if needed. The biggest thing with the game is the EventBus
- this tool allows the Phaser code to interact with the rest of the application.
The actual Phaser stuff is configured in the frontend/src/game/main.ts file - that sets up the basic game, pointing to the scenes within the scenes/ folder.
Each scene in Phaser inherits from the Scene
class. Within those classes (e.g., Collect
), game components and interactions can be added. This is the main place to control the actual physical gameplay - Phaser can be used for graphical and physical gameplay, whereas React is more for state control and communication.
Backend
For now, don't worry too much about the backend. Ultimately, the backend will allow players to save their game data in the cloud, as well as present collectibles.
Actual Hacking
Some intrepid young game players may want to actually hack the game instead of the pseudo-hacking built into the game. Ideally, there would be layers of security in place to prevent them from doing so. However, if the game is to be accessible offline (e.g., without a server or database), verifying their actual progress could be incredibly difficult. Most of the gameplay almost inherently has to be hackable. However, the players should also have their own accounts and be able to represent themselves and their progress without fear of being hacked by other players. Thus, the overall philosophy regarding hacking is this:
There will be no protections against hacking the basic gameplay of the application, but user information and "Collectibles" will be secure in the backend.
The basic gameplay includes things like the number of bits, currency amount, upgrades, achievements, and more. It may be very easy for players to open up localStorage
in their web browser and change the numbers around - but really, it's all for fun anyway, so that's fine.
The user information, like username, password, profile, and collected items, can remain behind the security of authentication. This will hopefully create a community economy in which unhackable user data holds much more weight than amount of in-game data or in-game currency.
Essentially, a user can "hack" whatever garbage they want into their saved game data - it is not worth preventing this from happening. However, they will not be able to "hack" a new collectible item into their showcase, or "hack" another player's user information.
Overall, this should make it much easier to work with game data offline, while still encouraging players to create accounts, collect collectibles, and connect with other players.