From 96835eff479ed903a8d4e09205aa6ddafe1d5534 Mon Sep 17 00:00:00 2001 From: Sagnik Mandal Date: Sun, 2 Jun 2024 11:53:56 +0530 Subject: [PATCH] throw card DRAFT --- ARCHITECTURE.md | 12 ++- backend/src/types.d.ts | 4 +- backend/uno-game-engine/engine.ts | 16 +++- .../uno-game-engine/events/throwCard.test.ts | 38 +++++++++ backend/uno-game-engine/events/throwCard.ts | 84 +++++++++++++++++++ 5 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 backend/uno-game-engine/events/throwCard.test.ts create mode 100644 backend/uno-game-engine/events/throwCard.ts diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d56e8ab..619f58c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -26,11 +26,17 @@ format of the message is: ```json { - "type": "DRAW_CARD", - "player": "player1", + "type": "THROW_CARD", + "playerId": "1", "data": { - "cardID": "red-5" + "card": { + "color": "red", + "value": "5", + "type": "number", + "id": "card-number-red-5" + } } + } ``` Other possible values for `type` are `THROW_CARD`, `ANNOUNCE_UNO`, etc. diff --git a/backend/src/types.d.ts b/backend/src/types.d.ts index 0ac9c89..3fa2f81 100644 --- a/backend/src/types.d.ts +++ b/backend/src/types.d.ts @@ -1,9 +1,9 @@ // We declare those types which are used throughout the application here. // For types that are used only in one file, we can declare them in that file itself. -type CardType = 'number' | 'special' | 'wild'; +type CardType = 'number' | 'special'; -type CardColor = 'red' | 'blue' | 'green' | 'yellow'; +type CardColor = 'red' | 'blue' | 'green' | 'yellow' | 'wild'; type SpecialCardNames = 'skip' | 'reverse' | 'draw2' | 'draw4' | 'colchange'; type CardNumbers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; diff --git a/backend/uno-game-engine/engine.ts b/backend/uno-game-engine/engine.ts index 17bf2fe..a4a576d 100644 --- a/backend/uno-game-engine/engine.ts +++ b/backend/uno-game-engine/engine.ts @@ -47,11 +47,19 @@ export class GameEngine { this.currentPlayerIndex = (this.currentPlayerIndex + this.direction) % this.players.length; } - drawCardFromDeck(player: Player) { + drawCardFromDeck(player: Player, numCards = 1) { //todo: Handle the case when the deck is empty and we have to move the thrown cards back to the deck - this.players - .find((p: Player) => p.id === player.id) - .cards.push(this.cardDeck.pop()); + // this.players + // .find((p: Player) => p.id === player.id) + // .cards.push(this.cardDeck.pop()); + const currentPlayer = this.players.find((p) => p.id === player.id); + if (!currentPlayer) { + throw new Error('Player not found'); + } + + for (let i = 0; i < numCards; i++) { + currentPlayer.cards.push(this.cardDeck.pop()); + } } dispatchEvent(event: GameEvent) { // handle different types of events based on event.type diff --git a/backend/uno-game-engine/events/throwCard.test.ts b/backend/uno-game-engine/events/throwCard.test.ts new file mode 100644 index 0000000..0768f2f --- /dev/null +++ b/backend/uno-game-engine/events/throwCard.test.ts @@ -0,0 +1,38 @@ +// TODO: Currently the test fails, because +// we should have a way to generate the set of cards, +// and add them to the game. + +// import { createGame, retrieveGame } from '../../gameStore'; +// import { handleEvent } from "../gameEvents"; + +// function CreateUNOTestGame() { +// const gameId = createGame(); +// const game = retrieveGame(gameId); +// if (!game) { +// throw new Error('Game not found'); +// } + +// game.addPlayer({ id: '1', cards: [] }); +// game.addPlayer({ id: '2', cards: [] }); +// game.startGame(); +// game.allotCards(); +// return game; +// } + +// test('Throw a card', () => { +// const game = CreateUNOTestGame(); +// const player = game.players[0]; +// const card = player.cards[0]; +// const event: GameEvent = { +// type: 'THROW_CARD', +// playerId: player.id, +// data: { +// card, +// }, +// }; +// const result = handleEvent(game, event); +// expect(result.type).toBe('SUCCESS'); +// expect(player.cards.length).toBe(6); +// expect(game.thrownCards.length).toBe(1); +// expect(game.lastThrownCard).toBe(card); +// }); diff --git a/backend/uno-game-engine/events/throwCard.ts b/backend/uno-game-engine/events/throwCard.ts new file mode 100644 index 0000000..a6564a4 --- /dev/null +++ b/backend/uno-game-engine/events/throwCard.ts @@ -0,0 +1,84 @@ +import { registerEventHandler } from '../gameEvents'; +import { GameEngine } from '../engine'; + +function findPlayer(game: GameEngine, playerId: string) { + return game.players.find((p) => p.id === playerId); +} + +function findCard(player, cardId: string) { + return player.cards.find((c) => c.id === cardId); +} + +export function throwCard(game: GameEngine, event: GameEvent): EventResult { + const { currentPlayerIndex, players } = game; + const currentPlayer = players[currentPlayerIndex]; + + // check if the player is the current player + if (currentPlayer.id !== event.playerId) { + return { type: 'ERROR', message: 'It is not your turn' }; + } + + // check if the card is present in the player's hand + const player = findPlayer(game, event.playerId); + if (!player) { + return { type: 'ERROR', message: 'Player not found' }; + } + const card = findCard(player, event.data.card.id); + if (!card) { + return { type: 'ERROR', message: 'Card not found' }; + } + + // check if the card can be thrown + if (game.lastThrownCard && !canThrowCard(game.lastThrownCard, card)) { + return { type: 'ERROR', message: 'Cannot throw this card' }; + } + + player.cards = player.cards.filter((c) => c.id !== card.id); + + game.thrownCards.push(card); + game.lastThrownCard = card; + + game.nextPlayer(); + + // handle special cards + if (card.type === 'special') { + handleSpecialCard(game, card); + } + + return { type: 'SUCCESS', message: 'Card thrown successfully' }; +} + +function canThrowCard(lastThrownCard: UNOCard, card: UNOCard): boolean { + return ( + lastThrownCard.color === card.color || + lastThrownCard.value === card.value || + card.color === 'wild' + ); +} + +function handleSpecialCard(game: GameEngine, card: UNOCard) { + switch (card.value) { + case 'skip': + game.nextPlayer(); + break; + case 'reverse': + game.direction *= -1; + break; + case 'draw2': + { + const nextPlayer = game.players[game.currentPlayerIndex]; + game.drawCardFromDeck(nextPlayer, 2); + } + break; + case 'draw4': + { + const currentPlayer = game.players[game.currentPlayerIndex]; + game.drawCardFromDeck(currentPlayer, 4); + } + break; + default: + break; + } +} + +registerEventHandler('THROW_CARD', throwCard);