174 lines
6.3 KiB
TypeScript
174 lines
6.3 KiB
TypeScript
import { CornerType, Game, PlayerColor } from "../routes/games/types";
|
|
import { layout } from "./layout";
|
|
|
|
const isRuleEnabled = (game: any, rule: string): boolean => {
|
|
return rule in game.rules && game.rules[rule].enabled;
|
|
};
|
|
|
|
const getValidRoads = (game: any, color: string): number[] => {
|
|
const limits: number[] = [];
|
|
|
|
/* For each road, if the road is set, skip it.
|
|
* If no color is set, check the two corners. If the corner
|
|
* has a matching color, add this to the set. Otherwise skip.
|
|
*/
|
|
layout.roads.forEach((road, roadIndex) => {
|
|
// Skip if placements or roads missing, or if this road is already occupied
|
|
// Treat the explicit sentinel "unassigned" as "not available" so only
|
|
// consider a road occupied when a color is present and not "unassigned".
|
|
if (
|
|
!game.placements ||
|
|
!game.placements.roads ||
|
|
(game.placements.roads[roadIndex] &&
|
|
game.placements.roads[roadIndex].color &&
|
|
game.placements.roads[roadIndex].color !== "unassigned")
|
|
) {
|
|
return;
|
|
}
|
|
let valid = false;
|
|
for (let c = 0; !valid && c < road.corners.length; c++) {
|
|
const cornerIndex = road.corners[c] as number;
|
|
if (cornerIndex == null || (layout as any).corners[cornerIndex] == null) {
|
|
continue;
|
|
}
|
|
const corner = (layout as any).corners[cornerIndex];
|
|
const cornerColor =
|
|
(game as any).placements &&
|
|
(game as any).placements.corners &&
|
|
(game as any).placements.corners[cornerIndex] &&
|
|
(game as any).placements.corners[cornerIndex].color;
|
|
/* Roads do not pass through other player's settlements.
|
|
* Consider a corner with color === "unassigned" as empty. */
|
|
if (cornerColor && cornerColor !== "unassigned" && cornerColor !== color) {
|
|
continue;
|
|
}
|
|
for (let r = 0; !valid && r < (corner.roads || []).length; r++) {
|
|
/* This side of the corner is pointing to the road being validated. Skip it. */
|
|
if (!corner.roads || corner.roads[r] === roadIndex) {
|
|
continue;
|
|
}
|
|
const rr = corner.roads[r];
|
|
if (rr == null) {
|
|
continue;
|
|
}
|
|
const placementsRoads = (game as any).placements && (game as any).placements.roads;
|
|
if (placementsRoads && placementsRoads[rr] && placementsRoads[rr].color === color) {
|
|
valid = true;
|
|
}
|
|
}
|
|
}
|
|
if (valid) {
|
|
limits.push(roadIndex);
|
|
}
|
|
});
|
|
|
|
return limits;
|
|
};
|
|
|
|
const getValidCorners = (game: Game, color: PlayerColor = "unassigned", type?: CornerType): number[] => {
|
|
const limits: number[] = [];
|
|
|
|
console.log("getValidCorners", color, type);
|
|
/* For each corner, if the corner already has a color set, skip it if type
|
|
* isn't set. If type is set and is a match, and the color is a match,
|
|
* add it to the list.
|
|
*
|
|
* If we are limiting based on active player, a corner is only valid
|
|
* if it connects to a road that is owned by that player.
|
|
*
|
|
* If no color is set, walk each road that leaves that corner and
|
|
* check to see if there is a settlement placed at the end of that road
|
|
*
|
|
* If so, this location cannot have a settlement.
|
|
*
|
|
* If still valid, and we are in initial settlement placement, and if
|
|
* Volcano is enabled, verify the tile is not the Volcano.
|
|
*/
|
|
layout.corners.forEach((corner, cornerIndex) => {
|
|
const placement = game.placements && game.placements.corners ? game.placements.corners[cornerIndex] : undefined;
|
|
if (!placement) {
|
|
// Treat a missing placement as unassigned (no owner)
|
|
// Continue processing using a falsy placement where appropriate
|
|
}
|
|
if (type) {
|
|
if (placement && placement.color === color && placement.type === type) {
|
|
limits.push(cornerIndex);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// If the corner has a color set and it's not the explicit sentinel
|
|
// "unassigned" then it's occupied and should be skipped.
|
|
// Note: placement.color may be undefined (initial state), treat that
|
|
// the same as unassigned.
|
|
if (placement && placement.color && placement.color !== "unassigned") {
|
|
return;
|
|
}
|
|
|
|
let valid;
|
|
// Treat either a falsy color (""/undefined) or the explicit sentinel
|
|
// "unassigned" as meaning "do not filter by player".
|
|
if (!color || color === "unassigned") {
|
|
valid = true; /* Not filtering based on current player */
|
|
} else {
|
|
valid = false;
|
|
for (let r = 0; !valid && r < (corner.roads || []).length; r++) {
|
|
const rr = corner.roads[r];
|
|
if (rr == null) {
|
|
continue;
|
|
}
|
|
const placementsRoads = game.placements && game.placements.roads;
|
|
valid = !!(placementsRoads && placementsRoads[rr] && placementsRoads[rr].color === color);
|
|
}
|
|
}
|
|
|
|
for (let r = 0; valid && r < (corner.roads || []).length; r++) {
|
|
if (!corner.roads) {
|
|
break;
|
|
}
|
|
const ridx = corner.roads[r];
|
|
if (ridx == null || layout.roads[ridx] == null) {
|
|
continue;
|
|
}
|
|
const road = layout.roads[ridx];
|
|
for (let c = 0; valid && c < (road.corners || []).length; c++) {
|
|
/* This side of the road is pointing to the corner being validated.
|
|
* Skip it. */
|
|
if (road.corners[c] === cornerIndex) {
|
|
continue;
|
|
}
|
|
/* There is a settlement within one segment from this
|
|
* corner, so it is invalid for settlement placement */
|
|
const cc = road.corners[c] as number;
|
|
const ccPlacement = game.placements && game.placements.corners ? game.placements.corners[cc] : undefined;
|
|
const ccColor = ccPlacement ? ccPlacement.color : undefined;
|
|
if (ccColor && ccColor !== "unassigned") {
|
|
valid = false;
|
|
}
|
|
}
|
|
}
|
|
if (valid) {
|
|
/* During initial placement, if volcano is enabled, do not allow
|
|
* placement on a corner connected to the volcano (robber starts
|
|
* on the volcano) */
|
|
if (
|
|
!(
|
|
game.state === "initial-placement" &&
|
|
isRuleEnabled(game, "volcano") &&
|
|
game.robber &&
|
|
layout.tiles &&
|
|
layout.tiles[game.robber] &&
|
|
Array.isArray(layout.tiles[game.robber]?.corners) &&
|
|
layout.tiles[game.robber]?.corners.indexOf(cornerIndex as number) !== -1
|
|
)
|
|
) {
|
|
limits.push(cornerIndex);
|
|
}
|
|
}
|
|
});
|
|
|
|
return limits;
|
|
};
|
|
|
|
export { getValidCorners, getValidRoads, isRuleEnabled };
|