remote-resistance
remote-resistance copied to clipboard
Play Resistance with your friends anywhere – a real-time remote version of the game. Visit resistance.quest
Remote Resistance
Play Resistance with your friends anywhere – a real-time remote version of the game. Play with friends at resistance.quest!
Installation
Prerequisites
Startup
- Run
npm run setup, which copies.envand runsnpm install. - Run
npm startand visithttp://localhost:3000/
Tech
Structure and Content
./actions: Public events emitted and listened for with socket.io../components: App UI, written in Svelte../entry: CSS + JavaScript entry files../server: App server../stores: Reactive Svelte stores used by the UI. Stores are similar to Models in design. Anything that should submit an public action should use a store../tests: Jest tests. All tests live in a flat directory so import statements match elsewhere. Store tests are prefixed with__store__*by convention../types: Global types for the app.
Deploying
Remote Resistance is served with Heroku. Use the following commands for deploying:
# Deploying the main branch
git push heroku main
# Deploying a non-main branch
git push heroku other-branch:main
# Reset to main branch (must do after deploying another branch)
git push -f heroku main:main
Additional Heroku Commands
# Tail logs
heroku logs --tail
# Run the Heroku app locally
npm run build
heroku local web
# Redeploy without changes (deploys twice and reverts empty commit)
git commit --allow-empty -m "Redeploy"; git push heroku main
git reset HEAD~; git push -f heroku main
History
The app hosts multiple rooms via unique URL and shares history with any visitor to that URL. History is built from public actions and emitted with websockets on app load once with history::init. It follows an Event-Driven Architecture pattern.
If the visitor loses their connection, arrives late, or refreshes the page, the history replays events to bring them to the current state.
Players are "logged in" via SessionStorage and can only join a game prior to it starting. If a player loses their connection they will re-join the game if their login key matches a player in the game.
Replay History in Development
In development, you can run npm run injectHistory to generate a state in the app on a given namespace. injectHistory adds events into a Redis key as if the events were running in a specific game. It requires HISTORY and NAME env variables.
HISTORY: the variable name of theexport constfor a given state. All states live in./tests/history-states.ts.NAME: the game URL, without the prepended slash. ie.pizza, not/pizza. This can be any string.
After you've injected history, visit the name of the game: localhost:3000/pizza.
# From the CLI
HISTORY=withPlayers NAME=pizza npm run injectHistory
HISTORY=roundOneStart NAME=pizza npm run injectHistory
HISTORY=roundOneTeamApproved NAME=pizza npm run injectHistory
# OR In your .env file
HISTORY=withPlayers
NAME=pizza
Rounds use the following pattern:
round{Number}Start, as inroundOneStartround{Number}Team, as inroundOneTeamround{Number}VotesApproved, as inroundOneVotesApprovedround{Number}VotesRejected, as inroundOneVotesRejectedround{Number}VotesPending, as inroundOneVotesPendinground{Number}TeamApproved, as inroundOneTeamApprovedround{Number}TeamRejected, as inroundOneTeamRejectedround{Number}NewVote, as inroundOneNewVoteround{Number}LastVote, as inroundOneLastVoteround{Number}MissionPassed, as inroundOneMissionPassedround{Number}MissionFailed, as inroundOneMissionFailed
History states outside of rounds:
withPlayersbefore the game has started
Rounds alternate resistance then spy win conditions:
roundTwoStart= Round 1 resistance winroundThreeStart= Round 2 spy winroundFourStart= Round 3 resistance winroundFiveStart= Round 4 spy win
AdminController
AdminController.svelte is a tool for controlling player state. Change the logged-in player or the leader, see the spies, and log-out. It's turned on for development.
Testing
Tests are written with Svelte Testing Library and Jest.
Run npm run test for the Jest watcher.
Test Helpers
AppFixture.svelte: For wrapping a given Svelte component for isolated testing. Takes thesocketconnection andcomponent.history-states.ts: Actions to rebuild history to any given state.test-helper.ts: Helper functions.
Test setup
Most tests need the following boilerplate:
import { render } from '@testing-library/svelte';
import { get } from 'svelte/store';
import AppFixture from './AppFixture.svelte';
import Component from './Component.svelte';
import { currentPlayerId } from '../stores/player';
import { createHistoryEvent, historyState, players } from './history-states';
const socket = require('socket.io-client')('test');
test('should do a thing', () => {
const [player] = players;
currentPlayerId.set(player.id);
const { getByRole } = render(AppFixture, {
socket,
component: Component,
historyState: historyState,
});
const element = getByRole();
});
Using history-states is the easiest way to build up a true state in the application with little effort. Import the history events needed to land at any given state.
Troubleshooting
- Tests are failing as a group, but pass individually
- Jest runs tests with shared state, so you need to add
afterEach(() => { …; return; })to undo the state. - The app is running, but the loading state never ends
- You probably have a typo in your ENV URLS, likely `VITE_CORS_ORIGIN_URL`. Make sure there are no trailing slashes at the end of the URL. The socket queries against `window.location.pathname`, which returns something like `/game-id`.