cuttle-cards / cuttle

A two-player battle card game for all ages, built with nodejs, sailsjs, and vuejs
MIT License
142 stars 113 forks source link

[Feature]: Implement `sevenFaceCard()` in GameState API #959

Open itsalaidbacklife opened 4 months ago

itsalaidbacklife commented 4 months ago

Feature Summary

We need to implement playing faceCards via a seven in the GameState API in order to support the full game logic in this API. See the spec for a general overview of the API, its underlying motivations, and the development approach.

Playing a face card from a seven (sevenFaceCard) means removing the playedCard from the deck and putting it into the player's faceCards array. It should also remove the current oneOff (the seven being resolved) and put it into the scrap, then increment the turn and set the phase to GamePhase.MAIN. See api/helpers/gamestate/moves/seven-points/execute.js for an example of a seven move, and api/helpers/gamestate/moves/face-card/execute.js` for an example of playing a faceCard.

Detailed Description

Implementation should take the general approach shown in the Backend Control Flow section of the GameState API Spec and follow the example of #1025

Making this move, like all others, will be triggered via the endpoint:

POST /api/game/:gameId/move

Which move gets made is determined by the request body, which should specify a moveType field with a value from the MoveType enum. Playing a jack will be triggered by sending a request body like:

{
  moveType: MoveType.SEVEN_FACE_CARD,
  cardId: 'KC',
};

When a moveType: MoveType.SEVEN_FACE_CARD is sent in the req body, the endpoint will attempt to utilize the sevenFaceCard helpers: sails.helpers.gamestate.moves.sevenFaceCard.validate() and sails.helpers.gamestate.moves.sevenFaceCard.execute(). Each of these should follow the paradigm outlined in the docs:

There are four pieces to implement:

  1. Update the api/policies/hasValidMoveBody policy to allow and validate requests with a moveType of MoveType.SEVEN_FACE_CARD
  2. Implement the api/helpers/gamestate/moves/seven-face-card/validate helper to verify if the move is legal
  3. Implement the api/helpers/gamestate/moves/seven-face-card/execute helper to create a new GameState that plays the chosen faceCard from the top of the deck.
  4. Unskip the relevant tests by removing the cy.skipOnOnGameStateApi() commands from tests which can now pass in the GameState API

Updating the api/policies/hasValidMoveBody policy

We must also update the policy api/policies/hasValidMoveBody.js to accommodate the MoveType.SEVEN_FACE_CARD moveType. This policy is already applied to the endpoint for making a move and it uses a switch() statement on req.body.moveType to validate that a given req body has all the data necessary for the given moveType. We should add a MoveType.SEVEN_FACE_CARD case to this policy.

A moveType of MoveType.SEVEN_FACE_CARD requires a cardId which is a valid card id. If cardId is present in req.body and can be found in utils/DeckIds.json, then we can return res.next() in this switch case. Otherwise we should return res.badRequest() with an error that explains whether req.body.cardId attribute is missing or invalid.

Implementing validate()

Create a new api/helpers/gamestate/moves/seven-face-card/validate.js helper file that matches the general setup of the existing api/helpers/gamestate/moves/face-card/validate.js helper. It should throw errors if the requested move is illegal. It should have three inputs:

  1. currentState - the unpacked GameState from before the move is made
  2. requestedMove -req body describing the move being made3.
  3. playedBy - 0 or 1 for whether p0 or p1 is making the move.

The general idea is that the move is legal if it's your turn, we're resolving a seven, the card you picked can be played as a face card, and the card you picked is in the top two cards of the deck.

validate() should throw if:

Implementing execute()

Create a new api/helpers/gamestate/moves/seven-face-card/execute.js helper file that matches the general approach of the existing execute helper api/helpers/gamestate/moves/face-card/execute.js. It should create and return a new GameState that is the result of making the sevenFaceCard move for the requested player i.e. it should remove the playedCard from the deck and put it into the requesting player's faceCards. The playedBy and playedCard attributes should be included in the returned GameState. The GameState's phase should be set to GamePhase.MAIN, the turn should be incremented, the oneOff should be put into the scrap. The seven which was the oneOff should also be set as the resolved attribute of the GameState.

execute() should have 3 inputs:

  1. currentState - the unpacked GameState from before the move is made
  2. requestedMove -req body describing the move being made
  3. playedBy - 0 or 1 for whether p0 or p1 is making the move.

Unskipping Tests

Because we are approaching the GameState API project with a feature-flag approach, all changes are merged into themainbranch and the application and tests are set to dynamically run in either the GameState API mode, or the legacy mode, based on theVITE_USE_GAMESTATE_API` env variable. In CI, we run the full suite of tests against the legacy version of the application and then run a subset of the tests against the GameState API version of the application.

Which tests are run in the GameState API version of CI is controlled via the test command cy.skipOnGameStateApi() which programmatically skips a test if the env var VITE_USE_GAMESTATE_API is enabled. As we develop new features in the GameState API, we should remove the cy.skipOnGameStateApi() command from more and more of the tests.

Essential tests For this PR, at minimum, we should unskip the following tests, which should be able to pass once it is working correctly. All the following tests are in the tests/e2e/specs/in-game/one-offs/7_sevens folder:

You can run the tests against the GameState API locally by: booting the server in gamestate mode with npm run start:server:gamestate

Then booting the client in gamestate mode with npm run start:client:gamestate

running the tests in gamestate mode with npm run e2e:client:gamestate.

danielmantic commented 6 days ago

can I work on this issue?