Skip to content

Commit

Permalink
Confirm Pharmacy Union effects occur before cards with positive Micro…
Browse files Browse the repository at this point in the history
…be effects.

The Splice/Pharmacy Union issue was limited specifically to when the played microbe card did NOT store microbe resources. So that part of splice is now deferred, and that ought to do it.

Greens gp03 had a similar behavior and was also adjusted.
  • Loading branch information
kberg committed Jul 6, 2024
1 parent 4b3da5a commit dfb7bf7
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/server/cards/promo/PharmacyUnion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class PharmacyUnion extends CorporationCard {
constructor() {
super({
name: CardName.PHARMACY_UNION,
startingMegaCredits: 46, // 54 minus 8 for the 2 deseases
startingMegaCredits: 46, // 54 minus 8 for the 2 diseases
resourceType: CardResource.DISEASE,

behavior: {
Expand Down
35 changes: 19 additions & 16 deletions src/server/cards/promo/Splice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {Resource} from '../../../common/Resource';
import {all} from '../Options';
import {message} from '../../logs/MessageBuilder';
import {ICard} from '../ICard';
import {GainResources} from '../../deferredActions/GainResources';

export class Splice extends CorporationCard {
constructor() {
Expand Down Expand Up @@ -51,36 +52,38 @@ export class Splice extends CorporationCard {
return this.onCardPlayed(player, card);
}

public onCardPlayed(player: IPlayer, card: ICard): OrOptions | undefined {
if (card.tags.includes(Tag.MICROBE) === false) {
return undefined;
public onCardPlayed(player: IPlayer, card: ICard): undefined {
const game = player.game;
const microbeTags = player.tags.cardTagCount(card, Tag.MICROBE);
if (microbeTags === 0) {
return;
}
const gainPerMicrobe = 2;
const microbeTagsCount = player.tags.cardTagCount(card, Tag.MICROBE);
const megacreditsGain = microbeTagsCount * gainPerMicrobe;

const addResource = new SelectOption('Add a microbe resource to this card', 'Add microbe').andThen(() => {
const gain = microbeTags * 2;

const gainResource = new SelectOption('Add a microbe resource to this card', 'Add microbe').andThen(() => {
player.addResourceTo(card);
return undefined;
});

const getMegacredits = new SelectOption(
message('Gain ${0} M€', (b)=>b.number(megacreditsGain)),
const gainMC = new SelectOption(
message('Gain ${0} M€', (b) => b.number(gain)),
'Gain M€')
.andThen(() => {
player.stock.add(Resource.MEGACREDITS, megacreditsGain, {log: true});
game.defer(new GainResources(player, Resource.MEGACREDITS, {count: gain, log: true}));
return undefined;
});

// Splice owner get 2M€ per microbe tag
player.game.getCardPlayerOrThrow(this.name).stock.add(Resource.MEGACREDITS, megacreditsGain, {log: true});
// Splice owner gets 2M€ per microbe tag
const cardPlayer = game.getCardPlayerOrThrow(this.name);
game.defer(new GainResources(cardPlayer, Resource.MEGACREDITS, {count: gain, log: true}));

// Card player choose between 2 M€ and a microbe on card, if possible
if (card.resourceType === CardResource.MICROBE) {
return new OrOptions(addResource, getMegacredits);
// Card player chooses between 2 M€ and a microbe on card, if possible
player.defer(new OrOptions(gainResource, gainMC));
} else {
player.stock.add(Resource.MEGACREDITS, megacreditsGain, {log: true});
return undefined;
gainMC.cb(undefined);
}
return undefined;
}
}
2 changes: 1 addition & 1 deletion src/server/turmoil/parties/Greens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class GreensPolicy03 implements IPolicy {
const tags = [Tag.ANIMAL, Tag.PLANT, Tag.MICROBE];
const tagCount = card.tags.filter((tag) => tags.includes(tag)).length;

player.stock.add(Resource.MEGACREDITS, tagCount * 2);
player.defer(() => player.stock.add(Resource.MEGACREDITS, tagCount * 2));
}
}

Expand Down
10 changes: 9 additions & 1 deletion tests/cards/promo/Merger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,17 @@ describe('Merger', () => {
expect(player.isCorporation(CardName.SATURN_SYSTEMS)).is.true;

player2.playCard(new VestaShipyard());
expect(player.production.megacredits).to.eq(1); // Saturn Sys
runAllActions(game);

expect(player.production.megacredits).to.eq(1); // Saturn Systems

player2.playCard(new Ants());

runAllActions(game);
const orOptions = cast(player2.popWaitingFor(), OrOptions);
orOptions.options[1].cb(); // Gain MC.
runAllActions(game);

expect(player.megaCredits).to.eq(2); // Splice
});

Expand Down
111 changes: 110 additions & 1 deletion tests/cards/promo/PharmacyUnion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ import {SelectInitialCards} from '../../../src/server/inputs/SelectInitialCards'
import {OrOptions} from '../../../src/server/inputs/OrOptions';
import {TestPlayer} from '../../TestPlayer';
import {Virus} from '../../../src/server/cards/base/Virus';
import {cast, runAllActions} from '../../TestingUtils';
import {cast, runAllActions, setRulingParty} from '../../TestingUtils';
import {Player} from '../../../src/server/Player';
import {testGame} from '../../TestGame';
import {Leavitt} from '../../../src/server/cards/community/Leavitt';
import {Splice} from '../../../src/server/cards/promo/Splice';
import {Merger} from '../../../src/server/cards/promo/Merger';
import {SelectCard} from '../../../src/server/inputs/SelectCard';
import {ICorporationCard} from '../../../src/server/cards/corporation/ICorporationCard';
import {GMOContract} from '../../../src/server/cards/turmoil/GMOContract';
import {Tardigrades} from '../../../src/server/cards/base/Tardigrades';
import {SymbioticFungus} from '../../../src/server/cards/base/SymbioticFungus';
import {PartyName} from '../../../src/common/turmoil/PartyName';

describe('PharmacyUnion', function() {
let card: PharmacyUnion;
Expand Down Expand Up @@ -227,4 +235,105 @@ describe('PharmacyUnion', function() {
expect(card.resourceCount).to.eq(1);
expect(player.getTerraformRating()).to.eq(21);
});

describe('Prioritize effect order', () => {
it('Compatible with Splice', () => {
const card = new PharmacyUnion();
const [/* game */, player, player2] = testGame(2);

player2.playCorporationCard(new Splice());

expect(player2.megaCredits).eq(48);

player.playCorporationCard(card);

// PU starts with 46, gains 4 from Splice
expect(player.megaCredits).eq(50);
// Splice starts with 48, gains 4 from PU
expect(player2.megaCredits).eq(52);
});

it('Merge with Splice', () => {
const card = new PharmacyUnion();
const [game, player/* , player2 */] = testGame(2);

player.playCorporationCard(new Splice());

// Splice starts with 48
expect(player.megaCredits).eq(48);

player.playCard(new Merger());
runAllActions(game);

const selectCorp = cast(player.popWaitingFor(), SelectCard<ICorporationCard>);
selectCorp.cb([card]);
runAllActions(game);

// 48 // Splice value
// - 42 = 6 // Merger cost
// + 46 = 52 // Pharmacy Union MC
// + 8 = 60 // Splice rewards.
// PU costs already taken accounted for. See card for details.
expect(player.megaCredits).eq(60);
});
});

it('Splice + PU during gameplay', () => {
const card = new PharmacyUnion();
const [game, player/* , player2 */] = testGame(2);

// The test should have Splice first. I think it's not vital, but
// that's how onCardPlayed actions are resolved.
player.corporations.push(new Splice(), card);

player.megaCredits = 1;
// Symbiotic Fungus has a microbe tag, and doesn't hold microbes, which simplifies Splice's decision.
// And is actually the case where Pharmacy Union was not working out.
player.playCard(new SymbioticFungus());

// Expect this to be the PU action.
game.deferredActions.runNext();
expect(player.megaCredits).eq(0);
cast(player.popWaitingFor(), undefined);

game.deferredActions.runNext();
cast(player.popWaitingFor(), undefined);

game.deferredActions.runNext();
cast(player.popWaitingFor(), undefined);

expect(game.deferredActions.length).eq(0);

expect(player.megaCredits).eq(4);
});

it('Compatible with GMO Contract', () => {
const card = new PharmacyUnion();
const [game, player/* , player2 */] = testGame(2, {turmoilExtension: true});

player.corporations.push(card);
player.playedCards.push(new GMOContract());
player.megaCredits = 2;
player.playCard(new Tardigrades());

runAllActions(game);

// Gained 2MC from GMO which it did not lose because PU went first.
expect(player.megaCredits).eq(2);
});

it('Compatible with Greens policy gp03', () => {
const card = new PharmacyUnion();
const [game, player/* , player2 */] = testGame(2, {turmoilExtension: true});

player.corporations.push(card);
setRulingParty(game, PartyName.GREENS, 'gp03');
player.megaCredits = 2;
player.playCard(new Tardigrades());

runAllActions(game);

// Gained 2MC from gp03 which it did not lose because PU went first.
expect(player.megaCredits).eq(2);
});
});
84 changes: 45 additions & 39 deletions tests/cards/promo/Splice.spec.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,94 @@
import {expect} from 'chai';
import {cast, formatMessage} from '../../TestingUtils';
import {cast, formatMessage, runAllActions} from '../../TestingUtils';
import {Tardigrades} from '../../../src/server/cards/base/Tardigrades';
import {PharmacyUnion} from '../../../src/server/cards/promo/PharmacyUnion';
import {Recyclon} from '../../../src/server/cards/promo/Recyclon';
import {Splice} from '../../../src/server/cards/promo/Splice';
import {testGame} from '../../TestGame';
import {SelectInitialCards} from '../../../src/server/inputs/SelectInitialCards';
import {OrOptions} from '../../../src/server/inputs/OrOptions';
import {TestPlayer} from '../../TestPlayer';
import {SelectOption} from '../../../src/server/inputs/SelectOption';
import {IGame} from '../../../src/server/IGame';

describe('Splice', function() {
let card: Splice;
let game: IGame;
let player: TestPlayer;
let player2: TestPlayer;

beforeEach(function() {
card = new Splice();
[/* game */, player, player2] = testGame(2, {skipInitialCardSelection: false});
[game, player, player2] = testGame(2);
});

it('Should play', function() {
const card2 = new Tardigrades();
const tardigrades = new Tardigrades();
const play = card.play(player);
expect(play).is.undefined;

player.corporations.push(card);

player2.playedCards.push(card2);
const action = cast(card.onCardPlayed(player2, card2), OrOptions);
player2.playedCards.push(tardigrades);
cast(card.onCardPlayed(player2, tardigrades), undefined);
runAllActions(game);
const orOptions = cast(player2.popWaitingFor(), OrOptions);

expect(action.options).has.lengthOf(2);
const orOptions = cast(action.options[0], SelectOption);
expect(orOptions.options).has.lengthOf(2);

orOptions.cb(undefined);
expect(card2.resourceCount).to.eq(1);
const selectMoney = cast(orOptions.options[0], SelectOption);
selectMoney.cb(undefined);
runAllActions(game);
cast(player.popWaitingFor(), undefined);

expect(tardigrades.resourceCount).to.eq(1);
expect(player.megaCredits).to.eq(2);
});

it('Should play with multiple microbe tags', function() {
player.popWaitingFor(); // Select initial cards
const card2 = new PharmacyUnion();
const play = card.play(player);
const pharmacyUnion = new PharmacyUnion();
cast(card.play(player), undefined);
player.corporations.push(card);
const play2 = card2.play(player);
player2.corporations.push(card2);
expect(play).is.undefined;
expect(play2).is.undefined;

const action = card.onCardPlayed(player2, card2);
cast(action, undefined);
runAllActions(game);
cast(player.getWaitingFor(), undefined);
cast(player2.popWaitingFor(), undefined);

cast(pharmacyUnion.play(player), undefined);
player2.corporations.push(pharmacyUnion);

cast(card.onCardPlayed(player2, pharmacyUnion), undefined);

runAllActions(game);
cast(player.popWaitingFor(), undefined);
cast(player2.popWaitingFor(), undefined);

expect(player.megaCredits).to.eq(4);
expect(player2.megaCredits).to.eq(4);
});

it('Should grant Recyclon a Microbe or 2MC', function() {
const card2 = new Recyclon();
// Player 1 picks Splice
const pi = cast(player.getWaitingFor(), SelectInitialCards);
pi.options[0].cb([card]);
pi.options[1].cb([]);
pi.cb(undefined);
// Player 2 picks Recyclon
const pi2 = cast(player2.getWaitingFor(), SelectInitialCards);
pi2.options[0].cb([card2]);
pi2.options[1].cb([]);
pi2.cb(undefined);
it('Should grant Recyclon a microbe or 2MC', function() {
const recyclon = new Recyclon();

player.playCorporationCard(card);
player2.playCorporationCard(recyclon);

// Default resource on Recyclon and player2's MC
expect(card2.resourceCount).to.eq(1);
expect(recyclon.resourceCount).to.eq(1);
expect(player2.megaCredits).to.eq(38);

// Player 2 should have the option to pick a microbe or 2 MC
const pi3 = cast(player2.getWaitingFor(), OrOptions);
expect(pi3.options).has.lengthOf(2);
expect(pi3.options[0].title).to.eq('Add a microbe resource to this card');
expect(formatMessage(pi3.options[1].title)).to.eq('Gain 2 M€');
const orOptions = cast(player2.getWaitingFor(), OrOptions);
expect(orOptions.options).has.lengthOf(2);
expect(orOptions.options[0].title).to.eq('Add a microbe resource to this card');
expect(formatMessage(orOptions.options[1].title)).to.eq('Gain 2 M€');

// Pick the microbe
pi3.options[0].cb();
expect(card2.resourceCount).to.eq(2);
orOptions.options[0].cb();
expect(recyclon.resourceCount).to.eq(2);

// Pick 2 MC
pi3.options[1].cb();
orOptions.options[1].cb();
runAllActions(game);
expect(player2.megaCredits).to.eq(40);
});
});
6 changes: 3 additions & 3 deletions tests/turmoil/parties/Greens.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {expect} from 'chai';
import {IGame} from '../../../src/server/IGame';
import {Space} from '../../../src/server/boards/Space';
import {cast, setRulingParty, addGreenery} from '../../TestingUtils';
import {cast, setRulingParty, addGreenery, runAllActions} from '../../TestingUtils';
import {TestPlayer} from '../../TestPlayer';
import {GREENS_BONUS_1, GREENS_BONUS_2, GREENS_POLICY_4} from '../../../src/server/turmoil/parties/Greens';
import {Lichen} from '../../../src/server/cards/base/Lichen';
Expand Down Expand Up @@ -58,8 +58,8 @@ describe('Greens', function() {
it('Ruling policy 3: When you play an animal, plant or microbe tag, gain 2 MC', function() {
setRulingParty(game, PartyName.GREENS, 'gp03');

const lichen = new Lichen();
player.playCard(lichen);
player.playCard(new Lichen());
runAllActions(game);
expect(player.megaCredits).to.eq(2);
});

Expand Down

0 comments on commit dfb7bf7

Please sign in to comment.