From 8476c4f01ba698ac5848a0d9d08df378e30a0ed2 Mon Sep 17 00:00:00 2001 From: Grimiku Date: Tue, 22 Jul 2025 16:49:08 +0200 Subject: [PATCH 1/4] Allow town connections module introduction --- src/maps/denmark/build_validator.ts | 5 ----- src/maps/denmark/settings.ts | 4 ++++ src/maps/portugal/lisboa.ts | 2 -- src/maps/portugal/settings.ts | 8 ++++++-- src/modules/allow_town_connection.ts | 17 +++++++++++++++++ 5 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 src/modules/allow_town_connection.ts diff --git a/src/maps/denmark/build_validator.ts b/src/maps/denmark/build_validator.ts index 2363abed..18b2a06f 100644 --- a/src/maps/denmark/build_validator.ts +++ b/src/maps/denmark/build_validator.ts @@ -40,11 +40,6 @@ export class DenmarkBuildPhase extends BuildPhase { export class DenmarkConnectCitiesAction extends ConnectCitiesAction { private readonly ferryClaimCount = injectState(FERRY_CLAIM_COUNT); - protected validateUrbanizedCities(_: InterCityConnection): void { - // On the Denmark map, towns do not need to be urbanized to claim a link, though goods cannot - // be moved across until a route is built. See DenmarkMoveValidator. - } - validate(data: ConnectCitiesData): void { assert(this.ferryClaimCount() < 2, { invalidInput: "Can only claim two ferry routes per turn", diff --git a/src/maps/denmark/settings.ts b/src/maps/denmark/settings.ts index b4aed4e6..7de8c93e 100644 --- a/src/maps/denmark/settings.ts +++ b/src/maps/denmark/settings.ts @@ -12,6 +12,7 @@ import { AvailableActionsModule } from "../../modules/available_actions"; import { InstantProductionModule } from "../../modules/instant_production/module"; import { OneClaimLimitModule } from "../../modules/one_claim_limit"; import { PhasesModule } from "../../modules/phases"; +import { AllowTownConnectionModule } from "../../modules/allow_town_connection"; import { remove } from "../../utils/functions"; import { interCityConnections } from "../factory"; import { DenmarkActionNamingProvider } from "./actions"; @@ -110,6 +111,9 @@ export class DenmarkMapSettings implements MapSettings { new PhasesModule({ replace: (phases) => remove(phases, Phase.GOODS_GROWTH), }), + // On the Denmark map, towns do not need to be urbanized to claim a link, though goods cannot + // be moved across until a route is built. See DenmarkMoveValidator. + new AllowTownConnectionModule(), ]; } } diff --git a/src/maps/portugal/lisboa.ts b/src/maps/portugal/lisboa.ts index bb9d2dcb..9ebffd46 100644 --- a/src/maps/portugal/lisboa.ts +++ b/src/maps/portugal/lisboa.ts @@ -66,8 +66,6 @@ export class LisboaBuildAction extends BuildAction { export class LisboaConnectAction extends ConnectCitiesAction { private readonly connected = injectState(CONNECTED_TO_LISBOA); - protected validateUrbanizedCities(): void {} - validate(data: ConnectCitiesData): void { super.validate(data); // Only one connection out of Lisboa can be built per turn, per player. diff --git a/src/maps/portugal/settings.ts b/src/maps/portugal/settings.ts index 360e8f22..cbb64f29 100644 --- a/src/maps/portugal/settings.ts +++ b/src/maps/portugal/settings.ts @@ -7,6 +7,7 @@ import { import { Module } from "../../engine/module/module"; import { BOTTOM, TOP } from "../../engine/state/tile"; import { OneClaimLimitModule } from "../../modules/one_claim_limit"; +import { AllowTownConnectionModule } from "../../modules/allow_town_connection"; import { interCityConnections } from "../factory"; import { PortugalGoodsGrowthPhase } from "./goods"; import { map } from "./grid"; @@ -72,6 +73,9 @@ export class PortugalMapSettings implements MapSettings { } getModules(): Array { - return [new OneClaimLimitModule()]; + return [ + new OneClaimLimitModule(), + new AllowTownConnectionModule(), + ]; } -} +} \ No newline at end of file diff --git a/src/modules/allow_town_connection.ts b/src/modules/allow_town_connection.ts new file mode 100644 index 00000000..b21e7dc0 --- /dev/null +++ b/src/modules/allow_town_connection.ts @@ -0,0 +1,17 @@ +import { Module } from "../engine/module/module"; +import { ConnectCitiesAction } from "../engine/build/connect_cities"; +import { SimpleConstructor } from "../engine/framework/dependency_stack"; + +export class AllowTownConnectionModule extends Module { + installMixins(): void { + this.installMixin(ConnectCitiesAction, allowTownConnectionMixin); + } +} + +function allowTownConnectionMixin( + Ctor: SimpleConstructor +): SimpleConstructor { + return class extends Ctor { + protected validateUrbanizedCities(): void {} + }; +} From faf7e3e28484083575c2065528b61d8e829a14c3 Mon Sep 17 00:00:00 2001 From: Grimiku Date: Wed, 13 Aug 2025 14:39:53 +0200 Subject: [PATCH 2/4] Allow for building connections in new module --- src/engine/state/inter_city_connection.ts | 5 ++ src/maps/denmark/build_validator.ts | 7 ++- src/maps/denmark/settings.ts | 6 +-- src/maps/factory.ts | 3 ++ src/maps/portugal/lisboa.ts | 20 +------- src/maps/portugal/settings.ts | 16 +++---- src/modules/allow_town_connection.ts | 17 ------- src/modules/towns_and_sea_links.ts | 57 +++++++++++++++++++++++ 8 files changed, 80 insertions(+), 51 deletions(-) delete mode 100644 src/modules/allow_town_connection.ts create mode 100644 src/modules/towns_and_sea_links.ts diff --git a/src/engine/state/inter_city_connection.ts b/src/engine/state/inter_city_connection.ts index 79472a11..7572b0c5 100644 --- a/src/engine/state/inter_city_connection.ts +++ b/src/engine/state/inter_city_connection.ts @@ -12,9 +12,14 @@ export type Offset = z.infer; export const InterCityConnection = z.object({ id: z.string(), connects: CoordinatesZod.array(), + connectedTownExit: z.union([ + DirectionZod, + DirectionZod.array() + ]).optional(), cost: z.number(), center: CoordinatesZod.optional(), offset: Offset.optional(), + // No owner means the connection isn't built. An owner but no color means it's built but unowned. owner: z.object({ color: PlayerColorZod.optional() }).optional(), }); diff --git a/src/maps/denmark/build_validator.ts b/src/maps/denmark/build_validator.ts index 18b2a06f..5b435b6c 100644 --- a/src/maps/denmark/build_validator.ts +++ b/src/maps/denmark/build_validator.ts @@ -40,6 +40,11 @@ export class DenmarkBuildPhase extends BuildPhase { export class DenmarkConnectCitiesAction extends ConnectCitiesAction { private readonly ferryClaimCount = injectState(FERRY_CLAIM_COUNT); + protected validateUrbanizedCities(_: InterCityConnection): void { + // On the Denmark map, towns do not need to be urbanized to claim a link, though goods cannot + // be moved across until a route is built. See DenmarkMoveValidator. + } + validate(data: ConnectCitiesData): void { assert(this.ferryClaimCount() < 2, { invalidInput: "Can only claim two ferry routes per turn", @@ -169,4 +174,4 @@ function hasDuplicateOwnedRoute(routes: RouteInfo[]): boolean { } } return false; -} +} \ No newline at end of file diff --git a/src/maps/denmark/settings.ts b/src/maps/denmark/settings.ts index 2b465b75..b4af449d 100644 --- a/src/maps/denmark/settings.ts +++ b/src/maps/denmark/settings.ts @@ -12,7 +12,6 @@ import { AvailableActionsModule } from "../../modules/available_actions"; import { InstantProductionModule } from "../../modules/instant_production/module"; import { OneClaimLimitModule } from "../../modules/one_claim_limit"; import { PhasesModule } from "../../modules/phases"; -import { AllowTownConnectionModule } from "../../modules/allow_town_connection"; import { remove } from "../../utils/functions"; import { interCityConnections } from "../factory"; import { DenmarkActionNamingProvider } from "./actions"; @@ -113,9 +112,6 @@ export class DenmarkMapSettings implements MapSettings { new PhasesModule({ replace: (phases) => remove(phases, Phase.GOODS_GROWTH), }), - // On the Denmark map, towns do not need to be urbanized to claim a link, though goods cannot - // be moved across until a route is built. See DenmarkMoveValidator. - new AllowTownConnectionModule(), ]; } -} +} \ No newline at end of file diff --git a/src/maps/factory.ts b/src/maps/factory.ts index 1c3d7d7c..9a3e0623 100644 --- a/src/maps/factory.ts +++ b/src/maps/factory.ts @@ -11,6 +11,7 @@ import { OnRoll, OnRollData } from "../engine/state/roll"; import { CityData, LandData } from "../engine/state/space"; import { Coordinates } from "../utils/coordinates"; import { duplicate } from "../utils/functions"; +import { Direction } from "../engine/state/tile"; export const PLAIN: LandData = { type: SpaceType.PLAIN, @@ -150,6 +151,7 @@ interface InterCityConnectionFactoryProps { cost?: number; center?: [number, number]; offset?: Offset; + connectedTownExit?: Direction | Direction[]; } export function interCityConnections( @@ -167,6 +169,7 @@ export function interCityConnections( id: "" + (idx + 1), connects: [cities.get(spec.connects[0])!, cities.get(spec.connects[1])!], cost: spec.cost ?? 2, + connectedTownExit: spec.connectedTownExit, offset: spec.offset, center: spec.center ? Coordinates.from({ q: spec.center[0], r: spec.center[1] }) diff --git a/src/maps/portugal/lisboa.ts b/src/maps/portugal/lisboa.ts index acff08d6..30db3271 100644 --- a/src/maps/portugal/lisboa.ts +++ b/src/maps/portugal/lisboa.ts @@ -5,8 +5,7 @@ import { ConnectCitiesData, } from "../../engine/build/connect_cities"; import { BuildPhase } from "../../engine/build/phase"; -import { Validator, InvalidBuildReason } from "../../engine/build/validator"; -import { BOTTOM, Direction } from "../../engine/state/tile"; +import { BOTTOM } from "../../engine/state/tile"; import { MoveValidator, RouteInfo } from "../../engine/move/validator"; import { PlayerColor } from "../../engine/state/player"; import { DanglerInfo } from "../../engine/map/grid"; @@ -87,23 +86,6 @@ export class LisboaConnectAction extends ConnectCitiesAction { } } -export class PortugalValidator extends Validator { - protected connectionAllowed( - land: Land, - exit: Direction, - ): InvalidBuildReason | undefined { - if ( - (land.name() === "Sagres" || land.name() === "Sines") && - land.hasTown() && - exit === BOTTOM - ) { - return undefined; - } - - return super.connectionAllowed(land, exit); - } -} - export class PortugalMoveValidator extends MoveValidator { protected getAdditionalRoutesFromLand(location: Land): RouteInfo[] { const grid = this.grid(); diff --git a/src/maps/portugal/settings.ts b/src/maps/portugal/settings.ts index cbb64f29..d71f2afb 100644 --- a/src/maps/portugal/settings.ts +++ b/src/maps/portugal/settings.ts @@ -7,14 +7,13 @@ import { import { Module } from "../../engine/module/module"; import { BOTTOM, TOP } from "../../engine/state/tile"; import { OneClaimLimitModule } from "../../modules/one_claim_limit"; -import { AllowTownConnectionModule } from "../../modules/allow_town_connection"; +import { TownsAndSeaLinksModule } from "../../modules/towns_and_sea_links"; import { interCityConnections } from "../factory"; import { PortugalGoodsGrowthPhase } from "./goods"; import { map } from "./grid"; import { LisboaBuildAction, LisboaConnectAction, - PortugalValidator, PortugalMoveValidator, PortugalBuildPhase, } from "./lisboa"; @@ -53,12 +52,12 @@ export class PortugalMapSettings implements MapSettings { center: [11, 13], offset: { direction: TOP, distance: 0.2 }, }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 7] }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 8] }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 9] }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 10] }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 7], connectedTownExit: BOTTOM }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 8], connectedTownExit: BOTTOM }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 9], connectedTownExit: BOTTOM }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 10], connectedTownExit: BOTTOM }, { connects: ["Lisboa", "Porto"], cost: 6, center: [6, 13] }, - { connects: ["Lisboa", "Sines"], cost: 6, center: [13, 9] }, + { connects: ["Lisboa", "Sines"], cost: 6, center: [13, 9], connectedTownExit: BOTTOM }, ]); getOverrides() { @@ -66,7 +65,6 @@ export class PortugalMapSettings implements MapSettings { LisboaBuildAction, LisboaConnectAction, PortugalGoodsGrowthPhase, - PortugalValidator, PortugalMoveValidator, PortugalBuildPhase, ]; @@ -75,7 +73,7 @@ export class PortugalMapSettings implements MapSettings { getModules(): Array { return [ new OneClaimLimitModule(), - new AllowTownConnectionModule(), + new TownsAndSeaLinksModule(), ]; } } \ No newline at end of file diff --git a/src/modules/allow_town_connection.ts b/src/modules/allow_town_connection.ts deleted file mode 100644 index b21e7dc0..00000000 --- a/src/modules/allow_town_connection.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from "../engine/module/module"; -import { ConnectCitiesAction } from "../engine/build/connect_cities"; -import { SimpleConstructor } from "../engine/framework/dependency_stack"; - -export class AllowTownConnectionModule extends Module { - installMixins(): void { - this.installMixin(ConnectCitiesAction, allowTownConnectionMixin); - } -} - -function allowTownConnectionMixin( - Ctor: SimpleConstructor -): SimpleConstructor { - return class extends Ctor { - protected validateUrbanizedCities(): void {} - }; -} diff --git a/src/modules/towns_and_sea_links.ts b/src/modules/towns_and_sea_links.ts new file mode 100644 index 00000000..731ad18b --- /dev/null +++ b/src/modules/towns_and_sea_links.ts @@ -0,0 +1,57 @@ +import { Module } from "../engine/module/module"; +import { ConnectCitiesAction } from "../engine/build/connect_cities"; +import { SimpleConstructor } from "../engine/framework/dependency_stack"; +import { Land } from "../engine/map/location"; +import { Direction } from "../engine/state/tile"; +import { InvalidBuildReason, Validator } from "../engine/build/validator"; +import { SpaceType } from "../engine/state/location_type"; + +export class TownsAndSeaLinksModule extends Module { + installMixins(): void { + this.installMixin(ConnectCitiesAction, skipCityValidationMixin); + this.installMixin(Validator, allowTownConnectionMixin); + } +} + +function skipCityValidationMixin( + Ctor: SimpleConstructor +): SimpleConstructor { + return class extends Ctor { + protected validateUrbanizedCities(): void {} + }; +} + +function allowTownConnectionMixin( + Ctor: SimpleConstructor +): SimpleConstructor { + return class extends Ctor { + protected connectionAllowed( + land: Land, + exit: Direction, + ): InvalidBuildReason | undefined { + if (this.isExitTowardsSea(land, exit) + && land.hasTown() + && this.isExitTowardsInterCity(land, exit)) { + return undefined; + } + return super.connectionAllowed(land, exit); + } + + protected isExitTowardsSea(space: Land, exit: Direction): boolean { + const neighbor = this.grid().getNeighbor(space.coordinates, exit)?.data.type; + if (neighbor === SpaceType.WATER) {return true} + return false ; + } + + protected isExitTowardsInterCity(space: Land, exit: Direction): boolean { + return this.grid().connections.some(connection => + connection.connects.some(c => c.equals(space.coordinates)) + && ( + Array.isArray(connection.connectedTownExit) + ? connection.connectedTownExit.includes(exit) + : connection.connectedTownExit === exit + ) + ); +} + } +} \ No newline at end of file From 2fedf2dc5985cea1caf2ae780a37deeafbe6e25b Mon Sep 17 00:00:00 2001 From: Grimiku Date: Wed, 13 Aug 2025 16:37:49 +0200 Subject: [PATCH 3/4] Allow for moving goods in new module --- src/maps/portugal/lisboa.ts | 68 ------------------------------ src/maps/portugal/settings.ts | 2 - src/modules/towns_and_sea_links.ts | 67 ++++++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 71 deletions(-) diff --git a/src/maps/portugal/lisboa.ts b/src/maps/portugal/lisboa.ts index 30db3271..187e1640 100644 --- a/src/maps/portugal/lisboa.ts +++ b/src/maps/portugal/lisboa.ts @@ -6,10 +6,8 @@ import { } from "../../engine/build/connect_cities"; import { BuildPhase } from "../../engine/build/phase"; import { BOTTOM } from "../../engine/state/tile"; -import { MoveValidator, RouteInfo } from "../../engine/move/validator"; import { PlayerColor } from "../../engine/state/player"; import { DanglerInfo } from "../../engine/map/grid"; -import { OwnedInterCityConnection } from "../../engine/state/inter_city_connection"; import { injectState } from "../../engine/framework/execution_context"; import { Key } from "../../engine/framework/key"; import { City } from "../../engine/map/city"; @@ -86,72 +84,6 @@ export class LisboaConnectAction extends ConnectCitiesAction { } } -export class PortugalMoveValidator extends MoveValidator { - protected getAdditionalRoutesFromLand(location: Land): RouteInfo[] { - const grid = this.grid(); - return grid.connections - .filter((connection) => - connection.connects.some((c) => c.equals(location.coordinates)), - ) - .filter((connection) => connection.owner != null) - .flatMap((connection) => { - const otherEnd = grid.get( - connection.connects.find((c) => !location.coordinates.equals(c))!, - ); - if (!(otherEnd instanceof City)) { - return []; - } - if ( - (isLisboa(otherEnd) || otherEnd.name() === "Madeira") && - (location.name() === "Sagres" || location.name() === "Sines") && - location.hasTown() - ) { - return [ - { - type: "connection", - destination: otherEnd.coordinates, - connection: connection as OwnedInterCityConnection, - owner: connection.owner!.color, - }, - ]; - } - return []; - }); - } - - protected getAdditionalRoutesFromCity(location: City): RouteInfo[] { - const grid = this.grid(); - return grid.connections - .filter((connection) => - connection.connects.some((c) => c.equals(location.coordinates)), - ) - .filter((connection) => connection.owner != null) - .flatMap((connection) => { - const otherEnd = grid.get( - connection.connects.find((c) => !location.coordinates.equals(c))!, - ); - if (!(otherEnd instanceof Land)) { - return []; - } - if ( - (isLisboa(location) || location.name() === "Madeira") && - (otherEnd.name() === "Sagres" || otherEnd.name() === "Sines") && - otherEnd.hasTown() - ) { - return [ - { - type: "connection", - destination: otherEnd.coordinates, - connection: connection as OwnedInterCityConnection, - owner: connection.owner!.color, - }, - ]; - } - return []; - }); - } -} - export class PortugalBuildPhase extends BuildPhase { private readonly connected = injectState(CONNECTED_TO_LISBOA); onStartTurn(): void { diff --git a/src/maps/portugal/settings.ts b/src/maps/portugal/settings.ts index d71f2afb..b46f97e3 100644 --- a/src/maps/portugal/settings.ts +++ b/src/maps/portugal/settings.ts @@ -14,7 +14,6 @@ import { map } from "./grid"; import { LisboaBuildAction, LisboaConnectAction, - PortugalMoveValidator, PortugalBuildPhase, } from "./lisboa"; @@ -65,7 +64,6 @@ export class PortugalMapSettings implements MapSettings { LisboaBuildAction, LisboaConnectAction, PortugalGoodsGrowthPhase, - PortugalMoveValidator, PortugalBuildPhase, ]; } diff --git a/src/modules/towns_and_sea_links.ts b/src/modules/towns_and_sea_links.ts index 731ad18b..ec86d1ce 100644 --- a/src/modules/towns_and_sea_links.ts +++ b/src/modules/towns_and_sea_links.ts @@ -4,12 +4,16 @@ import { SimpleConstructor } from "../engine/framework/dependency_stack"; import { Land } from "../engine/map/location"; import { Direction } from "../engine/state/tile"; import { InvalidBuildReason, Validator } from "../engine/build/validator"; +import { MoveValidator, RouteInfo } from "../engine/move/validator"; import { SpaceType } from "../engine/state/location_type"; +import { City } from "../engine/map/city"; +import { OwnedInterCityConnection } from "../engine/state/inter_city_connection"; export class TownsAndSeaLinksModule extends Module { installMixins(): void { this.installMixin(ConnectCitiesAction, skipCityValidationMixin); this.installMixin(Validator, allowTownConnectionMixin); + this.installMixin(MoveValidator, allowGoodsMovementMixin); } } @@ -51,7 +55,68 @@ function allowTownConnectionMixin( ? connection.connectedTownExit.includes(exit) : connection.connectedTownExit === exit ) - ); + ); + } + } } + +function allowGoodsMovementMixin( + Ctor: SimpleConstructor +): SimpleConstructor { + return class extends Ctor { + protected getAdditionalRoutesFromLand(location: Land): RouteInfo[] { + const grid = this.grid(); + return grid.connections + .filter((connection) => + connection.connects.some((c) => c.equals(location.coordinates)), + ) + .filter((connection) => connection.owner != null) + .flatMap((connection) => { + const otherEnd = grid.get( + connection.connects.find((c) => !location.coordinates.equals(c))!, + ); + if (!(otherEnd instanceof City)) { + return []; + } + return [ + { + type: "connection", + destination: otherEnd.coordinates, + connection: connection as OwnedInterCityConnection, + owner: connection.owner!.color, + }, + ]; + + }); + } + + protected getAdditionalRoutesFromCity(originCity: City): RouteInfo[] { + const grid = this.grid(); + return grid.connections + .filter((connection) => + connection.connects.some((c) => c.equals(originCity.coordinates)), + ) + .filter((connection) => connection.owner != null) + .flatMap((connection) => { + const otherEnd = grid.get( + connection.connects.find((c) => !originCity.coordinates.equals(c))!, + ); + if ( + otherEnd != null && + !(otherEnd instanceof City) && + otherEnd.hasTown() + ) { + return [ + { + type: "connection", + destination: otherEnd.coordinates, + connection: connection as OwnedInterCityConnection, + owner: connection.owner!.color, + }, + ]; + } + return []; + }); + } } } \ No newline at end of file From 61b615fbf1ee3d1652059324e2504023d53376b2 Mon Sep 17 00:00:00 2001 From: Grimiku Date: Fri, 15 Aug 2025 15:26:21 +0200 Subject: [PATCH 4/4] Adding skip of danglers and making connectedTownExits an array --- src/engine/state/inter_city_connection.ts | 5 +-- src/maps/factory.ts | 4 +- src/maps/portugal/lisboa.ts | 26 ------------ src/maps/portugal/settings.ts | 10 ++--- src/modules/towns_and_sea_links.ts | 49 ++++++++++++++++++++--- 5 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/engine/state/inter_city_connection.ts b/src/engine/state/inter_city_connection.ts index 7572b0c5..b3c9baab 100644 --- a/src/engine/state/inter_city_connection.ts +++ b/src/engine/state/inter_city_connection.ts @@ -12,10 +12,7 @@ export type Offset = z.infer; export const InterCityConnection = z.object({ id: z.string(), connects: CoordinatesZod.array(), - connectedTownExit: z.union([ - DirectionZod, - DirectionZod.array() - ]).optional(), + connectedTownExits: DirectionZod.array().optional(), cost: z.number(), center: CoordinatesZod.optional(), offset: Offset.optional(), diff --git a/src/maps/factory.ts b/src/maps/factory.ts index 9a3e0623..b41be9fe 100644 --- a/src/maps/factory.ts +++ b/src/maps/factory.ts @@ -151,7 +151,7 @@ interface InterCityConnectionFactoryProps { cost?: number; center?: [number, number]; offset?: Offset; - connectedTownExit?: Direction | Direction[]; + connectedTownExits?: Direction[]; } export function interCityConnections( @@ -169,7 +169,7 @@ export function interCityConnections( id: "" + (idx + 1), connects: [cities.get(spec.connects[0])!, cities.get(spec.connects[1])!], cost: spec.cost ?? 2, - connectedTownExit: spec.connectedTownExit, + connectedTownExits: spec.connectedTownExits, offset: spec.offset, center: spec.center ? Coordinates.from({ q: spec.center[0], r: spec.center[1] }) diff --git a/src/maps/portugal/lisboa.ts b/src/maps/portugal/lisboa.ts index 187e1640..e2f20194 100644 --- a/src/maps/portugal/lisboa.ts +++ b/src/maps/portugal/lisboa.ts @@ -5,9 +5,6 @@ import { ConnectCitiesData, } from "../../engine/build/connect_cities"; import { BuildPhase } from "../../engine/build/phase"; -import { BOTTOM } from "../../engine/state/tile"; -import { PlayerColor } from "../../engine/state/player"; -import { DanglerInfo } from "../../engine/map/grid"; import { injectState } from "../../engine/framework/execution_context"; import { Key } from "../../engine/framework/key"; import { City } from "../../engine/map/city"; @@ -95,29 +92,6 @@ export class PortugalBuildPhase extends BuildPhase { this.connected.delete(); super.onEndTurn(); } - - getDanglersAsInfo(color?: PlayerColor): DanglerInfo[] { - return this.grid() - .getDanglers(color) - .filter((track) => { - if ( - //Sines - ((track.coordinates.q === 14 && track.coordinates.r === 7) || - //Sagres - (track.coordinates.q === 17 && track.coordinates.r === 6)) && - this.grid().getImmovableExitReference(track) === BOTTOM - ) { - return false; - } - - return true; - }) - .map((track) => ({ - coordinates: track.coordinates, - immovableExit: this.grid().getImmovableExitReference(track), - length: this.grid().getRoute(track).length, - })); - } } function isLisboa(space: City | Land | undefined): boolean { diff --git a/src/maps/portugal/settings.ts b/src/maps/portugal/settings.ts index b46f97e3..c12e2c05 100644 --- a/src/maps/portugal/settings.ts +++ b/src/maps/portugal/settings.ts @@ -51,12 +51,12 @@ export class PortugalMapSettings implements MapSettings { center: [11, 13], offset: { direction: TOP, distance: 0.2 }, }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 7], connectedTownExit: BOTTOM }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 8], connectedTownExit: BOTTOM }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 9], connectedTownExit: BOTTOM }, - { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 10], connectedTownExit: BOTTOM }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 7], connectedTownExits: [BOTTOM] }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 8], connectedTownExits: [BOTTOM] }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 9], connectedTownExits: [BOTTOM] }, + { connects: ["Sagres", "Madeira"], cost: 6, center: [17, 10], connectedTownExits: [BOTTOM] }, { connects: ["Lisboa", "Porto"], cost: 6, center: [6, 13] }, - { connects: ["Lisboa", "Sines"], cost: 6, center: [13, 9], connectedTownExit: BOTTOM }, + { connects: ["Lisboa", "Sines"], cost: 6, center: [13, 9], connectedTownExits: [BOTTOM] }, ]); getOverrides() { diff --git a/src/modules/towns_and_sea_links.ts b/src/modules/towns_and_sea_links.ts index ec86d1ce..4b81f08f 100644 --- a/src/modules/towns_and_sea_links.ts +++ b/src/modules/towns_and_sea_links.ts @@ -8,12 +8,16 @@ import { MoveValidator, RouteInfo } from "../engine/move/validator"; import { SpaceType } from "../engine/state/location_type"; import { City } from "../engine/map/city"; import { OwnedInterCityConnection } from "../engine/state/inter_city_connection"; +import { BuildPhase } from "../engine/build/phase"; +import { DanglerInfo } from "../engine/map/grid"; +import { PlayerColor } from "../engine/state/player"; export class TownsAndSeaLinksModule extends Module { installMixins(): void { this.installMixin(ConnectCitiesAction, skipCityValidationMixin); this.installMixin(Validator, allowTownConnectionMixin); this.installMixin(MoveValidator, allowGoodsMovementMixin); + this.installMixin(BuildPhase, noSeaRouteDanglersMixin); } } @@ -50,11 +54,8 @@ function allowTownConnectionMixin( protected isExitTowardsInterCity(space: Land, exit: Direction): boolean { return this.grid().connections.some(connection => connection.connects.some(c => c.equals(space.coordinates)) - && ( - Array.isArray(connection.connectedTownExit) - ? connection.connectedTownExit.includes(exit) - : connection.connectedTownExit === exit - ) + && Array.isArray(connection.connectedTownExits) + && connection.connectedTownExits.includes(exit) ); } } @@ -119,4 +120,42 @@ function allowGoodsMovementMixin( }); } } +} + +function noSeaRouteDanglersMixin( + Ctor: SimpleConstructor +): SimpleConstructor { + return class extends Ctor { + getDanglersAsInfo(color?: PlayerColor): DanglerInfo[] { + const ownedConnectionsWithSeaRoutes = this.grid().connections + .filter(connection => + Array.isArray(connection.connectedTownExits) && + connection.owner != undefined + ); + + return this.grid() + .getDanglers(color) + .filter(track => { + const matchingConnection = ownedConnectionsWithSeaRoutes.find(conn => + conn.connects.some(coord => + coord.q === track.coordinates.q + && coord.r === track.coordinates.r + ) + ); + + if (!matchingConnection) return true; + + const immovableExit = this.grid().getImmovableExitReference(track); + const isMatchingExit = matchingConnection.connectedTownExits?.includes(immovableExit) ?? false; + + if (isMatchingExit) { return false } + return true; + }) + .map(track => ({ + coordinates: track.coordinates, + immovableExit: this.grid().getImmovableExitReference(track), + length: this.grid().getRoute(track).length, + })); + } + }; } \ No newline at end of file