Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gameEvents: created CHALLENGE_UNO event. #154

Merged
merged 1 commit into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});
Loading