Socket
Socket
Sign inDemoInstall

@gaia-project/engine

Package Overview
Dependencies
Maintainers
2
Versions
272
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@gaia-project/engine - npm Package Compare versions

Comparing version 4.8.22 to 4.8.23

2

dist/index.js

@@ -59,2 +59,3 @@ "use strict";

Object.defineProperty(exports, "conversionToFreeAction", { enumerable: true, get: function () { return available_command_1.conversionToFreeAction; } });
Object.defineProperty(exports, "MAX_SHIPS_PER_HEX", { enumerable: true, get: function () { return available_command_1.MAX_SHIPS_PER_HEX; } });
var engine_2 = require("./src/engine");

@@ -74,2 +75,3 @@ Object.defineProperty(exports, "AuctionVariant", { enumerable: true, get: function () { return engine_2.AuctionVariant; } });

Object.defineProperty(exports, "FinalTile", { enumerable: true, get: function () { return enums_1.FinalTile; } });
Object.defineProperty(exports, "isShip", { enumerable: true, get: function () { return enums_1.isShip; } });
Object.defineProperty(exports, "Operator", { enumerable: true, get: function () { return enums_1.Operator; } });

@@ -76,0 +78,0 @@ Object.defineProperty(exports, "Phase", { enumerable: true, get: function () { return enums_1.Phase; } });

2

dist/package.json
{
"name": "@gaia-project/engine",
"version": "4.8.22",
"version": "4.8.23",
"description": "Javascript engine for project gaia",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -25,3 +25,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.choosableFactions = exports.possiblePISwaps = exports.possibleTechTiles = exports.canTakeAdvancedTechTile = exports.possibleFederationTiles = exports.possibleCoverTechTiles = exports.getTaklonsExtraLeechOffers = exports.possibleLeech = exports.possibleGaiaFreeActions = exports.possibleIncomes = exports.possibleFederations = exports.possibleRoundBoosters = exports.possibleSpaceLostPlanet = exports.possibleResearchAreas = exports.canResearchField = exports.possibleLabDowngrades = exports.transformToSpendCommand = exports.freeActionData = exports.possibleFreeActions = exports.possibleBoardActions = exports.possibleSpecialActions = exports.possibleMineBuildings = exports.possibleSpaceStations = exports.possibleBuildings = exports.generate = exports.conversionToFreeAction = exports.Offer = exports.UPGRADE_RESEARCH_COST = void 0;
exports.choosableFactions = exports.possiblePISwaps = exports.possibleTechTiles = exports.canTakeAdvancedTechTile = exports.possibleFederationTiles = exports.possibleCoverTechTiles = exports.getTaklonsExtraLeechOffers = exports.possibleLeech = exports.possibleGaiaFreeActions = exports.possibleIncomes = exports.possibleFederations = exports.possibleRoundBoosters = exports.possibleSpaceLostPlanet = exports.possibleResearchAreas = exports.canResearchField = exports.possibleLabDowngrades = exports.transformToSpendCommand = exports.freeActionData = exports.possibleFreeActions = exports.possibleBoardActions = exports.possibleSpecialActions = exports.possibleMineBuildings = exports.possibleSpaceStations = exports.possibleShipMovements = exports.possibleBuildings = exports.shipsInHex = exports.generate = exports.conversionToFreeAction = exports.ShipAction = exports.Offer = exports.MAX_SHIPS_PER_HEX = exports.UPGRADE_RESEARCH_COST = void 0;
const lodash_1 = require("lodash");

@@ -41,2 +41,4 @@ const actions_1 = require("./actions");

exports.UPGRADE_RESEARCH_COST = "4k";
exports.MAX_SHIPS_PER_HEX = 3;
const SHIP_ACTION_RANGE = 1;
class Offer {

@@ -49,2 +51,6 @@ constructor(offer, cost) {

exports.Offer = Offer;
var ShipAction;
(function (ShipAction) {
ShipAction["BuildColony"] = "buildColony";
})(ShipAction = exports.ShipAction || (exports.ShipAction = {}));
function conversionToFreeAction(act) {

@@ -86,5 +92,8 @@ const entry = Object.entries(actions_1.freeActionConversions).find(([k, v]) => v.cost === act.cost && v.income === act.income);

return [{ name: enums_1.Command.BrainStone, player, data }];
// case SubPhase.MoveShip:
// return possibleShipMovements(engine, player);
case enums_1.SubPhase.BeforeMove: {
return [
...possibleBuildings(engine, player),
...possibleShipMovements(engine, player),
...possibleFederations(engine, player),

@@ -153,3 +162,3 @@ ...possibleResearchAreas(engine, player, exports.UPGRADE_RESEARCH_COST),

}
const check = pl.canBuild(planet, building, lastRound, replay, {
const check = pl.canBuild(map, hex, planet, building, lastRound, replay, {
addedCost: [new reward_1.default(qicNeeded.amount, enums_1.Resource.Qic)],

@@ -183,11 +192,35 @@ });

}
function shipsInHex(location, data) {
return data.players.flatMap((p) => p.data.ships).filter((s) => s.location === location);
}
exports.shipsInHex = shipsInHex;
function possibleShips(pl, engine, map, hex) {
const buildings = [];
for (const ship of Object.values(enums_1.Building).filter((b) => enums_1.isShip(b))) {
const check = pl.canBuild(null, null, null, ship, engine.isLastRound, engine.replay);
if (check) {
for (const h of map.withinDistance(hex, 1)) {
if (!h.hasPlanet() && shipsInHex(h.toString(), engine).length < exports.MAX_SHIPS_PER_HEX) {
buildings.push(newAvailableBuilding(ship, h, check, false));
}
}
}
}
return buildings;
}
function possibleBuildings(engine, player) {
const map = engine.map;
const pl = engine.player(player);
const { data } = pl;
const buildings = [];
for (const hex of engine.map.toJSON()) {
// upgrade existing player's building
if (hex.buildingOf(player)) {
const building = hex.buildingOf(player);
const building = hex.buildingOf(player);
if (building) {
// excluding Transdim planet until transformed into Gaia planets
if (hex.data.planet === enums_1.Planet.Transdim) {
continue;
}
if (buildings_1.stdBuildingValue(building) > 0 && engine.expansions === enums_1.Expansion.Frontiers) {
buildings.push(...possibleShips(pl, engine, map, hex));
}
if (player !== hex.data.player) {

@@ -197,6 +230,2 @@ // This is a secondary building, so we can't upgrade it

}
// excluding Transdim planet until transformed into Gaia planets
if (hex.data.planet === enums_1.Planet.Transdim) {
continue;
}
// Lost planet can't be upgraded

@@ -213,3 +242,3 @@ if (hex.data.planet === enums_1.Planet.Lost) {

for (const _pl of engine.players) {
if (_pl !== engine.player(player)) {
if (_pl !== pl) {
for (const loc of _pl.data.occupied) {

@@ -224,5 +253,5 @@ if (loc.hasStructure() && map.distance(loc, hex) < ISOLATED_DISTANCE) {

})();
const upgraded = buildings_1.upgradedBuildings(building, engine.player(player).faction);
const upgraded = buildings_1.upgradedBuildings(building, pl.faction);
for (const upgrade of upgraded) {
const check = engine.player(player).canBuild(hex.data.planet, upgrade, engine.isLastRound, engine.replay, {
const check = pl.canBuild(map, hex, hex.data.planet, upgrade, engine.isLastRound, engine.replay, {
isolated,

@@ -250,3 +279,3 @@ existingBuilding: building,

player,
data: { buildings },
data: { buildings: lodash_1.uniq(buildings) },
},

@@ -258,6 +287,73 @@ ];

exports.possibleBuildings = possibleBuildings;
function shipTargets(source, hex, range, targets, engine) {
if (!targets.find((t) => t.coordinates === hex) && shipsInHex(hex, engine).length < exports.MAX_SHIPS_PER_HEX) {
targets.push({ coordinates: hex });
}
if (range === 0) {
return targets;
}
const map = engine.map;
for (const h of map.withinDistance(map.getS(hex), 1)) {
const c = h.toString();
if (!h.hasPlanet() && c !== source) {
shipTargets(source, c, range - 1, targets, engine);
}
}
return targets;
}
function possibleColonyShipActions(engine, ship, shipLocation) {
const map = engine.map;
const pl = engine.player(ship.player);
const locations = map.withinDistance(map.getS(shipLocation), SHIP_ACTION_RANGE).flatMap((h) => {
if (h.hasPlanet() && !h.occupied() && h.data.planet !== enums_1.Planet.Transdim) {
const check = pl.canBuild(map, h, h.data.planet, enums_1.Building.Colony, engine.isLastRound, engine.replay);
if (check) {
return [newAvailableBuilding(enums_1.Building.Colony, h, check, false)];
}
}
return [];
});
if (locations.length > 0) {
return [
{
type: ShipAction.BuildColony,
locations,
},
];
}
return [];
}
function possibleShipActions(engine, ship, shipLocation) {
switch (ship.type) {
case enums_1.Building.ColonyShip:
return possibleColonyShipActions(engine, ship, shipLocation);
}
return [];
}
function possibleShipMovements(engine, player) {
const pl = engine.player(player);
const ships = pl.data.ships.filter((s) => !s.moved);
if (ships.length === 0) {
return [];
}
const shipRange = engine.player(player).data.shipRange;
return [
{
name: enums_1.Command.MoveShip,
player,
data: ships.map((s) => ({
ship: s.type,
source: s.location,
targets: shipTargets(s.location, s.location, shipRange, [], engine).map((t) => ({
location: t,
actions: possibleShipActions(engine, s, t.coordinates),
})),
})),
},
];
}
exports.possibleShipMovements = possibleShipMovements;
function possibleSpaceStations(engine, player) {
const map = engine.map;
const pl = engine.player(player);
const { data } = pl;
const buildings = [];

@@ -269,4 +365,3 @@ for (const hex of map.toJSON()) {

}
const building = enums_1.Building.SpaceStation;
addPossibleNewPlanet(map, hex, pl, pl.planet, building, buildings, engine.isLastRound, engine.replay);
addPossibleNewPlanet(map, hex, pl, pl.planet, enums_1.Building.SpaceStation, buildings, engine.isLastRound, engine.replay);
}

@@ -273,0 +368,0 @@ if (buildings.length > 0) {

@@ -31,2 +31,3 @@ "use strict";

case __1.Building.Academy2:
case __1.Building.Colony:
return 3;

@@ -33,0 +34,0 @@ }

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubPhase = exports.Phase = exports.PowerArea = exports.FinalTile = exports.ScoringTile = exports.BoardAction = exports.Federation = exports.AdvTechTilePos = exports.AdvTechTile = exports.TechTilePos = exports.TechPos = exports.TechTile = exports.Booster = exports.RoundScoring = exports.Round = exports.Player = exports.Command = exports.Faction = exports.Building = exports.Condition = exports.Operator = exports.Resource = exports.Expansion = exports.ResearchField = exports.Planet = void 0;
exports.SubPhase = exports.Phase = exports.PowerArea = exports.FinalTile = exports.ScoringTile = exports.BoardAction = exports.Federation = exports.AdvTechTilePos = exports.AdvTechTile = exports.TechTilePos = exports.TechPos = exports.TechTile = exports.Booster = exports.RoundScoring = exports.Round = exports.Player = exports.Command = exports.Faction = exports.isShip = exports.Building = exports.Condition = exports.Operator = exports.Resource = exports.Expansion = exports.ResearchField = exports.Planet = void 0;
var Planet;

@@ -29,3 +29,5 @@ (function (Planet) {

(function (Expansion) {
Expansion[Expansion["All"] = 1] = "All";
// 1 was the old spaceships expansion
Expansion[Expansion["Frontiers"] = 2] = "Frontiers";
Expansion[Expansion["All"] = 2] = "All";
})(Expansion = exports.Expansion || (exports.Expansion = {}));

@@ -147,3 +149,26 @@ (function (ResearchField) {

Building["SpaceStation"] = "sp";
//frontiers
Building["Colony"] = "colony";
Building["ColonyShip"] = "colonyShip";
Building["TradeShip"] = "tradeShip";
Building["ConstructionShip"] = "constructionShip";
Building["ResearchShip"] = "researchShip";
Building["Scout"] = "scout";
Building["Frigate"] = "frigate";
Building["BattleShip"] = "battleShip";
})(Building = exports.Building || (exports.Building = {}));
function isShip(b) {
switch (b) {
case Building.ColonyShip:
case Building.TradeShip:
case Building.ConstructionShip:
case Building.ResearchShip:
case Building.Scout:
case Building.Frigate:
case Building.BattleShip:
return true;
}
return false;
}
exports.isShip = isShip;
var Faction;

@@ -185,5 +210,5 @@ (function (Faction) {

Command["Init"] = "init";
Command["MoveShip"] = "move";
Command["PISwap"] = "swap-PI";
Command["Pass"] = "pass";
Command["PickReward"] = "pick";
Command["PISwap"] = "swap-PI";
Command["PlaceLostPlanet"] = "lostPlanet";

@@ -494,2 +519,3 @@ Command["RotateSectors"] = "rotate";

Phase["RoundGaia"] = "roundGaia";
Phase["RoundShip"] = "roundShip";
Phase["RoundMove"] = "roundMove";

@@ -516,4 +542,3 @@ Phase["RoundLeech"] = "roundLeech";

SubPhase["DowngradeLab"] = "down-lab";
SubPhase["PickRewards"] = "pickRewards";
})(SubPhase = exports.SubPhase || (exports.SubPhase = {}));
//# sourceMappingURL=enums.js.map

@@ -35,17 +35,9 @@ "use strict";

const remaining = spec.substr(operatorString.length).trimLeft();
return [op, remaining, 0];
return [op, remaining];
}
}
// If there's one space in the string, that means that the second part HAS to be a condition
if (spec.split(" ").length === 2) {
const [operator, remaining] = spec.split(" ");
const toPick = parseInt(operator, 10);
return [operator.slice(("" + toPick).length), remaining, toPick];
}
return [enums_1.Operator.Once, spec, 0];
return [enums_1.Operator.Once, spec];
}
class Event {
constructor(spec, source) {
/** Number of rewards to pick. Default to ALL */
this.toPick = 0;
this.activated = false;

@@ -72,3 +64,3 @@ if (typeof spec === "object") {

[this.condition, remaining] = findCondition(this.spec);
[this.operator, remaining, this.toPick] = findOperator(remaining);
[this.operator, remaining] = findOperator(remaining);
this.rewards = reward_1.default.parse(remaining);

@@ -75,0 +67,0 @@ }

@@ -51,2 +51,39 @@ "use strict";

},
//frontiers
[enums_1.Building.Colony]: {
cost: "~",
income: [
["+3c", "+3pw", "+3vp", "2vp"],
["+3c", "+3pw", "+4vp", "2vp"],
["+3c", "+3pw", "+5vp", "2vp"],
],
},
[enums_1.Building.ColonyShip]: {
cost: "4c,3o",
income: [[], [], []],
},
[enums_1.Building.ConstructionShip]: {
cost: "3c,2o",
income: [[], [], []],
},
[enums_1.Building.ResearchShip]: {
cost: "3c,2o",
income: [[], [], []],
},
[enums_1.Building.TradeShip]: {
cost: "5c,1o",
income: [[], [], []],
},
[enums_1.Building.Scout]: {
cost: "1c,1o",
income: [[], [], []],
},
[enums_1.Building.Frigate]: {
cost: "3c,2o",
income: [[], [], []],
},
[enums_1.Building.BattleShip]: {
cost: "5c,5o",
income: [[], [], []],
},
},

@@ -78,3 +115,3 @@ income: ["3k,4o,15c,q", "+o,k"],

}
cost(targetPlanet, building, isolated = true) {
cost(building, isolated = true) {
if (building === enums_1.Building.TradingStation && isolated) {

@@ -81,0 +118,0 @@ return this.buildings[building].isolatedCost;

@@ -53,2 +53,8 @@ "use strict";

this.buildings = lodash_1.fromPairs(Object.values(enums_1.Building).map((bld) => [bld, 0]));
this.destroyedShips = lodash_1.fromPairs(Object.values(enums_1.Building)
.filter((b) => enums_1.isShip(b))
.map((bld) => [bld, 0]));
this.deployedShips = lodash_1.fromPairs(Object.values(enums_1.Building)
.filter((b) => enums_1.isShip(b))
.map((bld) => [bld, 0]));
this.satellites = 0;

@@ -64,2 +70,3 @@ this.research = {

this.range = 1;
this.shipRange = 2;
/** Total number of gaiaformers gained (including those on the board & the gaia area) */

@@ -79,2 +86,3 @@ this.gaiaformers = 0;

this.occupied = [];
this.ships = [];
this.tokenModifier = 1;

@@ -109,4 +117,8 @@ this.lostPlanet = 0;

buildings: this.buildings,
destroyedShips: this.destroyedShips,
deployedShips: this.deployedShips,
federationCount: this.federationCount,
lostPlanet: this.lostPlanet,
ships: this.ships,
shipRange: this.shipRange,
};

@@ -113,0 +125,0 @@ return ret;

@@ -181,10 +181,4 @@ "use strict";

}
gainRewards(rewards, source, toPick = 0) {
if (toPick) {
this.data.toPick = { count: toPick, rewards: [...rewards], source };
this.emit("pick-rewards");
}
else {
this.data.gainRewards(rewards.map((rew) => this.factionReward(rew, source)), false, source);
}
gainRewards(rewards, source) {
this.data.gainRewards(rewards.map((rew) => this.factionReward(rew, source)), false, source);
}

@@ -204,3 +198,3 @@ maxPayRange(cost) {

}
canBuild(targetPlanet, building, lastRound, replay, { isolated, addedCost, existingBuilding, } = {}) {
canBuild(map, hex, targetPlanet, building, lastRound, replay, { isolated, addedCost, existingBuilding, } = {}) {
if (this.data.buildings[building] >= this.maxBuildings(building)) {

@@ -228,3 +222,3 @@ // Too many buildings of the same kind

}
else if (building === enums_1.Building.Mine) {
else if (building === enums_1.Building.Mine || building === enums_1.Building.Colony) {
// habitability costs

@@ -264,3 +258,3 @@ if (targetPlanet === enums_1.Planet.Gaia) {

}
const cost = reward_1.default.merge(this.board.cost(targetPlanet, building, isolated), addedCost);
const cost = reward_1.default.merge(this.board.cost(building, isolated), addedCost);
const creditCost = (r) => r.filter((r) => r.type === enums_1.Resource.Credit)[0].count;

@@ -278,10 +272,17 @@ if (building === enums_1.Building.TradingStation &&

}
if (!this.data.canPay(cost)) {
return null;
if (hex && this.faction !== enums_1.Faction.Ivits) {
for (const h of map.withinDistance(hex, 1)) {
if (h.belongsToFederationOf(this.player)) {
warnings.push("building-will-be-part-of-federation");
break;
}
}
}
return {
cost,
steps,
warnings,
};
return !this.data.canPay(cost)
? null
: {
cost,
steps,
warnings,
};
}

@@ -334,3 +335,3 @@ maxBuildings(building) {

const times = this.eventConditionCount(event.condition);
this.gainRewards(event.rewards.map((reward) => new reward_1.default(reward.count * times, reward.type)), event.source, event.toPick);
this.gainRewards(event.rewards.map((reward) => new reward_1.default(reward.count * times, reward.type)), event.source);
}

@@ -408,3 +409,3 @@ }

// excluding Gaiaformers as occupied
if (building !== enums_1.Building.GaiaFormer) {
if (building !== enums_1.Building.GaiaFormer && !enums_1.isShip(building)) {
if (!wasOccupied) {

@@ -450,13 +451,18 @@ this.data.occupied.push(hex);

const isAdditionalMine = !upgradedBuilding && hex.occupied();
if (isAdditionalMine) {
hex.data.additionalMine = this.player;
if (this.data.hasPlanetaryInstitute()) {
this.gainRewards([new reward_1.default("2k")], enums_1.Faction.Lantids);
}
if (enums_1.isShip(building)) {
this.placeShip(building, hex);
}
else {
hex.data.building = building;
hex.data.player = this.player;
if (isAdditionalMine) {
hex.data.additionalMine = this.player;
if (this.data.hasPlanetaryInstitute()) {
this.gainRewards([new reward_1.default("2k")], enums_1.Faction.Lantids);
}
}
else {
hex.data.building = building;
hex.data.player = this.player;
}
this.addBuildingToNearbyFederation(building, hex, map);
}
this.addBuildingToNearbyFederation(building, hex, map);
// get triggered income for new building

@@ -486,2 +492,19 @@ this.receiveBuildingTriggerIncome(building, hex.data.planet, isAdditionalMine);

}
placeShip(ship, hex) {
this.data.ships.push({ type: ship, location: hex.toString(), moved: false, player: this.player });
}
findUnmovedShip(ship, location) {
return this.data.ships.find((s) => s.location === location && s.type === ship && !s.moved);
}
removeShip(ship, destroyed) {
const l = this.data.ships;
l.splice(l.indexOf(ship), 1);
if (destroyed) {
this.data.destroyedShips[ship.type]++;
this.data.buildings[ship.type]--;
}
else {
this.data.deployedShips[ship.type]++;
}
}
resetTemporaryVariables() {

@@ -591,3 +614,3 @@ // reset temporary benefits

if (event.condition === condition) {
this.gainRewards(event.rewards, event.source, event.toPick);
this.gainRewards(event.rewards, event.source);
}

@@ -594,0 +617,0 @@ }

@@ -7,3 +7,3 @@ "use strict";

[enums_1.ResearchField.Terraforming]: [[], ["2o"], ["d"], ["d", "3pw"], ["2o"], []],
[enums_1.ResearchField.Navigation]: [[], ["q"], ["r"], ["q", "3pw"], ["r"], ["r"]],
[enums_1.ResearchField.Navigation]: [[], ["q"], ["r", "ship-range"], ["q", "3pw"], ["r", "ship-range"], ["r", "2ship-range"]],
[enums_1.ResearchField.Intelligence]: [[], ["q"], ["q"], ["2q", "3pw"], ["2q"], ["4q"]],

@@ -10,0 +10,0 @@ [enums_1.ResearchField.GaiaProject]: [[], [">gf"], ["3t"], [">gf", "3pw"], [">gf"], ["4vp", "g > vp"]],

@@ -6,4 +6,5 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.possibleSetupBoardActions = exports.applySetupOption = exports.initCustomSetup = exports.SetupType = void 0;
exports.possibleSetupBoardActions = exports.applySetupOption = exports.initCustomSetup = exports.applyRandomBoardSetup = exports.SetupType = void 0;
const assert_1 = __importDefault(require("assert"));
const shuffle_seed_1 = __importDefault(require("shuffle-seed"));
const enums_1 = require("./enums");

@@ -81,3 +82,3 @@ var SetupType;

}
const factories = (engine) => [
const getFactories = (engine, nbPlayers = engine.players.length) => [
{

@@ -87,3 +88,3 @@ type: SetupType.Booster,

for (const b of enums_1.Booster.values(engine.expansions)) {
engine.tiles.boosters[b] = false;
delete engine.tiles.boosters[b];
}

@@ -95,3 +96,4 @@ },

const used = boosters.filter((b) => engine.tiles.boosters[b]).length;
if (used < engine.players.length + 3) {
// Choose nbPlayers+3 boosters as part of the pool
if (used < nbPlayers + 3) {
const b = left[0];

@@ -109,3 +111,3 @@ return {

},
techFactory(engine, SetupType.TechTile, enums_1.TechTilePos.values(engine.expansions), enums_1.TechTile.values(engine.expansions), engine.players.length),
techFactory(engine, SetupType.TechTile, enums_1.TechTilePos.values(engine.expansions), enums_1.TechTile.values(engine.expansions), nbPlayers),
techFactory(engine, SetupType.AdvTechTile, enums_1.AdvTechTilePos.values(engine.expansions), enums_1.AdvTechTile.values(engine.expansions), 1),

@@ -157,4 +159,21 @@ {

];
function applyRandomBoardSetup(engine, seed, nbPlayers) {
//map has too many quirks to keep test cases compatible
const factories = getFactories(engine, nbPlayers).filter((f) => f.type !== SetupType.MapTile);
for (const factory of factories) {
factory.init();
let options;
let next;
while ((next = factory.nextAvailable()) !== null) {
if (!options) {
//only shuffle once for compatability with old test cases
options = shuffle_seed_1.default.shuffle(next.options, engine.map.rng());
}
factory.applyOption(options.shift(), next.position);
}
}
}
exports.applyRandomBoardSetup = applyRandomBoardSetup;
function initCustomSetup(engine) {
for (const factory of factories(engine)) {
for (const factory of getFactories(engine)) {
factory.init();

@@ -165,3 +184,3 @@ }

function nextAvailableSetupOption(engine) {
for (const factory of factories(engine)) {
for (const factory of getFactories(engine)) {
const o = factory.nextAvailable();

@@ -179,3 +198,3 @@ if (o) {

function applySetupOption(engine, type, position, option) {
for (const factory of factories(engine)) {
for (const factory of getFactories(engine)) {
const o = factory.nextAvailable();

@@ -182,0 +201,0 @@ if (o) {

@@ -20,2 +20,5 @@ "use strict";

}
if (expansions.includes("frontiers")) {
options.frontiers = true;
}
const engine = new engine_1.default([`init ${nbPlayers} ${seed}`], options);

@@ -22,0 +25,0 @@ engine.generateAvailableCommandsIfNeeded();

@@ -19,5 +19,7 @@ import { finalRankings, gainFinalScoringVictoryPoints } from "./src/algorithms/scoring";

AvailableBuilding,
AvailableFederation,
AvailableFreeAction,
AvailableFreeActionData,
AvailableHex,
AvailableMoveShipData,
AvailableResearchData,

@@ -31,2 +33,3 @@ AvailableResearchTrack,

HighlightHex,
MAX_SHIPS_PER_HEX,
} from "./src/available-command";

@@ -53,2 +56,3 @@ export {

FinalTile,
isShip,
Operator,

@@ -71,3 +75,3 @@ Phase,

export { factionPlanet } from "./src/factions";
export { federationCost, parseFederationLocation } from "./src/federation";
export { federationCost, FederationInfo, parseFederationLocation } from "./src/federation";
export { GaiaHex, GaiaHexData } from "./src/gaia-hex";

@@ -74,0 +78,0 @@ export { applyChargePowers } from "./src/income";

{
"name": "@gaia-project/engine",
"version": "4.8.22",
"version": "4.8.23",
"description": "Javascript engine for project gaia",

@@ -5,0 +5,0 @@ "main": "dist/index.js",

@@ -11,3 +11,3 @@ import { difference, range, uniq } from "lodash";

} from "./actions";
import { upgradedBuildings } from "./buildings";
import { stdBuildingValue, upgradedBuildings } from "./buildings";
import { qicForDistance } from "./cost";

@@ -25,2 +25,3 @@ import Engine, { AuctionVariant, BoardActions } from "./engine";

Federation,
isShip,
Operator,

@@ -32,2 +33,3 @@ Phase,

Resource,
Ship,
SubPhase,

@@ -49,2 +51,4 @@ TechTile,

export const UPGRADE_RESEARCH_COST = "4k";
export const MAX_SHIPS_PER_HEX = 3;
const SHIP_ACTION_RANGE = 1;

@@ -127,2 +131,12 @@ export type BrainstoneWarning = "brainstone-charges-wasted";

export enum ShipAction {
BuildColony = "buildColony",
}
export type AvailableShipAction = { type: ShipAction; locations: AvailableBuilding[] };
export type AvailableShipTarget = { location: AvailableHex; actions: AvailableShipAction[] };
export type AvailableMoveShipData = { ship: Building; source: string; targets: AvailableShipTarget[] };
interface CommandData {

@@ -149,2 +163,3 @@ [Command.Action]: AvailableBoardActionData;

[Command.PlaceLostPlanet]: { spaces: AvailableHex[] };
[Command.MoveShip]: AvailableMoveShipData[];
[Command.RotateSectors]: never;

@@ -161,3 +176,9 @@ [Command.Setup]: AvailableSetupOption;

export type HighlightHex = { cost?: string; warnings?: BuildWarning[] };
export type HighlightHex = {
cost?: string;
warnings?: BuildWarning[];
building?: Building;
hideBuilding?: Building;
preventClick?: boolean;
};
export type AvailableHex = HighlightHex & { coordinates: string };

@@ -214,5 +235,8 @@

return [{ name: Command.BrainStone, player, data }];
// case SubPhase.MoveShip:
// return possibleShipMovements(engine, player);
case SubPhase.BeforeMove: {
return [
...possibleBuildings(engine, player),
...possibleShipMovements(engine, player),
...possibleFederations(engine, player),

@@ -301,3 +325,3 @@ ...possibleResearchAreas(engine, player, UPGRADE_RESEARCH_COST),

const check = pl.canBuild(planet, building, lastRound, replay, {
const check = pl.canBuild(map, hex, planet, building, lastRound, replay, {
addedCost: [new Reward(qicNeeded.amount, Resource.Qic)],

@@ -336,6 +360,24 @@ });

export function shipsInHex(location: string, data): Ship[] {
return data.players.flatMap((p) => p.data.ships).filter((s) => s.location === location);
}
function possibleShips(pl: PlayerObject, engine: Engine, map: SpaceMap, hex: GaiaHex) {
const buildings: AvailableBuilding[] = [];
for (const ship of Object.values(Building).filter((b) => isShip(b))) {
const check = pl.canBuild(null, null, null, ship, engine.isLastRound, engine.replay);
if (check) {
for (const h of map.withinDistance(hex, 1)) {
if (!h.hasPlanet() && shipsInHex(h.toString(), engine).length < MAX_SHIPS_PER_HEX) {
buildings.push(newAvailableBuilding(ship, h, check, false));
}
}
}
}
return buildings;
}
export function possibleBuildings(engine: Engine, player: Player): AvailableCommand<Command.Build>[] {
const map = engine.map;
const pl = engine.player(player);
const { data } = pl;
const buildings: AvailableBuilding[] = [];

@@ -345,5 +387,13 @@

// upgrade existing player's building
if (hex.buildingOf(player)) {
const building = hex.buildingOf(player);
const building = hex.buildingOf(player);
if (building) {
// excluding Transdim planet until transformed into Gaia planets
if (hex.data.planet === Planet.Transdim) {
continue;
}
if (stdBuildingValue(building) > 0 && engine.expansions === Expansion.Frontiers) {
buildings.push(...possibleShips(pl, engine, map, hex));
}
if (player !== hex.data.player) {

@@ -354,7 +404,2 @@ // This is a secondary building, so we can't upgrade it

// excluding Transdim planet until transformed into Gaia planets
if (hex.data.planet === Planet.Transdim) {
continue;
}
// Lost planet can't be upgraded

@@ -373,3 +418,3 @@ if (hex.data.planet === Planet.Lost) {

for (const _pl of engine.players) {
if (_pl !== engine.player(player)) {
if (_pl !== pl) {
for (const loc of _pl.data.occupied) {

@@ -386,6 +431,6 @@ if (loc.hasStructure() && map.distance(loc, hex) < ISOLATED_DISTANCE) {

const upgraded = upgradedBuildings(building, engine.player(player).faction);
const upgraded = upgradedBuildings(building, pl.faction);
for (const upgrade of upgraded) {
const check = engine.player(player).canBuild(hex.data.planet, upgrade, engine.isLastRound, engine.replay, {
const check = pl.canBuild(map, hex, hex.data.planet, upgrade, engine.isLastRound, engine.replay, {
isolated,

@@ -414,3 +459,3 @@ existingBuilding: building,

player,
data: { buildings },
data: { buildings: uniq(buildings) }, //ship locations may be duplicated
},

@@ -423,6 +468,84 @@ ];

function shipTargets(
source: string,
hex: string,
range: number,
targets: AvailableHex[],
engine: Engine
): AvailableHex[] {
if (!targets.find((t) => t.coordinates === hex) && shipsInHex(hex, engine).length < MAX_SHIPS_PER_HEX) {
targets.push({ coordinates: hex });
}
if (range === 0) {
return targets;
}
const map = engine.map;
for (const h of map.withinDistance(map.getS(hex), 1)) {
const c = h.toString();
if (!h.hasPlanet() && c !== source) {
shipTargets(source, c, range - 1, targets, engine);
}
}
return targets;
}
function possibleColonyShipActions(engine: Engine, ship: Ship, shipLocation: string): AvailableShipAction[] {
const map = engine.map;
const pl = engine.player(ship.player);
const locations: AvailableHex[] = map.withinDistance(map.getS(shipLocation), SHIP_ACTION_RANGE).flatMap((h) => {
if (h.hasPlanet() && !h.occupied() && h.data.planet !== Planet.Transdim) {
const check = pl.canBuild(map, h, h.data.planet, Building.Colony, engine.isLastRound, engine.replay);
if (check) {
return [newAvailableBuilding(Building.Colony, h, check, false)];
}
}
return [];
});
if (locations.length > 0) {
return [
{
type: ShipAction.BuildColony,
locations,
} as AvailableShipAction,
];
}
return [];
}
function possibleShipActions(engine: Engine, ship: Ship, shipLocation: string): AvailableShipAction[] {
switch (ship.type) {
case Building.ColonyShip:
return possibleColonyShipActions(engine, ship, shipLocation);
}
return [];
}
export function possibleShipMovements(engine: Engine, player: Player): AvailableCommand<Command.MoveShip>[] {
const pl = engine.player(player);
const ships = pl.data.ships.filter((s) => !s.moved);
if (ships.length === 0) {
return [];
}
const shipRange = engine.player(player).data.shipRange;
return [
{
name: Command.MoveShip,
player,
data: ships.map((s) => ({
ship: s.type,
source: s.location,
targets: shipTargets(s.location, s.location, shipRange, [], engine).map((t) => ({
location: t,
actions: possibleShipActions(engine, s, t.coordinates),
})),
})),
},
];
}
export function possibleSpaceStations(engine: Engine, player: Player): AvailableCommand<Command.Build>[] {
const map = engine.map;
const pl = engine.player(player);
const { data } = pl;
const buildings = [];

@@ -436,4 +559,3 @@

const building = Building.SpaceStation;
addPossibleNewPlanet(map, hex, pl, pl.planet, building, buildings, engine.isLastRound, engine.replay);
addPossibleNewPlanet(map, hex, pl, pl.planet, Building.SpaceStation, buildings, engine.isLastRound, engine.replay);
}

@@ -440,0 +562,0 @@

@@ -30,2 +30,3 @@ import { Building, Faction } from "..";

case Building.Academy2:
case Building.Colony:
return 3;

@@ -32,0 +33,0 @@ }

import assert from "assert";
import { isEqual, range, set, uniq } from "lodash";
import shuffleSeed from "shuffle-seed";
import { version } from "../package.json";

@@ -9,2 +8,3 @@ import { boardActions } from "./actions";

import AvailableCommand, {
AvailableBuilding,
AvailableFreeActionData,

@@ -14,2 +14,3 @@ BrainstoneActionData,

Offer,
ShipAction,
} from "./available-command";

@@ -28,2 +29,3 @@ import { stdBuildingValue } from "./buildings";

FinalTile,
isShip,
Operator,

@@ -53,2 +55,3 @@ Phase,

import {
applyRandomBoardSetup,
applySetupOption,

@@ -66,3 +69,2 @@ initCustomSetup,

// const ISOLATED_DISTANCE = 3;
const LEECHING_DISTANCE = 2;

@@ -101,2 +103,4 @@

flexibleFederations?: boolean;
/** Frontiers expansion */
frontiers?: boolean;
/** auction */

@@ -239,3 +243,3 @@ auction?: AuctionVariant;

techs: {},
scorings: { round: null, final: null },
scorings: { round: [], final: [] },
federations: {},

@@ -256,4 +260,4 @@ };

get expansions() {
return 0;
get expansions(): Expansion {
return 0 | (this.options.frontiers ? Expansion.Frontiers : 0);
}

@@ -497,3 +501,2 @@

});
player.on("pick-rewards", () => this.processNextMove(SubPhase.PickRewards));

@@ -1017,3 +1020,3 @@ /* For advanced log */

this.checkCommand(move.command);
(this[move.command] as any)(this.playerToMove, ...move.args);
(this[move.command === Command.MoveShip ? "_move" : move.command] as any)(this.playerToMove, ...move.args);

@@ -1203,2 +1206,5 @@ return move;

player.loadEvents(this.currentRoundScoringEvents);
player.data.ships?.forEach((s) => {
s.moved = false;
});
}

@@ -1550,20 +1556,4 @@

// Choose nbPlayers+3 boosters as part of the pool
const boosters = shuffleSeed.shuffle(Booster.values(this.expansions), this.map.rng()).slice(0, nbPlayers + 3);
for (const booster of boosters) {
this.tiles.boosters[booster] = true;
}
applyRandomBoardSetup(this, seed, nbPlayers);
// Shuffle tech tiles
const techtiles = shuffleSeed.shuffle(TechTile.values(this.expansions), this.map.rng());
TechTilePos.values(this.expansions).forEach((pos, i) => {
this.tiles.techs[pos] = { tile: techtiles[i], count: nbPlayers };
});
// Choose adv tech tiles as part of the pool
const advtechtiles = shuffleSeed.shuffle(AdvTechTile.values(this.expansions), this.map.rng());
AdvTechTilePos.values(this.expansions).forEach((pos, i) => {
this.tiles.techs[pos] = { tile: advtechtiles[i], count: 1 };
});
// powerActions

@@ -1574,17 +1564,2 @@ BoardAction.values(this.expansions).forEach((pos: BoardAction) => {

const feds = Federation.values();
this.terraformingFederation = shuffleSeed.shuffle(feds, this.map.rng())[0];
for (const federation of feds) {
this.tiles.federations[federation] = 3;
}
this.tiles.federations[this.terraformingFederation] -= 1;
// Choose roundScoring Tiles as part of the pool
const roundscoringtiles = shuffleSeed.shuffle(ScoringTile.values(this.expansions), this.map.rng()).slice(0, 6);
this.tiles.scorings.round = roundscoringtiles;
// Choose finalScoring Tiles as part of the pool
const finalscoringtiles = shuffleSeed.shuffle(FinalTile.values(this.expansions), this.map.rng()).slice(0, 2);
this.tiles.scorings.final = finalscoringtiles;
this.players = [];

@@ -1682,12 +1657,3 @@ this.setup = [];

if (elem.building === building && isEqual(this.map.parse(elem.coordinates), parsed)) {
const { q, r } = this.map.parse(location);
const hex = this.map.grid.get({ q, r });
pl.build(building, hex, Reward.parse(elem.cost), this.map, elem.steps);
// will trigger a LeechPhase
if (this.phase === Phase.RoundMove) {
this.leechSources.unshift({ player, coordinates: location });
}
this.placeBuilding(pl, elem);
return;

@@ -1703,2 +1669,12 @@ }

private placeBuilding(pl: Player, building: AvailableBuilding) {
const hex = this.map.getS(building.coordinates);
pl.build(building.building, hex, Reward.parse(building.cost), this.map, building.steps);
// will trigger a LeechPhase
if ((this.phase === Phase.RoundMove || this.phase === Phase.RoundShip) && !isShip(building.building)) {
this.leechSources.unshift({ player: pl.player, coordinates: building.coordinates });
}
}
[Command.UpgradeResearch](player: PlayerEnum, field: ResearchField) {

@@ -1835,2 +1811,46 @@ const { tracks } = this.avCommand<Command.UpgradeResearch>().data;

[`_${Command.MoveShip}`](
player: PlayerEnum,
shipType: Building,
source: string,
dest: string,
actionType?: ShipAction,
actionLocation?: string
) {
const pl = this.player(player);
const data = this.avCommand<Command.MoveShip>().data;
const shipCommand = data.find((s) => s.ship === shipType && s.source === source);
assert(shipCommand, `There is no ship ${shipType} at ${source}`);
const target = shipCommand.targets.find((t) => t.location.coordinates === dest);
assert(target, `The ship ${shipType} doesn't have the range to move from ${source} to ${dest}`);
const ship = pl.findUnmovedShip(shipType, source);
assert(ship, `No ${shipType} at ${source} (or has already moved)`);
ship.moved = true;
ship.location = dest;
const actions = target.actions;
if (actionType) {
const action = actions?.find((a) => a.type === actionType);
assert(action, `action ${actionType} not possible for ship ${shipType} at ship location ${dest}`);
assert(actionLocation, "no action location provided");
const location = action.locations.find((l) => l.coordinates === actionLocation);
assert(location, `action ${actionType} not possible for ship ${shipType} at action location ${actionLocation}`);
switch (actionType) {
case ShipAction.BuildColony:
this.placeBuilding(pl, location);
pl.removeShip(ship, false);
break;
}
}
}
[Command.Spend](player: PlayerEnum, costS: string, _for: "for", incomeS: string) {

@@ -1942,4 +1962,3 @@ const command = this.avCommand<Command.Spend>();

if (isEqual(this.map.parse(elem.coordinates), parsed)) {
const { q, r } = this.map.parse(location);
const hex = this.map.grid.get({ q, r });
const hex = this.map.getS(location);

@@ -1946,0 +1965,0 @@ if (hex.buildingOf(player) === Building.Mine) {

@@ -25,3 +25,5 @@ export enum Planet {

export enum Expansion {
All = 1,
// 1 was the old spaceships expansion
Frontiers = 2,
All = 2,
}

@@ -149,4 +151,37 @@

SpaceStation = "sp",
//frontiers
Colony = "colony",
ColonyShip = "colonyShip",
TradeShip = "tradeShip",
ConstructionShip = "constructionShip",
ResearchShip = "researchShip",
Scout = "scout",
Frigate = "frigate",
BattleShip = "battleShip",
}
export type Ship = {
type: Building;
player: Player;
location: string;
moved: boolean;
};
export function isShip(b: Building) {
switch (b) {
case Building.ColonyShip:
case Building.TradeShip:
case Building.ConstructionShip:
case Building.ResearchShip:
case Building.Scout:
case Building.Frigate:
case Building.BattleShip:
return true;
}
return false;
}
export enum Faction {

@@ -187,5 +222,5 @@ Terrans = "terrans",

Init = "init",
MoveShip = "move",
PISwap = "swap-PI",
Pass = "pass",
PickReward = "pick",
PISwap = "swap-PI",
PlaceLostPlanet = "lostPlanet",

@@ -500,2 +535,3 @@ RotateSectors = "rotate",

RoundGaia = "roundGaia",
RoundShip = "roundShip",
RoundMove = "roundMove",

@@ -522,3 +558,2 @@ RoundLeech = "roundLeech",

DowngradeLab = "down-lab",
PickRewards = "pickRewards",
}

@@ -40,3 +40,3 @@ import {

function findOperator(spec: string): [Operator, string, number] {
function findOperator(spec: string): [Operator, string] {
let operatorMatch = /^(.+?)(\b| )/.exec(spec);

@@ -53,14 +53,7 @@

const remaining = spec.substr(operatorString.length).trimLeft();
return [op, remaining, 0];
return [op, remaining];
}
}
// If there's one space in the string, that means that the second part HAS to be a condition
if (spec.split(" ").length === 2) {
const [operator, remaining] = spec.split(" ");
const toPick = parseInt(operator, 10);
return [operator.slice(("" + toPick).length) as Operator, remaining, toPick];
}
return [Operator.Once, spec, 0];
return [Operator.Once, spec];
}

@@ -93,4 +86,2 @@

rewards: Reward[];
/** Number of rewards to pick. Default to ALL */
toPick = 0;
activated = false;

@@ -119,3 +110,3 @@

[this.condition, remaining] = findCondition(this.spec);
[this.operator, remaining, this.toPick] = findOperator(remaining);
[this.operator, remaining] = findOperator(remaining);
this.rewards = Reward.parse(remaining);

@@ -122,0 +113,0 @@ }

import { get, set } from "lodash";
import { FactionVariant } from "../engine";
import { Building, Command, Faction, Operator, Phase, Planet, PowerArea } from "../enums";
import { Building, Command, Faction, Operator, Phase, PowerArea } from "../enums";
import Event from "../events";

@@ -64,2 +64,3 @@ import Player from "../player";

},
[Building.GaiaFormer]: {

@@ -73,2 +74,42 @@ cost: "6t->tg",

},
//frontiers
[Building.Colony]: {
cost: "~",
income: [
["+3c", "+3pw", "+3vp", "2vp"],
["+3c", "+3pw", "+4vp", "2vp"],
["+3c", "+3pw", "+5vp", "2vp"],
],
},
[Building.ColonyShip]: {
cost: "4c,3o",
income: [[], [], []],
},
[Building.ConstructionShip]: {
cost: "3c,2o",
income: [[], [], []],
},
[Building.ResearchShip]: {
cost: "3c,2o",
income: [[], [], []],
},
[Building.TradeShip]: {
cost: "5c,1o",
income: [[], [], []],
},
[Building.Scout]: {
cost: "1c,1o",
income: [[], [], []],
},
[Building.Frigate]: {
cost: "3c,2o",
income: [[], [], []],
},
[Building.BattleShip]: {
cost: "5c,5o",
income: [[], [], []],
},
},

@@ -123,3 +164,3 @@ income: ["3k,4o,15c,q", "+o,k"],

cost(targetPlanet: Planet, building: Building, isolated = true): Reward[] {
cost(building: Building, isolated = true): Reward[] {
if (building === Building.TradingStation && isolated) {

@@ -126,0 +167,0 @@ return this.buildings[building].isolatedCost;

@@ -6,2 +6,4 @@ import assert from "assert";

type PlayerShip = { player: Player; ship: Building; moved: boolean };
export interface GaiaHexData {

@@ -8,0 +10,0 @@ planet: Planet;

@@ -374,3 +374,3 @@ import assert from "assert";

getS(coords: string) {
getS(coords: string): GaiaHex {
return this.grid.get(this.parse(coords));

@@ -377,0 +377,0 @@ }

@@ -12,5 +12,7 @@ import { EventEmitter } from "eventemitter3";

Federation,
isShip,
PowerArea,
ResearchField,
Resource,
Ship,
TechTile,

@@ -69,2 +71,18 @@ TechTilePos,

destroyedShips: {
[key in Building]: number;
} = fromPairs(
Object.values(Building)
.filter((b) => isShip(b))
.map((bld) => [bld, 0])
) as any;
deployedShips: {
[key in Building]: number;
} = fromPairs(
Object.values(Building)
.filter((b) => isShip(b))
.map((bld) => [bld, 0])
) as any;
satellites = 0;

@@ -82,2 +100,3 @@ research: {

range = 1;
shipRange = 2;
/** Total number of gaiaformers gained (including those on the board & the gaia area) */

@@ -104,2 +123,3 @@ gaiaformers = 0;

occupied: GaiaHex[] = [];
ships: Ship[] = [];
leechPossible: number;

@@ -138,4 +158,8 @@ tokenModifier = 1;

buildings: this.buildings,
destroyedShips: this.destroyedShips,
deployedShips: this.deployedShips,
federationCount: this.federationCount,
lostPlanet: this.lostPlanet,
ships: this.ships,
shipRange: this.shipRange,
};

@@ -142,0 +166,0 @@

@@ -17,3 +17,3 @@ import { expect } from "chai";

const { cost } = player.canBuild(Planet.Terra, Building.Mine, false, false, {
const { cost } = player.canBuild(null, null, Planet.Terra, Building.Mine, false, false, {
addedCost: [new Reward(1, Resource.Qic)],

@@ -20,0 +20,0 @@ });

@@ -18,2 +18,3 @@ import assert from "assert";

FinalTile,
isShip,
Operator,

@@ -26,2 +27,3 @@ Phase,

Resource,
Ship,
TechPos,

@@ -91,3 +93,4 @@ TechTile,

| "gaia-former-would-extend-range"
| "gaia-former-last-round";
| "gaia-former-last-round"
| "building-will-be-part-of-federation";

@@ -248,13 +251,8 @@ export type BuildCheck = { cost: Reward[]; steps: number; warnings: BuildWarning[] };

gainRewards(rewards: Reward[], source: EventSource, toPick = 0) {
if (toPick) {
this.data.toPick = { count: toPick, rewards: [...rewards], source };
this.emit("pick-rewards");
} else {
this.data.gainRewards(
rewards.map((rew) => this.factionReward(rew, source)),
false,
source
);
}
gainRewards(rewards: Reward[], source: EventSource) {
this.data.gainRewards(
rewards.map((rew) => this.factionReward(rew, source)),
false,
source
);
}

@@ -279,3 +277,5 @@

canBuild(
targetPlanet: Planet,
map: SpaceMap,
hex: GaiaHex | null,
targetPlanet: Planet | null,
building: Building,

@@ -319,3 +319,3 @@ lastRound: boolean,

addedCost.push(new Reward(-this.data.gaiaFormingDiscount(), Resource.MoveTokenToGaiaArea));
} else if (building === Building.Mine) {
} else if (building === Building.Mine || building === Building.Colony) {
// habitability costs

@@ -357,3 +357,3 @@ if (targetPlanet === Planet.Gaia) {

const cost = Reward.merge(this.board.cost(targetPlanet, building, isolated), addedCost);
const cost = Reward.merge(this.board.cost(building, isolated), addedCost);
const creditCost = (r: Reward[]) => r.filter((r) => r.type === Resource.Credit)[0].count;

@@ -374,11 +374,18 @@ if (

}
if (hex && this.faction !== Faction.Ivits) {
for (const h of map.withinDistance(hex, 1)) {
if (h.belongsToFederationOf(this.player)) {
warnings.push("building-will-be-part-of-federation");
break;
}
}
}
if (!this.data.canPay(cost)) {
return null;
}
return {
cost,
steps,
warnings,
};
return !this.data.canPay(cost)
? null
: {
cost,
steps,
warnings,
};
}

@@ -444,4 +451,3 @@

event.rewards.map((reward) => new Reward(reward.count * times, reward.type)),
event.source,
event.toPick
event.source
);

@@ -538,3 +544,3 @@ }

// excluding Gaiaformers as occupied
if (building !== Building.GaiaFormer) {
if (building !== Building.GaiaFormer && !isShip(building)) {
if (!wasOccupied) {

@@ -584,12 +590,16 @@ this.data.occupied.push(hex);

if (isAdditionalMine) {
hex.data.additionalMine = this.player;
if (this.data.hasPlanetaryInstitute()) {
this.gainRewards([new Reward("2k")], Faction.Lantids);
if (isShip(building)) {
this.placeShip(building, hex);
} else {
if (isAdditionalMine) {
hex.data.additionalMine = this.player;
if (this.data.hasPlanetaryInstitute()) {
this.gainRewards([new Reward("2k")], Faction.Lantids);
}
} else {
hex.data.building = building;
hex.data.player = this.player;
}
} else {
hex.data.building = building;
hex.data.player = this.player;
this.addBuildingToNearbyFederation(building, hex, map);
}
this.addBuildingToNearbyFederation(building, hex, map);

@@ -625,2 +635,21 @@ // get triggered income for new building

placeShip(ship: Building, hex: GaiaHex) {
this.data.ships.push({ type: ship, location: hex.toString(), moved: false, player: this.player });
}
findUnmovedShip(ship: Building, location: string): Ship | null {
return this.data.ships.find((s) => s.location === location && s.type === ship && !s.moved);
}
removeShip(ship: Ship, destroyed: boolean) {
const l = this.data.ships;
l.splice(l.indexOf(ship), 1);
if (destroyed) {
this.data.destroyedShips[ship.type]++;
this.data.buildings[ship.type]--;
} else {
this.data.deployedShips[ship.type]++;
}
}
resetTemporaryVariables() {

@@ -749,3 +778,3 @@ // reset temporary benefits

if (event.condition === condition) {
this.gainRewards(event.rewards, event.source, event.toPick);
this.gainRewards(event.rewards, event.source);
}

@@ -752,0 +781,0 @@ }

@@ -5,3 +5,3 @@ import { ResearchField } from "./enums";

[ResearchField.Terraforming]: [[], ["2o"], ["d"], ["d", "3pw"], ["2o"], []],
[ResearchField.Navigation]: [[], ["q"], ["r"], ["q", "3pw"], ["r"], ["r"]],
[ResearchField.Navigation]: [[], ["q"], ["r", "ship-range"], ["q", "3pw"], ["r", "ship-range"], ["r", "2ship-range"]],
[ResearchField.Intelligence]: [[], ["q"], ["q"], ["2q", "3pw"], ["2q"], ["4q"]],

@@ -8,0 +8,0 @@ [ResearchField.GaiaProject]: [[], [">gf"], ["3t"], [">gf", "3pw"], [">gf"], ["4vp", "g > vp"]],

import assert from "assert";
import shuffleSeed from "shuffle-seed";
import AvailableCommand from "./available-command";

@@ -117,3 +118,3 @@ import Engine from "./engine";

const factories = (engine: Engine): SetupFactory[] => [
const getFactories = (engine: Engine, nbPlayers = engine.players.length): SetupFactory[] => [
{

@@ -123,3 +124,3 @@ type: SetupType.Booster,

for (const b of Booster.values(engine.expansions)) {
engine.tiles.boosters[b] = false;
delete engine.tiles.boosters[b];
}

@@ -131,3 +132,4 @@ },

const used = boosters.filter((b) => engine.tiles.boosters[b]).length;
if (used < engine.players.length + 3) {
// Choose nbPlayers+3 boosters as part of the pool
if (used < nbPlayers + 3) {
const b = left[0];

@@ -150,3 +152,3 @@ return {

TechTile.values(engine.expansions),
engine.players.length
nbPlayers
),

@@ -218,4 +220,24 @@ techFactory(

export function applyRandomBoardSetup(engine: Engine, seed: string, nbPlayers: number) {
//map has too many quirks to keep test cases compatible
const factories = getFactories(engine, nbPlayers).filter((f) => f.type !== SetupType.MapTile);
for (const factory of factories) {
factory.init();
let options: SetupOption[];
let next: SetupFactoryOption;
while ((next = factory.nextAvailable()) !== null) {
if (!options) {
//only shuffle once for compatability with old test cases
options = shuffleSeed.shuffle(next.options, engine.map.rng());
}
factory.applyOption(options.shift(), next.position);
}
}
}
export function initCustomSetup(engine: Engine) {
for (const factory of factories(engine)) {
for (const factory of getFactories(engine)) {
factory.init();

@@ -226,3 +248,3 @@ }

function nextAvailableSetupOption(engine: Engine): AvailableSetupOption | null {
for (const factory of factories(engine)) {
for (const factory of getFactories(engine)) {
const o = factory.nextAvailable();

@@ -241,3 +263,3 @@ if (o) {

export function applySetupOption(engine: Engine, type: SetupType, position: SetupPosition, option: SetupOption) {
for (const factory of factories(engine)) {
for (const factory of getFactories(engine)) {
const o = factory.nextAvailable();

@@ -244,0 +266,0 @@ if (o) {

@@ -22,2 +22,6 @@ import assert from "assert";

if (expansions.includes("frontiers")) {
options.frontiers = true;
}
const engine = new Engine([`init ${nbPlayers} ${seed}`], options);

@@ -24,0 +28,0 @@ engine.generateAvailableCommandsIfNeeded();

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc