From a966c29a2e32ce885be59b1d42c72e1c8ee686e6 Mon Sep 17 00:00:00 2001 From: Iku-turso Date: Mon, 13 Mar 2023 15:29:39 +0200 Subject: [PATCH] Introduce more stages of the kata Co-authored-by: Janne Savolainen Signed-off-by: Iku-turso --- .../src/monster-beatdown.test.ts | 134 ++++++++++++++++-- .../kata-for-gabriel/src/monster-beatdown.ts | 58 +++++++- 2 files changed, 170 insertions(+), 22 deletions(-) diff --git a/packages/kata-for-gabriel/src/monster-beatdown.test.ts b/packages/kata-for-gabriel/src/monster-beatdown.test.ts index 2024ccd3cb..b52589aceb 100644 --- a/packages/kata-for-gabriel/src/monster-beatdown.test.ts +++ b/packages/kata-for-gabriel/src/monster-beatdown.test.ts @@ -1,18 +1,23 @@ import { createGame, Dependencies } from "./monster-beatdown"; import asyncFn, { AsyncFnMock } from "@async-fn/jest"; +import { getPromiseStatus } from "@k8slens/test-utils"; describe("monster-beatdown", () => { - let game: { start: () => void }; - let messageToPlayerMock: jest.Mocked; + let game: { start: () => Promise }; + let messageToPlayerMock: jest.Mock; let questionToPlayerMock: AsyncFnMock; + let castDieMock: AsyncFnMock; + let gamePromise: Promise; beforeEach(() => { messageToPlayerMock = jest.fn(); questionToPlayerMock = asyncFn(); + castDieMock = asyncFn(); game = createGame({ messageToPlayer: messageToPlayerMock, questionToPlayer: questionToPlayerMock, + castDie: castDieMock, }); }); @@ -24,50 +29,149 @@ describe("monster-beatdown", () => { it("does not question player about anything", () => { expect(questionToPlayerMock).not.toHaveBeenCalled(); }); + + it("no dice are cast", () => { + expect(castDieMock).not.toHaveBeenCalled(); + }); }); describe("when game is started", () => { beforeEach(() => { - game.start(); + gamePromise = game.start(); }); it("player encounters a monster", () => { - expect(messageToPlayerMock).toHaveBeenCalledWith("You encounter a monster"); + expect(messageToPlayerMock.mock.calls).toEqual([ + ["You encounter a monster with 3 hit-points"], + ]); }); it("player is asked if they wants to attack the monster", () => { - expect(questionToPlayerMock).toHaveBeenCalledWith("Attack the monster?"); + expect(questionToPlayerMock.mock.calls).toEqual([["Attack the monster?"]]); }); describe("when the player chooses to not attack the monster", () => { beforeEach(async () => { + messageToPlayerMock.mockClear(); + await questionToPlayerMock.resolve(false); }); it("the player gets eaten", () => { - expect(messageToPlayerMock).toHaveBeenCalledWith( - "You chose not to attack the monster, and the monster eats you dead, in disappointment.", - ); + expect(messageToPlayerMock.mock.calls).toEqual([ + [ + "You chose not to attack the monster, and the monster eats you dead, in disappointment.", + ], + ["You lose. Game over!"], + ]); }); - it("the player does not attack", () => { - expect(messageToPlayerMock).not.toHaveBeenCalledWith("You attack the monster."); + it("no dice are cast", () => { + expect(castDieMock).not.toHaveBeenCalled(); + }); + + it("the game ends", async () => { + const promiseStatus = await getPromiseStatus(gamePromise); + + expect(promiseStatus.fulfilled).toBe(true); }); }); describe("when the player chooses to attack the monster", () => { beforeEach(async () => { + messageToPlayerMock.mockClear(); + await questionToPlayerMock.resolve(true); }); it("the player attacks the monster", () => { - expect(messageToPlayerMock).toHaveBeenCalledWith("You attack the monster."); + expect(messageToPlayerMock.mock.calls).toEqual([["You attack the monster."]]); }); - it("the player does not get eaten yet", () => { - expect(messageToPlayerMock).not.toHaveBeenCalledWith( - "You chose not to attack the monster, and the monster eats you dead, in disappointment.", - ); + it("a die is cast", () => { + expect(castDieMock).toHaveBeenCalled(); + }); + + it("when the die lands on 4 or more, the monster loses a hit-point", async () => { + messageToPlayerMock.mockClear(); + + await castDieMock.resolve(4); + + expect(messageToPlayerMock.mock.calls).toEqual([ + [ + "You successfully land a hit on the monster, and it now only has 2 hit-points remaining.", + ], + ]); + }); + + it("when the die lands on 3 or less, the monster doesn't lose a hit-point", async () => { + messageToPlayerMock.mockClear(); + + await castDieMock.resolve(3); + + expect(messageToPlayerMock.mock.calls).toEqual([ + ["You fail to land a hit on the monster, and it still has 3 hit-points remaining."], + ]); + }); + + it("after the die lands, the player is asked to attack again", async () => { + questionToPlayerMock.mockClear(); + + await castDieMock.resolve(0); + + expect(questionToPlayerMock.mock.calls).toEqual([["Do you want to attack again?"]]); + }); + + describe("when the player hits, and chooses to keep hitting until the monster is reduced to 0 hit-points", () => { + beforeEach(async () => { + await castDieMock.resolve(4); + await questionToPlayerMock.resolve(true); + await castDieMock.resolve(4); + await questionToPlayerMock.resolve(true); + + messageToPlayerMock.mockClear(); + await castDieMock.resolve(4); + }); + + it("the player wins", () => { + expect(messageToPlayerMock.mock.calls).toEqual([ + ["You successfully land a final hit on the monster, and it is now properly beat."], + ["You win. Congratulations!"], + ]); + }); + + it("the game ends", async () => { + const promiseStatus = await getPromiseStatus(gamePromise); + + expect(promiseStatus.fulfilled).toBe(true); + }); + }); + + describe("given the player hits, and chooses to keep hitting until the monster is reduced to only 1 hit-points, when the player chooses to not hit the monster", () => { + beforeEach(async () => { + await castDieMock.resolve(4); + await questionToPlayerMock.resolve(true); + await castDieMock.resolve(4); + + messageToPlayerMock.mockClear(); + await questionToPlayerMock.resolve(false); + }); + + it("the player loses", async () => { + expect(messageToPlayerMock.mock.calls).toEqual([ + [ + "You lose your nerve mid-beat-down, and try to run away. You get eaten by a sad, disappointed monster.", + ], + + ["You lose. Game over!"], + ]); + }); + + it("the game ends", async () => { + const promiseStatus = await getPromiseStatus(gamePromise); + + expect(promiseStatus.fulfilled).toBe(true); + }); }); }); }); diff --git a/packages/kata-for-gabriel/src/monster-beatdown.ts b/packages/kata-for-gabriel/src/monster-beatdown.ts index a31ff9030c..ebd81a273c 100644 --- a/packages/kata-for-gabriel/src/monster-beatdown.ts +++ b/packages/kata-for-gabriel/src/monster-beatdown.ts @@ -1,20 +1,64 @@ export type Dependencies = { messageToPlayer: (message: string) => void; questionToPlayer: (message: string) => Promise; + castDie: () => Promise; }; -const createGame = ({ messageToPlayer, questionToPlayer }: Dependencies) => ({ +const createGame = ({ messageToPlayer, questionToPlayer, castDie }: Dependencies) => ({ start: async () => { - messageToPlayer("You encounter a monster"); + let monsterHitPoints = 3; + + messageToPlayer(`You encounter a monster with ${monsterHitPoints} hit-points`); const playerWantsToAttack = await questionToPlayer("Attack the monster?"); if (playerWantsToAttack) { - messageToPlayer("You attack the monster."); - } else { - messageToPlayer( - "You chose not to attack the monster, and the monster eats you dead, in disappointment.", - ); + while (true) { + messageToPlayer("You attack the monster."); + + const dieResult = await castDie(); + + const playerLandsHitOnMonster = dieResult > 3; + if (playerLandsHitOnMonster) { + monsterHitPoints--; + + if (!monsterHitPoints) { + messageToPlayer( + "You successfully land a final hit on the monster, and it is now properly beat.", + ); + + messageToPlayer("You win. Congratulations!"); + + return; + } + + messageToPlayer( + `You successfully land a hit on the monster, and it now only has ${monsterHitPoints} hit-points remaining.`, + ); + } else { + messageToPlayer( + `You fail to land a hit on the monster, and it still has ${monsterHitPoints} hit-points remaining.`, + ); + } + + const playerWantsToAttackAgain = await questionToPlayer("Do you want to attack again?"); + + if (!playerWantsToAttackAgain) { + messageToPlayer( + "You lose your nerve mid-beat-down, and try to run away. You get eaten by a sad, disappointed monster.", + ); + + messageToPlayer("You lose. Game over!"); + + return; + } + } } + + messageToPlayer( + "You chose not to attack the monster, and the monster eats you dead, in disappointment.", + ); + + messageToPlayer("You lose. Game over!"); }, });