@gamepark/rules-api
Version:
API to implement the rules of a board game
311 lines • 13.9 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.MaterialMutator = void 0;
var isEqual_1 = __importDefault(require("lodash/isEqual"));
var merge_1 = __importDefault(require("lodash/merge"));
var location_1 = require("../location");
var moves_1 = require("../moves");
var index_1 = require("./index");
/**
* Helper class to change the state of any {@link MaterialItem} in a game implemented with {@link MaterialRules}.
*
* @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 MaterialMutator = /** @class */ (function () {
/**
* @param type Type of items this mutator will work on
* @param items Items to work with
* @param locationsStrategies The strategies that these items must follow
* @param canMerge Whether to items at the exact same location can merge into one item with a quantity
* @param rulesClassName Constructor name of the main rules class for logging
*/
function MaterialMutator(type, items, locationsStrategies, canMerge, rulesClassName) {
if (locationsStrategies === void 0) { locationsStrategies = {}; }
if (canMerge === void 0) { canMerge = true; }
if (rulesClassName === void 0) { rulesClassName = ''; }
this.type = type;
this.items = items;
this.locationsStrategies = locationsStrategies;
this.canMerge = canMerge;
this.rulesClassName = rulesClassName;
}
/**
* Executes a move on the game items
* @param move
*/
MaterialMutator.prototype.applyMove = function (move) {
switch (move.type) {
case moves_1.ItemMoveType.Create:
this.create(move);
break;
case moves_1.ItemMoveType.CreateAtOnce:
for (var _i = 0, _a = move.items; _i < _a.length; _i++) {
var item = _a[_i];
this.create(__assign(__assign({}, move), { type: moves_1.ItemMoveType.Create, item: item }));
}
break;
case moves_1.ItemMoveType.Move:
this.move(move);
break;
case moves_1.ItemMoveType.MoveAtOnce:
this.moveItemsAtOnce(move);
break;
case moves_1.ItemMoveType.Roll:
this.roll(move);
break;
case moves_1.ItemMoveType.Delete:
this.delete(move);
break;
case moves_1.ItemMoveType.DeleteAtOnce:
for (var _b = 0, _c = move.indexes; _b < _c.length; _b++) {
var index = _c[_b];
this.removeItem(this.items[index], Infinity);
}
break;
case moves_1.ItemMoveType.Shuffle:
this.shuffle(move);
break;
case moves_1.ItemMoveType.Select:
this.select(move);
break;
}
};
/**
* Find the index of an existing item we could merge a new item with (create a single item with a quantity)
*
* @param newItem An item to compare with existing items
* @returns {number} Index of the existing item we can merge with, or -1 if there is no possible merge
*/
MaterialMutator.prototype.findMergeIndex = function (newItem) {
if (!this.canMerge)
return -1;
var q1 = newItem.quantity, data1 = __rest(newItem, ["quantity"]);
return this.items.findIndex(function (_a) {
var q2 = _a.quantity, data2 = __rest(_a, ["quantity"]);
return q1 !== 0 && q2 !== 0 && (0, isEqual_1.default)(data1, data2);
});
};
MaterialMutator.prototype.addItem = function (item) {
this.applyAddItemStrategy(item);
var availableIndex = this.items.findIndex(function (item) { return item.quantity === 0; });
if (availableIndex !== -1) {
this.items[availableIndex] = item;
}
else {
this.items.push(item);
}
};
/**
* Provides the index that the new item will have
* @param newItem An item that is going to be created
* @returns {number} the future index of that item
*/
MaterialMutator.prototype.getItemCreationIndex = function (newItem) {
var mergeIndex = this.findMergeIndex(newItem);
if (mergeIndex !== -1)
return mergeIndex;
var availableIndex = this.items.findIndex(function (item) { return item.quantity === 0; });
if (availableIndex !== -1)
return availableIndex;
return this.items.length;
};
MaterialMutator.prototype.applyAddItemStrategy = function (item) {
if (item.location.type in this.locationsStrategies) {
var strategy = this.locationsStrategies[item.location.type];
if (strategy.addItem) {
var material = new index_1.Material(this.type, this.items)
.location(item.location.type).player(item.location.player).locationId(item.location.id).parent(item.location.parent);
strategy.addItem(material, item);
}
}
};
MaterialMutator.prototype.applyMoveItemStrategy = function (item, index) {
if (item.location.type in this.locationsStrategies) {
var strategy = this.locationsStrategies[item.location.type];
if (strategy.moveItem) {
var material = new index_1.Material(this.type, this.items)
.location(item.location.type).player(item.location.player).locationId(item.location.id).parent(item.location.parent);
strategy.moveItem(material, item, index);
}
}
};
MaterialMutator.prototype.removeItem = function (item, quantity) {
var _a;
if (quantity === void 0) { quantity = 1; }
item.quantity = Math.max(0, ((_a = item.quantity) !== null && _a !== void 0 ? _a : 1) - quantity);
if (item.quantity === 0) {
this.applyRemoveItemStrategy(item);
}
};
MaterialMutator.prototype.applyRemoveItemStrategy = function (item) {
if (item.location.type in this.locationsStrategies) {
var strategy = this.locationsStrategies[item.location.type];
if (strategy.removeItem) {
var material = new index_1.Material(this.type, this.items)
.location(item.location.type).player(item.location.player).locationId(item.location.id).parent(item.location.parent);
strategy.removeItem(material, item);
}
}
};
MaterialMutator.prototype.create = function (move) {
var _a, _b;
var mergeIndex = this.findMergeIndex(move.item);
if (mergeIndex !== -1) {
var mergeItem = this.items[mergeIndex];
mergeItem.quantity = ((_a = mergeItem.quantity) !== null && _a !== void 0 ? _a : 1) + ((_b = move.item.quantity) !== null && _b !== void 0 ? _b : 1);
}
else {
if (move.item.quantity && !this.canMerge) {
console.error("".concat(this.rulesClassName, ": do not use quantity on items that cannot merge. Items that can be hidden cannot merge."));
}
this.addItem(JSON.parse(JSON.stringify(move.item)));
}
};
MaterialMutator.prototype.move = function (move) {
var _a, _b;
var quantity = (_a = move.quantity) !== null && _a !== void 0 ? _a : 1;
var sourceItem = this.items[move.itemIndex];
var itemAfterMove = this.getItemAfterMove(move);
var mergeIndex = this.findMergeIndex(itemAfterMove);
if (mergeIndex !== -1) {
if (mergeIndex === move.itemIndex) {
console.warn("".concat(this.rulesClassName, ": item is moved to the location he already has - ").concat(JSON.stringify(move)));
}
else {
var mergeItem = this.items[mergeIndex];
mergeItem.quantity = ((_b = mergeItem.quantity) !== null && _b !== void 0 ? _b : 1) + quantity;
this.removeItem(sourceItem, quantity);
}
}
else if (sourceItem.quantity && sourceItem.quantity > quantity) {
sourceItem.quantity -= quantity;
this.addItem(itemAfterMove);
}
else {
this.moveItem(sourceItem, itemAfterMove, move.itemIndex);
}
};
MaterialMutator.prototype.roll = function (move) {
var sourceItem = this.items[move.itemIndex];
var itemAfterMove = __assign(__assign({}, sourceItem), { location: JSON.parse(JSON.stringify(move.location)) });
this.moveItem(sourceItem, itemAfterMove, move.itemIndex);
};
MaterialMutator.prototype.moveItem = function (item, newItem, index) {
if (!(0, location_1.isSameLocationArea)(newItem.location, item.location)) {
this.applyAddItemStrategy(newItem);
}
else {
this.applyMoveItemStrategy(newItem, index);
}
this.items[index] = newItem;
if (!(0, location_1.isSameLocationArea)(newItem.location, item.location)) {
this.applyRemoveItemStrategy(item);
}
};
MaterialMutator.prototype.moveItemsAtOnce = function (move) {
for (var _i = 0, _a = move.indexes; _i < _a.length; _i++) {
var index = _a[_i];
var sourceItem = this.items[index];
var itemAfterMove = this.getItemAfterMoveAtOnce(move, index);
if (!(0, location_1.isSameLocationArea)(itemAfterMove.location, sourceItem.location)) {
this.applyAddItemStrategy(itemAfterMove);
}
else {
this.applyMoveItemStrategy(itemAfterMove, index);
}
this.items[index] = itemAfterMove;
if (!(0, location_1.isSameLocationArea)(itemAfterMove.location, sourceItem.location)) {
this.applyRemoveItemStrategy(sourceItem);
}
}
};
/**
* Provides the state of an item after it is moved
* @param move The move that is going to happen
* @return {MaterialItem} state of the item after the move is executed
*/
MaterialMutator.prototype.getItemAfterMove = function (move) {
var item = this.getItemWithLocation(move.location, move.itemIndex);
if (move.reveal) {
(0, merge_1.default)(item, move.reveal);
}
if (move.quantity) {
item.quantity = move.quantity;
}
else {
delete item.quantity;
}
return item;
};
/**
* Provides the state of an item after it is moved
* @param move The move that is going to happen
* @param index Index of the item to consider
* @return {MaterialItem} state of the item after the move is executed
*/
MaterialMutator.prototype.getItemAfterMoveAtOnce = function (move, index) {
var item = this.getItemWithLocation(move.location, index);
if (move.reveal && move.reveal[index]) {
(0, merge_1.default)(item, move.reveal[index]);
}
return item;
};
MaterialMutator.prototype.getItemWithLocation = function (location, index) {
var moveLocation = JSON.parse(JSON.stringify(location));
var actualItem = this.items[index];
var newLocation = location.type === undefined ? __assign(__assign({}, actualItem.location), moveLocation) : moveLocation;
return __assign(__assign({}, actualItem), { location: newLocation });
};
MaterialMutator.prototype.delete = function (move) {
return this.removeItem(this.items[move.itemIndex], move.quantity);
};
MaterialMutator.prototype.shuffle = function (move) {
var _this = this;
if (!(0, moves_1.isShuffleRandomized)(move))
return; // Nothing to do on front-end side for a shuffle. The index swap is only required on the backend.
var shuffledItems = move.indexes.map(function (index) { return _this.items[index]; });
move.newIndexes.forEach(function (newIndex, i) {
_this.items[newIndex] = __assign(__assign({}, shuffledItems[i]), { location: _this.items[newIndex].location });
});
};
MaterialMutator.prototype.select = function (move) {
var _a;
var item = this.items[move.itemIndex];
if (move.selected === false) {
delete item.selected;
}
else {
item.selected = (_a = move.quantity) !== null && _a !== void 0 ? _a : true;
}
};
return MaterialMutator;
}());
exports.MaterialMutator = MaterialMutator;
//# sourceMappingURL=MaterialMutator.js.map