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 cc97d5e commit 14aa71c
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 0 deletions.
6 changes: 6 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export enum GameEventTypes {
JOIN_GAME = 'JOIN_GAME',
LEAVE_GAME = 'LEAVE_GAME',
ANNOUNCE_UNO = 'ANNOUNCE_UNO',
CHALLENGE_UNO = 'CHALLENGE_UNO',
STATE_SYNC = 'STATE_SYNC',
}
export type GameEvent =
Expand Down Expand Up @@ -84,6 +85,11 @@ export type GameEvent =
playerId: string;
data?: null;
}
| {
type: GameEventTypes.CHALLENGE_UNO;
playerId: string;
data?: null;
}
| {
type: GameEventTypes.STATE_SYNC;
data: {
Expand Down
37 changes: 37 additions & 0 deletions backend/src/uno-game-engine/events/challengeUno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import assert from 'assert';
import { EventResult, GameEvent, Player } from '../../types';
import { GameEngine } from '../engine';
import { getPlayer } from './eventHandlerUtils';

export function canChallengeUNO(game: GameEngine, player: Player): 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.playerId);
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' };
}
84 changes: 84 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,86 @@ 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: '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',
});

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 throwStatus = throwCard(game, {
type: GameEventTypes.THROW_CARD,
playerId: '1',
data: { cardId: 'card-number-yellow-1-1' },
});

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

const drawStatus = drawCard(game, {
type: GameEventTypes.DRAW_CARD,
playerId: '2',
});

expect(drawStatus.type).toBe('SUCCESS');
expect(player2?.cards.length).toBe(3);
expect(game.runningEvents.hasAnnouncedUNO).toBe(null);
expect(game.runningEvents.vulnerableToUNO).not.toBe(player1);

const challengeStatus = challengeUNO(game, {
type: GameEventTypes.CHALLENGE_UNO,
playerId: '1',
});

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

0 comments on commit 14aa71c

Please sign in to comment.