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, [])];
class CardFactory {
    static create(name) {
        switch (name) {
            case “Bang”: return new Bang();
            case “Missed !”: return new Missed();
            case “Beer”: return new Beer();
            default: throw new Error(Invalid card name: $ {
                name
            }
            );
        }
    }
}

const deck=[ “Bang”, “Bang”,
“Bang”,
“Bang”,
“Bang”,
“Bang”,
“Bang”,
“Bang”,
“Missed !”,
“Missed !”,
“Missed !”,
“Missed !”,
“Beer”,
“Beer”,
“Beer”,
“Beer”,
“Beer”,
“Beer”,
“Beer”,
“Beer”].map(CardFactory.create);
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()));
}


Understand what's written here, and refactor a code