Changing array indices in JavaScript by splicing elements

I'm experiencing a curious issue that seems to involve overwriting array indices after splicing, or at least that's what I suspect. This problem arises in a small game project built using phaser 2 - essentially, it's a multiplayer jumping game aimed at gaining experience with client/server architecture. We're utilizing socket.io and express for this project. The trouble lies on the server side: when a client disconnects, their removal from the player list causes the remaining player(s) to overwrite the disconnected player's index. To debug this, I have been heavily relying on console logs, using a for loop to iterate through the list of players and display their respective socket IDs.

Initially, I considered an aliasing issue within the onNewPlayer(data) function because of duplication in variable names (var currentInfo). I subsequently changed the second object to var info. So, could this be an aliasing problem, or should I investigate elsewhere for the root cause? If necessary, I can provide additional code; thus far, all callbacks related to player creation and movement appear to be functioning correctly. Thank you.

Below is the relevant server-side code:

var players[];
//When a new player is created, save them
function onNewPlayer(data) {
  var newPlayer = new Player(data.x, data.y, this.id);

  var currentInfo = {
    x: newPlayer.x,
    y: newPlayer.y,
    id: newPlayer.id,
  };

  for(i = 0; i < players.length; i++) {
    //broadcast the new player's information to all other players in the list  
    this.broadcast.emit("newEnemy", currentInfo);
  }

  //check if existing players are present, then send their info to the new player
  if(players.length > 0) {
    for(i = 0; i < players.length; i++) {
        var info = {
            x: players[i].x,
            y: players[i].y,
            id: players[i].id,
        };
        this.emit("newEnemy", info);
    }
  }

  players.push(newPlayer);
  for(i = 0; i < players.length; i++) {
    console.log(players[i].id);
  }
}

function onDisconnect(){
    console.log("User " + this.id + " disconnected");
    //find the user in the list of players and remove them, then inform the client
    for(i = 0; i < players.length; i++) {
        if(players[i].id === this.id) {
            console.log("removing this player " + this.id);
            //TODO trying a different broadcast
            this.broadcast.emit("playerDisconnect", this.id);
            console.log(players[i].id);
            players.splice(i, 1);
        }
    }
}

Below is the relevant client-side code:

//We've lost connection with the server!
function onSocketDisconnect() {
    console.log("Lost connection with server!");
};

//When the server notifies the client that an enemy has disconnected,
//search for it in the enemy list and stop rendering it
function onEnemyDisconnect(data) {
    //TODO
    for(i = 0; i < enemies.length; i++) {
        if(enemies[i].id == data) {
            //TODO
            console.log("destroying");
            enemies[i].destroy();
            enemies.splice(i, 1);
        }
    }
}

Answer №1

When iterating through an array in the forward direction using a for loop and removing elements with .splice(), issues may arise. This method does not work as expected because when an element is removed from the array, the indexes of the subsequent elements shift down by one. Consequently, if the index in the loop points to the next element after removal, certain items will be skipped during iteration.

To address this issue, there are several potential solutions:

  1. Iterate through the array backwards instead of forwards. This way, unprocessed elements remain intact while utilizing .splice().

  2. Avoid modifying the array within the loop. Collect the indexes of elements to be deleted and remove them after the loop completes, preferable from back to front.

  3. Utilize .filter() to create a new array based on specific criteria. Upon completion, assign this filtered array to the original variable for continued use.

  4. If retaining the current iteration order is necessary, adjust the loop index post-.splice() operation by decreasing it by one.

An illustration of reverse array iteration is demonstrated here:

// When informed about an enemy disconnecting, search and remove from enemies list
function onEnemyDisconnect(data) {
    // Reverse iteration prevents skipping due to .splice()
    for (let i = enemies.length - 1; i >= 0; i--)
        if(enemies[i].id == data) {
            console.log("Destroying enemy");
            enemies[i].destroy();
            enemies.splice(i, 1);
        }
    }
}

An example showcasing the use of .filter(), assuming the new array permanently replaces the existing one, is provided below:

// Respond to enemy disconnect notification by filtering out enemies
function onEnemyDisconnect(data) {
    enemies = enemies.filter(item => {
        if (item.id === data) {
            console.log("Destroying enemy");
            item.destroy();
            return false;    // Exclude this item
        }
        return true;
    });
}

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

If the <option> "anyTableName" </option> is chosen, then display the column names of the selected table (PHP, MySQL)

Hey there, I'm a newbie on stackoverflow so feel free to correct me if I'm off base ;) Here's my current dilemma: I have a text.php file that contains 2 <select> elements. The first one allows me to choose a table (like "accounts", "c ...

Anticipating the completion of post requests

I am currently working on implementing a file upload feature in Angular. I have tackled the issue of file size restrictions by creating an API endpoint that can receive file chunks. Once all the chunks are received, another endpoint needs to be triggered ...

What is the reason behind the necessity of adding an extra slash when reloading the page and controller in AngularJS by using $location.path()?

In AngularJS, when you use $location.path() and pass the same URL as the current one, it does not reload the page and controller. However, if you add an extra slash at the end of the URL like this: $location.path('/currentURL/'); it forces a re ...

Is there a way to update an array within my class and access its updated output from outside the class?

let arr = [] class Test extends React.Component { handleConvertString = (event) => { let str = this.inputRef.value; let solutions = ['abb','klopp','lopp','hkhk','g','gh','a&apo ...

What is the best way to retrieve the dimensions of a custom pop-up window from the user?

Currently, I am in the process of developing a website that allows users to input width and height parameters within an HTML file. The idea is to use JavaScript to take these user inputs and generate a pop-up window of a custom size based on the values pro ...

Advantages of using index.js within a component directory

It seems to be a common practice to have an index file in the component/container/module folders of react or angular2 projects. Examples of this can be seen in: angular2-webpack-starter react-boilerplate What advantages does this bring? When is it recom ...

Is there a way for me to immediately send data after receiving it?

When I try to perform onPress={() => kakaoLosing() I am attempting to retrieve data (profile) from getProfile using async await and immediately dispatch that data to KAKAOLOG_IN_REQUEST, This is my current code snippet: import { ...

Set the current time to ISO8601 format

I need assistance with creating a "time passed" counter for my website based on an API call that returns data in the following format: "created_at": "2018-05-16T14:00:00Z", What is the best approach to calculate and display the time that has passed since ...

I am interested in creating a ranking system in JavaScript using JSON data based on points

I have a desire to create the following: var users = {jhon: {name: 'jhon', points: 30}, markus:{name: 'Markus', points: 20}}; // I want it to return like this 1. Jhon with number of points: 30 // 2. Markus with number of points: 20 ...

Create a custom overlay for an image that is centered horizontally and does not have a fixed width

I'm working with this HTML setup: <div class="container"> <img class="image" /> <div class="overlay"> <div class="insides">more content here</div> </div> &l ...

The element type provided is not valid: it should be a string (for built-in components) or a class/function. Utilizing SSR with Node.js, React, and React-

Playground: https://codesandbox.io/s/focused-dream-ko68k?file=/server/server.js Issue Error: Encountered an error stating that the element type is invalid. It was expecting a string or a class/function, but received undefined instead. This could be due ...

The Invalid_grant OAuth2 error occurs when attempting to access the Google Drive API using

SOLVED! The issue was resolved by correcting the time on my Linux Server. Google's server was blocking access due to incorrect time settings. I used the following command on my Server to synchronize the time: ntpdate 0.europe.pool.ntp.org Original P ...

Angular.js: The $setDirty() method on a form does not automatically affect its child form elements

Currently, I am working on a form validation project using Angular.js. A specific challenge that I am facing is setting the dirty state on a child element of a form in an isolated scope within a directive. Does anyone know how to achieve this and set the i ...

Cannot utilize structuredClone() on the value of the reference variable

I am looking to implement the structuredClone() function in my Vue application. I want to use it for creating a deep clone without resorting to methods like stringify and parse or third-party libraries. In my setup function, the following code works fine: ...

Secure your data with public key encryption and decryption using the Crypto Module in NodeJS

I have a challenge with encrypting/decrypting data using a public key that is stored in a file. The code snippet below illustrates my approach: encryptWithKey (toEncrypt, publicKeyPath) { var publicKey = fs.readFileSync(publicKeyPath, "utf8"); ...

Implementing Entity addition to a Data Source post initialization in TypeORM

The original entity is defined as shown below: import { Entity, PrimaryGeneratedColumn} from "typeorm" @Entity() export class Product { @PrimaryGeneratedColumn() id: number The DataSource is initialized with the following code: import ...

Creating a mandatory 'Select' component in Material UI with React JS

Is there a method to show an error message in red until a choice is selected? ...

Employing Multer and Express in conjunction with TypeScript

Overview Currently, I am working on a project that involves creating a user-friendly website where individuals can easily upload images. For this particular task, I have employed Node.js, React, Multer, and Typescript. Issue at Hand app.post('/admi ...

Troubleshooting: Issues with jQuery Validate plugin's rules method in Javascript

I'm encountering an issue with a simple javascript file that is supposed to run the rules method, but it's not working as expected. I have confirmed that my custom javascript file is being rendered correctly since input masking is functioning pro ...

The conflict between Material UI's CSSBaseline and react-mentions is causing issues

Wondering why the CSSBaseline of Material UI is causing issues with the background color alignment of React-mentions and seeking a solution (https://www.npmjs.com/package/react-mentions) Check out this setup: https://codesandbox.io/s/frosty-wildflower-21w ...