Skip to content

Commit

Permalink
Update scores and bonus points at turn end
Browse files Browse the repository at this point in the history
The logic here is relatively complex so worthwhile testing.
I have opted to only test the exported functions as the smaller, local
ones are very simple.

Co-authored-by: Rich James <[email protected]>
  • Loading branch information
yndajas and Rich James committed Aug 21, 2024
1 parent bb8b424 commit 8d12d7e
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 18 deletions.
7 changes: 6 additions & 1 deletion server/@types/entities.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ type Question = {
subject: string;
};

export type { Answer, Colour, Player, Question };
type PlayerScore = {
player: Player;
score: number;
};

export type { Answer, Colour, Player, PlayerScore, Question };
4 changes: 3 additions & 1 deletion server/machines/round.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { assign, setup } from "xstate";
import type { Question } from "../@types/entities";
import type { PlayerScore, Question } from "../@types/entities";
import questions from "../data/questions.json";

const context = {
questions: questions as Question[],
playerScores: [] as PlayerScore[],
selectedQuestion: {} as Question | undefined,
bonusPoints: 0,
};

type Context = typeof context;
Expand Down
27 changes: 15 additions & 12 deletions server/models/round.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { context, roundMachine } from "../machines/round";
import { turnMachine } from "../machines/turn";
import type { SocketServer } from "../socketServer";
import { machineLogger } from "../utils/loggingUtils";
import { getUpdatedPlayerScoresAndBonusPoints } from "../utils/scoringUtils";

class Round {
machine: Actor<typeof roundMachine>;
Expand Down Expand Up @@ -43,18 +44,20 @@ class Round {
.selectedQuestion as Question,
},
});
this.turnMachine.subscribe((state) => {
switch (state.value) {
case "finished": {
// TODO:
// - add logic for updating scores then checking if there's a clear winner in the round machine
// - delete the console.info below
console.info(
"turn machine finished with context:",
this.turnMachine?.getSnapshot().context,
);
}
}

this.turnMachine.subscribe({
complete: () => {
const roundMachineSnapshot = this.machine.getSnapshot();
this.machine.send({
type: "turnEnd",
scoresAndBonusPoints: getUpdatedPlayerScoresAndBonusPoints(
roundMachineSnapshot.context.bonusPoints,
roundMachineSnapshot.context.playerScores,
this.turnMachine?.getSnapshot()?.output?.correctPlayerSocketIds ||
[],
),
});
},
});
this.turnMachine.start();
}
Expand Down
103 changes: 101 additions & 2 deletions server/utils/scoringUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { describe, expect, it } from "bun:test";
import type { Colour } from "../@types/entities";
import { getCorrectSocketIdsFromAnswers } from "./scoringUtils";
import type { Colour, Player, PlayerScore } from "../@types/entities";
import {
getCorrectSocketIdsFromAnswers,
getUpdatedPlayerScoresAndBonusPoints,
} from "./scoringUtils";

describe("scoringUtils", () => {
describe("getCorrectSocketIdsFromAnswers", () => {
Expand Down Expand Up @@ -32,4 +35,100 @@ describe("scoringUtils", () => {
).toBeArrayOfSize(0);
});
});

describe("getUpdatedPlayerScoresAndBonusPoints", () => {
describe("if all players are correct", () => {
it("increments the bonus points and returns the player scores unchanged", () => {
const currentBonusPoints = 0;
const correctPlayerSocketIds = ["1", "2"];
const currentPlayerScores: PlayerScore[] = [
{
player: { name: "olaf", socketId: correctPlayerSocketIds[0] },
score: 1,
},
{
player: { name: "alex", socketId: correctPlayerSocketIds[1] },
score: 0,
},
];

expect(
getUpdatedPlayerScoresAndBonusPoints(
currentBonusPoints,
currentPlayerScores,
correctPlayerSocketIds,
),
).toEqual({
bonusPoints: 1,
playerScores: currentPlayerScores,
});
});
});

describe("if all players are incorrect", () => {
it("resets the bonus points and returns the player scores unchanged", () => {
const currentBonusPoints = 3;
const correctPlayerSocketIds: Player["socketId"][] = [];
const currentPlayerScores: PlayerScore[] = [
{
player: { name: "olaf", socketId: "1" },
score: 1,
},
{
player: { name: "alex", socketId: "2" },
score: 0,
},
];

expect(
getUpdatedPlayerScoresAndBonusPoints(
currentBonusPoints,
currentPlayerScores,
correctPlayerSocketIds,
),
).toEqual({
bonusPoints: 0,
playerScores: currentPlayerScores,
});
});
});

describe("if some players are correct and others incorrect", () => {
describe("and there are no bonus points", () => {
it("awards points to the correct players and returns the player scores", () => {
const currentBonusPoints = 2;
const correctPlayerSocketIds = ["1", "3"];
const currentPlayerScores: PlayerScore[] = [
{
player: { name: "olaf", socketId: correctPlayerSocketIds[0] },
score: 0,
},
{
player: { name: "alex", socketId: "2" },
score: 0,
},
{
player: { name: "james", socketId: correctPlayerSocketIds[1] },
score: 0,
},
];

expect(
getUpdatedPlayerScoresAndBonusPoints(
currentBonusPoints,
currentPlayerScores,
correctPlayerSocketIds,
),
).toEqual({
bonusPoints: 0,
playerScores: [
{ player: { name: "olaf", socketId: "1" }, score: 3 },
{ player: { name: "alex", socketId: "2" }, score: 0 },
{ player: { name: "james", socketId: "3" }, score: 3 },
],
});
});
});
});
});
});
61 changes: 59 additions & 2 deletions server/utils/scoringUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,61 @@
import type { Answer, Question } from "../@types/entities";
import type { Answer, Player, PlayerScore, Question } from "../@types/entities";

const allCorrect = (
totalPlayerCount: number,
correctPlayerSocketIds: Player["socketId"][],
): boolean => {
return correctPlayerSocketIds.length === totalPlayerCount;
};

const allIncorrect = (
correctPlayerSocketIds: Player["socketId"][],
): boolean => {
return correctPlayerSocketIds.length === 0;
};

const getUpdatedPlayerScores = (
currentPlayerScores: PlayerScore[],
bonusPoints: number,
correctPlayerSocketIds: Player["socketId"][],
): PlayerScore[] => {
const numberOfIncorrectAnswers =
currentPlayerScores.length - correctPlayerSocketIds.length;
const pointsToAward = numberOfIncorrectAnswers + bonusPoints;

return currentPlayerScores.map(({ player, score }) => {
if (correctPlayerSocketIds.includes(player.socketId)) {
return { player, score: score + pointsToAward };
}

return { player, score };
});
};

const getUpdatedPlayerScoresAndBonusPoints = (
currentBonusPoints: number,
currentPlayerScores: PlayerScore[],
correctPlayerSocketIds: Player["socketId"][],
): { bonusPoints: number; playerScores: PlayerScore[] } => {
if (allCorrect(currentPlayerScores.length, correctPlayerSocketIds)) {
return {
bonusPoints: currentBonusPoints + 1,
playerScores: currentPlayerScores,
};
}

if (allIncorrect(correctPlayerSocketIds)) {
return { bonusPoints: 0, playerScores: currentPlayerScores };
}

return {
bonusPoints: 0,
playerScores: getUpdatedPlayerScores(
currentPlayerScores,
currentBonusPoints,
correctPlayerSocketIds,
),
};
};

const getCorrectSocketIdsFromAnswers = (
answers: Answer[],
Expand All @@ -15,4 +72,4 @@ const getCorrectSocketIdsFromAnswers = (
.map((answer) => answer.socketId);
};

export { getCorrectSocketIdsFromAnswers };
export { getCorrectSocketIdsFromAnswers, getUpdatedPlayerScoresAndBonusPoints };

0 comments on commit 8d12d7e

Please sign in to comment.