the-game-domain
Version:
Complete domain layer for 'The Game' card game with rich business entities, automatic persistence, action tracking, and advanced game mechanics. Features Game, Player, Card, Deck, Pile entities with repository pattern, turn management, victory/defeat dete
553 lines • 25.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("../index");
class MockRepository {
constructor() {
this.savedGames = [];
this.deletedGames = [];
}
async save(game) {
this.savedGames.push(game);
}
async findById(id) {
return this.savedGames.find(g => g.id === id) || null;
}
async findAll() {
return [...this.savedGames];
}
async delete(id) {
this.deletedGames.push(id);
}
}
describe('Game', () => {
let mockRepository;
beforeEach(() => {
mockRepository = new MockRepository();
index_1.Player.resetCounter();
});
describe('Constructor and Properties', () => {
it('should create game with initial piles and deck', () => {
const game = new index_1.Game(1);
expect(game.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
expect(game.piles).toHaveLength(4);
expect(game.deck.size).toBe(92);
});
it('should validate piles count', () => {
const game = new index_1.Game(1);
expect(game.piles).toHaveLength(4);
});
});
describe('Player Management', () => {
it('should get player cards', () => {
const game = new index_1.Game(1);
const playerId = game.players[0]?.id || '';
const playerCards = game.getPlayerCards(playerId);
expect(playerCards).toHaveLength(6);
});
it('should throw error when getting cards of non-existent player', () => {
const game = new index_1.Game(1);
expect(() => game.getPlayerCards('non-existent-id')).toThrow('Player non-existent-id not found in the game');
});
});
describe('Card Placement Validation', () => {
it('should validate card placement on ascending pile', () => {
const game = new index_1.Game(1);
expect(game.canPlaceCardOnPile(2, 1)).toBe(true);
expect(game.canPlaceCardOnPile(10, 1)).toBe(true);
expect(game.canPlaceCardOnPile(1, 1)).toBe(false);
});
it('should validate card placement on descending pile', () => {
const game = new index_1.Game(1);
expect(game.canPlaceCardOnPile(99, 3)).toBe(true);
expect(game.canPlaceCardOnPile(90, 3)).toBe(true);
expect(game.canPlaceCardOnPile(100, 3)).toBe(false);
});
it('should validate difference of 10 rule for ascending pile', () => {
const game = new index_1.Game(1);
const pile = game.piles.find(p => p.number === 1);
pile.addCard(new index_1.Card(50));
expect(game.canPlaceCardOnPile(40, 1)).toBe(true);
expect(game.canPlaceCardOnPile(45, 1)).toBe(false);
expect(game.canPlaceCardOnPile(60, 1)).toBe(true);
});
it('should validate difference of 10 rule for descending pile', () => {
const game = new index_1.Game(1);
const pile = game.piles.find(p => p.number === 3);
pile.addCard(new index_1.Card(50));
expect(game.canPlaceCardOnPile(60, 3)).toBe(true);
expect(game.canPlaceCardOnPile(55, 3)).toBe(false);
expect(game.canPlaceCardOnPile(40, 3)).toBe(true);
});
it('should return false for non-existent pile', () => {
const game = new index_1.Game(1);
expect(game.canPlaceCardOnPile(10, 5)).toBe(false);
expect(game.canPlaceCardOnPile(10, 0)).toBe(false);
});
});
describe('Game Creation', () => {
it('should create game with specified number of players', () => {
const game = new index_1.Game(3);
expect(game.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
expect(game.players).toHaveLength(3);
expect(game.piles).toHaveLength(4);
expect(game.deck.size).toBe(80);
});
it('should create players with unique IDs', () => {
const game = new index_1.Game(4);
const playerIds = game.players.map(player => player.id);
const uniqueIds = new Set(playerIds);
expect(uniqueIds.size).toBe(4);
expect(playerIds.length).toBe(4);
});
it('should distribute 6 cards to all players', () => {
const game = new index_1.Game(3);
expect(game.players[0]?.cardCount).toBe(6);
expect(game.players[1]?.cardCount).toBe(6);
expect(game.players[2]?.cardCount).toBe(6);
});
it('should set first player as current turn', () => {
const game = new index_1.Game(3);
expect(game.currentPlayerId).toBe(game.players[0]?.id);
expect(game.players[0]?.isCurrentTurn).toBe(true);
});
it('should validate minimum number of players', () => {
expect(() => new index_1.Game(0)).toThrow('Number of players must be between 1 and 5');
expect(() => new index_1.Game(-1)).toThrow('Number of players must be between 1 and 5');
});
it('should validate maximum number of players', () => {
expect(() => new index_1.Game(6)).toThrow('Number of players must be between 1 and 5');
expect(() => new index_1.Game(10)).toThrow('Number of players must be between 1 and 5');
});
it('should reset player counter when creating new game', () => {
const game1 = new index_1.Game(2);
expect(game1.players[0]?.name).toBe('Player 1');
expect(game1.players[1]?.name).toBe('Player 2');
const game2 = new index_1.Game(2);
expect(game2.players[0]?.name).toBe('Player 1');
expect(game2.players[1]?.name).toBe('Player 2');
});
});
describe('Move Card to Pile', () => {
it('should move card from player to pile', () => {
const game = new index_1.Game(1);
const playerCards = game.getPlayerCards(game.players[0]?.id || '');
const cardToMove = playerCards[0];
if (cardToMove) {
game.moveCardToPile(game.players[0]?.id || '', cardToMove.value, 1);
expect(game.getPlayerCards(game.players[0]?.id || '')).toHaveLength(5);
expect(game.piles[0]?.lastCard?.value).toBe(cardToMove.value);
}
});
it('should validate player exists when moving card', async () => {
const game = new index_1.Game(1);
await expect(game.moveCardToPile('non-existent-id', 5, 1)).rejects.toThrow('Player non-existent-id not found in the game');
});
it('should validate card exists in player hand', async () => {
const game = new index_1.Game(1);
await expect(game.moveCardToPile(game.players[0]?.id || '', 999, 1)).rejects.toThrow('Card with value 999 not found in player');
});
it('should validate pile exists', async () => {
const game = new index_1.Game(1);
const playerCards = game.getPlayerCards(game.players[0]?.id || '');
await expect(game.moveCardToPile(game.players[0]?.id || '', playerCards[0]?.value || 0, 5)).rejects.toThrow('Pile 5 not found');
});
it('should validate card can be added to pile', () => {
const game = new index_1.Game(1);
const playerCards = game.getPlayerCards(game.players[0]?.id || '');
const cardWithValue1 = playerCards.find(card => card.value === 1);
if (cardWithValue1) {
expect(() => game.moveCardToPile(game.players[0]?.id || '', cardWithValue1.value, 1)).toThrow('Card 1 cannot be added to pile 1');
}
else {
expect(true).toBe(true);
}
});
it('should validate that only current player can move cards', async () => {
const game = new index_1.Game(2);
const player1 = game.players[0];
const player2 = game.players[1];
if (!player1 || !player2)
throw new Error('Players not found');
game['_currentPlayerId'] = player1.id;
const player1Cards = game.getPlayerCards(player1.id);
if (player1Cards.length > 0) {
const validCard = player1Cards.find(card => card.value > 1);
if (validCard) {
await expect(game.moveCardToPile(player1.id, validCard.value, 1)).resolves.not.toThrow();
}
}
const player2Cards = game.getPlayerCards(player2.id);
if (player2Cards.length > 0) {
const firstCard = player2Cards[0];
if (firstCard) {
await expect(game.moveCardToPile(player2.id, firstCard.value, 1)).rejects.toThrow(`Player ${player2.id} cannot move cards - it's not their turn`);
}
}
});
});
describe('Next Turn', () => {
it('should move to next player', async () => {
const game = new index_1.Game(2);
const firstPlayer = game.players[0];
const secondPlayer = game.players[1];
expect(firstPlayer).toBeDefined();
expect(secondPlayer).toBeDefined();
expect(game.currentPlayerId).toBe(firstPlayer?.id);
const card1 = firstPlayer?.cards[0];
const card2 = firstPlayer?.cards[1];
if (!card1 || !card2)
throw new Error('No cards available');
await game.moveCardToPile(firstPlayer.id, card1.value, 1);
await game.moveCardToPile(firstPlayer.id, card2.value, 2);
await game.nextTurn();
expect(game.currentPlayerId).toBe(secondPlayer?.id);
expect(firstPlayer?.isCurrentTurn).toBe(false);
expect(secondPlayer?.isCurrentTurn).toBe(true);
});
it('should cycle back to first player', async () => {
const game = new index_1.Game(2);
const [player1, player2] = game.players;
if (!player1 || !player2)
throw new Error('Players not found');
const card1 = player1.cards[0];
const card2 = player1.cards[1];
if (card1 && card2) {
game.recordAction(index_1.GameAction.cardPlayed(game.id, player1.id, player1.name, card1.value, 1));
game.recordAction(index_1.GameAction.cardPlayed(game.id, player1.id, player1.name, card2.value, 1));
player1.removeCard(card1.value);
player1.removeCard(card2.value);
}
expect(game.currentPlayerId).toBe(player1.id);
expect(player1.isCurrentTurn).toBe(true);
expect(player2.isCurrentTurn).toBe(false);
await game.nextTurn();
expect(game.currentPlayerId).toBe(player2.id);
expect(player1.isCurrentTurn).toBe(false);
expect(player2.isCurrentTurn).toBe(true);
const card3 = player2.cards[0];
const card4 = player2.cards[1];
if (card3 && card4) {
game.recordAction(index_1.GameAction.cardPlayed(game.id, player2.id, player2.name, card3.value, 1));
game.recordAction(index_1.GameAction.cardPlayed(game.id, player2.id, player2.name, card4.value, 1));
player2.removeCard(card3.value);
player2.removeCard(card4.value);
}
await game.nextTurn();
expect(game.currentPlayerId).toBe(player1.id);
expect(player1.isCurrentTurn).toBe(true);
expect(player2.isCurrentTurn).toBe(false);
});
it('should throw error when changing turn in finished game', async () => {
const game = new index_1.Game(1);
Object.defineProperty(game, '_isFinished', { value: true });
await expect(game.nextTurn()).rejects.toThrow('Cannot change turn in a finished game');
});
it('should throw error when no players in game', async () => {
const game = new index_1.Game(1);
Object.defineProperty(game, '_players', { value: [] });
Object.defineProperty(game, '_currentPlayerId', { value: undefined });
await expect(game.nextTurn()).rejects.toThrow('No players in the game');
});
});
describe('Game End Detection', () => {
it('should detect game end when all players have no cards', () => {
const game = new index_1.Game(2);
Object.defineProperty(game, 'isFinished', { get: () => true });
expect(game.isFinished).toBe(true);
});
it('should detect game end when deck is empty', () => {
const game = new index_1.Game(1);
while (!game.deck.isEmpty) {
game.deck.drawCard();
}
expect(game.deck.isEmpty).toBe(true);
});
});
describe('Equals and Comparison', () => {
it('should compare games correctly', () => {
const game1 = new index_1.Game(2);
const game2 = new index_1.Game(2);
const game3 = new index_1.Game(2);
expect(game1.equals(game1)).toBe(true);
expect(game1.equals(game2)).toBe(false);
expect(game2.equals(game3)).toBe(false);
});
});
describe('ToString', () => {
it('should return correct string representation', () => {
const game = new index_1.Game(1);
const result = game.toString();
expect(result).toMatch(/^Game\([0-9a-f-]+, 4 piles, 1 players, \d+ cards in deck\)$/);
});
});
describe('JSON Serialization', () => {
it('should serialize and deserialize correctly', () => {
const game = new index_1.Game(1);
const json = game.toJSON();
const reconstructed = index_1.Game.fromJSON(json);
expect(reconstructed.id).toBe(game.id);
expect(reconstructed.players).toHaveLength(game.players.length);
expect(reconstructed.piles).toHaveLength(game.piles.length);
expect(reconstructed.deck.size).toBe(game.deck.size);
});
});
describe('Static Methods', () => {
it('should create new game with players', () => {
const game = new index_1.Game(3);
expect(game.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
expect(game.players).toHaveLength(3);
expect(game.piles).toHaveLength(4);
});
});
describe('Persistence', () => {
it('should automatically persist when created with repository', async () => {
const game = new index_1.Game(2, mockRepository);
await new Promise(resolve => setTimeout(resolve, 10));
expect(mockRepository.savedGames).toHaveLength(1);
expect(mockRepository.savedGames[0]?.id).toBe(game.id);
});
it('should persist when moving card to pile', async () => {
const game = new index_1.Game(2, mockRepository);
const player = game.players[0];
const card = player?.cards[0];
if (!player || !card) {
throw new Error('Player or card not found');
}
await new Promise(resolve => setTimeout(resolve, 10));
mockRepository.savedGames = [];
await game.moveCardToPile(player.id, card.value, 1);
expect(mockRepository.savedGames).toHaveLength(1);
expect(mockRepository.savedGames[0]?.id).toBe(game.id);
});
it('should persist when changing turn', async () => {
const game = new index_1.Game(2, mockRepository);
mockRepository.savedGames = [];
const firstPlayer = game.players[0];
if (!firstPlayer)
throw new Error('First player not found');
game['_currentPlayerId'] = firstPlayer.id;
firstPlayer['_isCurrentTurn'] = true;
const validCards = firstPlayer.cards.filter(card => card.value > 1).slice(0, 2);
if (validCards.length < 2)
throw new Error('Not enough valid cards');
await game.moveCardToPile(firstPlayer.id, validCards[0].value, 1);
await game.moveCardToPile(firstPlayer.id, validCards[1].value, 2);
await game.nextTurn();
expect(mockRepository.savedGames.length).toBeGreaterThan(0);
expect(mockRepository.savedGames.some(savedGame => savedGame.id === game.id)).toBe(true);
});
it('should allow manual persistence', async () => {
const game = new index_1.Game(2, mockRepository);
mockRepository.savedGames = [];
await game.persist();
expect(mockRepository.savedGames).toHaveLength(1);
expect(mockRepository.savedGames[0]?.id).toBe(game.id);
});
it('should handle repository management', () => {
const game = new index_1.Game(2);
expect(game.hasRepository()).toBe(false);
game.setRepository(mockRepository);
expect(game.hasRepository()).toBe(true);
game.removeRepository();
expect(game.hasRepository()).toBe(false);
});
it('should not break when repository is not provided', async () => {
const game = new index_1.Game(2);
const player = game.players[0];
const card = player?.cards[0];
if (!player || !card) {
throw new Error('Player or card not found');
}
await expect(game.moveCardToPile(player.id, card.value, 1)).resolves.not.toThrow();
});
});
describe('Auto-draw cards', () => {
it('should automatically draw cards when player has less than 6 cards', async () => {
const game = new index_1.Game(2);
const player = game.players[0];
if (!player)
throw new Error('Player not found');
const initialDeckSize = game.deck.size;
const cardsToRemove = 3;
for (let i = 0; i < cardsToRemove; i++) {
const card = player.cards[0];
if (card) {
player.removeCard(card.value);
}
}
expect(player.cardCount).toBe(6 - cardsToRemove);
game['_currentPlayerId'] = player.id;
player['_isCurrentTurn'] = true;
const validCards = player.cards.filter(card => card.value > 1).slice(0, 2);
if (validCards.length < 2)
throw new Error('Not enough valid cards');
await game.moveCardToPile(player.id, validCards[0].value, 1);
await game.moveCardToPile(player.id, validCards[1].value, 2);
await game.nextTurn();
const expectedCards = Math.min(6, initialDeckSize - cardsToRemove);
expect(player.cardCount).toBe(expectedCards);
});
it('should not draw cards when player already has 6 cards', async () => {
const game = new index_1.Game(2);
const player = game.players[0];
if (!player)
throw new Error('Player not found');
const initialDeckSize = game.deck.size;
expect(player.cardCount).toBe(6);
game['_currentPlayerId'] = player.id;
player['_isCurrentTurn'] = true;
const validCards = player.cards.filter(card => card.value > 1).slice(0, 2);
if (validCards.length < 2)
throw new Error('Not enough valid cards');
await game.moveCardToPile(player.id, validCards[0].value, 1);
await game.moveCardToPile(player.id, validCards[1].value, 2);
await game.nextTurn();
expect(player.cardCount).toBe(6);
expect(game.deck.size).toBe(initialDeckSize - 2);
});
it('should draw available cards when deck has fewer cards than needed', async () => {
const game = new index_1.Game(2);
const player = game.players[0];
if (!player)
throw new Error('Player not found');
while (player.cardCount > 1) {
const card = player.cards[0];
if (card) {
player.removeCard(card.value);
}
}
while (game.deck.size > 2) {
game.deck.drawCard();
}
const remainingDeckCards = game.deck.size;
expect(player.cardCount).toBe(1);
expect(remainingDeckCards).toBe(2);
game['_currentPlayerId'] = player.id;
player['_isCurrentTurn'] = true;
const validCard = player.cards.find(card => card.value > 1);
if (!validCard)
throw new Error('No valid cards available');
await game.moveCardToPile(player.id, validCard.value, 1);
while (game.deck.size > 0) {
game.deck.drawCard();
}
await game.nextTurn();
expect(player.cardCount).toBeGreaterThanOrEqual(0);
expect(player.cardCount).toBeLessThanOrEqual(1);
expect(game.deck.size).toBe(0);
});
it('should detect defeat when current player cannot play any cards', async () => {
const game = new index_1.Game(2);
const player1 = game.players[0];
const player2 = game.players[1];
if (!player1 || !player2) {
throw new Error('Players not found');
}
while (player1.cardCount > 0) {
const card = player1.cards[0];
if (!card)
break;
player1.removeCard(card.value);
}
player1.addCard(new index_1.Card(99));
player1.addCard(new index_1.Card(98));
game['_currentPlayerId'] = player1.id;
player1['_isCurrentTurn'] = true;
while (game.deck.size > 0) {
game.deck.drawCard();
}
await expect(game.nextTurn()).rejects.toThrow(/Jogador deve jogar pelo menos/);
});
it('should skip players with no cards when checking for defeat', async () => {
const game = new index_1.Game(3);
const player1 = game.players[0];
const player2 = game.players[1];
const player3 = game.players[2];
if (!player1 || !player2 || !player3) {
throw new Error('Players not found');
}
while (player1.cardCount > 0) {
const card = player1.cards[0];
if (!card)
break;
player1.removeCard(card.value);
}
while (player2.cardCount > 0) {
const card = player2.cards[0];
if (!card)
break;
player2.removeCard(card.value);
}
player2.addCard(new index_1.Card(99));
player2.addCard(new index_1.Card(98));
while (player3.cardCount > 0) {
const card = player3.cards[0];
if (!card)
break;
player3.removeCard(card.value);
}
player3.addCard(new index_1.Card(2));
while (game.deck.size > 0) {
game.deck.drawCard();
}
game['_currentPlayerId'] = player2.id;
player2['_isCurrentTurn'] = true;
await expect(game.nextTurn()).rejects.toThrow(/Jogador deve jogar pelo menos/);
});
it('should detect victory when all players have no cards after skipping', async () => {
const game = new index_1.Game(2);
const player1 = game.players[0];
const player2 = game.players[1];
if (!player1 || !player2) {
throw new Error('Players not found');
}
while (player1.cardCount > 0) {
const card = player1.cards[0];
if (!card)
break;
player1.removeCard(card.value);
}
while (player2.cardCount > 0) {
const card = player2.cards[0];
if (!card)
break;
player2.removeCard(card.value);
}
while (game.deck.size > 0) {
game.deck.drawCard();
}
game['_currentPlayerId'] = player1.id;
await game['_checkGameEnd']();
expect(game.isFinished).toBe(true);
expect(game.gameResult).toBe('won');
});
it('should check victory before defeat in moveCardToPile', async () => {
const game = new index_1.Game(2);
const player1 = game.players[0];
const player2 = game.players[1];
if (!player1 || !player2) {
throw new Error('Players not found');
}
while (game.deck.size > 0) {
game.deck.drawCard();
}
while (player1.cardCount > 0) {
const card = player1.cards[0];
if (!card)
break;
player1.removeCard(card.value);
}
while (player2.cardCount > 0) {
const card = player2.cards[0];
if (!card)
break;
player2.removeCard(card.value);
}
game['_currentPlayerId'] = player1.id;
await game['_checkGameEnd']();
expect(game.isFinished).toBe(true);
expect(game.gameResult).toBe('won');
});
});
});
//# sourceMappingURL=Game.test.js.map