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

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

  isPlayable(target, player, playedCards) {
    const misseds = playedCards.filter(card => card instanceof 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 instanceof 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;
  }

  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 = [];
  }

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

  nextPlayer() {
    this.currentPlayerIndex = (this.currentPlayerIndex + 1) % this.players.length;
  }

  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 Missed())) {
      playedCard = player.hand.find(card => card instanceof Missed);
    } else if (player.hasCard(new Bang())) {
      playedCard = player.hand.find(card => card instanceof Bang);
    }
    if (playedCard !== null) {
      this.playCard(playedCard, targetPlayer);
    } else if (player.hasCard(new Beer())) {
      this.playCard(player.hand.find(card => card instanceof Beer), player);
    }
    this.nextPlayer();
  }
}

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