diff --git a/models/round.js b/models/round.js new file mode 100644 index 0000000..e00af1e --- /dev/null +++ b/models/round.js @@ -0,0 +1,135 @@ +"use strict"; + +/** A specific team. + * @enum {number} + */ +export const Team = Object.freeze({ + /** The "we" team, from the perspective of the score keeper. */ + We: 1, + /** The "they" team, from the perspective of the score keeper. */ + They: 2, +}); + +/** A single round of watten. + * + * A game consists of multiple rounds, for each of which points can be won. + * Rounds are mostly independet from each other. The only bleedover is how + * often each team can raise the points available. + * + * This class is specifically meant to represent the current round, and is not + * ideal for storing past results. + * + * This project is not concerned with creating an online version of the game, + * the aim is to create a convenient score keeping system. Therefore this class + * only implements the raising mechanics, and no actual game play. + */ +export class Round { + /** The maximum the "we" team may raise to. */ + #weLimit = 11; + /** The maximum the "they" team may raise to. */ + #theyLimit = 11; + + constructor(weLimit, theyLimit) { + if (weLimit !== undefined && weLimit !== null) { + if (typeof weLimit !== "number") + throw new TypeError("if specified, `weLimit` must be a number"); + if (weLimit < this.#points) + throw new RangeError("`weLimit` must be larger than default points"); + this.#weLimit = weLimit; + } + + if (theyLimit !== undefined && theyLimit !== null) { + if (typeof theyLimit !== "number") + throw new TypeError("if specified, `theyLimit` must be a number"); + if (theyLimit < this.#points) + throw new RangeError("`theyLimit` must be larger than default points"); + this.#theyLimit = theyLimit; + } + } + + /** How many points the game is worth. */ + #points = 2; + + /** Get how many points the current game is worth. */ + get points() { + return this.#points; + } + + /** Which team raised last. + * @type {?Team} + */ + #raisedLast = null; + + /** Who won the round. + * @type {?Team} + */ + #winner = null; + + /** Get the winner of the round. + * + * @returns {?Team} The winning team, or `null` if the round is not yet + * decided. + */ + get winner() { + return this.#winner; + } + + /** Check whether the round has been decided. */ + get decided() { + return this.#winner !== null; + } + + /** A team has won the round. + * + * @param {Team} team The team that won the round. + */ + won(team) { + if (team !== Team.We && team !== Team.They) + throw new TypeError("only actual teams can win"); + if (this.decided) + throw new Error("decided round cannot be won again"); + + this.#winner = team; + } + + /** Check whether a team can raise. + * + * Note that this only checks if the team can raise. It does not check + * whether the team may raise. + * + * @param {Team} team The team to check for. + * @returns {boolean} Whether the team can raise. + */ + canRaise(team) { + if (team !== Team.We && team !== Team.They) + throw new TypeError("only actual teams can raise"); + return !this.decided && this.#raisedLast !== team; + } + + /** A team raises the points. + * + * Does nothing if the team cannot raise. Ends the round if a team raises + * that may not do so. Raises the points otherwise. + * + * @param {Team} team The team that wishes to raise. + */ + raise(team) { + if (team !== Team.We && team !== Team.They) + throw new TypeError("only actual teams can raise"); + + if (!this.canRaise(team)) return; + + if (team === Team.We && this.points >= this.#weLimit) { + this.#winner = Team.They; + return; + } + + if (team === Team.They && this.points >= this.#theyLimit) { + this.#winner = Team.We; + return; + } + + this.#raisedLast = team; + this.#points += 1; + } +} diff --git a/models/round.test.js b/models/round.test.js new file mode 100644 index 0000000..3dada8c --- /dev/null +++ b/models/round.test.js @@ -0,0 +1,95 @@ +"use strict"; + +import { Round, Team } from "./round.js"; + +QUnit.module("models", function() { + QUnit.module("round", function() { + QUnit.test("setup", function(assert) { + let round = new Round(); + assert.strictEqual(round.points, 2, "initial points"); + assert.strictEqual(round.winner, null, "no initial winner"); + assert.false(round.decided, "initially undecided"); + assert.true(round.canRaise(Team.We), "we initially can raise"); + assert.true(round.canRaise(Team.They), "they initially can raise"); + }); + + QUnit.test("immediate victory", function(assert) { + let round = new Round(); + round.won(Team.We); + assert.strictEqual(round.points, 2, "initial points"); + assert.true(round.decided, "there is a winner"); + assert.strictEqual(round.winner, Team.We, "correct winner"); + assert.false(round.canRaise(Team.We), "cannot raise finished game"); + assert.false(round.canRaise(Team.They), "cannot raise finished game"); + }); + + QUnit.test("multiple victories", function(assert) { + let round = new Round(); + round.won(Team.They); + assert.throws(function() { + round.won(Team.We); + }, "victory cannot be stolen"); + }); + + QUnit.test("single raise", function(assert) { + let round = new Round(); + round.raise(Team.We); + assert.strictEqual(round.points, 3, "raised points"); + assert.false(round.canRaise(Team.We), "raising team cannot raise"); + assert.true(round.canRaise(Team.They), "other team can raise"); + }); + + QUnit.test("double raise", function(assert) { + let round = new Round(); + round.raise(Team.We); + round.raise(Team.They); + assert.strictEqual(round.points, 4, "raised points"); + assert.true(round.canRaise(Team.We), "first raiser can raise"); + assert.false(round.canRaise(Team.They), "second raiser cannot raise"); + }); + + QUnit.test("raise to eleven and above", function(assert) { + let round = new Round(); + round.raise(Team.We); // 3 + round.raise(Team.They); // 4 + round.raise(Team.We); // 5 + round.raise(Team.They); // 6 + round.raise(Team.We); // 7 + round.raise(Team.They); // 8 + round.raise(Team.We); // 9 + + round.raise(Team.They); // 10 + assert.strictEqual(round.points, 10, "points before limit are reached"); + assert.false(round.decided, "round is not decided before limit"); + + round.raise(Team.We); // 11 + assert.strictEqual(round.points, 11, "points limit is reached"); + assert.false(round.decided, "round is not decidid at limit"); + + round.raise(Team.They); // 12 + assert.strictEqual(round.points, 11, "no invalid raise"); + assert.true(round.decided, "round is decided"); + assert.strictEqual(round.winner, Team.We, "we team won"); + assert.false(round.canRaise(Team.We), "winner cannot raise"); + assert.false(round.canRaise(Team.They), "looser cannot raise"); + }); + + QUnit.test("raise to lower limit and above", function(assert) { + let round = new Round(3, 4); + round.raise(Team.We); // 3 + assert.strictEqual(round.points, 3, "our points limit is reached"); + assert.false(round.decided, "round is not decidid at our limit"); + + round.raise(Team.They); // 4 + assert.strictEqual(round.points, 4, "their points limit is reached"); + assert.false(round.decided, "round is not decidid at their limit"); + + round.raise(Team.We); // 5 + assert.strictEqual(round.points, 4, "no invalid raise"); + assert.true(round.decided, "round is decided"); + assert.strictEqual(round.winner, Team.They, "they team won"); + assert.false(round.canRaise(Team.We), "looser cannot raise"); + assert.false(round.canRaise(Team.They), "winner cannot raise"); + }); + }); +}); diff --git a/test.html b/test.html index db578fd..8aceb3d 100644 --- a/test.html +++ b/test.html @@ -11,6 +11,7 @@
+