I've introduced the game idea and its rules in the previous part of this series. In this article., I'll be going over the architecture of the project.
This article is a work in progress and I'll frequently update it.
Game Requirements
The first step to building any real world application is requirements analysis and gathering.
Goal Building an online word guessing game with an early 2000's/grade school aesthetic.
Real time
The game is going to be online
meaning we'd need to allow users to play in real time.
The game allows players to create or join public/private rooms and play with others in real-time. Such functionality is achieved via web sockets.
I'll be using GraphQL subscriptions to achieve this functionality since we're using Hasura.
I've enabled authentication via Facebook and Google to make it easier to invite friends for new games.
Retro aesthetic
I needed to use a UI library to simplify my development process because I wanted the game to have the following:
- Consistent theme
- Layout components
- Customisable components
I found Chakra UI to be a good library because it gave me tons of flexibility by allowing me to use my own custom styles in most situations. I've only needed to use Chakra components for accessibility and well designed APIs.
Development experience
I chose Next.js as my framework due to it's performance optimisation and easy to follow structure.
Next.js is an opinionated framework for building server rendered applications.
State management
I want the state of my game to be predictable and I also wanted to limit the number of states that I'd have to deal with.
For this I'm going to be using xState which is a state management library that uses state machines
.
If you're interested in learning more about state machines checkout this awesome article from Kent C. Dodds.
Directory Structure
I've used Next.js which is an opinionated JavaScript framework but I had an option to create my directorates.
📦src
┣ 📂Game
┣ 📂Rooms
┣ 📂User
┣ 📂components
┣ 📂hooks
┣ 📂layout
┣ 📂lib
┃ ┣ 📂graphql
┣ 📂machines
┃ ┣ 📂game
┃ ┗ 📂player
┣ 📂providers
┣ 📂utils
Root
The root consists of configuration files and project directories.
Config files
At the very root of my project, I have some config files which I had use to initialize the project.
-
nvm.rc
to dynamically switch to the desired Node version for this project. -
tsconfig.json
for my TypeScript rules -
codegen
to generate types for my GraphQL schema -
eslint.rc
to enforce proper code styles and rule` -
.husky
I use it forpre-commit
hooks to ensure that my code adheres to quality standards before pushing it. It mainlylints
andformats
my code before every commit.
Directories
I have structured my directories based on the domain.
Components
These are common UI components that are shared between different parts of the application. They include Button
, Inputs
timers etc.
Game
Contains game specific components.
Rooms
Contains room specific components.
Hooks
I have created hooks to help aid abstract my GraphQL operations. This is general structure for many of my network related hooks.
Here's how the raw gql
mutation looks like:
export const INSERT_ROOM_MEMBER = gql
mutation insertRoomMember($member: rooms_members_insert_input!) {
insert_rooms_members_one(object: $member) {
id
role
roomId
member {
id
lastSeen
createdAt
email
}
}
}
`;
`
This is how it looks like after wrapping the mutation in a hook.
`
export const useInsertRoomMember = () => {
const [insertRoomMember, { data, loading, error }] = useMutation<
InsertRoomMemberMutation,
InsertRoomMemberMutationVariables
(INSERT_ROOM_MEMBER);
return useMemo(
() => ({
memberLoading: loading,
memberError: error,
member: data?.insert_rooms_members_one,
insertRoomMember: (member: OperationVariables) => {
return insertRoomMember({ variables: { member } }).then(
({ data }) => data?.insert_rooms_members_one
);
},
}),
[loading, error, data?.insert_rooms_members_one, insertRoomMember]
);
};
`
Machines
I use this directory to define state machines.
I have created two machines to represent/manage the state of my game.
-
player
controls player specific actions -
game
controls the flow of the game
Providers
This is for global providers which I use to wrap my entire project. I mainly use two providers.
-
Theme
to control theme of the entire game -
Game
to control the data of the game
Utils
A set of utility functions to help with the control of the game.
Lib
For any 3rd party libraries that I intend to use in my game.
Here is where I initialise my Apollo Client and point it to my running Hasura instance.
I've created a few tables and enabled read/write permissions for operations necessary to play the game.
Codegen
I have also setup codegen
which is a very useful tool to generate types for your schema.
I have a codegen
script in my package.json
file which uses apollo-codegen to generates relevant types to be mapped to our GraphQL queries and mutations. These generated types are what we then use in our query and mutation hooks to state the types.
To specify to the codegen what we would like to generate, a file codegen.js is passed to it.
Game Logic
- Players are required to guess 4 words within 40 seconds.
- Active players are shown along with their results for each round.
- The last player to submit is eliminated while the others proceed to the next round.
- Each round generates a new letter and thus a new set of words to be guessed.
Top comments (0)