This project is setup as a monorepo contains both a client (written in Next.js) and a server (written in PartyKit).
The client and server share types and the game logic is updated using a 'reducer' pattern (similar to React's useReducer and Redux).
First, run the client's development server:
npm run devThen (in a different terminal) start the server:
npm run dev:serverOpen http://localhost:3000 with your browser to see the result.
The code of this project is setup to get going quickly. There are two files that you should modify to create your own game:
/src/components/Game.tsxis the frontend of your game. It is responsible for visualizing aGameStateobject./game/logic.tscontains all the logic for the game, the most important parts are theGameStateinterface, theGameActiontype and thegameUpdaterfunction.
This interface represent all the information you need to keep track of for your game. By default it has two keys:
usersa list ofUserobjects (you can modify theUserinterface to save additional information about your users)logwhich is array of messages with a unix timestamp (dt).
You can add extra information to your GameState by modifying the interface. In the guessing game, we only need to keep track of one thing: the current target number (see /game/logic.ts line 21 to see that).
The GameState of your room will be provided to the Game.tsx component using the useGameRoom hook.
Think of a GameAction like something that can happen to your game. Stuff like 'rolling a dice' or 'making a bet' can be represented by GameAction.
Every GameAction is required to have a type: string field that uniquely defines that action. In our example we only have one GameAction with type: "guess". Imagine you wanted to add another action, representing 'placing a bet' we could modify this type like so:
// Here are all the actions we can dispatch for a user
type GameAction =
| { type: "guess"; guess: number }
| { type: "bet"; amount: number };Now there are two actions available to our game. We can place a bet, and make a guess.
You can add whatever information you want to an action (in this case we ask for the amount) as long as it has a unique type field.
This function is responsible for taking GameActions (received from the client, more on that later) and actually updating the GameState as needed.
It's a function that receives two arguments:
- a
actionof typeServerAction, this is on of yourGameActionsyou defined. - a
stateof typeGameState, representing the current state of the game
The function must return a new GameState.
This function uses a switch statement to match on the action.type field and returns an updates GameState.
On the client we only need this hook that provides us with two things:
gameState, the currentGameStatefor this roomdispatch, a function the client can use to sendGameActionsto the server
You can use the gameState to visualize your game.
The dispatch function is the only way to communicate with the server. It sends any of your defined GameActions, you do not need to send the user field as that is handled on the server. You can see in Game.tsx line 31 that we dispatch a guess action when the user clicks the guess button.
When you dispatch an action, it is:
- send to the server from the client
- on the server, the action is modified with the user that did the dispatch
- the server passes the augmented action to the
gameUpdaterwhich results in a newGameState - All clients receive the new
GameState(that is called a 'broadcast') - The server waits until a new action is dispatched
To build your own game we recommend:
- Start by defining the
GameState, just like a database model this will hold all the information you need. - Once the state has been decided on, define some
GameActionsto modify it. What should players be allowed to do and how should theGameStatebe updated? - Start rendering the
GameStatein theGame.tsxcomponent and add some buttons to dispatch theGameActions
Happy coding! Make it a party 🎈