1
0

implement model for rounds

This commit is contained in:
Adrian Wannenmacher 2026-02-08 23:00:05 +01:00
parent b9ded8c367
commit 41823cd8ad
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
3 changed files with 231 additions and 0 deletions

135
models/round.js Normal file
View File

@ -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;
}
}

95
models/round.test.js Normal file
View File

@ -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");
});
});
});

View File

@ -11,6 +11,7 @@
<div id="qunit"></div> <div id="qunit"></div>
<div id="qunit-fixture"></div> <div id="qunit-fixture"></div>
<script src="vendored/qunit-2.25.0.js"></script> <script src="vendored/qunit-2.25.0.js"></script>
<script src="models/round.test.js" type="module"></script>
</body> </body>
</html> </html>