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

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

isPlayable(target, player, playedCards) {
return true;
}

play(player, target) {
const missed = playedCards.find(
(card) => card.constructor === Missed
);

missed ? target.removeCard(missed) : (target.health -= 1);
}
}

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

isPlayable() {
return false;
}
}

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;
}

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

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

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

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 = this.getRandomPlayer();
this.playedCards = [];
}

getRandomPlayer() {
return Math.floor(Math.random() * this.players.length);
}

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

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

dealCards(numCards) {
this.players.forEach((player) => {
for (let i = 0; i < numCards; i++) {
player.hand.push(this.drawCard());
}
});
}

isGameOver() {
const alivePlayers = this.players.filter(
(player) => player.health > 0
);

return alivePlayers.length < 2;
}

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

playCard(card, targetPlayer) {
if (!this.currentPlayer.hasCard(card)) {
throw new Error(“You don’t have that card in your hand.”);
}

if (!card.isPlayable(targetPlayer, this.currentPlayer, this.playedCards)) {
throw new Error(“You can’t play that card right now.”);
}

card.play(this.currentPlayer, targetPlayer);
this.playedCards.push(card);
this.currentPlayer.removeCard(card);
}

playTurn(targetPlayerIndex) {
const targetPlayer = this.players[targetPlayerIndex];
const playableCards = this.currentPlayer.hand.filter(
(card) => card.isPlayable(targetPlayer, this.currentPlayer, this.playedCards)
);

if (playableCards.length === 0) {
throw new Error(“You don’t have any playable cards.”);
}

const chosenCardIndex = Math.floor(Math.random() * playableCards.length);
const chosenCard = playableCards[chosenCardIndex];

this.playCard(chosenCard, targetPlayer);
this.currentPlayerIndex = (this.currentPlayerIndex + 1) % this.players.length;
}

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

// 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([8, 6, 4][players.length - 2]);

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


Understand what's written here, and refactor a code