Javascript bazat pe joc de cărți de război – final (Revizuirea codului, Javascript, Orientat Pe Obiecte, Cărți De Joc)

Robert a intrebat.

Aceasta va fi ultima mea trimitere a acestui proiect pentru revizuire, deoarece este trimis în această seară. Mi-am actualizat codul pe baza feedback-ului primit de la alți utilizatori. Aș aprecia orice și orice feedback din partea comunității. Mulțumesc în avans.

Trimiterea originală

Urmărire

HTML

<!doctype html>
<html>
    <head>
        <title></title>
        <link rel="stylesheet" type="text/css" href="styles.css">
    </head>
    <body>
        <div id="player1CurrentCard" class="card">
            <div class="warDeck">
                <div class="warCardsHolder">
                    <div class="text">
                        <p>War Card</p>
                    </div>
                </div>
                <div class="warCardsHolder">
                    <div class="text">
                        <p>War Card</p>
                    </div>
                </div>
            </div>
            <div class="cardHolder"></div>
            <div class="text">
                <p>Player 1 Card</p>
            </div>
            <div id="player1CurrentDeck" class="currentDeck">
                <div class="currentCardsHolder"></div>
                <div class="text">
                    <p>Current Deck</p>
                </div>
            </div>
            <div id="player1WonDeck" class="wonDeck">
                <div class="wonCardsHolder"></div>
                <div class="text">
                    <p>Won Deck</p>
                </div>
            </div>

        </div>
        <div id="player2CurrentCard" class="card">
            <div class="cardHolder"></div>
            <div class="warDeck">
                <div class="warCardsHolder">
                    <div class="text">
                        <p>War Card</p>
                    </div>
                </div>
                <div class="warCardsHolder">
                    <div class="text">
                        <p>War Card</p>
                    </div>
                </div>
            </div>
            <div class="text">
                <p>Player 2 Card</p>
            </div>
            <div id="player2CurrentDeck" class="currentDeck">
                <div class="currentCardsHolder"></div>
                <div class="text">
                    <p>Current Deck</p>
                </div>
            </div>
            <div id="player2WonDeck" class="wonDeck">
                <div class="wonCardsHolder"></div>
                <div class="text">
                    <p>Won Deck</p>
                </div>
            </div>
        </div>
        <button id="dealCards">Deal Cards</button>
        <script src="scripts/classes.js"></script>
        <script src="scripts/main.js"></script>
        <script src="scripts/unitTests.js"></script>
    </body>
</html>

**CSS*

.card{
    position: relative;
    float: left;
    width: 350px;
    height: 500px;
    text-align: center;
}

.wonDeck{
    position: absolute;
    left: 100px;
    bottom: 0;
    width: 75px;
    height: 100px;
    text-align: center;
}

.currentDeck{
    position: absolute;
    bottom: 0;
    width: 75px;
    height: 100px;
    text-align: center;
}

.card{
    margin: 0 5%;
}

.card:first-of-type{
    margin-left: 0;
}

.card:last-of-type{
    margin-right: 0;
}

.card .text{
    position: absolute;
    margin: 0 0 0 -25%;
    left: 35%;
    height: 30%;
    width: 50%;
    font-size: 26px;
    color: rgb(150, 150, 150);
}

.wonDeck .text, .currentDeck .text{
    position: absolute;
    margin: 0 0 0 -25%;
    left: 50%;
    height: 30%;
    width: 50%;
    font-size: 16px;
    color: rgb(150, 150, 150);
}

.warDeck .text{
    position: absolute;
    margin: 15% 0 0 -25%;
    left: 50%;
    height: 30%;
    width: 50%;
    font-size: 16px;
    color: rgb(150, 150, 150);
}

.cardHolder{
    position: absolute;
    top: 0;
    left: 0;
    width: 75%;
    height: 350px;
    font-size: 26px;
    color: rgb(0, 0, 0);
    border: 1px dashed black;
    background-color: rgba(0, 0, 0, 0.5);
}

.wonCardsHolder, .warCardsHolder, .currentCardsHolder{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    font-size: 16px;
    color: rgb(0, 0, 0);
    border: 1px dashed black;
    background-color: rgba(0, 0, 0, 0.5);
}

.warCardsHolder:nth-of-type(2), .warCardsHolder:last-of-type{
    top: 125px;
}

.warDeck{
    position: relative;
    float: right;
    width: 75px;
    height: 100px;
}

main.js

var dealCards = document.getElementById("dealCards");
var cardHolder = document.getElementsByClassName("cardHolder");
var currentCardsHolder = document.getElementsByClassName("currentCardsHolder");
var wonCardsHolder = document.getElementsByClassName("wonCardsHolder");
var warCardsHolder = document.getElementsByClassName("warCardsHolder");

var Player1 = new Player("Player 1");
var Player2 = new Player("Player 2");
Deck.StartGame(Player1, Player2);
function GameLogic(player1, player2){
    if((player1.wonDeck.length === 0) && (player1.currentDeck.length === 0)){
        player1.GameOver(player2);
        Player1Wins(player1);
    }
    else if((player2.wonDeck.length === 0) && (player2.currentDeck.length === 0)){
        player2.GameOver(player1);
        Player2Wins(player2);
    }
    else{
        if(player1.currentDeck.length === 0){
            Deck.ShuffleDeck(player1.wonDeck, player1.currentDeck);
        }
        if(player2.currentDeck.length === 0){
            Deck.ShuffleDeck(player2.wonDeck, player2.currentDeck);
        }
        player1.GetCurrentCard();
        player2.GetCurrentCard();
        warCardsHolder[1].textContent = "";
        warCardsHolder[3].textContent = "";
        cardHolder[0].textContent = player1.currentCard.cardText +" of " +player1.currentCard.suit;
        cardHolder[1].textContent = player2.currentCard.cardText +" of " +player2.currentCard.suit;
        currentCardsHolder[0].textContent = player1.currentDeck.length;
        currentCardsHolder[1].textContent = player2.currentDeck.length;
        wonCardsHolder[0].textContent = player1.wonDeck.length;
        wonCardsHolder[1].textContent = player2.wonDeck.length;
        if(player1.currentCard.faceValue === player2.currentCard.faceValue){
            player1.warDeck.push(player1.currentCard);
            player2.warDeck.push(player2.currentCard);
            player1.GoToWar(player2);
        }
        else{
            if(player1.currentCard.faceValue > player2.currentCard.faceValue){
                player1.wonDeck.push(player1.currentCard);
                player1.wonDeck.push(player2.currentCard);
            }
            else{
                player2.wonDeck.push(player1.currentCard);
                player2.wonDeck.push(player2.currentCard);
            }
        }
    }
}
function PlayerWins(player){
    if(player.name === "Player 1"){
        warCardsHolder[1].textContent = "";
        warCardsHolder[3].textContent = "";
        cardHolder[0].textContent = "";
        cardHolder[1].textContent = "";
        currentCardsHolder[0].textContent = player.currentDeck.length;
        currentCardsHolder[1].textContent = "";
        wonCardsHolder[0].textContent = player.wonDeck.length;
        wonCardsHolder[1].textContent = "";
    }
    else{
        warCardsHolder[1].textContent = "";
        warCardsHolder[3].textContent = "";
        cardHolder[0].textContent = "";
        cardHolder[1].textContent = "";
        currentCardsHolder[0].textContent = "";
        currentCardsHolder[1].textContent = player.currentDeck.length;
        wonCardsHolder[0].textContent = "";
        wonCardsHolder[1].textContent = player.wonDeck.length;
    }
    dealCards.disabled = true;
}
dealCards.onclick = function(){
    GameLogic(Player1, Player2);
}

clase.js

function Player(name, currentDeck, wonDeck, warDeck){
    this.name = name;
    this.currentDeck = currentDeck !== undefined && currentDeck instanceof Array ? currentDeck : [];
    this.wonDeck = wonDeck !== undefined && wonDeck instanceof Array ? wonDeck: [];
    this.warDeck = warDeck !== undefined && warDeck instanceof Array ? warDeck: [];
}
Player.prototype.GetCurrentCard = function(){
    this.currentCard = this.currentDeck.pop();
}
Player.prototype.GoToWar = function(opponent){
    var tied = false;
    console.log("War");
    do{
        this.CanContinueWar(opponent);
        opponent.CanContinueWar(this);
        if(this.warDeck.length !== 0){
            warCardsHolder[1].textContent = this.warDeck[this.warDeck.length - 1].cardText +" of " +this.warDeck[this.warDeck.length - 1].suit;
        }
        if(opponent.warDeck.length !== 0){
            warCardsHolder[3].textContent = opponent.warDeck[opponent.warDeck.length - 1].cardText +" of " +opponent.warDeck[opponent.warDeck.length - 1].suit;
        }
        currentCardsHolder[0].textContent = this.currentDeck.length;
        currentCardsHolder[1].textContent = opponent.currentDeck.length;
        wonCardsHolder[0].textContent = this.wonDeck.length;
        wonCardsHolder[1].textContent = opponent.wonDeck.length;
        if(this.warDeck[this.warDeck.length - 1].faceValue === opponent.warDeck[opponent.warDeck.length - 1].faceValue){
            tied = true;
        }
        console.log("Tied");
    }
    while(tied === true);       
    if(this.warDeck[this.warDeck.length - 1].faceValue > opponent.warDeck[opponent.warDeck.length - 1].faceValue){
        console.log(this.name +" wins war");
        this.WonWar(opponent);
    }
    else{
        console.log(opponent.name +" wins war");
        opponent.WonWar(this);
    }
}
Player.prototype.CanContinueWar = function(opponent){
    Deck.DealWarCards(this, 2);
    if(this.currentDeck.length < 2){
        if(this.wonDeck.length < 2){
            if((this.currentDeck.length === 1) && (this.wonDeck.length === 1)){
                console.log(this.name +" continuing war");
                Deck.ShuffleDeck(this.wonDeck, this.currentDeck);
            }
            else if((this.currentDeck.length === 0) && (this.wonDeck.length === 1)){
                console.log(this.name +" loses war");
                Deck.ShuffleDeck(this.wonDeck, opponent.wonDeck);
                opponent.WonWar(this);
                this.GameOver(opponent);
            }
            else if((this.currentDeck.length === 1) && (this.wonDeck.length === 0)){
                console.log(this.name +" loses war");
                Deck.ShuffleDeck(this.currentDeck, opponent.wonDeck);
                opponent.WonWar(this);
                this.GameOver(opponent);
            }
            else{
                console.log(this.name +" loses");
                opponent.WonWar(this);
                this.GameOver(opponent);
            }
        }
        Deck.ShuffleDeck(this.wonDeck, this.currentDeck);
    }
}
Player.prototype.WonWar = function(opponent){
    this.wonDeck = this.wonDeck.concat(this.warDeck, opponent.warDeck);
    this.warDeck = [];
    opponent.warDeck = [];
}
Player.prototype.GameOver = function(opponent){
    console.log(opponent.name +" wins!");
    opponent.wonDeck = opponent.wonDeck.concat(opponent.warDeck, this.currentDeck, this.wonDeck, this.warDeck);
    opponent.warDeck = [];
    this.currentDeck = [];
    this.wonDeck = [];
    this.warDeck = [];
    PlayerWins(opponent);
}
function Card(options){
    this.suit = options.suit;
    this.faceValue = options.faceValue;
    this.cardText = (function(){
        switch(this.faceValue){
            case 14:
                {return "Ace"};
            case 13:
                {return "King"};
            case 12:
                {return "Queen"};
            case 11:
                {return "Jack"};
            default:
                {return String(this.faceValue)};
            break;
        }
    }).call(this);
}
Deck = {
    suits: ["Clubs", "Diamonds", "Hearts", "Spades"],
    cards: [14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2],
    deck: [],
    shuffledDeck: [],
    BuildDeck: function(){
        for(var suit = 0; suit < this.suits.length; suit++){
            for(var card = 0; card < this.cards.length; card++){
                this.deck.push(new Card({suit: this.suits[suit], faceValue: this.cards[card]}));
            }
        }
    },
    ShuffleDeck: function(unshuffledDeck, shuffledDeck){
        while(unshuffledDeck.length){
            var index = Math.floor(Math.random() * unshuffledDeck.length); 
            shuffledDeck.push(unshuffledDeck.splice(index, 1)[0]);    
        }
        unshuffledDeck = [];
    },
    DistributeCards: function(player1Deck, player2Deck){
        for(var i = 0; i < this.shuffledDeck.length / 2; i++){
            player1Deck.push(this.shuffledDeck[i]);
            player2Deck.push(this.shuffledDeck[this.shuffledDeck.length - i - 1]);
        }
    },
    DealWarCards: function(player, num){
        for(var i = 0; i < num; i++){
            player.GetCurrentCard();
            player.warDeck.push(player.currentCard);
        }
        console.log(player);
    },
    StartGame: function(player1, player2){
        this.BuildDeck();
        this.ShuffleDeck(this.deck, this.shuffledDeck);
        this.DistributeCards(player1.currentDeck, player2.currentDeck);
    }
}

1 răspunsuri
Joseph

Singurul lucru cu care mi-e greu să mă confrunt când vine vorba de OOP este cât de legată este logica de obiecte, iar obiectele sunt de obiecte. De exemplu, de ce știe clasa Deck despre jucători. Nu ar trebui să se ocupe doar de cărți?

O altă problemă în acest sens este testabilitatea. Să zicem că ați întâlnit un bug în mijlocul jocului și ați vrea să îl reproduceți. Cum procedezi pentru a extrage aceste informații din joc? Cum ajungi în starea în care s-a produs bug-ul? Cum testați acest cod?

Jocurile de cărți sunt ca niște mașini de stat. Ele au o stare definită în orice moment, iar acțiunile pe care le fac jucătorii modifică această stare. Puteți defini pur și simplu structura de stare astfel:

const game = {
  phase: 'start', // start, in-game, end, etc.
  winner: null,
  deck: [],
  players: [
    { name: 'player1', wonDeck: [], warDeck: [] },
    { name: 'player2', wonDeck: [], warDeck: [] },
  ]
}

De aici, puteți spune, uitându-vă la date, ce cărți sunt în pachet, câți jucători sunt, cine sunt jucătorii, ce cărți au. Puteți serializa cu ușurință această structură de date, o puteți utiliza pentru logare, depanare, raportare.

Acum să luăm o tranziție de stare. De exemplu, amestecarea pachetului ar fi ca și cum:

const previousState = {
  phase: 'start', // start, in-game, end, etc.
  winner: null,
  deck: [ ...values... ],
  players: [
    { name: 'player1', hand: [/* empty hand */] },
    { name: 'player2', hand: [/* empty hand */] },
  ]
}

function shuffleDeck(state){
  return {
    // Copy everything over
    ...state,
    // But override deck
    deck: state.deck.reduce((shuffled, card) => {
      // For each card in the previous state's deck, insert them in random
      // positions in the array and return that array.
      shuffled.splice(Math.floor(Math.random() * shuffled.length), 0, card);
      return shuffled;
    }, []);
  }
}

const newState = shuffleDeck(state);

După cum puteți vedea, amestecarea pachetului este o nouă stare construită din vechea stare, cu deck modificată. Din punct de vedere al testabilității, puteți crea cu ușurință vechea stare dintr-o grămadă de obiecte și matrici, o puteți introduce în funcție și veți obține rezultatul. Nu este nevoie să creați instanțe ale Deck, , de Player, , fără mocking, fără a trece prin, fără a trece prin, fără niciunul dintre aceste voodoo. De fapt, acesta este deja un caz de testare. Ceea ce lipsește sunt afirmațiile.

Având în vedere acest sfat, jocul ar progresa astfel:

  1. Generarea stării inițiale (o stare goală ca în primul fragment)
  2. Așteptați intrarea utilizatorului
  3. Mutarea stării (al doilea fragment)
  4. Redă rezultatele din 3
  5. Repetați pașii 2-4 până când se găsește un câștigător

În ceea ce privește restul codului, există… câteva probleme:

Obiectul de domeniu (Player) și logica jocului nu ar trebui să fie conștiente de DOM. În MVC, doar logica rămâne în controler. Renderizarea se face într-un strat de vizualizare care ar trebui să primească doar date deja procesate.

dealCards.onclick = function(){
  GameLogic(Player1, Player2);
}

Utilizați addEventListener. Deși onclick funcționează, din punct de vedere tehnic suprapuneți onclick proprietate. Orice gestionari adăugați anterior vor fi înlocuiți.

console.log(this.name +" loses war");

Ar trebui să eliminați codul de depanare. Deși este de înțeles că aceasta este o aplicație mică, este totuși un obicei prost. Am văzut oameni cu ani de zile în dezvoltarea web care încă fac această greșeală simplă.

if(this.currentDeck.length < 2){
    if(this.wonDeck.length < 2){
        if((this.currentDeck.length === 1) && (this.wonDeck.length === 1)){

Aveți o mulțime de „numere magice” în codul dumneavoastră. Ce înseamnă 2 înseamnă? Ce înseamnă 1 semnifică? Pot să schimb asta în 42? De ce? De ce nu?. Se recomandă să stocați aceste date în variabile cu nume corespunzător, denumite după scopul lor.


De asemenea, ați menționat un „potențial angajator” în revizuirile anterioare. Dacă acest angajator este ceea ce cred eu că este, se va uita la structura codului, mentenabilitate și testabilitate. Aceste lucruri ar trebui să le urmăriți în codul dumneavoastră.

De asemenea, ar dori să reducă timpul necesar pentru a dezvolta astfel de aplicații. Acest lucru începe prin a nu repeta ceea ce alți dezvoltatori au creat deja. Luați în considerare posibilitatea de a vă aventura în utilizarea unui framework, oricare va fi suficient. Acest lucru va elimina problemele legate de manipularea DOM, astfel încât tot ce trebuie să vă preocupe sunt datele și logica. Vue.js este un candidat bun pentru un cadru mic și ușor de început.

Comentarii

  • Vă mulțumesc pentru feedback și sfaturi. Dacă nu vă supărați că vă întreb, cine credeți că este potențialul angajator?-  > Por Robert.
  • @Robert Ceea ce încercam să spun este că există angajatori cărora le pasă dacă știi să scrii cod, și există cei cărora le pasă cum scrii cod. Dacă angajatorul tău este de acest din urmă tip, va trebui să te uiți mai departe de a face doar cod funcțional.  > Por Joseph.
  • Am înțeles ce ai vrut să spui.-  > Por Robert.