Skip to content

Commit

Permalink
Intial networked round state working!!
Browse files Browse the repository at this point in the history
  • Loading branch information
Half-Shot committed Jan 18, 2025
1 parent 97e7f05 commit b4f36c4
Show file tree
Hide file tree
Showing 14 changed files with 267 additions and 210 deletions.
2 changes: 1 addition & 1 deletion src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Flags extends EventEmitter {
constructor() {
super();
// Don't assume that window exists (e.g. searching)
const qs = new URLSearchParams(globalThis?.location?.search ?? "");
const qs = new URLSearchParams(globalThis.location.hash?.slice?.(1) ?? '');
this.DebugView = qs.get("debug")
? DebugLevel.PhysicsOverlay
: DebugLevel.None;
Expand Down
3 changes: 2 additions & 1 deletion src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { sound } from "@pixi/sound";
import Logger from "./log";
import { CriticalGameError } from "./errors";
import { getGameSettings } from "./settings";
import { NetGameWorld } from "./net/netGameWorld";

const worldWidth = 1920;
const worldHeight = 1080;
Expand Down Expand Up @@ -70,7 +71,7 @@ export class Game {
// the interaction module is important for wheel to work properly when renderer.view is placed or scaled
events: this.pixiApp.renderer.events,
});
this.world = new GameWorld(this.rapierWorld, this.pixiApp.ticker);
this.world = netGameInstance ? new NetGameWorld(this.rapierWorld, this.pixiApp.ticker, netGameInstance) : new GameWorld(this.rapierWorld, this.pixiApp.ticker);
this.pixiApp.stage.addChild(this.viewport);
this.viewport.decelerate().drag();
this.viewport.zoom(8);
Expand Down
47 changes: 23 additions & 24 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
line-height: 1.5;
font-weight: 400;

color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;

Expand All @@ -14,29 +13,29 @@
-webkit-text-size-adjust: 100%;
}

@media (prefers-color-scheme: dark) {
:root {
--text: #bdbdbd;
--bg: #2e2e2e;
--highlight: #b96724;
--links: #df8b1c;
--subheading: #adadad;

--team-red-bg: #cc3333;
--team-red-fg: #db6f6f;
--team-blue-bg: #2649d9;
--team-blue-fg: #7085db;
--team-purple-bg: #a226d9;
--team-purple-fg: #bb70db;
--team-yellow-bg: #d9c526;
--team-yellow-fg: #dbcf70;
--team-orange-bg: #d97a26;
--team-orange-fg: #dba270;
--team-green-bg: #30d926;
--team-green-fg: #75db70;
--team-unk-bg: #cc00cc;
--team-unk-fg: #111111;
}
:root {
--text: #bdbdbd;
--bg: #2e2e2e;
--highlight: #b96724;
--links: #df8b1c;
--subheading: #adadad;

--color-danger: #d41414;

--team-red-bg: #cc3333;
--team-red-fg: #db6f6f;
--team-blue-bg: #2649d9;
--team-blue-fg: #7085db;
--team-purple-bg: #a226d9;
--team-purple-fg: #bb70db;
--team-yellow-bg: #d9c526;
--team-yellow-fg: #dbcf70;
--team-orange-bg: #d97a26;
--team-orange-fg: #dba270;
--team-green-bg: #30d926;
--team-green-fg: #75db70;
--team-unk-bg: #cc00cc;
--team-unk-fg: #111111;
}

a {
Expand Down
37 changes: 16 additions & 21 deletions src/logic/gamestate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Logger from "../log";
import { EntityType } from "../entities/type";
import { GameWorld } from "../world";
import { IWeaponCode } from "../weapons/weapon";
import { BehaviorSubject, distinctUntilChanged, map, skip } from "rxjs";
import { BehaviorSubject, distinctUntilChanged, map, skip, tap } from "rxjs";

Check failure on line 6 in src/logic/gamestate.ts

View workflow job for this annotation

GitHub Actions / ci

'tap' is defined but never used. Allowed unused vars must match /^_/u

export interface GameRules {
roundDurationMs?: number;
Expand Down Expand Up @@ -47,7 +47,11 @@ export class GameState {
protected currentTeam = new BehaviorSubject<TeamInstance | undefined>(
undefined,
);
protected currentWorm = new BehaviorSubject<WormInstance | undefined>(
undefined,
);
public readonly currentTeam$ = this.currentTeam.asObservable();
public readonly currentWorm$ = this.currentWorm.asObservable();
protected readonly teams: Map<string, TeamInstance>;
protected nextTeamStack: TeamInstance[];

Expand Down Expand Up @@ -115,6 +119,8 @@ export class GameState {
}
this.nextTeamStack = [...this.teams.values()];
this.roundDurationMs = rules.roundDurationMs ?? 45000;
this.roundState.subscribe((s) => logger.info(`Round state changed => ${s}`));
this.currentTeam.subscribe((s) => logger.info(`Current team is now => ${s?.name} ${s?.playerUserId}`));
}

public pauseTimer() {
Expand Down Expand Up @@ -153,7 +159,6 @@ export class GameState {

public markAsFinished() {
this.roundState.next(RoundState.Finished);
this.recordGameStare();
}

public update(ticker: { deltaMS: number }) {
Expand All @@ -176,35 +181,32 @@ export class GameState {
this.playerMoved();
} else {
this.roundState.next(RoundState.Finished);
this.recordGameStare();
}
}

public playerMoved() {
this.roundState.next(RoundState.Playing);
logger.debug("playerMoved", this.roundDurationMs);
this.remainingRoundTimeMs.next(this.roundDurationMs);
this.recordGameStare();
}

public beginRound() {
if (this.roundState.value !== RoundState.WaitingToBegin) {
throw Error(
`Expected round to be WaitingToBegin for advanceRound(), but got ${this.roundState}`,
`Expected round to be WaitingToBegin for advanceRound(), but got ${this.roundState.value}`,
);
}
this.roundState.next(RoundState.Preround);
logger.debug("beginRound", PREROUND_TIMER_MS);
this.remainingRoundTimeMs.next(PREROUND_TIMER_MS);
this.recordGameStare();
}

public advanceRound():
| { nextTeam: TeamInstance; nextWorm: WormInstance }
| { winningTeams: TeamInstance[] } {
if (this.roundState.value !== RoundState.Finished) {
throw Error(
`Expected round to be Finished for advanceRound(), but got ${this.roundState}`,
`Expected round to be Finished for advanceRound(), but got ${this.roundState.value}`,
);
}
logger.debug("Advancing round");
Expand All @@ -215,13 +217,13 @@ export class GameState {

// 5 seconds preround
this.stateIteration++;
const nextWorm = firstTeam.popNextWorm();
this.roundState.next(RoundState.WaitingToBegin);

this.recordGameStare();
this.currentWorm.next(nextWorm);
return {
nextTeam: firstTeam,
// Team *should* have at least one healthy worm.
nextWorm: firstTeam.popNextWorm(),
nextWorm: nextWorm,
};
}
const previousTeam = this.currentTeam.value;
Expand Down Expand Up @@ -256,32 +258,25 @@ export class GameState {
if (this.currentTeam.value === previousTeam) {
this.stateIteration++;
if (this.rules.winWhenOneGroupRemains) {
// All remaining teams are part of the same group
this.recordGameStare();
return {
winningTeams: this.getActiveTeams(),
};
} else if (previousTeam.health === 0) {
// This is a draw
this.recordGameStare();
return {
winningTeams: [],
};
}
}
this.stateIteration++;
// 5 seconds preround
this.remainingRoundTimeMs.next(0);
this.remainingRoundTimeMs.next(PREROUND_TIMER_MS);
const nextWorm = this.currentTeam.value.popNextWorm();
this.roundState.next(RoundState.WaitingToBegin);
this.recordGameStare();
this.currentWorm.next(nextWorm);
return {
nextTeam: this.currentTeam.value,
// We should have already validated that this team has healthy worms.
nextWorm: this.currentTeam.value.popNextWorm(),
nextWorm: nextWorm,
};
}

protected recordGameStare() {
return;
}
}
2 changes: 1 addition & 1 deletion src/logic/worminstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ export class WormInstance {
}

setHealth(health: number) {
this.healthSubject.next(health);
this.healthSubject.next(Math.ceil(health));
}
}
8 changes: 5 additions & 3 deletions src/net/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,9 @@ export class RunningNetGameInstance extends NetGameInstance {
if (r?.roomId !== this.roomId) {
return;
}
if (event.getType() === GameActionEventType && !event.isState()) {
this.player.handleEvent(event.getContent());
// Filter our won events out.
if (event.getType() === GameActionEventType && !event.isState() && event.getSender() !== this.myUserId) {
void this.player.handleEvent(event.getContent());
}
});
}
Expand Down Expand Up @@ -604,7 +605,7 @@ export class RunningNetGameInstance extends NetGameInstance {
]);

const expectedCount = Object.values(this.initialConfig.members).length;

logger.info("Ready check", expectedCount, setOfReady);
if (setOfReady.size === expectedCount) {
return;
}
Expand All @@ -614,6 +615,7 @@ export class RunningNetGameInstance extends NetGameInstance {
if (event.getType() === GameClientReadyEventType && !event.isState()) {
setOfReady.add(event.getSender()!);
}
logger.info("Ready check", expectedCount, setOfReady);
if (setOfReady.size === expectedCount) {
resolve();
}
Expand Down
77 changes: 64 additions & 13 deletions src/net/netGameState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import { StateRecorder } from "../state/recorder";
import { GameWorld } from "../world";
import { Team } from "../logic/teams";
import { StateRecordWormGameState } from "../state/model";
import { combineLatest, map } from "rxjs";
import Logger from "../log";


const log = new Logger("NetGameState");
export class NetGameState extends GameState {
get clientActive() {
return this.activeTeam?.playerUserId === this.myUserId;
protected clientActive$ = this.currentTeam$.pipe(map(t => t?.playerUserId === this.myUserId));

get shouldControlState(): boolean {
return this.currentTeam.value?.playerUserId === this.myUserId;
}

get peekNextTeam() {
Expand Down Expand Up @@ -34,6 +40,11 @@ export class NetGameState extends GameState {
private readonly myUserId: string,
) {
super(teams, world, rules);
combineLatest([this.roundState$]).subscribe(([state]) => {
if (this.shouldControlState) {
this.recordGameState(state);
}
});
}

protected networkSelectNextTeam() {
Expand All @@ -57,7 +68,13 @@ export class NetGameState extends GameState {
}
}

public applyGameStateUpdate(stateUpdate: StateRecordWormGameState["data"]) {
public applyGameStateUpdate(stateUpdate: StateRecordWormGameState["data"]): ReturnType<typeof this.advanceRound>|null {
// if (this.iteration >= stateUpdate.iteration) {
// log.debug("Ignoring iteration because it's stale", this.iteration, stateUpdate.iteration);
// // Skip
// return;
// }
log.debug("Applying round state", stateUpdate.round_state);
for (const teamData of stateUpdate.teams) {
const teamWormSet = this.teams.get(teamData.uuid)?.worms;
if (!teamWormSet) {
Expand All @@ -76,22 +93,25 @@ export class NetGameState extends GameState {
) {
this.remainingRoundTimeMs.next(5000);
}
this.roundState.next(stateUpdate.round_state);
this.wind = stateUpdate.wind;
if (this.roundState.value === RoundState.WaitingToBegin) {
this.networkSelectNextTeam();
if (stateUpdate.round_state === RoundState.WaitingToBegin) {
const result = this.advanceRound();
this.wind = stateUpdate.wind;
return result;
} else if (stateUpdate.round_state === RoundState.Playing) {
this.playerMoved();
} else {
// TODO?
this.roundState.next(stateUpdate.round_state);
}
return null;
}

protected recordGameStare() {
if (!this.clientActive) {
console.log("Not active client");
return;
}
protected recordGameState(roundState: RoundState) {
log.debug("Recording round state", roundState);
const iteration = this.iteration;
const teams = this.getTeams();
this.recorder.recordGameState({
round_state: this.roundState.value,
round_state: roundState,
iteration: iteration,
wind: this.currentWind,
teams: teams.map((t) => ({
Expand All @@ -106,4 +126,35 @@ export class NetGameState extends GameState {
})),
});
}

public update(ticker: { deltaMS: number }) {
const roundState = this.roundState.value;
let remainingRoundTimeMs = this.remainingRoundTimeMs.value;
if (
roundState === RoundState.Finished ||
roundState === RoundState.Paused ||
roundState === RoundState.WaitingToBegin
) {
return;
}

remainingRoundTimeMs = Math.max(0, remainingRoundTimeMs - ticker.deltaMS);
this.remainingRoundTimeMs.next(remainingRoundTimeMs);
if (remainingRoundTimeMs) {
return;
}

if (!this.shouldControlState) {
// Waiting for other client to make the move.
return;
} else {

Check failure on line 150 in src/net/netGameState.ts

View workflow job for this annotation

GitHub Actions / ci

Empty block statement

}

if (roundState === RoundState.Preround) {
this.playerMoved();
} else {
this.roundState.next(RoundState.Finished);
}
}
}
Loading

0 comments on commit b4f36c4

Please sign in to comment.