implement model for full games
This commit is contained in:
parent
13971d2073
commit
1cb03ef70c
147
models/game.js
Normal file
147
models/game.js
Normal file
@ -0,0 +1,147 @@
|
||||
"use strict";
|
||||
|
||||
import { Round, Team } from "./round.js";
|
||||
import RoundResult from "./round_result.js";
|
||||
|
||||
export default class Game {
|
||||
/** The finished rounds.
|
||||
* @type {RoundResult[]}
|
||||
*/
|
||||
#rounds = [];
|
||||
|
||||
/** Get the finished rounds.
|
||||
*
|
||||
* DO NOT write to the returned object.
|
||||
*/
|
||||
get rounds() {
|
||||
return this.#rounds;
|
||||
}
|
||||
|
||||
/** How many points a team needs to win. */
|
||||
#goal = 11;
|
||||
|
||||
/** Get how many points are needed to win. */
|
||||
get goal() {
|
||||
return this.#goal;
|
||||
}
|
||||
|
||||
/** The current round.
|
||||
* @type {?Round}
|
||||
*/
|
||||
#currentRound = null;
|
||||
|
||||
/** Get the current round of the game. */
|
||||
get currentRound() {
|
||||
return this.#currentRound;
|
||||
}
|
||||
|
||||
constructor(value) {
|
||||
if (value === undefined || typeof value === "number") {
|
||||
if (typeof value === "number")
|
||||
this.#goal = value;
|
||||
|
||||
this.#currentRound = new Round(this.#goal, this.#goal);
|
||||
this.#currentRound.addEventListener(
|
||||
Round.winEvent, this.#boundRoundFinishedHandler);
|
||||
} else if (typeof value === "object") {
|
||||
if (!("goal" in value))
|
||||
throw new TypeError("missing goal in deserialization object");
|
||||
if (typeof value.goal !== "number")
|
||||
throw new TypeError("goal in deserialization object must be number");
|
||||
this.#goal = value.goal;
|
||||
|
||||
if (!("rounds" in value))
|
||||
throw new TypeError("missing rounds in deserialization object");
|
||||
if (!Array.isArray(value.rounds))
|
||||
throw new TypeError("rounds in deserialization object must be array");
|
||||
for (let r of value.rounds)
|
||||
this.#rounds.push(new RoundResult(r));
|
||||
|
||||
if (!("currentRound" in value))
|
||||
throw new TypeError("missing currentRound in deserialization object");
|
||||
if (this.result.winner === null)
|
||||
this.#currentRound = new Round(value.currentRound);
|
||||
else if (value.currentRound !== null)
|
||||
throw new TypeError("currentRound in finished game must be null");
|
||||
} else {
|
||||
throw new TypeError("unknown form of Game constructor");
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the results of the game. */
|
||||
get result() {
|
||||
let ourPoints = 0;
|
||||
let theirPoints = 0;
|
||||
let tailor = null;
|
||||
const tailorGoal = this.#goal - 2;
|
||||
|
||||
for (let r of this.#rounds) {
|
||||
if (r.winner === Team.We)
|
||||
ourPoints += r.points;
|
||||
else if (r.winner === Team.They)
|
||||
theirPoints += r.points;
|
||||
|
||||
if (tailor === null && (
|
||||
(ourPoints >= tailorGoal && theirPoints === 0)
|
||||
|| (theirPoints >= tailorGoal && ourPoints === 0)))
|
||||
{
|
||||
tailor = r.winner;
|
||||
}
|
||||
}
|
||||
|
||||
let weWon = ourPoints >= this.goal;
|
||||
let theyWon = theirPoints >= this.goal;
|
||||
let winner;
|
||||
|
||||
if (!weWon && !theyWon) {
|
||||
return {winner: null, points: 0, ourPoints, theirPoints};
|
||||
} else if (weWon && theyWon) {
|
||||
throw new Error("game with multiple winners");
|
||||
} else if (weWon) {
|
||||
winner = Team.We;
|
||||
} else {
|
||||
winner = Team.They;
|
||||
}
|
||||
|
||||
let points;
|
||||
if (tailor !== null && winner !== tailor) {
|
||||
points = 4;
|
||||
} else if (tailor !== null && winner === tailor) {
|
||||
points = 2;
|
||||
} else {
|
||||
points = 1;
|
||||
}
|
||||
|
||||
return {winner, points, ourPoints, theirPoints};
|
||||
}
|
||||
|
||||
/** Handle it when the current round is finished. */
|
||||
#handleRoundFinished() {
|
||||
this.#currentRound.removeEventListener(
|
||||
Round.winEvent, this.#boundRoundFinishedHandler);
|
||||
this.#rounds.push(
|
||||
new RoundResult(this.#currentRound.points, this.#currentRound.winner));
|
||||
this.#currentRound = null;
|
||||
|
||||
let result = this.result;
|
||||
|
||||
if (result.winner === null) {
|
||||
this.#currentRound = new Round(
|
||||
Math.max(this.#goal - result.ourPoints, 2),
|
||||
Math.max(this.#goal - result.theirPoints, 2));
|
||||
this.#currentRound.addEventListener(
|
||||
Round.winEvent, this.#boundRoundFinishedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
#boundRoundFinishedHandler = this.#handleRoundFinished.bind(this);
|
||||
|
||||
/** Export needed data for JSON serialization. */
|
||||
toJSON() {
|
||||
return {
|
||||
goal: this.#goal,
|
||||
rounds: this.#rounds,
|
||||
currentRound: this.#currentRound,
|
||||
};
|
||||
}
|
||||
}
|
||||
346
models/game.test.js
Normal file
346
models/game.test.js
Normal file
@ -0,0 +1,346 @@
|
||||
"use strict";
|
||||
|
||||
import { Round, Team } from "./round.js";
|
||||
import RoundResult from "./round_result.js";
|
||||
import Game from "./game.js";
|
||||
|
||||
QUnit.module("models", function() {
|
||||
QUnit.module("game", function() {
|
||||
QUnit.test("default construction", function(assert) {
|
||||
let game = new Game();
|
||||
assert.strictEqual(game.rounds.length, 0, "no past rounds");
|
||||
assert.equal(game.goal, 11, "default goal");
|
||||
assert.notStrictEqual(game.currentRound, null, "current round there");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: null,
|
||||
points: 0,
|
||||
ourPoints: 0,
|
||||
theirPoints: 0
|
||||
},
|
||||
"initial results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("higher goal", function(assert) {
|
||||
let game = new Game(15);
|
||||
assert.strictEqual(game.rounds.length, 0, "no past rounds");
|
||||
assert.equal(game.goal, 15, "higher goal");
|
||||
assert.notStrictEqual(game.currentRound, null, "current round there");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: null,
|
||||
points: 0,
|
||||
ourPoints: 0,
|
||||
theirPoints: 0
|
||||
},
|
||||
"initial results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("single round played", function(assert) {
|
||||
let game = new Game();
|
||||
game.currentRound.winner = Team.We;
|
||||
|
||||
assert.equal(game.rounds.length, 1, "one round played");
|
||||
assert.deepEqual(
|
||||
game.rounds[0].toJSON(),
|
||||
{ points: 2, winner: Team.We},
|
||||
"first round correct");
|
||||
assert.notStrictEqual(game.currentRound, null, "current round there");
|
||||
assert.false(game.currentRound.decided, "current round is not decided");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: null,
|
||||
points: 0,
|
||||
ourPoints: 2,
|
||||
theirPoints: 0
|
||||
},
|
||||
"intermediate results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("two rounds played", function(assert) {
|
||||
let game = new Game();
|
||||
game.currentRound.winner = Team.We;
|
||||
game.currentRound.raise(Team.We);
|
||||
game.currentRound.winner = Team.They;
|
||||
|
||||
assert.equal(game.rounds.length, 2, "two round played");
|
||||
assert.deepEqual(
|
||||
game.rounds[1].toJSON(),
|
||||
{ points: 3, winner: Team.They},
|
||||
"second round correct");
|
||||
assert.notStrictEqual(game.currentRound, null, "current round there");
|
||||
assert.false(game.currentRound.decided, "current round is not decided");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: null,
|
||||
points: 0,
|
||||
ourPoints: 2,
|
||||
theirPoints: 3
|
||||
},
|
||||
"intermediate results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("regular victory", function(assert) {
|
||||
let game = new Game();
|
||||
game.currentRound.winner = Team.We; // 2
|
||||
game.currentRound.winner = Team.They; // 2
|
||||
game.currentRound.winner = Team.We; // 4
|
||||
game.currentRound.winner = Team.We; // 6
|
||||
game.currentRound.winner = Team.We; // 8
|
||||
game.currentRound.winner = Team.We; // 10
|
||||
game.currentRound.winner = Team.We; // 12
|
||||
|
||||
assert.equal(game.rounds.length, 7, "seven rounds played");
|
||||
assert.strictEqual(game.currentRound, null, "no further rounds");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: Team.We,
|
||||
points: 1,
|
||||
ourPoints: 12,
|
||||
theirPoints: 2,
|
||||
},
|
||||
"final results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("tailor victory", function(assert) {
|
||||
let game = new Game();
|
||||
game.currentRound.winner = Team.They; // 2
|
||||
game.currentRound.winner = Team.They; // 4
|
||||
game.currentRound.winner = Team.They; // 6
|
||||
game.currentRound.winner = Team.They; // 8
|
||||
game.currentRound.winner = Team.They; // 10
|
||||
game.currentRound.winner = Team.They; // 12
|
||||
|
||||
assert.equal(game.rounds.length, 6, "seven rounds played");
|
||||
assert.strictEqual(game.currentRound, null, "no further rounds");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: Team.They,
|
||||
points: 2,
|
||||
ourPoints: 0,
|
||||
theirPoints: 12,
|
||||
},
|
||||
"final results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("reverse tailor victory", function(assert) {
|
||||
let game = new Game();
|
||||
game.currentRound.winner = Team.We; // 2
|
||||
game.currentRound.winner = Team.We; // 4
|
||||
game.currentRound.winner = Team.We; // 6
|
||||
game.currentRound.winner = Team.We; // 8
|
||||
game.currentRound.winner = Team.We; // 10
|
||||
game.currentRound.winner = Team.They; // 2
|
||||
game.currentRound.winner = Team.They; // 4
|
||||
game.currentRound.winner = Team.They; // 6
|
||||
game.currentRound.winner = Team.They; // 8
|
||||
game.currentRound.winner = Team.They; // 10
|
||||
game.currentRound.winner = Team.They; // 12
|
||||
|
||||
assert.equal(game.rounds.length, 11, "eleven rounds played");
|
||||
assert.strictEqual(game.currentRound, null, "no further rounds");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: Team.They,
|
||||
points: 4,
|
||||
ourPoints: 10,
|
||||
theirPoints: 12,
|
||||
},
|
||||
"final results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("reverse tailor victory with low goal", function(assert) {
|
||||
let game = new Game(3);
|
||||
game.currentRound.winner = Team.They; // 2
|
||||
game.currentRound.winner = Team.We; // 2
|
||||
game.currentRound.winner = Team.We; // 4
|
||||
|
||||
assert.equal(game.rounds.length, 3, "three rounds played");
|
||||
assert.strictEqual(game.currentRound, null, "no further rounds");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: Team.We,
|
||||
points: 4,
|
||||
ourPoints: 4,
|
||||
theirPoints: 2,
|
||||
},
|
||||
"final results",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("serialization - unfinished", function(assert) {
|
||||
let game = new Game();
|
||||
game.currentRound.winner = Team.We;
|
||||
game.currentRound.raise(Team.They);
|
||||
game.currentRound.winner = Team.They;
|
||||
game.currentRound.raise(Team.We);
|
||||
|
||||
let json = game.toJSON();
|
||||
json.currentRound = json.currentRound.toJSON();
|
||||
for (let i = 0; i < json.rounds.length; i++)
|
||||
json.rounds[i] = json.rounds[i].toJSON();
|
||||
|
||||
assert.deepEqual(
|
||||
json,
|
||||
{
|
||||
goal: 11,
|
||||
rounds: [
|
||||
{ points: 2, winner: Team.We },
|
||||
{ points: 3, winner: Team.They },
|
||||
],
|
||||
currentRound: {
|
||||
points: 3,
|
||||
raisedLast: Team.We,
|
||||
winner: null,
|
||||
weLimit: 9,
|
||||
theyLimit: 8,
|
||||
},
|
||||
},
|
||||
"serialized data"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("serialization - finished", function(assert) {
|
||||
let game = new Game(3);
|
||||
game.currentRound.winner = Team.We;
|
||||
game.currentRound.raise(Team.They);
|
||||
game.currentRound.winner = Team.They;
|
||||
|
||||
let json = game.toJSON();
|
||||
for (let i = 0; i < json.rounds.length; i++)
|
||||
json.rounds[i] = json.rounds[i].toJSON();
|
||||
|
||||
assert.deepEqual(
|
||||
json,
|
||||
{
|
||||
goal: 3,
|
||||
rounds: [
|
||||
{ points: 2, winner: Team.We },
|
||||
{ points: 3, winner: Team.They },
|
||||
],
|
||||
currentRound: null,
|
||||
},
|
||||
"serialized data"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("deserialize - unfinished", function(assert) {
|
||||
let game = new Game({
|
||||
goal: 3,
|
||||
rounds: [{ winner: Team.We, points: 2 }],
|
||||
currentRound: {
|
||||
points: 3,
|
||||
raisedLast: Team.They,
|
||||
winner: null,
|
||||
weLimit: 2,
|
||||
theyLimit: 3,
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(game.goal, 3, "goal");
|
||||
assert.strictEqual(game.rounds.length, 1, "one round played");
|
||||
assert.deepEqual(
|
||||
game.rounds[0].toJSON(),
|
||||
{ winner: Team.We, points: 2 },
|
||||
"correct past round");
|
||||
assert.deepEqual(
|
||||
game.currentRound.toJSON(),
|
||||
{
|
||||
points: 3,
|
||||
raisedLast: Team.They,
|
||||
winner: null,
|
||||
weLimit: 2,
|
||||
theyLimit: 3,
|
||||
},
|
||||
"correct current round");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: null,
|
||||
points: 0,
|
||||
ourPoints: 2,
|
||||
theirPoints: 0,
|
||||
},
|
||||
"intermediate results");
|
||||
});
|
||||
|
||||
QUnit.test("deserialize - finished", function(assert) {
|
||||
let game = new Game({
|
||||
goal: 3,
|
||||
rounds: [{ winner: Team.They, points: 3 }],
|
||||
currentRound: null,
|
||||
});
|
||||
|
||||
assert.strictEqual(game.goal, 3, "goal");
|
||||
assert.strictEqual(game.rounds.length, 1, "one round played");
|
||||
assert.deepEqual(
|
||||
game.rounds[0].toJSON(),
|
||||
{ winner: Team.They, points: 3 },
|
||||
"correct past round");
|
||||
assert.strictEqual(game.currentRound, null, "no current round");
|
||||
assert.deepEqual(
|
||||
game.result,
|
||||
{
|
||||
winner: Team.They,
|
||||
points: 2,
|
||||
ourPoints: 0,
|
||||
theirPoints: 3,
|
||||
},
|
||||
"final results");
|
||||
});
|
||||
|
||||
QUnit.test("deserialize - invalid", function(assert) {
|
||||
let deso = {};
|
||||
assert.throws(function() { new Game(deso); }, "no goal");
|
||||
|
||||
deso.goal = "5";
|
||||
assert.throws(function() { new Game(deso); }, "string goal");
|
||||
|
||||
deso.goal = 5;
|
||||
assert.throws(function() { new Game(deso); }, "no rounds");
|
||||
|
||||
deso.rounds = ["nonono"];
|
||||
assert.throws(function() { new Game(deso); }, "string rounds");
|
||||
|
||||
deso.rounds = [];
|
||||
assert.throws(function() { new Game(deso); }, "no currentRound");
|
||||
|
||||
deso.currentRound = null;
|
||||
assert.throws(function() { new Game(deso); }, "missing currentRound");
|
||||
|
||||
deso.currentRound = "nonono";
|
||||
assert.throws(function() { new Game(deso); }, "broken currentRound");
|
||||
|
||||
deso.rounds = [{ winner: Team.We, points: 5 }];
|
||||
deso.currentRound = {
|
||||
points: 2,
|
||||
raisedLast: Team.They,
|
||||
winner: null,
|
||||
weLimit: 2,
|
||||
theyLimit: 5};
|
||||
assert.throws(function() { new Game(deso); }, "unneeded currentRound");
|
||||
|
||||
deso.goal = 11;
|
||||
new Game(deso);
|
||||
|
||||
deso.goal = 5;
|
||||
deso.currentRound = null;
|
||||
new Game(deso);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user