163 lines
4.5 KiB
JavaScript
163 lines
4.5 KiB
JavaScript
"use strict";
|
|
|
|
import { Round, Team } from "./round.js";
|
|
import RoundResult from "./round_result.js";
|
|
|
|
/** A single game of watten.
|
|
*
|
|
* A game consists of several rounds, and continues until either team reaches
|
|
* a points goal.
|
|
*
|
|
* This class keeps track of individual rounds and their results, and sets up
|
|
* new ones until the game is finished. It also has a `results` property, that
|
|
* calculates who won and how many points they earned.
|
|
*/
|
|
export default class Game extends EventTarget {
|
|
/** The event triggered when the game is finished. */
|
|
static finishedEvent = "gameFinished";
|
|
|
|
/** 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) {
|
|
super();
|
|
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);
|
|
} else {
|
|
this.dispatchEvent(new CustomEvent(Game.finishedEvent));
|
|
}
|
|
}
|
|
|
|
#boundRoundFinishedHandler = this.#handleRoundFinished.bind(this);
|
|
|
|
/** Export needed data for JSON serialization. */
|
|
toJSON() {
|
|
return {
|
|
goal: this.#goal,
|
|
rounds: this.#rounds,
|
|
currentRound: this.#currentRound,
|
|
};
|
|
}
|
|
}
|