简体   繁体   中英

.forEach method applying update to all items in array instead of individual item

I'm making a small exercise for some students of mine where I am automating a kind of 10 pin bowling game I have put it into a JsBin here https://jsbin.com/qilizo/edit?html,js,output . I don't know whether I am tired, stupid or it's just because I am working on a national holiday but something has me puzzled. When i start the game I prompt the user to set up a number of desired players. This automatically produces an object array of Players like so:

[{score: Array[10], spareCount: 0, strikeCount: 0, username: "Player 1"}, ...]

Now later I allow the user to play frames where each Player in our array has two throws... I collect the score and add it to the certain player's score array. However when I try to perform this action using a .forEach method the score I generate is applied to all items in my Players array (play the game and see). I have put my code in a jsBin and the problem is on line 109 : a.score[currentFrame - 1] = playFrame();

I have tried to amend my code but I can't work out why the current (or last) frame score is being applied to all Player objects! If you can understand my syntax error and explain why I would be most appreciative. Play the game (just click the button after setting the player numbers) and you will see what I mean...

Snippet:

 var players, currentFrame = 0, currentThrow = 0; // helper functions // Accurate isNumber function... Thank you Crockford (see JavaScript: The Good Parts) function isNumber(value) { return typeof(value === 'number') && isFinite(value); } function frameStyle(k) { var returnCssClass, k = k + 1; if (k < currentFrame) { returnCssClass = 'played-frame'; } else if (k === currentFrame) { returnCssClass = 'current-frame'; } else { returnCssClass = null; } return returnCssClass; } function setUpPlayers(num) { var tempArray = [], tempName = 'Player ', emptyScores = Array(10).fill([-1, -1]); // set default to -1 as a rubbish player may hit no pins! for (var i = 0; i < num; i++) { tempArray.push({ username: tempName + (i + 1), score: emptyScores, strikeCount: 0, spareCount: 0 }); // the way I have named the tempName is technically an antipattern! } return tempArray; } function getTotals(scores) { var totalScore = scores.reduce(function(a, b) { return a + b.reduce(function(c, d) { return (c + (c + ((d > 0) ? d : 0))); }, 0); }, 0); return totalScore; } function displayScore(score) { // toDo reformat! var formatScore = score.map(function(a, b) { if (a === -1) { a = '-'; } else if (a === 10) { a = 'X'; } return a; }); return formatScore; } function createGrid() { // If only I was using ES6 I could have multi line support! var playerLen = players.length, scoresLen = players[0].score.length; boards = '<div class="score-board">' + '<!-- one row for each player -->'; // need to loop this through the players... for (var i = 0; i < playerLen; i++) { boards += '<div class="row">' + '<!-- first cell is the name -->' + '<div class="name">' + players[i].username + '</div>'; // need to loop this with the users scores for (var k = 0; k < scoresLen; k++) { boards += '<div class="game ' + frameStyle(k) + ' ">' + displayScore(players[i].score[k]) + '</div>'; } // don't forget the total boards += '<div class="player-total">' + getTotals(players[i].score) + '</div>'; boards += '</div>'; } boards += '</div>'; boards += '<div>Current Frame: ' + currentFrame + '</div>'; boards += '<button type="button" onclick="startGame()">Start Game</button>'; // fill the holder.... document.getElementById('boardHolder').innerHTML = boards; } function startGame() { if (currentFrame >= 10) { announceWinner(); } else { currentFrame++; // do the throws for Each Player! players.forEach(function(a, b) { a.score[currentFrame - 1] = playFrame(); }); // update the grid createGrid(); // recurrrrrrsion.... //startGame(); } } function throwBall(pinsStanding) { // i know it isn't a ball return Math.floor(Math.random() * (pinsStanding + 1)); } function playFrame() { // here we just create the array and determine if we have a strike or a spare! var pinsStanding = 10, frameScore = [], frameThrows = 2, pinsDown; for(var i = 0; i < frameThrows; i++) { pinsDown = throwBall(pinsStanding); pinsStanding = pinsStanding - pinsDown; // if it is the pinsStanding = 0 and it is the first throw - a strike! if(pinsStanding === 0 && i === 1) { pinsStanding = 10; frameThrows = 3; } // what if it is a spare? frameScore.push(pinsDown); } return frameScore; } function announceWinner() { } // kick it all off!!! window.onload = function() { // lets get some users.... players = prompt('Please enter the NUMBER of players?', 2); // check we have a number... if (isNumber(players)) { players = setUpPlayers(players); createGrid(); } }; 
 body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } /* classes */ .score-board { border: 1px solid #000; } .row { display: block; border-bottom: 1px solid #000; } .row:last-child { border-bottom: none; } .row > div { display: inline-block; padding: 5px; } .game { border-right: 1px solid #000; } .name { background-color: #f5f5f5; border-right: 1px solid #000; } .player-total { text-align: right; background-color: #d5eabb; } .played-frame { background-color: #aee1e8; } .current-frame { background-color: #ffc0cb; } 
 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <h1>Let's go bowling!</h1> <div id="boardHolder"> </div> </body> </html> 

Here is the bin! https://jsbin.com/qilizo/edit?html,js,output

You need to call Array(10).fill([-1, -1]) inside for loop, because otherwise all objects will share the same score array:

function setUpPlayers(num) {

    var tempArray = [],
        tempName = 'Player '; 

    for (var i = 0; i < num; i++) {
        tempArray.push({
            username: tempName + (i + 1),
            score: Array(10).fill([-1, -1]),// set default to -1 as a rubbish player may hit no pins!
            strikeCount: 0,
            spareCount: 0
        }); // the way I have named the tempName is technically an antipattern!
    }

    return tempArray;
}

https://jsbin.com/yeyupiteyu/1/edit?html,js,output

In JavaScript objects are passed by reference, and since array is an object, if you declare emptyScores outside the loop and then assign it to every element of the array, all elements will share the same score array.

You have make new emptyScores array for each element, so you have to declare it inside the loop:

var tempArray = [],
    tempName = 'Player ';

for (var i = 0; i < num; i++) {
    var emptyScores = Array(10).fill([-1, -1]);
    tempArray.push({
        username: tempName + (i + 1),
        score: emptyScores,
        strikeCount: 0,
        spareCount: 0
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM