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/eventRecipients.ts b/backend/eventRecipients.ts index 011a29e..fccd353 100644 --- a/backend/eventRecipients.ts +++ b/backend/eventRecipients.ts @@ -36,4 +36,4 @@ export function scheduleSend(clientId: ClientId, event: AppEvent) { // eslint-disable-next-line @typescript-eslint/no-unused-vars export function doSendEvent(clientId: ClientId, event: AppEvent) { //todo: Send all the events in the queue to the client, only if the response object is available. -} \ No newline at end of file +} diff --git a/backend/src/types.d.ts b/backend/src/types.d.ts index 6edcd03..638d9ac 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'; @@ -46,5 +46,5 @@ type GameEvent = }; // Represent all the events that can be sent to the client -type AppEvent = GameEvent +type AppEvent = GameEvent; //todo: Add more events 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.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);