import {Card, Suit} from 'mccarthyism-core'; import Client from './Client'; import logSocket from './logSocket'; import Room from './Room'; import server from './server'; class Player { game: Game; client: Client; cards: Card[] = []; stack: Card[] = []; flipped: Card[] = []; disconnected = false; disconnectListener?: () => void; rank = 0; constructor(game: Game, client: Client) { this.game = game; this.client = client; } sendGameState() { const i = this.game.players.indexOf(this); this.client.socket.emit('gameState', { cards: this.cards, rank: this.rank, players: this.game.players.map((p: Player) => ({ username: p.client.username, numCards: p.cards.length, stackSize: p.stack.length, flipped: p.flipped, rank: p.rank, })), phase: this.game.phase, lastPlayed: this.game.lastPlayed, lastPlayedPlayer: this.game.lastPlayedPlayer < 0 ? null : this.game.players[this.game.lastPlayedPlayer].client.username, playerTurn: this.game.players[this.game.playerTurn].client.username }); } } export default class Game { room: Room; players: Player[] = []; phase = 0; lastPlayed = 0; lastPlayedPlayer = -1; playerTurn = 0; playersFinished = 0; constructor(room: Room) { this.room = room; this.start(); } async start() { const cards = []; for (let i = 1; i <= 13; ++i) for (let j = 0; j < 4; ++j) cards.push({rank: i, suit: j}); const handSize = 5 - Math.floor(this.room.clients.length/7); // Shuffle red cards for (let i = 0; i < 26; ++i) { const j = Math.floor(Math.random() * (i+1)); [cards[i], cards[j]] = [cards[j], cards[i]]; } // Shuffle black cards for (let i = 0; i < 26; ++i) { const j = Math.floor(Math.random() * (i+1)); [cards[i+26], cards[j+26]] = [cards[j+26], cards[i+26]]; } for (let i = 0; i < this.room.clients.length; ++i) { this.players.push(new Player(this, this.room.clients[i])); this.players[i].cards.push(cards[i], cards[i+26]); // Make sure everyone has a red and black card } const remainingCards = []; for (let i = this.room.clients.length; i < 26; ++i) remainingCards.push(cards[i], cards[i+26]); // Shuffle remaining cards for (let i = 0; i < remainingCards.length; ++i) { const j = Math.floor(Math.random() * (i+1)); [remainingCards[i], remainingCards[j]] = [remainingCards[j], remainingCards[i]]; } for (let i = 0; i < this.room.clients.length; ++i) for (let j = 0; j < handSize-2; ++j) this.players[i].cards.push(remainingCards[i*(handSize-2)+j]); const startingPlayer = this.players[0]; // Pick a random starting player instead?? this.playerTurn = this.players.indexOf(startingPlayer); this.playersFinished = this.room.clients.length; // Run the game while (true) { const playersLeft: Player[] = []; this.players.forEach((p: Player) => { if (!p.rank && !p.disconnected) { if (p.cards.length === 0) p.rank = this.playersFinished--; else playersLeft.push(p); } }); if (playersLeft.length === 1) break; await this.round(); } this.broadcastGameState(); setTimeout(() => { server.to(this.room.name).emit('endGame'); this.room.host.once('startGame', () => this.room.startGame()); this.room.game = null; }, 5000); } broadcastGameState() { this.players.forEach((p: Player) => p.sendGameState()); } async round() { this.phase = 0; this.players.forEach((p: Player) => p.stack = []); this.players.forEach((p: Player) => p.flipped = []); await this.prepare(); // Phase 0 this.phase = 1; this.lastPlayed = 0; while (true) { // Phase 1 const p = this.players[this.playerTurn]; if (p.rank || p.disconnected) { this.playerTurn = (this.playerTurn + 1) % this.players.length; continue; } await this.turn(); if (this.phase === 2) break; // Called BS! this.lastPlayedPlayer = this.playerTurn; this.playerTurn = (this.playerTurn + 1) % this.players.length; } while (this.lastPlayed > 0) { // Phase 2 await this.flip(); if (this.phase === 3 as number) { // Oops, flipped over a red card! await this.giveup(); // The player who called BS won and now the challenged player must give up a card! return; } this.lastPlayed--; } this.phase = 3; await this.giveup(); // The player who called BS won and now they must give up a card! } async prepare() { // Players prepare their hand for this round this.broadcastGameState(); const playerPromises = []; for (let i = 0; i < this.room.clients.length; ++i) { const p = this.players[i]; if (p.rank || p.disconnected) continue; playerPromises.push(new Promise(resolve => { p.client.once('prepare', stack => { delete p.disconnectListener; (() => { p.stack = stack; p.cards = [...stack]; return; })(); resolve(); }); p.disconnectListener = () => { delete p.disconnectListener; p.client.removeAllListeners('prepare'); resolve(); }; })); } await Promise.all(playerPromises); } async turn() { // Do a turn const p = this.players[this.playerTurn]; this.broadcastGameState(); await new Promise(resolve => { p.client.once('turn', num => { delete p.disconnectListener; (() => { if (num === -1) { this.phase = 2; // Called BS, move on to the next phase! return; } if (num > this.lastPlayed) { this.lastPlayed = num; return; } p.client.socket.disconnect(); logSocket(p.client.socket, 'Bad cards argument on turn'); })(); resolve(); }); p.disconnectListener = () => { delete p.disconnectListener; p.client.removeAllListeners('turn'); resolve(); }; }); } async flip() { // Someone called BS and now the challenged player must flip over a card const p = this.players[this.lastPlayedPlayer]; this.broadcastGameState(); await new Promise(resolve => { p.client.once('flip', selectedPlayer => { delete p.disconnectListener; (() => { if (this.players[selectedPlayer].stack.length > 0) { if (this.players[selectedPlayer].stack[0].suit === Suit.Diamonds || this.players[selectedPlayer].stack[0].suit === Suit.Hearts) this.phase = 3; // Red card this.players[selectedPlayer].flipped.push(this.players[selectedPlayer].stack[0]); this.players[selectedPlayer].stack.splice(0, 1); return; } p.client.socket.disconnect(); logSocket(p.client.socket, 'Bad cards argument on turn'); })(); resolve(); }); p.disconnectListener = () => { delete p.disconnectListener; p.client.removeAllListeners('flip'); resolve(); }; }); } async giveup() { // Give up a card const p = this.players[this.lastPlayed > 0 ? this.lastPlayedPlayer : this.playerTurn]; this.broadcastGameState(); await new Promise(resolve => { p.client.once('giveup', card => { delete p.disconnectListener; (() => { p.cards.splice(card, 1); return; })(); resolve(); }); p.disconnectListener = () => { delete p.disconnectListener; p.client.removeAllListeners('giveup'); resolve(); }; }); } updateSocket(client: Client) { this.players.find((p: Player) => p.client === client)!.sendGameState(); } remove(client: Client) { const p = this.players.find((p: Player) => p.client === client)!; p.disconnected = true; if (p.disconnectListener) p.disconnectListener(); } };