Skip to content

Commit

Permalink
gameEvents: created CHALLENGE_UNO event.
Browse files Browse the repository at this point in the history
- Created `challengeUno.ts`.
- Checked if a player can be challenged.
- Player can be challenged only if `vulnerableToUNO` is equal to that player.
- If the challenge is successful, the player must draw two cards from the deck.
- Created tests for the challenge_uno event and its corner cases.

fixes: #145
  • Loading branch information
sethdivyansh committed Jun 23, 2024
1 parent f5c258f commit a7fa6da
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
8 changes: 8 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export enum GameEventTypes {
JOIN_GAME = 'JOIN_GAME',
LEAVE_GAME = 'LEAVE_GAME',
ANNOUNCE_UNO = 'ANNOUNCE_UNO',
CHALLENGE_UNO = 'CHALLENGE_UNO',
STATE_SYNC = 'STATE_SYNC',
START_GAME = 'START_GAME',
}
Expand Down Expand Up @@ -97,6 +98,13 @@ export type GameEvent =
playerId: string;
data?: null;
}
| {
type: GameEventTypes.CHALLENGE_UNO;
playerId: string;
data: {
challengedPlayerId: string;
};
}
| {
type: GameEventTypes.STATE_SYNC;
data: {
Expand Down
40 changes: 40 additions & 0 deletions backend/src/uno-game-engine/events/challengeUno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import assert from 'assert';
import { EventResult, GameEvent, GamePlayer } from '../../types';
import { GameEngine } from '../engine';
import { getPlayer } from './eventHandlerUtils';

export function canChallengeUNO(
game: GameEngine,
player: GamePlayer
): EventResult {
if (game.runningEvents.vulnerableToUNO !== player) {
return {
type: 'ERROR',
message: 'Cannot challenge this player',
};
}

return { type: 'SUCCESS', message: 'Can challenge UNO' };
}

export function challengeUNO(game: GameEngine, event: GameEvent): EventResult {
assert(event.type === 'CHALLENGE_UNO', 'Invalid event type');
const player = getPlayer(game, event.data.challengedPlayerId);
if (!player) {
return { type: 'ERROR', message: 'Player not found' };
}

const canChallenge = canChallengeUNO(game, player);
if (canChallenge.type === 'ERROR') {
return canChallenge;
}

// draw two cards as penalty
// drawCardFromDeck() will set vulnerableToUNO to null
const drawPenaltyCards = game.drawCardFromDeck(player, 2);
if (drawPenaltyCards.type === 'ERROR') {
return drawPenaltyCards;
}

return { type: 'SUCCESS', message: 'UNO challenged successfully' };
}
2 changes: 2 additions & 0 deletions backend/src/uno-game-engine/gameEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { EventResult, GameEvent, GameEventTypes } from '../types';
import { type GameEngine } from './engine';
import { announceUNO } from './events/announceUno';
import { challengeUNO } from './events/challengeUno';
import { drawCard } from './events/drawCard';
import { joinGame } from './events/joinGame';
import { leaveGame } from './events/leaveGame';
Expand Down Expand Up @@ -33,4 +34,5 @@ registerEventHandler(GameEventTypes.LEAVE_GAME, leaveGame);
registerEventHandler(GameEventTypes.DRAW_CARD, drawCard);
registerEventHandler(GameEventTypes.THROW_CARD, throwCard);
registerEventHandler(GameEventTypes.ANNOUNCE_UNO, announceUNO);
registerEventHandler(GameEventTypes.CHALLENGE_UNO, challengeUNO);
registerEventHandler(GameEventTypes.START_GAME, startGame);
86 changes: 86 additions & 0 deletions backend/tests/events.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CardNumber, GameEventTypes } from '../src/types';
import { GameEngine } from '../src/uno-game-engine/engine';
import { announceUNO } from '../src/uno-game-engine/events/announceUno';
import { challengeUNO } from '../src/uno-game-engine/events/challengeUno';
import { drawCard } from '../src/uno-game-engine/events/drawCard';
import { getPlayer } from '../src/uno-game-engine/events/eventHandlerUtils';
import { throwCard } from '../src/uno-game-engine/events/throwCard';
Expand Down Expand Up @@ -33,6 +34,7 @@ describe('Events', () => {
game = new GameEngine('dummyGame');
});

// test cases for UNO announcement
test('Announce UNO when player has only two cards and at least one of them is throwable', () => {
// when player announces UNO first, then throws a card
initializeMockGame(game, 3, 2, 1);
Expand Down Expand Up @@ -127,4 +129,88 @@ describe('Events', () => {
getPlayer(game, '1')
);
});

// test cases for challenging UNO
test('Player did not announce UNO, and a player challenged him', () => {
initializeMockGame(game, 3, 2, 1);

const player = getPlayer(game, '1');
const throwStatus = throwCard(game, {
type: GameEventTypes.THROW_CARD,
playerId: '1',
data: { cardId: 'card-number-yellow-1-1' },
});
expect(throwStatus.type).toBe('SUCCESS');
expect(player?.cards.length).toBe(1);
expect(game.runningEvents.hasAnnouncedUNO).toBe(null);
expect(game.runningEvents.vulnerableToUNO).toBe(getPlayer(game, '1'));

challengeUNO(game, {
type: GameEventTypes.CHALLENGE_UNO,
playerId: '2', // player 2 challenged player 1
data: { challengedPlayerId: '1' },
});

expect(game.runningEvents.vulnerableToUNO).toBe(null);
expect(player!.cards.length).toBe(3);
});

test('Player announced UNO, and a player challenged him', () => {
initializeMockGame(game, 3, 1, 1);

const player = getPlayer(game, '1');
const announceStatus = announceUNO(game, {
type: GameEventTypes.ANNOUNCE_UNO,
playerId: '1',
});

expect(announceStatus.type).toBe('SUCCESS');
expect(game.runningEvents.hasAnnouncedUNO).toBe(player);
expect(game.runningEvents.vulnerableToUNO).toBe(null);

const challengeStatus = challengeUNO(game, {
type: GameEventTypes.CHALLENGE_UNO,
playerId: '2', // player 2 challenged player 1
data: { challengedPlayerId: '1' },
});

expect(challengeStatus.type).toBe('ERROR');
expect(player!.cards.length).toBe(1);
});

test('Player did not announce UNO and next player played take his turn without challenging, then another player challenged the previous player', () => {
initializeMockGame(game, 3, 2, 1);

const player1 = getPlayer(game, '1');
const player2 = getPlayer(game, '2');

const throwStatus1 = throwCard(game, {
type: GameEventTypes.THROW_CARD,
playerId: '1',
data: { cardId: 'card-number-yellow-1-1' },
});

expect(throwStatus1.type).toBe('SUCCESS');
expect(player1?.cards.length).toBe(1);
expect(game.runningEvents.hasAnnouncedUNO).toBe(null);
expect(game.runningEvents.vulnerableToUNO).toBe(player1);

const throwStatus2 = throwCard(game, {
type: GameEventTypes.THROW_CARD,
playerId: '2',
data: { cardId: 'card-number-yellow-2-1' },
});

expect(throwStatus2.type).toBe('SUCCESS');
expect(player2?.cards.length).toBe(1);

const challengeStatus = challengeUNO(game, {
type: GameEventTypes.CHALLENGE_UNO,
playerId: '2', // player 2 challenged player 1
data: { challengedPlayerId: '1' },
});

expect(challengeStatus.type).toBe('ERROR');
expect(player1!.cards.length).toBe(1);
});
});

0 comments on commit a7fa6da

Please sign in to comment.