Skip to content

Commit

Permalink
Shift lobby to socket.io.
Browse files Browse the repository at this point in the history
  • Loading branch information
robholland committed Sep 4, 2024
1 parent 6723a3f commit ef58c32
Show file tree
Hide file tree
Showing 15 changed files with 395 additions and 291 deletions.
11 changes: 2 additions & 9 deletions game/src/activities.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Snake, Round, Lobby } from './workflows';
import { Snake, Round } from './workflows';
import { io } from 'socket.io-client';

const socket = io('http://localhost:5173');
const lobbySocket = io('http://localhost:5173/lobby');

export async function snakeNom(snakeId: string, durationMs: number) {
await new Promise((resolve) => setTimeout(resolve, durationMs));
Expand All @@ -12,14 +13,6 @@ export async function snakeMovedNotification(snake: Snake) {
socket.emit('snakeMoved', { snakeId: snake.id, segments: snake.segments });
}

export async function playerInvitation(playerId: string, snakeId: string) {
socket.emit('playerInvitation', { playerId, snakeId });
}

export async function lobbyNotification(lobby: Lobby) {
socket.emit('lobby', { lobby });
}

export async function roundStartedNotification(round: Round) {
socket.emit('roundStarted', { round });
}
Expand Down
128 changes: 23 additions & 105 deletions game/src/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
condition,
log,
startChild,
workflowInfo,
sleep,
getExternalWorkflowHandle,
defineQuery,
Expand All @@ -25,7 +24,7 @@ const { snakeNom } = proxyActivities<typeof activities>({
startToCloseTimeout: '5 seconds',
});

const { playerInvitation, snakeMovedNotification, roundStartedNotification, roundUpdateNotification, roundFinishedNotification, lobbyNotification } = proxyLocalActivities<typeof activities>({
const { snakeMovedNotification, roundStartedNotification, roundUpdateNotification, roundFinishedNotification } = proxyLocalActivities<typeof activities>({
startToCloseTimeout: '5 seconds',
});

Expand All @@ -41,32 +40,11 @@ type Game = {
teams: Teams;
};

export type Lobby = {
teams: TeamSummaries;
}

type TeamSummaries = Record<string, TeamSummary>;

type Team = {
name: string;
players: Player[];
score: number;
};

type TeamSummary = {
name: string;
players: number;
score: number;
};

type Player = {
id: string;
name: string;
score: number;
};

export type Teams = Record<string, Team>;
type Snakes = Record<string, Snake>;

export type Round = {
config: GameConfig;
Expand Down Expand Up @@ -95,11 +73,12 @@ type Segment = {

export type Snake = {
id: string;
teamName: string;
playerId: string;
teamName: string;
segments: Segment[];
ateApple?: boolean;
};
type Snakes = Record<string, Snake>;

type Direction = 'up' | 'down' | 'left' | 'right';

Expand All @@ -121,23 +100,15 @@ function oppositeDirection(direction: Direction): Direction {
}

export const gameStateQuery = defineQuery<Game>('gameState');
export const lobbyQuery = defineQuery<Lobby>('lobby');
export const roundStateQuery = defineQuery<Round>('roundState');

type RoundStartSignal = {
snakes: Snake[];
duration: number;
}
// UI -> GameWorkflow to start round
export const roundStartSignal = defineSignal<[RoundStartSignal]>('roundStart');

// Player UI -> GameWorkflow to join team
type PlayerJoinSignal = {
id: string;
name: string;
teamName: string;
}
export const playerJoinSignal = defineSignal<[PlayerJoinSignal]>('playerJoin');

// Player UI -> SnakeWorkflow to change direction
export const snakeChangeDirectionSignal = defineSignal<[Direction]>('snakeChangeDirection');

Expand All @@ -150,71 +121,51 @@ export async function GameWorkflow(config: GameConfig): Promise<void> {
const game: Game = {
config,
teams: config.teamNames.reduce<Teams>((acc, name) => {
acc[name] = { name, players: [], score: 0 };
return acc;
}, {}),
};
const lobby: Lobby = {
teams: config.teamNames.reduce<TeamSummaries>((acc, name) => {
acc[name] = { name, players: 0, score: 0 };
acc[name] = { name, score: 0 };
return acc;
}, {}),
};

setHandler(gameStateQuery, () => {
return game;
});
setHandler(lobbyQuery, () => {
return lobby;
});

setHandler(playerJoinSignal, async ({ id, name, teamName }) => {
const team = game.teams[teamName];

team.players.push({ id, name, score: 0 });
lobby.teams[teamName].players = team.players.length;

await lobbyNotification(lobby);
});

let roundStart = false;
let roundDuration = 0;
setHandler(roundStartSignal, async ({ duration }) => {
roundStart = true;
roundDuration = duration;
let newRound: RoundWorkflowInput | undefined;
setHandler(roundStartSignal, async ({ duration, snakes }) => {
newRound = { config, teams: buildRoundTeams(game), duration, snakes };
});

while (true) {
await condition(() => roundStart);
roundStart = false;
await condition(() => newRound !== undefined);
const roundWf = await startChild(RoundWorkflow, {
workflowId: ROUND_WF_ID,
args: [{ config, teams: buildRoundTeams(game), duration: roundDuration }]
args: [newRound!]
});
const round = await roundWf.result();

for (const team of Object.values(round.teams)) {
game.teams[team.name].score += team.score;
lobby.teams[team.name].score += team.score;
}
newRound = undefined;
}
}

type RoundWorkflowInput = {
config: GameConfig;
teams: Teams;
snakes: Snake[];
duration: number;
}

export async function RoundWorkflow({ config, teams, duration }: RoundWorkflowInput): Promise<Round> {
export async function RoundWorkflow({ config, teams, snakes, duration }: RoundWorkflowInput): Promise<Round> {
log.info('Starting round', { duration });

const round: Round = {
config: config,
duration: duration,
apple: OutOfBounds,
teams: teams,
snakes: buildSnakes(config, teams),
snakes: snakes.reduce<Snakes>((acc, snake) => { acc[snake.id] = snake; return acc; }, {}),
};

randomizeRound(round);
Expand Down Expand Up @@ -405,65 +356,32 @@ function randomEmptyPoint(round: Round): Point {
return { x: Math.ceil(Math.random() * round.config.width), y: Math.ceil(Math.random() * round.config.height) };
}

function buildSnakes(config: GameConfig, teams: Teams): Snakes {
const snakes: Snakes = {};

for (const teamName in teams) {
for (let i = 0; i < config.snakesPerTeam; i++) {
const snake = {
id: `${teamName}-${i}`,
teamName: teamName,
segments: [{ head: OutOfBounds, length: 1, direction: 'down' as Direction }],
playerId: teams[teamName].players[i].id,
};
snakes[snake.id] = snake;
}
};

return snakes;
}

function buildRoundTeams(game: Game): Teams {
const teams: Teams = {};

for (const team of Object.values(game.teams)) {
const players = Array.from({ length: game.config.snakesPerTeam }).map(() => nextPlayer(team));

teams[team.name] = { name: team.name, players: players, score: 0 };
teams[team.name] = { name: team.name, score: 0 };
}

return teams;
}

async function startSnakes(snakes: Snakes) {
const commands = Object.values(snakes).flatMap((snake) =>
[
startChild(SnakeWorkflow, {
workflowId: snake.id,
args: [{ roundId: ROUND_WF_ID, id: snake.id, direction: snake.segments[0].direction }]
}),
playerInvitation(snake.playerId, snake.id)
]
const commands = Object.values(snakes).map((snake) =>
startChild(SnakeWorkflow, {
workflowId: snake.id,
args: [{ roundId: ROUND_WF_ID, id: snake.id, direction: snake.segments[0].direction }]
})
)

// TODO: Do these all get started in same WFT?
await Promise.all(commands);
}

function nextPlayer(team: Team): Player {
const nextPlayer = team.players.shift();
if (!nextPlayer) {
throw new Error('No players left on team');
}
team.players.push(nextPlayer);
return nextPlayer;
}

function randomizeRound(round: Round) {
round.apple = randomEmptyPoint(round);
for (const snake of Object.values(round.snakes)) {
snake.segments[0].head = randomEmptyPoint(round);
snake.segments[0].direction = randomDirection();
snake.segments = [
{ head: randomEmptyPoint(round), direction: randomDirection(), length: 1 }
]
}
}

6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions snakes/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions snakes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/socket.io": "^3.0.2",
"@types/socket.io-client": "^3.0.0",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"prettier": "^3.1.1",
Expand Down
30 changes: 0 additions & 30 deletions snakes/src/lib/pages/PlayerRegisterPage.svelte

This file was deleted.

Loading

0 comments on commit ef58c32

Please sign in to comment.