Issue with Event Listeners Not Reattaching Upon Clicking "Play Again" Button in Rock-Paper-Scissors Game

Description: I have created a Rock-Paper-Scissors game using HTML, CSS, and JavaScript. The game functions properly initially, allowing users to select their choice and play against the computer. However, after completing a game and clicking the "Play Again" button, the event listeners on the game choices (rock, paper, scissor) do not reattach correctly. This results in users being unable to play another round after clicking "Play Again."

function getComputerChoice(){
    const choice = ['rock','paper','scissor']
    const length = choice.length
    return choice[Math.floor(Math.random() * length)]
}
const choicesImage = document.querySelectorAll('.choice img')
const choices = document.querySelectorAll('.choice')
const container = document.querySelector('.container')
const playerResult = document.querySelector('.playerChoice')
const computerResult = document.querySelector('.computerChoice')
const innerContent = container.innerHTML
const status = document.querySelector('.status')
let playerCurrentScore = 0 , computerCurrentScore = 0

function declareResult(){
    if(playerCurrentScore === 5 || computerCurrentScore === 5 ){
        container.classList.add('new')
        const newContent = document.querySelector('.new')
        newContent.textContent = playerCurrentScore === 5 ? 'Player Wins!':'Computer Wins!'
        newContent.style.fontSize = "35px"
        newContent.style.textAlign = "center"
        const btn = document.createElement('button')
        btn.textContent = "Play Again"
        newContent.append(btn)
        btn.addEventListener('click',changeContent)
    }  
}

function changeContent(){
    container.classList.remove('new')
    container.innerHTML = innerContent
    attachEvents();
}

function attachEvents(){
    choicesImage.forEach((choice) => {
        choice.addEventListener('click',game,true);
    });
}

attachEvents();

function game(e){
    let index = -Infinity
    if(e.target.alt === 'rock') index = 0
    else if(e.target.alt === 'paper') index = 1
    else if(e.target.alt === 'scissor') index = 2
    choices[index].classList.add('active')
        choicesImage.forEach((others,indices) => {
            if(index !== indices) choices[indices].classList.remove('active')
        });
        playerResult.src = 'images/rock.svg'
        computerResult.src = 'images/rock.svg'
        container.classList.add('start')
        status.textContent = "Loading.."
        setTimeout(() => {
            container.classList.remove('start')
            let user = e.target.alt
            let computer= getComputerChoice()
            if(user === 'scissor'){
                playerResult.src = `images/${user}.png`
            }else{
                playerResult.src = `images/${user}.svg`
            }
            if(computer === 'scissor'){
                computerResult.src = `images/${computer}.png`
            }else{
                computerResult.src = `images/${computer}.svg`
            }
            let playerScore = document.querySelector('.playerScore')
            let computerScore = document.querySelector('.computerScore')
            if(user === 'rock' && computer === 'scissor' || user === 'paper' && computer === 'rock' || 
            user === 'scissor' && computer === 'paper'){
                status.textContent = `You win! ${user} beats  ${computer}`
                playerCurrentScore++
                playerScore.textContent = playerCurrentScore
                computerScore.textContent = computerCurrentScore
            }else if(user === computer){
                status.textContent = `Draw Match...`
                playerScore.textContent = playerCurrentScore
                computerScore.textContent = computerCurrentScore
            }else{
                status.textContent = `You Lose! ${computer} beats ${user}`
                computerCurrentScore++
                playerScore.textContent = playerCurrentScore
                computerScore.textContent = computerCurrentScore
            }
            declareResult()
        },500)
}
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
:root{
    --gradient: linear-gradient(
        to right,
        #8B008Ba1,
        #800080b2,
        slateblue,
        darkslateblue
    );
    --bodyFont:'Poppins', sans-serif;
}

*{
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body{
    background-image: var(--gradient);
    background-repeat: no-repeat;
    background-attachment: fixed;
    animation: bg-animation 20s ease infinite 2s alternate;
    background-size: 300%;
    font-size: 62.5%;
    font-family: var(--bodyFont);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    overflow-y: hidden;
}

@keyframes bg-animation{
    0%{
        background-position: left;
    }

    50%{
        background-position: right;
    }

    100%{
        background-position: left;
    }
}

.container{
    background-color: white;
    padding: 2.5rem;
    width: 90%;
    max-width: 500px;
    border-radius: 1.5rem;
    box-shadow: 2px 2px 30px 4px rgba(60, 103, 108, 0.593);
}

.container.start .choice{
    pointer-events: none;
}

.game-container{
    display: flex;
    justify-content: space-around;
}

.playerChoice,.computerChoice{
    width: 6rem;
    height: 6rem;
    transform: rotate(90deg);
}

.container.start .playerChoice{
    animation: userShake .5s ease infinite;
    
}

@keyframes userShake{
    0%{
        transform: rotate(85deg);
    }

    50%{
        transform: rotate(99deg);
    }
}

.container.start .computerChoice{
    animation: computerShake .5s ease infinite;
}


@keyframes computerShake{
    0%{
        transform: rotate(265deg) rotateY(175deg);
    }

    50%{
        transform: rotate(279deg) rotateY(190deg);
    }

}

.computerChoice{
    transform: rotate(270deg) rotateY(180deg);
}

.status{
    text-align: center;
    margin: 2.5rem auto;
    font-size: 1.5rem;
}

.choice-container{
    display: flex;
    justify-content: space-around;
    align-items: center;
}

.choice{
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    opacity: 0.5;
    transition: opacity 0.1s;
}

.choice:hover{
    cursor:pointer;
    opacity: 1;
}

.active{
    opacity: 1;
}

.choice img{
    width: 4rem;
    height: 4rem;
}

.desc{
    font-size: 1.2rem;
    font-weight: bold;
}

.score{
    display:flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

.player,.computer{
    font-size: 1.2rem;
    margin-top: 2.0rem;
}

.playerScore,.computerScore{
    font-size: 1.5rem;
    vertical-align: middle;
    font-weight: bold;
    margin-left: 1.0rem;
}

.container.new p{
    font-size: 35px;
    text-align: center;
}

.container.new button{
    display: block;
    width: 250px;
    height: 50px;
    border-radius: 10px;
    font-size: 15px;
    margin: auto;
}
<div class="container">
    <div class="game-container">
        <img src="images/rock.svg" alt="player" class="playerChoice">
        <img src="images/rock.svg" alt="computer" class="computerChoice">
    </div>
    <p class="status"></p>
    <div class="choice-container">
        <span class="choice">
            <img src="images/rock.svg" alt="rock">
            <p class="desc">Rock</p>
        </span>
        <span class="choice">
            <img src="images/paper.svg" alt="paper">
            <p class="desc">Paper</p>
        </span>
        <span class="choice">
            <img src="images/scissor.png" alt="scissor">
            <p class="desc">Scissor</p>
        </span>
    </div>
    <div class="results">
        <div class="score">
            <p class="player">Player Score: <span class="playerScore">0</span></p>
            <p class="computer">Computer Score: <span class="computerScore">0</span></p>
        </div>
        <p class="announce"></p>
    </div>
</div>

Link:

  • I have attempted to remove and reattach event listeners, but it does not work as expected.
  • The game UI elements reset correctly, but the click events on the choices are not properly reattached.
  • I have checked the browser console for errors, but no errors are shown.
  • I have verified the HTML structure, CSS classes, and ensured that the event listener code is executed.

Why might the event listeners fail to reattach properly after pressing "Play Again"? Are there any potential issues or common mistakes I might be missing? Any suggestions or advice on how to debug and resolve this problem would be greatly appreciated.

Answer №1

While testing this, I ended up getting rate-limited by Stack Overflow because it made a significant number of server requests.

There are numerous areas where improvements can be implemented. Specifically focusing on the click event handlers...

The code currently replaces the entire HTML content with:

container.innerHTML = innerContent

However, it fails to update certain variables such as:

const choicesImage = document.querySelectorAll('.choice img')

This results in attaching click handlers to outdated element references when trying to re-attach them. You can verify this by updating this specific reference. Start by using let for re-assignment:

let choicesImage = document.querySelectorAll('.choice img')

Then re-assign it here:

function attachEvents(){
    choicesImage = document.querySelectorAll('.choice img');
    choicesImage.forEach((choice) => {
        choice.addEventListener('click',game,true);
    });
}

After making these changes, those particular click events will function properly once again.

Taking a broader view, I strongly advise against completely replacing the HTML. Although it may appear to be an easy way to "reset" the game, it ends up creating more issues than benefits. It would be more beneficial in the long run to refactor parts of the code (or even start from scratch) to maintain the same HTML structure on the page and simply adjust that markup based on the game's state changes.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Capture the JavaScript unload events

Imagine having web pages containing various elements with event handlers, such as onclick. When the handler triggers an unload event, like by setting window.location, how can I detect if this event will actually unload the page? The challenge: How can I a ...

Is it possible to enforce strict typing for a property within an object that is declared as type 'any'?

In my code, I am dealing with a parent object of type 'any' that remains constant and cannot be changed. Within this context, I need to define a property for the parent object, but no matter what I try, it always ends up being loosely typed as &a ...

How can I combine these scripts that are not working simultaneously?

I have two scripts on my site that are based on the meta title, and I'm trying to make them work together. I thought changing the function names would be enough, but when I use both scripts, one doesn't work. Why is this happening? Also, should I ...

Sharing data between controllers within an MVC architecture in JavaScript

I'm currently working on an app using Express and Node. Within my routes, I have '/new-poll' and '/poll-create' //The route poll-create allows users to create a new poll app.route('/poll-create') .get(functi ...

Tips for unveiling and concealing the ng-bootstrap datepicker using custom triggers and events

At the moment, I am utilizing: ng-bootstrap 1.0.0-alpha.24 angular/core 4.0.0 bootstrap 4.0.0-alpha.6 I am curious to know if anyone has a solution on how to automatically close the datepicker when focus is lost or another datepicker is opened. Additio ...

Identify the text that employs bold, underline, and italic styles

How do I search for text with specific properties as shown below? .css('font-weight', 'bold'); .css('font-style', 'italic'); .css('text-decoration', 'underline'); <b> Some Text </b> & ...

Transfer Data from a Factory to a Controller in AngularJS

Although it may seem like a simple question, it has taken me nearly 3 hours to try and figure out what went wrong here. Perhaps someone could help identify the issue and provide a solution (it seems like an easy fix, but I'm just not seeing it). So, h ...

What is the best method for eliminating script tags from a Magento web store's source code?

Below is the source code for the Magento site's homepage. I am looking to eliminate all JavaScript links from the code: ...

Is there a way to mock a keycloak API call for testing purposes during local development?

At my company, we utilize Keycloak for authentication integrated with LDAP to fetch a user object filled with corporate data. However, while working remotely from home, the need to authenticate on our corporate server every time I reload the app has become ...

Why is my v-model not being updated when using a radio button in Vue.js?

After reviewing the documentation, I attempted to implement the code provided. While I am able to successfully retrieve data for enquiryDesc, I am consistently getting a value of 5 for the rating field. I even experimented with changing the radio group to ...

Are there any effective methods to trigger an event when the page is loaded or resized?

To ensure that the main navigation appears and functions differently based on viewport width, I require a script to check the viewport width whenever the document is loaded or resized. //EventListener to get viewport width on load window.addEventListener( ...

Arrange the footer content into multiple columns

Hello! I'm currently working on creating a footer for my website. I've written this code and everything seems to be functioning correctly, except for the fact that the second row is positioned under the first row and taking up half of the bottom ...

"Rotating the TransformControl in threejs: A step-by-step guide

When the object attached to the transform control rotates, I want the transform control itself to rotate as well. Before the rotation: https://i.sstatic.net/yjTue.png After the rotation: https://i.sstatic.net/2opuU.png As shown in the image, before th ...

Move the navigation bullets of the Nivo Slider to the bottom instead of below the slider

Currently working on a website and have incorporated Nivo Slider to develop a jQuery slider. Encountering an issue with the bullets that indicate which slide the user is currently viewing. I'd like these bullets to be displayed on the images within t ...

Setting up an Angular 2 session service prior to the activation of a guard

Currently, I have implemented a guard in my app to secure certain routes. The guard relies on a session service to extract credentials from localStorage during NgOnInit. The goal is for the guard to check with the session service for valid credentials befo ...

Disabling an HTML select option control will prevent it from being included in the form submission

Within my HTML code, I am facing an issue where I want to disable a certain control based on the selection made in another client-side control. To achieve this, I implemented the following jQuery code: $('#dropDown1').attr('disabled', ...

Iterate over the HTML elements within a class and retrieve a specific property in JSON

Currently, I am in the process of developing a straightforward application that involves making an Ajax request to retrieve a Json object and then passing it to an HTML document. Essentially, this application functions as a vending machine where the produc ...

Error in Vue Vuelidate appearing when form is submitted with empty data property

I have been working on some basic validation for required fields and minimum length. The problem arises when I submit a network request for data insertion, the validation works perfectly fine. However, after the network call, if I try to make my text fie ...

Your program has encountered a crash and is currently on standby for any file modifications

=> MongoDB initialization in progress. W20210705-14:19:04.925(7)? (STDERR) /Users/alexio/.meteor/packages/promise/.0.11.2.cvynt8.js8ni++os+web.browser+web.browser.legacy+web.cordova/npm/node_modules/meteor-promise/promise_serv ...

yii CGridView refresh trigger an influx of Ajax requests

Does anyone know why the yii cgridview refresh button triggers multiple ajax calls? Upon refreshing, I have noticed that it initiates several ajax calls (this time it's 3, but sometimes it's 4 or even 5) GET http://localhost/ijob-css/index.php/ ...