129 lines
4.1 KiB
JavaScript
129 lines
4.1 KiB
JavaScript
"use strict";
|
|
|
|
/** Rules for how long teams can raise. */
|
|
export const RaisingRule = Object.freeze({
|
|
/** Teams can raìse unless they are stricken.
|
|
*
|
|
* This means a team can win until it needs two points or less to win the
|
|
* game. This corresponds to the starting amount of points.
|
|
*/
|
|
UnlessStricken: 1,
|
|
/** Teams can raise until they would win the game if they won the round. */
|
|
UntilEnough: 2,
|
|
|
|
/** Check if the passed value is a raising rule.
|
|
*
|
|
* @param {RaisingRule} rule The rule to check.
|
|
* @returns {boolean} Whether the value is a raising rule.
|
|
*/
|
|
isRaisingRule(rule) {
|
|
return (rule === RaisingRule.UnlessStricken)
|
|
|| (rule === RaisingRule.UntilEnough);
|
|
}
|
|
});
|
|
|
|
/** The rules of a specific game. */
|
|
export default class GameRules extends EventTarget {
|
|
/** The event triggered when something about the game rules changes. */
|
|
static get EVENT_CHANGE() { return "wb:game_rules:change"};
|
|
|
|
/** The points target needed to win a round. */
|
|
#goal = 11;
|
|
|
|
/** Get the points target needed to win a round. */
|
|
get goal() {
|
|
return this.#goal;
|
|
}
|
|
|
|
/** Set the points target needed to win a round.
|
|
*
|
|
* Must be at least 1.
|
|
*/
|
|
set goal(value) {
|
|
if (!Number.isInteger(value))
|
|
throw new TypeError("goal must be an integer value");
|
|
if (value < 1)
|
|
throw new RangeError("goal must be at least one");
|
|
this.#goal = value;
|
|
this.dispatchEvent(new CustomEvent(GameRules.EVENT_CHANGE));
|
|
}
|
|
|
|
/** The rules about how long teams can raise. */
|
|
#raising = RaisingRule.UnlessStricken;
|
|
|
|
/** Get the rules about how long teams can raise. */
|
|
get raising() {
|
|
return this.#raising;
|
|
}
|
|
|
|
/** Set the rules about how long teams can raise. */
|
|
set raising(value) {
|
|
if (!RaisingRule.isRaisingRule(value))
|
|
throw new TypeError("raising rule must be actual raising rule");
|
|
this.#raising = value;
|
|
this.dispatchEvent(new CustomEvent(GameRules.EVENT_CHANGE));
|
|
}
|
|
|
|
constructor(value) {
|
|
super();
|
|
if (value === undefined){
|
|
} else if (value instanceof GameRules) {
|
|
this.goal = value.goal;
|
|
this.raising = value.raising;
|
|
} else if (typeof value === "object") {
|
|
this.#fromStruct(value);
|
|
} else {
|
|
throw new TypeError("unknown form of GameRules constructor");
|
|
}
|
|
}
|
|
|
|
/** Calculate to what number a team can raise the points.
|
|
*
|
|
* @param {number} currentPoints The teams current points.
|
|
* @returns {number} The target to which the team can raise.
|
|
*/
|
|
raisingLimit(currentPoints) {
|
|
if (this.#raising === RaisingRule.UnlessStricken)
|
|
return (currentPoints >= (this.#goal - 2)) ? 2 : Number.MAX_SAFE_INTEGER;
|
|
if (this.#raising === RaisingRule.UntilEnough)
|
|
return Math.max(this.#goal - currentPoints, 2);
|
|
throw new TypeError("unknown raising rule");
|
|
}
|
|
|
|
/** Export the data of this `GameRules` as a plain JS object with fields.
|
|
*
|
|
* The internals of the returned object are not stabilized, even if they are
|
|
* visible. It should be treated as opaque.
|
|
*
|
|
* There are only two stabile uses of the object:
|
|
* 1. It can be passed to the `GameRules` constructor as a single argument.
|
|
* The constructor will then create a behaviourally identical instance to
|
|
* the one from which the object was created. This is guaranteed to be
|
|
* backwards compatible, i.e. a revised version of this class can still
|
|
* use the objects created by an older version.
|
|
* 2. It can be stored using IndexedDB.
|
|
*/
|
|
toStruct() {
|
|
return {
|
|
goal: this.#goal,
|
|
raising: this.#raising,
|
|
};
|
|
}
|
|
|
|
/** Read in an object created by `GameRules.toStruct` */
|
|
#fromStruct(value) {
|
|
if (typeof value !== "object")
|
|
throw new TypeError("struct must be an object");
|
|
|
|
if (typeof value.goal !== "number")
|
|
throw new TypeError("struct must contain goal as number");
|
|
if (!Number.isInteger(value.goal) || value.goal < 1)
|
|
throw new RangeError("struct must contain goal >= 1 as integer");
|
|
this.#goal = value.goal;
|
|
|
|
if (!("raising" in value) || !RaisingRule.isRaisingRule(value.raising))
|
|
throw new TypeError("struct must contain valid raising rule");
|
|
this.#raising = value.raising;
|
|
}
|
|
}
|