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 };