@gamepark/rules-api
Version:
API to implement the rules of a board game
644 lines • 29.4 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Material = void 0;
var isEqual_1 = __importDefault(require("lodash/isEqual"));
var maxBy_1 = __importDefault(require("lodash/maxBy"));
var minBy_1 = __importDefault(require("lodash/minBy"));
var orderBy_1 = __importDefault(require("lodash/orderBy"));
var sumBy_1 = __importDefault(require("lodash/sumBy"));
var location_1 = require("../location");
var moves_1 = require("../moves");
var index_1 = require("./index");
/**
* The Material class is the core helper class that help manipulate the game items in a simple way.
* It includes two kind of functions: functions to filter items, and functions to build {@link ItemMove} objects.
* Filter function will all return a new instance of Material with only the filtered items. This class is designed to be immutable.
*
* @typeparam P - identifier of a player. Either a number or a numeric enum (eg: PlayerColor)
* @typeparam M - Numeric enum of the types of material manipulated in the game
* @typeparam L - Numeric enum of the types of location in the game where the material can be located
*/
var Material = /** @class */ (function () {
/**
* Construct a new Material helper instance
* @param {number} type Type of items this instance will work on
* @param {MaterialItem[]} items The complete list of items of this type in current game state.
* @param {function} processMove if provided, this function will be executed on every move created with this instance
* @param {ItemEntry[]} entries The list of items to work on. Each entry consists of an array with the index of the item, and the item
*/
function Material(type, items, processMove, entries) {
if (items === void 0) { items = []; }
if (entries === void 0) { entries = Array.from(items.entries()).filter(function (entry) { return entry[1].quantity !== 0; }); }
this.type = type;
this.items = items;
this.processMove = processMove;
this.entries = entries;
}
/**
* Helper function to return a new instance of the same class (works also for children class)
* @param {ItemEntry[]} entries Filtered entries for the new class
* @returns {this} the new Material instance
* @protected
*/
Material.prototype.new = function (entries) {
var Class = this.constructor;
return new Class(this.type, this.items, this.processMove, entries);
};
/**
* Use this function to collect all the items in the current Material instance.
* For example, you can filter then collect the matching items: this.material(type).location(locationType).getItems()
*
* @typeparam Id - Identifier of the items (item.id)
* @param {function} predicate If provided, only the items matching the predicate will be returned
* @returns {MaterialItem[]} the items in this Material instance
*/
Material.prototype.getItems = function (predicate) {
var items = this.entries.map(function (entry) { return entry[1]; });
return predicate ? items.filter(predicate) : items;
};
/**
* Use this function to collect this first item in the current Material instance.
* You can use {@link sort} to sort the items first.
*
* @param {number | function} arg Index of the item, or predicate function
* @returns {MaterialItem | undefined} the item, or undefined if none match
*/
Material.prototype.getItem = function (arg) {
if (typeof arg === 'number') {
var entry = this.entries.find(function (entry) { return entry[0] === arg; });
if (!entry)
throw new Error("Could not find any item with index ".concat(arg, " for type ").concat(this.type));
return entry[1];
}
else if (typeof arg === 'function') {
var entries = this.entries.filter(function (_a) {
var item = _a[1];
return arg(item);
});
return entries.length ? entries[0][1] : undefined;
}
else {
return this.entries.length ? this.entries[0][1] : undefined;
}
};
/**
* @returns {number} index of the first item
*/
Material.prototype.getIndex = function () {
var _a, _b;
return (_b = (_a = this.entries[0]) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : -1;
};
/**
* @returns {number[]} indexes of the items
*/
Material.prototype.getIndexes = function () {
return this.entries.map(function (entry) { return entry[0]; });
};
/**
* Filter and return a new instance with only the items that match a specific index, or a specific index predicate
* @param {number | function} arg The index to keep, or the predicate matching the indexes to keep
*/
Material.prototype.index = function (arg) {
switch (typeof arg) {
case 'function':
return this.filter(function (_, index) { return arg(index); });
case 'number':
var item = this.entries.find(function (_a) {
var index = _a[0];
return index === arg;
});
return this.new(item ? [item] : []);
case 'undefined':
return this.new([]);
default:
var items = this.entries.filter(function (_a) {
var i = _a[0];
return arg.includes(i);
});
return this.new(items);
}
};
/**
* @deprecated Use {@link index} instead
*/
Material.prototype.indexes = function (indexes) {
var items = this.entries.filter(function (_a) {
var i = _a[0];
return indexes.includes(i);
});
return this.new(items);
};
Object.defineProperty(Material.prototype, "length", {
/**
* @returns {number} number of items
*/
get: function () {
return this.entries.length;
},
enumerable: false,
configurable: true
});
/**
* @returns {number} Sum of the quantity of all items
*/
Material.prototype.getQuantity = function () {
return (0, sumBy_1.default)(this.entries, function (_a) {
var _b;
var item = _a[1];
return (_b = item.quantity) !== null && _b !== void 0 ? _b : 1;
});
};
/**
* This function filter the items and returns a new instance with only the filtered items.
* This function is the top level filtering function, but other function can be used to simplify the code:
* {@link player}, {@link location}, {@link rotation}, {@link id}, {@link locationId}...
*
* @param {function} predicate The predicate function. Takes every item and index, and keep the item if it returns true
* @returns {this} New instance with only the items that match the predicate
*/
Material.prototype.filter = function (predicate) {
return this.new(this.entries.filter(function (_a) {
var index = _a[0], item = _a[1];
return predicate(item, index);
}));
};
/**
* Filters the items based on their ids.
*
* @param {function | string | number | Record} arg Id to keep, or predicate function to match the ids of the items to keep
* @returns {this} New instance with only the items which ids match the argument
*/
Material.prototype.id = function (arg) {
return this.filter(function (_a) {
var id = _a.id;
return typeof arg === 'function' ? arg(id) : (0, isEqual_1.default)(id, arg);
});
};
/**
* Filters the items based on their location type, or their location.
*
* @param {function | number} arg Location type to keep, or predicate function to match the location of the items to keep
* @returns {this} New instance with only the items which locations match the argument
*/
Material.prototype.location = function (arg) {
return this.filter(function (_a) {
var location = _a.location;
return typeof arg === 'function' ? arg(location) : location.type === arg;
});
};
/**
* Filters the items based on their rotation (item.location.rotation)
*
* @param {function | string | number | boolean | Record} arg rotation to keep, or predicate function to match the rotations of the items to keep
* @returns {this} New instance with only the items which rotation match the argument
*/
Material.prototype.rotation = function (arg) {
return this.location(function (_a) {
var rotation = _a.rotation;
return typeof arg === 'function' ? arg(rotation) : (0, isEqual_1.default)(rotation, arg);
});
};
/**
* Filters the items based on their owner (item.location.player)
*
* @param {function | number} arg player id to keep, or predicate function to match the owner player of the items to keep
* @returns {this} New instance with only the items which owner match the argument
*/
Material.prototype.player = function (arg) {
return this.location(function (_a) {
var player = _a.player;
return typeof arg === 'function' ? arg(player) : player === arg;
});
};
/**
* Filters the items based on their location's id (item.location.id)
*
* @param {function | number} arg location id to keep, or predicate function to match the location id of the items to keep
* @returns {this} New instance with only the items which location id match the argument
*/
Material.prototype.locationId = function (arg) {
return this.location(function (_a) {
var id = _a.id;
return typeof arg === 'function' ? arg(id) : (0, isEqual_1.default)(id, arg);
});
};
/**
* Filters the items based on their location's parent (item.location.parent).
*
* @param {function | number} arg location parent to keep, or predicate function to match the location parent of the items to keep
* @returns {this} New instance with only the items which location parent match the argument
*/
Material.prototype.parent = function (arg) {
return this.location(function (_a) {
var parent = _a.parent;
return typeof arg === 'function' ? arg(parent) : (0, isEqual_1.default)(parent, arg);
});
};
/**
* Filters the items that are selected (item.selected).
*
* @param {number | boolean} selected The selected value to compare (default is true)
* @returns {this} New instance with only the items which are selected
*/
Material.prototype.selected = function (selected) {
if (selected === void 0) { selected = true; }
return this.filter(function (item) { var _a; return ((_a = item.selected) !== null && _a !== void 0 ? _a : false) === selected; });
};
/**
* Keep only the item that has the minimum value returned by the selector argument.
* See {@link minBy} from Lodash
*
* @param {function} selector The function that evaluate the item's value
* @returns {this} New instance with only the item which has the minimum value
*/
Material.prototype.minBy = function (selector) {
var min = (0, minBy_1.default)(this.entries, function (entry) { return selector(entry[1]); });
return this.new(min ? [min] : []);
};
/**
* Keep only the item that has the maximum value returned by the selector argument.
* See {@link maxBy} from Lodash
*
* @param {function} selector The function that evaluate the item's value
* @returns {this} New instance with only the item which has the maximum value
*/
Material.prototype.maxBy = function (selector) {
var max = (0, maxBy_1.default)(this.entries, function (entry) { return selector(entry[1]); });
return this.new(max ? [max] : []);
};
/**
* Return a new material instance which items are ordered based on provided selector functions.
* See {@link orderBy} from Lodash
*
* @param {...function} selectors The function or functions that evaluate each item's value
* @returns {this} New instance with items ordered by the selector functions
*/
Material.prototype.sort = function () {
var selectors = [];
for (var _i = 0; _i < arguments.length; _i++) {
selectors[_i] = arguments[_i];
}
var orderedItems = (0, orderBy_1.default)(this.entries, selectors.map(function (s) { return function (entry) { return s(entry[1]); }; }));
return this.new(orderedItems);
};
/**
* Return a new material instance with only the first N items.
* You have to use {@link sort} first as the items are ordered initially by index, which is not relevant.
* Example: material.sort(item => !item.location.x!).limit(10)
*
* @param count Number of items to keep
* @returns {this} New instance with only the first "count" items
*/
Material.prototype.limit = function (count) {
return this.new(this.entries.slice(0, count));
};
Material.prototype.process = function (moves) {
if (this.processMove) {
for (var _i = 0, moves_2 = moves; _i < moves_2.length; _i++) {
var move = moves_2[_i];
this.processMove(move);
}
}
return moves;
};
/**
* Prepare a move that will create a new item
* @param {MaterialItem} item The item to create
* @returns {CreateItem} the move that creates an item when executed
*/
Material.prototype.createItem = function (item) {
return this.createItems([item])[0];
};
/**
* Prepare a list of moves to create new items
* @param {MaterialItem[]} items The items to create
* @returns {CreateItem[]} the moves that creates the new items when executed
*/
Material.prototype.createItems = function (items) {
var _this = this;
return this.process(items.map(function (item) { return ({
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.Create,
itemType: _this.type,
item: item
}); }));
};
/**
* Prepare one move to create new items
* @param {MaterialItem[]} items The items to create
* @returns {CreateItemsAtOnce} the move that creates the new items when executed
*/
Material.prototype.createItemsAtOnce = function (items) {
var move = { kind: moves_1.MoveKind.ItemMove, type: moves_1.ItemMoveType.CreateAtOnce, itemType: this.type, items: items };
return this.process([move])[0];
};
/**
* Prepare a move that will delete current first item in this material instance
*
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to remove. If undefined, the item is completely removed
* @returns {DeleteItem} the move that delete the item, or a part of its quantity, when executed
*/
Material.prototype.deleteItem = function (quantity) {
switch (this.length) {
case 0:
throw new Error('You are trying to delete an item that does not exists');
case 1:
return this.deleteItems(quantity)[0];
default:
return this.limit(1).deleteItems(quantity)[0];
}
};
/**
* Prepare moves that will delete all the items in this material instance
*
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to remove. If undefined, the items are completely removed
* @returns {DeleteItem[]} the moves that delete the items, or a part of their quantity, when executed
*/
Material.prototype.deleteItems = function (quantity) {
var _this = this;
return this.process(this.entries.map(function (entry) {
var move = {
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.Delete,
itemType: _this.type,
itemIndex: entry[0]
};
if (quantity)
move.quantity = quantity;
return move;
}));
};
/**
* Prepare one move that will delete all the items in this material instance
*
* @returns {DeleteItemsAtOnce} the move that delete the items when executed
*/
Material.prototype.deleteItemsAtOnce = function () {
var moves = { kind: moves_1.MoveKind.ItemMove, type: moves_1.ItemMoveType.DeleteAtOnce, itemType: this.type, indexes: this.getIndexes() };
return this.process([moves])[0];
};
/**
* Prepare a move that will change the location of the current first item in this material instance
*
* @param {Location | function} location The new location of the item. It can be a function to process the location based on the item current state.
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to move. If undefined, the item is completely moved.
* @returns {MoveItem} the move that will change the location of the item (or a part of its quantity) when executed
*/
Material.prototype.moveItem = function (location, quantity) {
switch (this.length) {
case 0:
throw new Error('You are trying to move an item that does not exists');
case 1:
return this.moveItems(location, quantity)[0];
default:
return this.limit(1).moveItems(location, quantity)[0];
}
};
/**
* Prepare moves that will change the location of all the items in this material instance
*
* @param {Location | function} location The new location of the items. It can be a function to process the location based on each item current state.
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to move. If undefined, the items are completely moved.
* @returns {MoveItem[]} the moves that will change the location of the items (or a part of their quantity) when executed
*/
Material.prototype.moveItems = function (location, quantity) {
var _this = this;
var getLocation = typeof location === 'function' ? location : function () { return location; };
return this.process(this.entries.map(function (entry) {
var location = getLocation(entry[1], entry[0]);
var move = {
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.Move,
itemType: _this.type,
itemIndex: entry[0],
location: location
};
if (quantity)
move.quantity = quantity;
return move;
}));
};
/**
* Prepare one move that will change the location of all the items in this material instance
*
* @param {Location} location The new location of the items. It can only be the same location for every item.
* @returns {MoveItemsAtOnce} the move that will change the location of the items when executed
*/
Material.prototype.moveItemsAtOnce = function (location) {
var move = {
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.MoveAtOnce,
itemType: this.type,
indexes: this.entries.map(function (_a) {
var index = _a[0];
return index;
}),
location: location
};
return this.process([move])[0];
};
/**
* Prepare a move that will select current first item in this material instance
*
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to select. If undefined, the item is completely selected.
* @returns {SelectItem} the move that will select the item (or a part of its quantity) when executed
*/
Material.prototype.selectItem = function (quantity) {
switch (this.length) {
case 0:
throw new Error('You are trying to select an item that does not exists');
case 1:
return this.selectItems(quantity)[0];
default:
return this.limit(1).selectItems(quantity)[0];
}
};
/**
* Prepare a move that will select all the items in this material instance
*
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to select. If undefined, the items are completely selected.
* @returns {SelectItem[]} the moves that will select the items (or a part of their quantity) when executed
*/
Material.prototype.selectItems = function (quantity) {
var _this = this;
return this.process(this.entries.map(function (entry) {
var move = {
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.Select,
itemType: _this.type,
itemIndex: entry[0]
};
if (quantity)
move.quantity = quantity;
return move;
}));
};
/**
* Prepare a move that will unselect current first item in this material instance
*
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to unselect. If undefined, the item is completely unselected.
* @returns {SelectItem} the move that will unselect the item (or a part of its quantity) when executed
*/
Material.prototype.unselectItem = function (quantity) {
switch (this.length) {
case 0:
throw new Error('You are trying to select an item that does not exists');
case 1:
return this.unselectItems(quantity)[0];
default:
return this.limit(1).unselectItems(quantity)[0];
}
};
/**
* Prepare a move that will unselect all the items in this material instance
*
* @param {number | undefined} quantity Optional: for items with a quantity, the number of items to unselect. If undefined, the items are completely unselected.
* @returns {SelectItem[]} the moves that will unselect the items (or a part of their quantity) when executed
*/
Material.prototype.unselectItems = function (quantity) {
var _this = this;
return this.process(this.entries.map(function (entry) {
var move = {
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.Select,
itemType: _this.type,
itemIndex: entry[0],
selected: false
};
if (quantity)
move.quantity = quantity;
return move;
}));
};
/**
* Prepare a move that will rotate current first item in this material instance.
* This function creates a {@link MoveItem} but copies the existing location values, only replacing the rotation property.
*
* @param {string | number | boolean | Record | function | undefined} arg Value of the rotation to give to the item.
* In case of a function, process the value based on the item state.
* @returns {MoveItem} the move that will rotate the item when executed
*/
Material.prototype.rotateItem = function (arg) {
switch (this.length) {
case 0:
throw new Error('You are trying to rotate an item that does not exists');
case 1:
return this.rotateItems(arg)[0];
default:
return this.limit(1).rotateItems(arg)[0];
}
};
/**
* Prepare a move that will rotate all the items in this material instance.
* This function creates an array of {@link MoveItem} but copies the existing locations values, only replacing the rotation property.
*
* @param {string | number | boolean | Record | function | undefined} arg Value of the rotation to give to the item.
* In case of a function, process the value based on the item state.
* @returns {MoveItem[]} the moves that will rotate the item when executed
*/
Material.prototype.rotateItems = function (arg) {
return this.moveItems(function (item) {
var rotation = typeof arg === 'function' ? arg(item) : arg;
var _a = item.location, oldRotation = _a.rotation, location = __rest(_a, ["rotation"]);
return rotation !== undefined ? (__assign(__assign({}, location), { rotation: rotation })) : location;
});
};
/**
* Prepare a move that will shuffle all the items in the current material instance
* @returns {Shuffle} the move that shuffle the items when executed
*/
Material.prototype.shuffle = function () {
var _this = this;
if (process.env.NODE_ENV === 'development' && this.entries.some(function (e) { return !(0, location_1.isSameLocationArea)(e[1].location, _this.entries[0][1].location); })) {
console.warn('Calling shuffle on items with different location areas might be a mistake.');
}
return this.process([{
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.Shuffle,
itemType: this.type,
indexes: this.entries.map(function (entry) { return entry[0]; })
}])[0];
};
/**
* Prepare a move that will roll the current first item in this material instance. See {@link RollItem}.
*
* @param {Location | function} location The new location of the item. It can be a function to process the location based on the item current state.
* @returns {RollItem} the move that rolls the item when executed
*/
Material.prototype.rollItem = function (location) {
switch (this.length) {
case 0:
throw new Error('You are trying to roll an item that does not exists');
case 1:
return this.rollItems(location)[0];
default:
return this.limit(1).rollItems(location)[0];
}
};
/**
* Prepare a move that will roll all the items in this material instance. See {@link RollItem}.
*
* @param {Location | function} location The new location of the items. It can be a function to process the location based on each item current state.
* @returns {RollItem[]} the moves that rolls the items when executed
*/
Material.prototype.rollItems = function (location) {
var _this = this;
if (location === void 0) { location = function (item) { return item.location; }; }
var getLocation = typeof location === 'function' ? location : function () { return location; };
return this.process(this.entries.map(function (entry) {
var location = getLocation(entry[1]);
return ({
kind: moves_1.MoveKind.ItemMove,
type: moves_1.ItemMoveType.Roll,
itemType: _this.type,
itemIndex: entry[0],
location: location
});
}));
};
/**
* Return a new {@link MaterialDeck} helper class, to deal cards easily.
*
* @param selector The sort to apply on the deck. See {@link sort}. Defaults to -item.location.x
*/
Material.prototype.deck = function (selector) {
if (selector === void 0) { selector = function (item) { return -item.location.x; }; }
return new index_1.MaterialDeck(this.type, this.items, this.processMove, this.entries).sort(selector);
};
/**
* Return a new {@link MaterialMoney} helper class, to deal with moving money units easily.
*
* @param units The different units that exists in stock to count this money
*/
Material.prototype.money = function (units) {
return new index_1.MaterialMoney(this.type, units, this.items, this.processMove, this.entries);
};
return Material;
}());
exports.Material = Material;
//# sourceMappingURL=Material.js.map