Skip to content

Commit b3172c4

Browse files
authored
Merge pull request #188 from dxw/update-answers-and-bonus-points-at-turn-end
Update scores and bonus points at turn end
2 parents 183ec74 + 1f086f6 commit b3172c4

File tree

6 files changed

+255
-25
lines changed

6 files changed

+255
-25
lines changed

server/@types/entities.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,9 @@ type Question = {
2828
subject: string;
2929
};
3030

31-
export type { Answer, Colour, Player, Question };
31+
type PlayerScore = {
32+
player: Player;
33+
score: number;
34+
};
35+
36+
export type { Answer, Colour, Player, PlayerScore, Question };

server/machines/round.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { assign, setup } from "xstate";
2-
import type { Question } from "../@types/entities";
2+
import type { PlayerScore, Question } from "../@types/entities";
33
import questions from "../data/questions.json";
44

55
const context = {
66
questions: questions as Question[],
7+
playerScores: [] as PlayerScore[],
78
selectedQuestion: {} as Question | undefined,
9+
bonusPoints: 0,
810
};
911

1012
type Context = typeof context;
@@ -40,6 +42,17 @@ const roundMachine = setup({
4042
states: {
4143
turn: {
4244
entry: [{ type: "setQuestion", params: dynamicParamFuncs.setQuestion }],
45+
on: {
46+
turnEnd: {
47+
target: "roundEnd",
48+
// guard: (_, __) => {
49+
// check to see if round end conditions are met
50+
// },
51+
},
52+
},
53+
},
54+
roundEnd: {
55+
type: "final",
4356
},
4457
},
4558
});

server/machines/turn.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assign, setup } from "xstate";
22
import type { Answer, Player, Question } from "../@types/entities";
3+
import { getCorrectSocketIdsFromAnswers } from "../utils/scoringUtils";
34

45
const context = {
56
answers: [] as Answer[],
@@ -18,6 +19,8 @@ type Events = PlayerSubmitsAnswerEvent;
1819

1920
type Input = { selectedQuestion: Question };
2021

22+
type Output = { correctPlayerSocketIds: Player["socketId"][] };
23+
2124
const dynamicParamFuncs = {
2225
addAnswer: ({
2326
context,
@@ -36,6 +39,7 @@ const turnMachine = setup({
3639
context: Context;
3740
events: Events;
3841
input: Input;
42+
output: Output;
3943
},
4044
actions: {
4145
addAnswer: assign({
@@ -49,17 +53,10 @@ const turnMachine = setup({
4953
_,
5054
params: ReturnType<typeof dynamicParamFuncs.recordCorrectPlayers>,
5155
) => {
52-
return params.finalAnswers
53-
.filter((answer) => {
54-
if (params.correctAnswer.length !== answer.colours.length) {
55-
return false;
56-
}
57-
58-
return params.correctAnswer.every((colour) =>
59-
answer.colours.includes(colour),
60-
);
61-
})
62-
.map((answer) => answer.socketId);
56+
return getCorrectSocketIdsFromAnswers(
57+
params.finalAnswers,
58+
params.correctAnswer,
59+
);
6360
},
6461
}),
6562
},
@@ -94,6 +91,9 @@ const turnMachine = setup({
9491
],
9592
},
9693
},
94+
output: ({ context }) => ({
95+
correctPlayerSocketIds: context.correctPlayerSocketIds,
96+
}),
9797
});
9898

9999
export { turnMachine };

server/models/round.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { context, roundMachine } from "../machines/round";
44
import { turnMachine } from "../machines/turn";
55
import type { SocketServer } from "../socketServer";
66
import { machineLogger } from "../utils/loggingUtils";
7+
import { getUpdatedPlayerScoresAndBonusPoints } from "../utils/scoringUtils";
78

89
class Round {
910
machine: Actor<typeof roundMachine>;
@@ -43,18 +44,20 @@ class Round {
4344
.selectedQuestion as Question,
4445
},
4546
});
46-
this.turnMachine.subscribe((state) => {
47-
switch (state.value) {
48-
case "finished": {
49-
// TODO:
50-
// - add logic for updating scores then checking if there's a clear winner in the round machine
51-
// - delete the console.info below
52-
console.info(
53-
"turn machine finished with context:",
54-
this.turnMachine?.getSnapshot().context,
55-
);
56-
}
57-
}
47+
48+
this.turnMachine.subscribe({
49+
complete: () => {
50+
const roundMachineSnapshot = this.machine.getSnapshot();
51+
this.machine.send({
52+
type: "turnEnd",
53+
scoresAndBonusPoints: getUpdatedPlayerScoresAndBonusPoints(
54+
roundMachineSnapshot.context.bonusPoints,
55+
roundMachineSnapshot.context.playerScores,
56+
this.turnMachine?.getSnapshot()?.output?.correctPlayerSocketIds ||
57+
[],
58+
),
59+
});
60+
},
5861
});
5962
this.turnMachine.start();
6063
}

server/utils/scoringUtils.test.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { describe, expect, it } from "bun:test";
2+
import type { Colour, Player, PlayerScore } from "../@types/entities";
3+
import {
4+
getCorrectSocketIdsFromAnswers,
5+
getUpdatedPlayerScoresAndBonusPoints,
6+
} from "./scoringUtils";
7+
8+
describe("scoringUtils", () => {
9+
describe("getCorrectSocketIdsFromAnswers", () => {
10+
const correctAnswer: Colour[] = ["red", "blue"];
11+
const incorrectAnswer: Colour[] = ["pink", "blue"];
12+
13+
it("returns the IDs of the players with the correct answers", () => {
14+
expect(
15+
getCorrectSocketIdsFromAnswers(
16+
[
17+
{ colours: correctAnswer, socketId: "1" },
18+
{ colours: incorrectAnswer, socketId: "2" },
19+
{ colours: correctAnswer, socketId: "3" },
20+
],
21+
correctAnswer,
22+
),
23+
).toEqual(["1", "3"]);
24+
});
25+
26+
it("returns an empty array if there are no correct answers", () => {
27+
expect(
28+
getCorrectSocketIdsFromAnswers(
29+
[
30+
{ colours: incorrectAnswer, socketId: "1" },
31+
{ colours: incorrectAnswer, socketId: "2" },
32+
],
33+
correctAnswer,
34+
),
35+
).toBeArrayOfSize(0);
36+
});
37+
});
38+
39+
describe("getUpdatedPlayerScoresAndBonusPoints", () => {
40+
describe("if all players are correct", () => {
41+
it("increments the bonus points and returns the player scores unchanged", () => {
42+
const currentBonusPoints = 0;
43+
const correctPlayerSocketIds = ["1", "2"];
44+
const currentPlayerScores: PlayerScore[] = [
45+
{
46+
player: { name: "olaf", socketId: correctPlayerSocketIds[0] },
47+
score: 1,
48+
},
49+
{
50+
player: { name: "alex", socketId: correctPlayerSocketIds[1] },
51+
score: 0,
52+
},
53+
];
54+
55+
expect(
56+
getUpdatedPlayerScoresAndBonusPoints(
57+
currentBonusPoints,
58+
currentPlayerScores,
59+
correctPlayerSocketIds,
60+
),
61+
).toEqual({
62+
bonusPoints: 1,
63+
playerScores: currentPlayerScores,
64+
});
65+
});
66+
});
67+
68+
describe("if all players are incorrect", () => {
69+
it("resets the bonus points and returns the player scores unchanged", () => {
70+
const currentBonusPoints = 3;
71+
const correctPlayerSocketIds: Player["socketId"][] = [];
72+
const currentPlayerScores: PlayerScore[] = [
73+
{
74+
player: { name: "olaf", socketId: "1" },
75+
score: 1,
76+
},
77+
{
78+
player: { name: "alex", socketId: "2" },
79+
score: 0,
80+
},
81+
];
82+
83+
expect(
84+
getUpdatedPlayerScoresAndBonusPoints(
85+
currentBonusPoints,
86+
currentPlayerScores,
87+
correctPlayerSocketIds,
88+
),
89+
).toEqual({
90+
bonusPoints: 0,
91+
playerScores: currentPlayerScores,
92+
});
93+
});
94+
});
95+
96+
describe("if some players are correct and others incorrect", () => {
97+
describe("and there are no bonus points", () => {
98+
it("awards points to the correct players and returns the player scores", () => {
99+
const currentBonusPoints = 2;
100+
const correctPlayerSocketIds = ["1", "3"];
101+
const currentPlayerScores: PlayerScore[] = [
102+
{
103+
player: { name: "olaf", socketId: correctPlayerSocketIds[0] },
104+
score: 0,
105+
},
106+
{
107+
player: { name: "alex", socketId: "2" },
108+
score: 0,
109+
},
110+
{
111+
player: { name: "james", socketId: correctPlayerSocketIds[1] },
112+
score: 0,
113+
},
114+
];
115+
116+
expect(
117+
getUpdatedPlayerScoresAndBonusPoints(
118+
currentBonusPoints,
119+
currentPlayerScores,
120+
correctPlayerSocketIds,
121+
),
122+
).toEqual({
123+
bonusPoints: 0,
124+
playerScores: [
125+
{ player: { name: "olaf", socketId: "1" }, score: 3 },
126+
{ player: { name: "alex", socketId: "2" }, score: 0 },
127+
{ player: { name: "james", socketId: "3" }, score: 3 },
128+
],
129+
});
130+
});
131+
});
132+
});
133+
});
134+
});

server/utils/scoringUtils.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type { Answer, Player, PlayerScore, Question } from "../@types/entities";
2+
3+
const allCorrect = (
4+
totalPlayerCount: number,
5+
correctPlayerSocketIds: Player["socketId"][],
6+
): boolean => {
7+
return correctPlayerSocketIds.length === totalPlayerCount;
8+
};
9+
10+
const allIncorrect = (
11+
correctPlayerSocketIds: Player["socketId"][],
12+
): boolean => {
13+
return correctPlayerSocketIds.length === 0;
14+
};
15+
16+
const getUpdatedPlayerScores = (
17+
currentPlayerScores: PlayerScore[],
18+
bonusPoints: number,
19+
correctPlayerSocketIds: Player["socketId"][],
20+
): PlayerScore[] => {
21+
const numberOfIncorrectAnswers =
22+
currentPlayerScores.length - correctPlayerSocketIds.length;
23+
const pointsToAward = numberOfIncorrectAnswers + bonusPoints;
24+
25+
return currentPlayerScores.map(({ player, score }) => {
26+
if (correctPlayerSocketIds.includes(player.socketId)) {
27+
return { player, score: score + pointsToAward };
28+
}
29+
30+
return { player, score };
31+
});
32+
};
33+
34+
const getUpdatedPlayerScoresAndBonusPoints = (
35+
currentBonusPoints: number,
36+
currentPlayerScores: PlayerScore[],
37+
correctPlayerSocketIds: Player["socketId"][],
38+
): { bonusPoints: number; playerScores: PlayerScore[] } => {
39+
if (allCorrect(currentPlayerScores.length, correctPlayerSocketIds)) {
40+
return {
41+
bonusPoints: currentBonusPoints + 1,
42+
playerScores: currentPlayerScores,
43+
};
44+
}
45+
46+
if (allIncorrect(correctPlayerSocketIds)) {
47+
return { bonusPoints: 0, playerScores: currentPlayerScores };
48+
}
49+
50+
return {
51+
bonusPoints: 0,
52+
playerScores: getUpdatedPlayerScores(
53+
currentPlayerScores,
54+
currentBonusPoints,
55+
correctPlayerSocketIds,
56+
),
57+
};
58+
};
59+
60+
const getCorrectSocketIdsFromAnswers = (
61+
answers: Answer[],
62+
correctAnswer: Question["colours"],
63+
) => {
64+
return answers
65+
.filter((answer) => {
66+
if (correctAnswer.length !== answer.colours.length) {
67+
return false;
68+
}
69+
70+
return correctAnswer.every((colour) => answer.colours.includes(colour));
71+
})
72+
.map((answer) => answer.socketId);
73+
};
74+
75+
export { getCorrectSocketIdsFromAnswers, getUpdatedPlayerScoresAndBonusPoints };

0 commit comments

Comments
 (0)