Search code examples
javascripthtmlcssvue.jsconfirm

Vue.js Battle - confirm box overrides reset function


i'm currently working through Udemy's Vue.js tutorial. I've reached the section where you are building a battle web app game. After finishing it, I decided to practice my refactoring and came across this bug.

When you click the attack buttons and then the confirm box comes up to ask if you want to play again, it seems to add one extra item in my log array instead of resetting the game fully.

I'm suspecting it is to do with pressing the attack buttons too quickly, and then the confirm box comes up before running an addToLog() and then it runs it afterwards.

Or it could be my bad code. lol

Note that I know that clicking cancel on the confirm box also comes up with bugs too.

index.html

<!DOCTYPE html>
<html>

<head>
    <title>Monster Slayer</title>
    <script src="https://npmcdn.com/vue/dist/vue.js"></script>
    <link rel="stylesheet" href="css/foundation.min.css">
    <link rel="stylesheet" href="css/app.css">
</head>

<body>
    <div id="app">
        <section class="row">
            <div class="small-6 columns">
                <h1 class="text-center">YOU</h1>
                <div class="healthbar">
                    <div class="healthbar text-center" style="background-color: green; margin: 0; color: white;" :style="{width: playerHealth + '%'}">
                        {{ playerHealth }}
                    </div>
                </div>
            </div>
            <div class="small-6 columns">
                <h1 class="text-center">BADDY</h1>
                <div class="healthbar">
                    <div class="healthbar text-center" style="background-color: green; margin: 0; color: white;" :style="{width: computerHealth + '%'}">
                        {{ computerHealth }}
                    </div>
                </div>
            </div>
        </section>
        <section class="row controls" v-if="!isRunning">
            <div class="small-12 columns">
                <button id="start-game" @click="startGame">START GAME</button>
            </div>
        </section>
        <section class="row controls" v-else>
            <div class="small-12 columns">
                <button id="attack" @click="attack">ATTACK</button>
                <button id="special-attack" @click="specialAttack">SPECIAL ATTACK</button>
                <button id="heal" @click="heal">HEAL</button>
                <button id="restart" @click="restart">RESTART</button>
            </div>
        </section>
        <section class="row log" v-if="turns.length > 0">
            <div class="small-12 columns">
                <ul>
                    <li v-for="turn in turns" :class="{'player-turn': turn.isPlayer, 'monster-turn': !turn.isPlayer}">
                        {{ turn.text }}
                    </li>
                </ul>
            </div>
        </section>
    </div>
    <script src="app.js"></script>
</body>

</html>

css/app.css

.text-center {
    text-align: center;
}

.healthbar {
    width: 80%;
    height: 40px;
    background-color: #eee;
    margin: auto;
    transition: width 500ms;
}

.controls,
.log {
    margin-top: 30px;
    text-align: center;
    padding: 10px;
    border: 1px solid #ccc;
    box-shadow: 0px 3px 6px #ccc;
}

.turn {
    margin-top: 20px;
    margin-bottom: 20px;
    font-weight: bold;
    font-size: 22px;
}

.log ul {
    list-style: none;
    font-weight: bold;
    text-transform: uppercase;
}

.log ul li {
    margin: 5px;
}

.log ul .player-turn {
    color: blue;
    background-color: #e4e8ff;
}

.log ul .monster-turn {
    color: red;
    background-color: #ffc0c1;
}

button {
    font-size: 20px;
    background-color: #eee;
    padding: 12px;
    box-shadow: 0 1px 1px black;
    margin: 10px;
}

#start-game {
    background-color: #aaffb0;
}

#start-game:hover {
    background-color: #76ff7e;
}

#attack {
    background-color: #ff7367;
}

#attack:hover {
    background-color: #ff3f43;
}

#special-attack {
    background-color: #ffaf4f;
}

#special-attack:hover {
    background-color: #ff9a2b;
}

#heal {
    background-color: #aaffb0;
}

#heal:hover {
    background-color: #76ff7e;
}

#restart {
    background-color: #ffffff;
}

#restart:hover {
    background-color: #c7c7c7;
}

app.js

new Vue({
    el: app,
    data: {
        playerHealth: 100,
        computerHealth: 100,
        isRunning: false,
        turns: [],
    },
    methods: {
        startGame: function() {
            this.isRunning = true;
            this.playerHealth = 100;
            this.computerHealth = 100;
            this.clearLog();
        },
        attackController: function(attacker, maxRange, minRange) {
            let receiver = this.setReceiver(attacker);
            let damage = 0;
            if (attacker === 'player') {
                damage = this.randomDamage(maxRange, minRange);
                this.computerHealth -= damage;
            }
            if (attacker === 'computer') {
                damage = this.randomDamage(maxRange, minRange);
                this.playerHealth -= damage;
            }
            this.addToLog(attacker, receiver, damage);
            if (this.checkWin()) {
                return;
            }
        },
        attack: function() {
            this.attackController('player', 10, 3);
            this.attackController('computer', 10, 3);
        },
        specialAttack: function() {
            this.attackController('player', 30, 5);
            this.attackController('computer', 30, 5);
        },
        heal: function() {
            if (this.playerHealth <= 90) {
                this.playerHealth += 10;
            } else {
                this.playerHealth = 100;
            }
            this.turns.unshift({
                isPlayer: true,
                text: 'Player heals for ' + 10,
            });
        },
        randomDamage: function(max, min) {
            return Math.floor(Math.random() * max, min);
        },
        checkWin: function() {
            if (this.computerHealth <= 0) {
                this.alertBox('YOU WIN! New Game?');
            } else if (this.playerHealth <= 0) {
                this.alertBox('LOSER!!! New Game?');
            }
            return false;
        },
        alertBox: function(message) {
            if (confirm(message)) {
                this.isRunning = false;
                this.startGame();
            } else {
                this.isRunning = false;
            }
            return true;
        },
        restart: function() {
            this.isRunning = false;
            this.startGame();
        },
        addToLog: function(attacker, receiver, damage) {
            this.turns.unshift({
                isPlayer: attacker === 'player',
                text: attacker + ' hits ' + receiver + ' for ' + damage,
            });
        },
        clearLog: function() {
            this.turns = [];
        },
        setReceiver: function(attacker) {
            if (attacker === 'player') {
                return 'computer';
            } else {
                return 'player';
            }
        },
        damageOutput: function(attacker, health) {
            if (attacker === 'player') {
                damage = this.randomDamage(maxRange, minRange);
                this.computerHealth -= damage;
            }
        },
    },
});

Github repo is here if you prefer that. Thanks!


Solution

  • Your attack (and specialAttack) function attacks for both players:

        attack: function() {
            this.attackController('player', 10, 3);
            this.attackController('computer', 10, 3);
        },
    

    Currently, it is checking for win at every attackController call. So when the first attacker (player) wins, the game resets AND the second player attacks.

    So, my suggestion, move the checkWin out of the attackController into the attack functions:

        attack: function() {
            this.attackController('player', 10, 3);
            this.attackController('computer', 10, 3);
            this.checkWin();
        },
    

    The same to specialAttack.

    Code/JSFiddle: https://jsfiddle.net/acdcjunior/wwc1xnyc/10/


    Note, when the player wins, in the code above, the computer will still "strike back", even though the game is over. If you want to halt that, make checkWin return if the game is over:

        checkWin: function() {
            if (this.computerHealth <= 0) {
                this.alertBox('YOU WIN! New Game?');
                return true;
            } else if (this.playerHealth <= 0) {
                this.alertBox('LOSER!!! New Game?');
                return true;
            }
            return false;
        },
    

    And add an if to attack (and specialAttack):

        attack: function() {
            this.attackController('player', 10, 3);
            if (this.checkWin()) return;
            this.attackController('computer', 10, 3);
            this.checkWin();
        },
    

    Updated fiddle: https://jsfiddle.net/acdcjunior/wwc1xnyc/13/