class Card {
constructor(name) {
this.name = name;
}
}

class Bang extends Card {
constructor() {
super(“Bang”);
}

isPlayable(target, player, playedCards) {
const misseds = playedCards.filter(card => card.constructor === Missed);
return target !== player && !target.hasMissed && misseds.length === 0;
}

play(player, target) {
target.health -= 1;
target.hasMissed = false;
}
}

class Missed extends Card {
constructor() {
super(“Missed!”);
}

isPlayable(target, player, playedCards) {
return playedCards.filter(card => card.constructor === Missed).length === 0;
}

play(player, target) {
target.hasMissed = true;
}
}

class Beer extends Card {
constructor() {
super(“Beer”);
}

isPlayable() {
return true;
}

play(player) {
player.health += 1;
}
}

class Player {
constructor(name, health, hand) {
this.name = name;
this.health = health;
this.hand = hand;
this._hasMissed = false;
}

get hasMissed() {
return this._hasMissed;
}

hasCard(card) {
return this.hand.includes(card);
}

removeCard(card) {
this.hand = this.hand.filter(c => c !== card);
}

toString() {
return ${this.name} (HP: ${this.health}, Hand: ${this.hand.map(card =&gt; card.name)});
}
}

class Game {
constructor(players, deck) {
if (players.length < 2) {
throw new Error(“At least 2 players are required.”);
}
this.players = players;
this.deck = deck;
this._currentPlayerIndex = Math.floor(Math.random() * players.length);
this.playedCards = [];
}

get currentPlayerIndex() {
return this._currentPlayerIndex;
}

set currentPlayerIndex(value) {
this._currentPlayerIndex = value % this.players.length;
}

get currentPlayer() {
return this.players[this.currentPlayerIndex];
}

drawCard() {
return this.deck.pop();
}

dealCards() {
const numCardsPerPlayer = [8, 6, 4][this.players.length - 2];
this.players.forEach(player => {
for (let i = 0; i < numCardsPerPlayer; i++) {
player.hand.push(this.drawCard());
}
});
}

gameOver() {
const alivePlayers = this.players.filter(player => player.health > 0);
return alivePlayers.length === 1 || alivePlayers.every(player => player === alivePlayers[0]);
}

winner() {
return this.players.find(player => player.health > 0);
}

playCard(card, targetPlayer) {
const currentPlayer = this.currentPlayer;
if (!currentPlayer.hasCard(card)) {
throw new Error(“You don’t have that card in your hand.”);
}
if (!card.isPlayable(targetPlayer, currentPlayer, this.playedCards)) {
throw new Error(“You can’t play that card right now.”);
}
card.play(currentPlayer, targetPlayer);
this.playedCards.push(card);
currentPlayer.removeCard(card);
}

playTurn(targetPlayer) {
const player = this.currentPlayer;
let playedCard = null;
if (player.hasCard(new Bang())) {
playedCard = player.hand.find(card => card.constructor === Bang);
}
if (playedCard !== null) {
const validTargets = this.getValidTargets(player, playedCard);
if (validTargets.length === 1) {
this.playCard(playedCard, validTargets[0]);
} else if (validTargets.length > 1) {
throw new Error("You must choose a target for this card: " + playedCard.name);
} else {
throw new Error("No valid targets for this card: " + playedCard.name);
}
} else if (player.hasCard(new Beer())) {
this.playCard(player.hand.find(card => card.constructor === Beer), player);
}
this.currentPlayerIndex++;
}

getValidTargets(player, card) {
const targets = this.players.filter(p => p !== player && card.isPlayable(p, player, this.playedCards));
return targets;
}
}



// Example usage
const players = [
new Player(“Alice”, 4, []),
new Player(“Bob”, 4, []),
new Player(“Charlie”, 4, [])
];
const deck = [
new Bang(),
new Bang(),
new Bang(),
new Bang(),
new Bang(),
new Bang(),
new Bang(),
new Bang(),
new Missed(),
new Missed(),
new Missed(),
new Missed(),
new Beer(),
new Beer(),
new Beer(),
new Beer(),
new Beer(),
new Beer(),
new Beer(),
new Beer()
];
const game = new Game(players, deck);
game.dealCards();

while (!game.gameOver()) {
const targetPlayerIndex = (game.currentPlayerIndex + 1) % game.players.length;
game.playTurn(game.players[targetPlayerIndex]);
console.log(game.players.map(player => player.toString()));
}

console.log(Game over. Winner: ${game.winner().name});


Understand what's written here, and refactor a code