From 3193b7d6910dce9f6334d4933124aed0f3bac132 Mon Sep 17 00:00:00 2001 From: Robert Konigsberg Date: Wed, 13 Jan 2021 01:04:39 -0500 Subject: [PATCH 1/3] Add Mare Sernitatis Mine card, and register moon expansion in decks so cards are testable. --- server.ts | 2 +- src/CardFinder.ts | 2 + src/CardLoader.ts | 2 + src/cards/base/RoboticWorkforce.ts | 4 +- src/cards/moon/MareSerenitatisMine.ts | 47 ++++++++++++++++++ src/cards/moon/MoonCardManifest.ts | 6 +-- src/components/CreateGameForm.ts | 4 ++ src/moon/PlaceMoonRoadTile.ts | 7 ++- tests/cards/MareSerenitatisMine.spec.ts | 66 +++++++++++++++++++++++++ 9 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 src/cards/moon/MareSerenitatisMine.ts create mode 100644 tests/cards/MareSerenitatisMine.spec.ts diff --git a/server.ts b/server.ts index e4055e8d98..0a81f130ae 100644 --- a/server.ts +++ b/server.ts @@ -432,13 +432,13 @@ function createGame(req: http.IncomingMessage, res: http.ServerResponse): void { aresExtension: gameReq.aresExtension, aresHazards: true, // Not a runtime option. politicalAgendasExtension: gameReq.politicalAgendasExtension, + moonExpansion: gameReq.moonExpansion, promoCardsOption: gameReq.promoCardsOption, communityCardsOption: gameReq.communityCardsOption, solarPhaseOption: gameReq.solarPhaseOption, removeNegativeGlobalEventsOption: gameReq.removeNegativeGlobalEventsOption, includeVenusMA: gameReq.includeVenusMA, - moonExpansion: false, draftVariant: gameReq.draftVariant, initialDraftVariant: gameReq.initialDraft, diff --git a/src/CardFinder.ts b/src/CardFinder.ts index 42771ae93e..153faaae13 100644 --- a/src/CardFinder.ts +++ b/src/CardFinder.ts @@ -13,6 +13,7 @@ import {VENUS_CARD_MANIFEST} from './cards/venusNext/VenusCardManifest'; import {COMMUNITY_CARD_MANIFEST} from './cards/community/CommunityCardManifest'; import {ARES_CARD_MANIFEST} from './cards/ares/AresCardManifest'; import {StandardProjectCard} from './cards/standardProjects/StandardProjectCard'; +import {MOON_CARD_MANIFEST} from './cards/moon/MoonCardManifest'; export class CardFinder { private static decks: undefined | Array; @@ -28,6 +29,7 @@ export class CardFinder { TURMOIL_CARD_MANIFEST, ARES_CARD_MANIFEST, COMMUNITY_CARD_MANIFEST, + MOON_CARD_MANIFEST, ]; } return CardFinder.decks; diff --git a/src/CardLoader.ts b/src/CardLoader.ts index 12379f094d..9ae037b742 100644 --- a/src/CardLoader.ts +++ b/src/CardLoader.ts @@ -13,6 +13,7 @@ import {ICardFactory} from './cards/ICardFactory'; import {Deck} from './Deck'; import {GameModule} from './GameModule'; import {GameOptions} from './Game'; +import {MOON_CARD_MANIFEST} from './cards/moon/MoonCardManifest'; export class CardLoader { private readonly gameOptions: GameOptions; @@ -31,6 +32,7 @@ export class CardLoader { [gameOptions.aresExtension, ARES_CARD_MANIFEST], [gameOptions.promoCardsOption, PROMO_CARD_MANIFEST], [gameOptions.communityCardsOption, COMMUNITY_CARD_MANIFEST], + [gameOptions.moonExpansion, MOON_CARD_MANIFEST], ]; this.manifests = manifests.filter((a) => a[0]).map((a) => a[1]); diff --git a/src/cards/base/RoboticWorkforce.ts b/src/cards/base/RoboticWorkforce.ts index 53576d1a2c..8e2a69b289 100644 --- a/src/cards/base/RoboticWorkforce.ts +++ b/src/cards/base/RoboticWorkforce.ts @@ -80,7 +80,7 @@ export class RoboticWorkforce extends Card implements IProjectCard { CardName.MARE_IMBRIUM_MINE, CardName.MARE_NECTARIS_MINE, CardName.MARE_NUBIUM_MINE, - // CardName.MARE_SERENITATIS_MINE, + CardName.MARE_SERENITATIS_MINE, CardName.MARTIAN_INDUSTRIES, CardName.MARTIAN_MEDIA_CENTER, CardName.MEDICAL_LAB, @@ -313,7 +313,7 @@ export class RoboticWorkforce extends Card implements IProjectCard { new Updater(CardName.MARE_IMBRIUM_MINE, 0, 0, 1, 1, 0, 0), new Updater(CardName.MARE_NECTARIS_MINE, 0, 0, 1, 0, 0, 0), new Updater(CardName.MARE_NUBIUM_MINE, 0, 0, 0, 1, 0, 0), - // new Updater(CardName.MARE_SERENITATIS_MINE, 0, 0, 1, 1, 0, 0), + new Updater(CardName.MARE_SERENITATIS_MINE, 0, 0, 1, 1, 0, 0), new Updater(CardName.MARTIAN_INDUSTRIES, 1, 0, 1, 0, 0, 0), new Updater(CardName.MARTIAN_MEDIA_CENTER, 0, 2, 0, 0, 0, 0), new Updater(CardName.MEDICAL_LAB, 0, Math.floor(player.getTagCount(Tags.BUILDING) / 2), 0, 0, 0, 0), diff --git a/src/cards/moon/MareSerenitatisMine.ts b/src/cards/moon/MareSerenitatisMine.ts new file mode 100644 index 0000000000..088ebc8922 --- /dev/null +++ b/src/cards/moon/MareSerenitatisMine.ts @@ -0,0 +1,47 @@ +import {CardName} from '../../CardName'; +import {Player} from '../../Player'; +import {CardType} from '../CardType'; +import {IProjectCard} from '../IProjectCard'; +import {Tags} from '../Tags'; +import {CardMetadata} from '../CardMetadata'; +import {CardRenderer} from '../render/CardRenderer'; +import {Resources} from '../../Resources'; +import {MoonSpaces} from '../../moon/MoonSpaces'; +import {MoonExpansion} from '../../moon/MoonExpansion'; +import {PlaceMoonRoadTile} from '../../moon/PlaceMoonRoadTile'; +import {Units} from '../../Units'; +import {SpaceType} from '../../SpaceType'; + +export class MareSerenitatisMine implements IProjectCard { + public cost = 21; + public tags = [Tags.MOON, Tags.BUILDING]; + public cardType = CardType.AUTOMATED; + public name = CardName.MARE_SERENITATIS_MINE; + public reserveUnits = Units.of({titanium: 2, steel: 1}); + + public play(player: Player) { + Units.deductUnits(this.reserveUnits, player); + player.addProduction(Resources.STEEL, 1); + player.addProduction(Resources.TITANIUM, 1); + MoonExpansion.addMineTile(player, MoonSpaces.MARE_SERENITATIS, this.name); + MoonExpansion.raiseMiningRate(player); + const moon = MoonExpansion.moonData(player.game).moon; + const spaces = moon.getAdjacentSpaces(moon.getSpace(MoonSpaces.MARE_SERENITATIS)); + const availableRoadSpaces = spaces.filter((space) => { + space.player === undefined && space.spaceType === SpaceType.LAND; + }); + player.game.defer(new PlaceMoonRoadTile(player, 'Select a space next to Mare Serintatis to play a road', availableRoadSpaces)); + return undefined; + } + + public readonly metadata: CardMetadata = { + description: 'Spend 2 titanium and 1 steel. Increase your steel production 1 step and your titanium production 1 step. ' + + 'Place a mine ON THE RESERVED AREA and adjacent to it road tile. Raise Mining Rate 1 step and Logistic Rate 1 step.', + cardNumber: 'M04', + renderData: CardRenderer.builder((b) => { + b.minus().titanium(2).minus().steel(1).br; + b.production((pb) => pb.steel(1).titanium(1)).br; + b.moonMine().asterix().nbsp.moonRoad().asterix(); + }), + }; +} diff --git a/src/cards/moon/MoonCardManifest.ts b/src/cards/moon/MoonCardManifest.ts index 383401e097..77f4b8f3f0 100644 --- a/src/cards/moon/MoonCardManifest.ts +++ b/src/cards/moon/MoonCardManifest.ts @@ -57,7 +57,7 @@ import {CardManifest} from '../CardManifest'; import {MareImbriumMine} from './MareImbriumMine'; import {MareNectarisMine} from './MareNectarisMine'; import {MareNubiumMine} from './MareNubiumMine'; -// import {MareSerenitatisMine} from './MareSerenitatisMine'; +import {MareSerenitatisMine} from './MareSerenitatisMine'; // import {MicrosingularityPlant} from './MicrosingularityPlant'; // import {MiningComplex} from './MiningComplex'; // import {MiningRobotsManufCenter} from './MiningRobotsManufCenter'; @@ -101,10 +101,10 @@ export const MOON_CARD_MANIFEST = new CardManifest({ projectCards: [ // These cards are done. {cardName: CardName.MARE_NECTARIS_MINE, Factory: MareNectarisMine}, - // // These cards have behavior and rendering. {cardName: CardName.MARE_NUBIUM_MINE, Factory: MareNubiumMine}, {cardName: CardName.MARE_IMBRIUM_MINE, Factory: MareImbriumMine}, - // {cardName: CardName.MARE_SERENITATIS_MINE, Factory: MareSerenitatisMine}, + {cardName: CardName.MARE_SERENITATIS_MINE, Factory: MareSerenitatisMine}, + // // These cards have behavior and rendering. // {cardName: CardName.HABITAT_14, Factory: Habitat14}, // {cardName: CardName.GEODESIC_TENTS, Factory: GeodesicTents}, // {cardName: CardName.SPHERE_HABITATS, Factory: SphereHabitats}, diff --git a/src/components/CreateGameForm.ts b/src/components/CreateGameForm.ts index 177bed8f9f..055f6787d8 100644 --- a/src/components/CreateGameForm.ts +++ b/src/components/CreateGameForm.ts @@ -47,6 +47,7 @@ export interface CreateGameModel { communityCardsOption: boolean; aresExtension: boolean; politicalAgendasExtension: AgendaStyle; + moonExpansion: boolean; undoOption: boolean; showTimers: boolean; fastModeOption: boolean; @@ -115,6 +116,7 @@ export const CreateGameForm = Vue.component('create-game-form', { communityCardsOption: false, aresExtension: false, politicalAgendasExtension: AgendaStyle.STANDARD, + moonExpansion: false, undoOption: false, showTimers: false, fastModeOption: false, @@ -383,6 +385,7 @@ export const CreateGameForm = Vue.component('create-game-form', { const communityCardsOption = component.communityCardsOption; const aresExtension = component.aresExtension; const politicalAgendasExtension = this.politicalAgendasExtension; + const moonExpansion = component.moonExpansion; const undoOption = component.undoOption; const showTimers = component.showTimers; const fastModeOption = component.fastModeOption; @@ -441,6 +444,7 @@ export const CreateGameForm = Vue.component('create-game-form', { communityCardsOption, aresExtension: aresExtension, politicalAgendasExtension: politicalAgendasExtension, + moonExpansion: moonExpansion, undoOption, showTimers, fastModeOption, diff --git a/src/moon/PlaceMoonRoadTile.ts b/src/moon/PlaceMoonRoadTile.ts index 24620aa681..72d9a6d377 100644 --- a/src/moon/PlaceMoonRoadTile.ts +++ b/src/moon/PlaceMoonRoadTile.ts @@ -1,3 +1,4 @@ +import {ISpace} from '../boards/ISpace'; import {DeferredAction} from '../deferredActions/DeferredAction'; import {SelectSpace} from '../inputs/SelectSpace'; import {Player} from '../Player'; @@ -8,11 +9,14 @@ export class PlaceMoonRoadTile implements DeferredAction { constructor( public player: Player, public title: string = 'Select a space on the Moon for a road tile.', + public spaces?: Array, ) {} public execute() { const moonData = MoonExpansion.moonData(this.player.game); - const spaces = moonData.moon.getAvailableSpacesOnLand(this.player); + const spaces = this.spaces !== undefined ? + this.spaces: + moonData.moon.getAvailableSpacesOnLand(this.player); if (spaces.length === 0) { return undefined; @@ -23,6 +27,7 @@ export class PlaceMoonRoadTile implements DeferredAction { (space) => { MoonExpansion.addRoadTile(this.player, space.id); MoonExpansion.raiseLogisticRate(this.player); + // TODO(kberg): do not raise production rate unless this is done with a standard project. this.player.addProduction(Resources.MEGACREDITS, 1, this.player.game); return undefined; }); diff --git a/tests/cards/MareSerenitatisMine.spec.ts b/tests/cards/MareSerenitatisMine.spec.ts new file mode 100644 index 0000000000..d0fe8b23ca --- /dev/null +++ b/tests/cards/MareSerenitatisMine.spec.ts @@ -0,0 +1,66 @@ +import {Game} from '../../src/Game'; +import {IMoonData} from '../../src/moon/IMoonData'; +import {MoonExpansion} from '../../src/moon/MoonExpansion'; +import {Player} from '../../src/Player'; +import {setCustomGameOptions, TestPlayers} from '../TestingUtils'; +import {MareSerenitatisMine} from '../../src/cards/moon/MareSerenitatisMine'; +import {expect} from 'chai'; +import {Resources} from '../../src/Resources'; +import {PlaceMoonRoadTile} from '../../src/moon/PlaceMoonRoadTile'; +import {MoonSpaces} from '../../src/moon/MoonSpaces'; +import {TileType} from '../../src/TileType'; + +const MOON_OPTIONS = setCustomGameOptions({moonExpansion: true}); + +describe('MareSerenitatisMine', () => { + let game: Game; + let player: Player; + let moonData: IMoonData; + let card: MareSerenitatisMine; + + beforeEach(() => { + player = TestPlayers.BLUE.newPlayer(); + game = Game.newInstance('id', [player], player, MOON_OPTIONS); + moonData = MoonExpansion.moonData(game); + card = new MareSerenitatisMine(); + }); + + it('can play', () => { + // TODO: Ensuring resources is going to require changes coming later. + }); + + it('play', () => { + player.titanium = 3; + player.steel = 3; + expect(player.getProduction(Resources.STEEL)).eq(0); + expect(player.getProduction(Resources.TITANIUM)).eq(0); + expect(player.getTerraformRating()).eq(14); + expect(moonData.miningRate).eq(0); + + card.play(player); + + expect(player.titanium).eq(1); + expect(player.steel).eq(2); + expect(player.getProduction(Resources.STEEL)).eq(1); + expect(player.getProduction(Resources.TITANIUM)).eq(1); + expect(player.getTerraformRating()).eq(15); + expect(moonData.miningRate).eq(1); + + const mareSerenitatis = moonData.moon.getSpace(MoonSpaces.MARE_SERENITATIS); + expect(mareSerenitatis.player).eq(player); + expect(mareSerenitatis.tile!.tileType).eq(TileType.MOON_MINE); + + const deferredAction = game.deferredActions.next() as PlaceMoonRoadTile; + const roadSpace = deferredAction.spaces![0]; + expect(roadSpace.tile).is.undefined; + expect(roadSpace.player).is.undefined; + expect(moonData.logisticRate).eq(0); + + deferredAction!.execute()!.cb(roadSpace); + expect(roadSpace.tile!.tileType).eq(TileType.MOON_ROAD); + expect(roadSpace.player).eq(player); + expect(moonData.logisticRate).eq(1); + expect(player.getTerraformRating()).eq(16); + }); +}); + From cf0e89cd10f619af3fe22fb70bc7d70b8f21857f Mon Sep 17 00:00:00 2001 From: Robert Konigsberg Date: Wed, 13 Jan 2021 22:30:25 -0500 Subject: [PATCH 2/3] Mare Serenitatis road space fix. --- src/cards/moon/MareSerenitatisMine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cards/moon/MareSerenitatisMine.ts b/src/cards/moon/MareSerenitatisMine.ts index 088ebc8922..d9d74c5b82 100644 --- a/src/cards/moon/MareSerenitatisMine.ts +++ b/src/cards/moon/MareSerenitatisMine.ts @@ -28,7 +28,7 @@ export class MareSerenitatisMine implements IProjectCard { const moon = MoonExpansion.moonData(player.game).moon; const spaces = moon.getAdjacentSpaces(moon.getSpace(MoonSpaces.MARE_SERENITATIS)); const availableRoadSpaces = spaces.filter((space) => { - space.player === undefined && space.spaceType === SpaceType.LAND; + return space.player === undefined && space.spaceType === SpaceType.LAND; }); player.game.defer(new PlaceMoonRoadTile(player, 'Select a space next to Mare Serintatis to play a road', availableRoadSpaces)); return undefined; From e6295a81c4d5ef7b2ec1a1237c41ed4b28acb965 Mon Sep 17 00:00:00 2001 From: Robert Konigsberg Date: Wed, 13 Jan 2021 22:41:42 -0500 Subject: [PATCH 3/3] Switch MareSerenitatisMine to use the abstract Card. --- src/cards/moon/MareSerenitatisMine.ts | 41 +++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/cards/moon/MareSerenitatisMine.ts b/src/cards/moon/MareSerenitatisMine.ts index d9d74c5b82..e39545da5b 100644 --- a/src/cards/moon/MareSerenitatisMine.ts +++ b/src/cards/moon/MareSerenitatisMine.ts @@ -1,9 +1,7 @@ import {CardName} from '../../CardName'; import {Player} from '../../Player'; import {CardType} from '../CardType'; -import {IProjectCard} from '../IProjectCard'; import {Tags} from '../Tags'; -import {CardMetadata} from '../CardMetadata'; import {CardRenderer} from '../render/CardRenderer'; import {Resources} from '../../Resources'; import {MoonSpaces} from '../../moon/MoonSpaces'; @@ -11,12 +9,30 @@ import {MoonExpansion} from '../../moon/MoonExpansion'; import {PlaceMoonRoadTile} from '../../moon/PlaceMoonRoadTile'; import {Units} from '../../Units'; import {SpaceType} from '../../SpaceType'; +import {IProjectCard} from '../IProjectCard'; +import {Card} from '../Card'; + +export class MareSerenitatisMine extends Card implements IProjectCard { + constructor() { + super({ + name: CardName.MARE_SERENITATIS_MINE, + cardType: CardType.AUTOMATED, + tags: [Tags.MOON, Tags.BUILDING], + cost: 21, + + metadata: { + description: 'Spend 2 titanium and 1 steel. Increase your steel production 1 step and your titanium production 1 step. ' + + 'Place a mine ON THE RESERVED AREA and adjacent to it road tile. Raise Mining Rate 1 step and Logistic Rate 1 step.', + cardNumber: 'M04', + renderData: CardRenderer.builder((b) => { + b.minus().titanium(2).minus().steel(1).br; + b.production((pb) => pb.steel(1).titanium(1)).br; + b.moonMine().asterix().nbsp.moonRoad().asterix(); + }), + }, + }); + } -export class MareSerenitatisMine implements IProjectCard { - public cost = 21; - public tags = [Tags.MOON, Tags.BUILDING]; - public cardType = CardType.AUTOMATED; - public name = CardName.MARE_SERENITATIS_MINE; public reserveUnits = Units.of({titanium: 2, steel: 1}); public play(player: Player) { @@ -33,15 +49,4 @@ export class MareSerenitatisMine implements IProjectCard { player.game.defer(new PlaceMoonRoadTile(player, 'Select a space next to Mare Serintatis to play a road', availableRoadSpaces)); return undefined; } - - public readonly metadata: CardMetadata = { - description: 'Spend 2 titanium and 1 steel. Increase your steel production 1 step and your titanium production 1 step. ' + - 'Place a mine ON THE RESERVED AREA and adjacent to it road tile. Raise Mining Rate 1 step and Logistic Rate 1 step.', - cardNumber: 'M04', - renderData: CardRenderer.builder((b) => { - b.minus().titanium(2).minus().steel(1).br; - b.production((pb) => pb.steel(1).titanium(1)).br; - b.moonMine().asterix().nbsp.moonRoad().asterix(); - }), - }; }