Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions api/controllers/game/load-fixture-gamestate.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ module.exports = async function (req, res) {
secondCard = null,
scrap = [],
deck,
// Overrides so tests can stage states no live move can produce anymore -- notably the
// legacy RESOLVING_FOUR phase, which stored games still hold and replay.
phase = GamePhase.MAIN,
turn = 0,
resolved = null,
oneOff = null,
} = req.body;

const allFixtureCards = new Set(Object.values(req.body).flat()
Expand Down Expand Up @@ -58,15 +64,15 @@ module.exports = async function (req, res) {
scrap: populatedScrap,
twos: [],
discardedCards: [],
oneOff: null,
oneOff,
oneOffTarget: null,
resolved: null,
resolved,
playedCard: null,
gameId: game.id,
playedBy: 0,
moveType: MoveType.LOADFIXTURE,
turn: 0,
phase: GamePhase.MAIN,
turn,
phase,
targetCard: null,
};

Expand Down
4 changes: 3 additions & 1 deletion api/helpers/game-states/ai/get-legal-moves.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ module.exports = {
},
sync: true,
fn: ({ currentState, playedBy, priorStates }, exits) => {
const disallowedMoveTypes = [ 'DEAL', 'CONCEDE', 'STALEMATE_REQUEST', 'STALEMATE_ACCEPT', 'FIZZLE', 'LOADFIXTURE' ];
// RESOLVE_FOUR is retained in the enum so stored games still render, but its move helper is
// gone -- fours resolve immediately now -- so it must not be looked up here.
const disallowedMoveTypes = [ 'DEAL', 'CONCEDE', 'STALEMATE_REQUEST', 'STALEMATE_ACCEPT', 'FIZZLE', 'LOADFIXTURE', 'RESOLVE_FOUR' ];
const moveTypes = Object.values(_.omit(MoveType, disallowedMoveTypes));
const res = [];
for (let moveType of moveTypes) {
Expand Down
24 changes: 0 additions & 24 deletions api/helpers/game-states/ai/get-move-bodies-for-move-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,30 +130,6 @@ module.exports = {
res = scrap.map((card) => ({ moveType, playedBy, cardId: card.id }));
break;

case MoveType.RESOLVE_FOUR: {

if (playerHand.length === 1) {
res = [
{
moveType,
playedBy,
cardId1: playerHand[0].id,
},
];
}

for (let i = 0; i < playerHand.length; i++) {
for (let j = i + 1; j < playerHand.length; j++) {
res.push({
moveType,
playedBy,
cardId1: playerHand[i].id,
cardId2: playerHand[j].id
});
}
}
break;
}

case MoveType.RESOLVE_FIVE:
if (!playerHand.length) {
Expand Down
13 changes: 11 additions & 2 deletions api/helpers/game-states/get-log.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,17 @@ module.exports = {
return `The ${resolvedCardName} resolves; the ${targetCardName} is scrapped.`;
case 3:
return `The ${resolvedCardName} one-off resolves; ${opponent} will draw one card of their choice from the Scrap pile.`;
case 4:
return `The ${resolvedCardName} one-off resolves; ${player} must discard two cards.`;
case 4: {
// Legacy rows (rules < 3.0.0) deferred the discard to a separate resolveFour
// move, so the cards are named on that row and this one carries none.
if (!discardedCards?.length) {
return `The ${resolvedCardName} one-off resolves; ${player} must discard two cards.`;
}
const discardNames = discardedCards.map(getFullCardName);
const discardList =
discardNames.length > 1 ? `the ${discardNames[0]} and the ${discardNames[1]}` : `the ${discardNames[0]}`;
return `The ${resolvedCardName} one-off resolves, discarding ${discardList} at random from ${player}'s hand.`;
}
case 5:
return `The ${resolvedCardName} one-off resolves; ${opponent} must discard 1 card, and will draw up to 3.`;
case 6:
Expand Down
6 changes: 2 additions & 4 deletions api/helpers/game-states/moves/one-off/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,9 @@ module.exports = {
}
return exits.success();

// Four requires opponent to have cards in hand
// Fours discard at random, so an empty hand just discards nothing. The arm has to
// stay: the default below rejects any rank that isn't explicitly allowed here.
case 4:
if (!opponent.hand.length) {
throw new Error('game.snackbar.oneOffs.four.opponentHasNoCards');
}
return exits.success();

// Five and sevens require cards in deck
Expand Down
73 changes: 0 additions & 73 deletions api/helpers/game-states/moves/resolve-four/execute.js

This file was deleted.

67 changes: 0 additions & 67 deletions api/helpers/game-states/moves/resolve-four/validate.js

This file was deleted.

4 changes: 3 additions & 1 deletion api/helpers/game-states/moves/resolve/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ module.exports = {
case 6:
result = sails.helpers.gameStates.moves.resolve.six(result);
break;
case 3:
case 4:
result = sails.helpers.gameStates.moves.resolve.four(result, playedBy);
break;
case 3:
case 5:
case 7:
// These one-offs require an additional user input after resolving
Expand Down
39 changes: 39 additions & 0 deletions api/helpers/game-states/moves/resolve/four.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
module.exports = {
friendlyName: 'Resolve Four One-Off',

description:
'Returns new GameState resulting from resolving a four, which discards two cards at random from the ' +
'hand of the player resolving it. A hand of fewer than two cards discards whatever it holds.',

inputs: {
currentState: {
type: 'ref',
description: 'The latest GameState before the four discards its two cards',
required: true,
},
playedBy: {
type: 'number',
description: 'Player number of player requesting move.',
}
},

sync: true, // synchronous helper
fn: ({ currentState, playedBy }, exits) => {
const result = _.cloneDeep(currentState);

// The player resolving the one-off is the opposite of the one who played it, so `playedBy`
// identifies the player whose hand is discarded -- not the four's caster.
const victim = playedBy ? result.p1 : result.p0;

// sampleSize returns *up to* two cards, so a short hand simply discards everything it has
const discardedCards = _.sampleSize(victim.hand, 2);
const discardedIds = new Set(discardedCards.map(({ id }) => id));

// Filter by id rather than splicing -- sampleSize returns the cards, not their positions
victim.hand = victim.hand.filter(({ id }) => !discardedIds.has(id));
result.scrap.push(...discardedCards);
result.discardedCards = discardedCards;

return exits.success(result);
},
};
5 changes: 2 additions & 3 deletions api/helpers/game-states/moves/seven-one-off/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,9 @@ module.exports = {
return exits.success();

// Four requires opponent to have cards in hand
// Fours discard at random, so an empty hand just discards nothing. The arm has to
// stay: the default below rejects any rank that isn't explicitly allowed here.
case 4:
if (!opponent.hand.length) {
throw new BadRequestError('game.snackbar.oneOffs.four.opponentHasNoCards');
}
return exits.success();

// Five and sevens require cards in deck
Expand Down
2 changes: 1 addition & 1 deletion api/models/GameStateRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ module.exports = {
* 1 - MAIN
* 2 - COUNTERING
* 3 - RESOLVING_THREE
* 4 - RESOLVING_FOUR
* 4 - RESOLVING_FOUR (legacy; rules < 3.0.0 only -- fours resolve immediately now)
* 5 - RESOLVING_FIVE
* 7 - RESOLVING_SEVEN
*/
Expand Down
18 changes: 1 addition & 17 deletions api/policies/hasValidMoveBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const DeckIds = require('../../utils/DeckIds.json');
*
*/
module.exports = function (req, res, next) {
const { moveType, cardId, cardId1, cardId2, targetId, targetType } = req.body;
const { moveType, cardId, targetId, targetType } = req.body;

switch (moveType) {
// These moves require no extra data
Expand Down Expand Up @@ -43,22 +43,6 @@ module.exports = function (req, res, next) {
}
return next();

// Requires `cardId1` and optionally accepts `cardId2`
case MoveType.RESOLVE_FOUR:
{
if (!cardId1) {
return res.badRequest({ message: 'Must specify a card' });
}

if (!DeckIds.includes(cardId1)) {
return res.badRequest({ message: `${cardId1} is not a valid cardId` });
}

if (cardId2 && !DeckIds.includes(cardId2)) {
return res.badRequest({ message: `${cardId2} is not a valid cardId` });
}
}
return next();

// Requires a non-empty `discardedCards` array of valid card IDs
case MoveType.DISCARD_TO_HAND_LIMIT:
Expand Down
2 changes: 1 addition & 1 deletion docs/game-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ On your turn you must make exactly one of the following actions below.
1. Counter **target One-Off** (play this when an opponent plays a **One-Off** effect to stop it from happening)
2. Scrap **target Royal or Glasses Eight**
- **Three**: Choose one card from the **Scrap Pile** and put it in your hand.
- **Four**: Your opponent discards two cards of their choice from their hand to the **Scrap Pile**
- **Four**: Two cards are discarded at random from your opponent's hand to the **Scrap Pile**. If they hold fewer than two cards, they discard whatever they have.
- **Five**: Discard one card then draw up to three cards from the **Deck** up to the 8 card hand limit.
- **Six**: Scrap all **Royals and Glasses Eights**, including your own.
- **Seven**: Reveal the top two cards from the **Deck** and choose one of them to play immediately. Play this card however you would like. The other card is placed back on top of the deck.
Expand Down
4 changes: 2 additions & 2 deletions docs/game-state-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Enum describing the phase of a turn the game is currently in. Used to validate w
* counter
* resolve
* resolvingThree \- Picking card from scrap. Only legal move is resolveThree
* resolvingFour \- Choosing cards to discard due to opponent four. Only legal move is resolveFour
* resolvingFour \- **Legacy (rules \< 3.0.0).** Choosing cards to discard due to opponent four; the only legal move was resolveFour. As of rules 3.0.0 a four discards two cards at random the moment it resolves, so no new game enters this phase. The value is retained because stored games still hold it and are re-rendered on replay.
* resolvingFive \- Choosing card to discard before drawing. Only legal move is resolveFive
* resolvingSeven \- Picking one of the top cards from the deck. Must play a seven move next. Legal next moves:
* sevenPoints
Expand Down Expand Up @@ -56,7 +56,7 @@ Enum designating which kind of move was made.
* resolve
* fizzle \- a one-off was countered, so it and everything played on it are scrapped
* resolveThree (picking a card from the scrap)
* resolveFour (discarding from hand)
* resolveFour (discarding from hand) \- **Legacy (rules \< 3.0.0);** no longer accepted, retained so stored games still render
* resolveFive (discarding before drawing)
* discardToHandLimit
* sevenPoints
Expand Down
Loading
Loading