Make two random enemies shoot projectiles every second Just finished my canvas game that is more or less replica of the old school arcade game &quot;Space Invaders&quot;.
The code below chooses two random enemies every second (approximately) from the enemies array to shoot projectiles. If there are two or more enemies left in the array I want two random enemies to shoot every second and if there is only one enemy left only that enemy will shoot every second.
I have two solutions for this problem. Both of them work. However the first one seems quite unnecessary repetitive and the second solution although short and elegant is probably unnecessary more &quot;expensive&quot;. Keep in mind I am beginner and I might be wrong here with my assumptions and conclusions.
First solution:
let req;
let timer = 0
function animate() {
  req = requestAnimationFrame(animate);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // other game logic

  // two random enemies shoot every second
  if (timer % 60 === 0 &amp;&amp; timer !== 0) {
    let enemiesLength = enemies.length;
    if (enemiesLength &gt; 2) {
      let rndIndex_1;
      let rndIndex_2;
      rndIndex_1 = Math.floor(Math.random() * enemiesLength);
      do {
        rndIndex_2 = Math.floor(Math.random() * enemiesLength)
      } while (rndIndex_1 == rndIndex_2);
      enemies[rndIndex_1].shootProjectile();
      enemies[rndIndex_2].shootProjectile();
    } else if (enemiesLength === 2) {
      enemies[0].shootProjectile();
      enemies[1].shootProjectile();
    } else if (enemiesLength === 1) {
      enemies[0].shootProjectile();
    }
  }

  // other game logic
  timer++
}

animate();

Second solution:
let req;
let timer = 0
function animate() {
  req = requestAnimationFrame(animate);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  // other game logic

  // two random enemies shoot every second
  if (timer % 60 === 0 &amp;&amp; timer !== 0) {
    enemies.sort(() =&gt; 0.5 - Math.random()).slice(0, 2).forEach(e =&gt; {
      e.shootProjectile();
    });
  }

  // other game logic
  timer++
}

animate();

Any other solutions and help is much appreciated.
EDIT:
In response to CertainPerformance answer. I changed the answer to this so it would work:
   if (timer % 60 === 0 &amp;&amp; timer !== 0) {
    const enemiesCopy = [...enemies];
    const enemiesToShoot = [];

    while (enemiesToShoot.length &lt; 2 &amp;&amp; enemiesCopy.length) {
        const [enemy] = enemiesCopy.splice(Math.floor(Math.random() * enemiesCopy.length), 1);
        enemiesToShoot.push(enemy);
    }
    for (const enemy of enemiesToShoot) {
        enemy.shootProjectile();
    }
}

It works fine like this. Is this how it should be? Some clarification please.
Games
Performance
Writing games using JavaScript presents some unique problems.

Jank and slowdown due to memory management. Though there have been vast improvements to what was the norm just a few years back, memory management remains a major point of performance degradation.
Games must always be aware of the issues of memory management. Many common pattern used in JavaScript are not suitable for games.

Platform variance. JavaScript games will run a vast range of platforms, from low end hand held devices, to power hungry overclocked water cooled desktop towers. The average consumer's device is generally of lower spec than the average developer's device.
The success of a browser based game is highly dependent on social networking, a game that does not have a strong focus on performance will not be as successful. Even minor performance problems can make the game unplayable on many more devices than would be  expected on a linear curve as the majority of devices are to the lower end of capability.
Bad reviews are worse than good in a market dominated by word of mouth (social networking) Poor performance is a major driver of bad reviews.


To get the best performance you must get a good understanding of what is slow and what is fast. This is true right down to the most trivial operations need to consider performance.
A look at performance.
Arrays are slow.
Creating and populating arrays are slow due to many factors.

Allocation on heap
Iteration to fill
De-allocation at end of execution (is scoped to execution content) or as a GC deferred slowdown if part of scope that is not released at execution idle.

The following snippet shows 3 examples, two from your question and a 3rd I created,
To ensure that the optimizer does not remove code that has no side effects the enemy shoot function returns a random value that is added to a global soak. All 3 methods use the same enemy, enemies and soak.
This test concentrates only on the shooting ignoring the time test.


    const enemyCount = 60;
    const enemy = { shootProjectile(){ return Math.random() }  }
    const enemies = $setOf(enemyCount,() =&gt; ({...enemy}));
    

    var soak = 0;
    function testA() {
        enemies.sort(() =&gt; 0.5 - Math.random()).slice(0, 2).forEach(e =&gt; {
          soak += e.shootProjectile();
    }

    function testB() {
        const enemiesCopy = [...enemies];
        const enemiesToShoot = [];
    
        while (enemiesToShoot.length &lt; 2 &amp;&amp; enemiesCopy.length) {
            const [enemy] = enemiesCopy.splice(Math.floor(Math.random() * enemiesCopy.length), 1);
            enemiesToShoot.push(enemy);
        }
        for (const enemy of enemiesToShoot) {
            soak += enemy.shootProjectile();
        }
    }

    function testC() {
        const shooterIdx = Math.random() * enemies.length | 0;
        var shooterIdx1 = Math.random() * enemies.length | 0;
        if (shooterIdx1 === shooterIdx) { shooterIdx1 = (shooterIdx1 + 1) % enemies.length }
        soak += enemies[shooterIdx].shootProjectile();
        soak += enemies[shooterIdx1].shootProjectile();
    }



Shooters: 60. Shots per call: 2




Test Function
Mean time
Max diff
OPS*
Relative Performance *1




TestA
31.929µs
±17.298µs
31,319
1%


TestB
0.609µs
±0.604µs
1,642,819
27%


TestC
0.166µs
±0.226µs
6,025,389
100%





µs millisecond. 1 millionth of second.
(*) OPS is calculated operations per second, where the function call is considered the operation
(*1) Percentage of best

As higher enemy counts will negatively effect the outcome of testA and testB and lower enemy counts will negatively effect the outcome of testC the following table is with an enemy count of 6
Shooters: 6. Shots per call: 2




Test Function
Mean time
Max diff
OPS
Relative Performance




TestA
1.208µs
±0.284µs
828,136
14%


TestB
0.490µs
±0.123µs
2,039,537
34%


TestC
0.168µs
±0.266µs
5,959,047
100%



Game compromises
I have been in the games industry for a long time and one thing I have learnt is that pushing for ideal implementations will hurt performance, while compromising in favor of performance goes completely unnoticed.
Selecting many from many
There is another factor that effects the performance of the code and that is the number of shooters. As function textC can only handle 2 shooters at a time we must rewrite it to work with any number of shooters.
At this point we make a sacrifice. Finding shooters randomly means that we need to test against shooter firing at the same time. This overhead should be ignored, rather we flag shooters that get to shoot so that we do not shoot twice.
The compromise
The function TestC will shoot about 7% less than the others giving it a slight advantage, but that advantage pales when compared to the 3* faster than testB and a huge 30* faster than testA
The updated code


const enemyCount = 60;
const enemyShoot = 10;
const enemy = {
    shoot: 0,
    shootProjectile(){ return Math.random() }
}
const enemies = $setOf(enemyCount,() =&gt; ({...enemy}));

var soak = 0;
var time = 0;
function testA() {
    enemies.sort(() =&gt; 0.5 - Math.random()).slice(0, enemyShoot).forEach(e =&gt; {
      soak += e.shootProjectile();
    });
    time ++;
}

function testB() {
    const enemiesCopy = [...enemies];
    const enemiesToShoot = [];
    while (enemiesToShoot.length &lt; enemyShoot &amp;&amp; enemiesCopy.length) {
        const [enemy] = enemiesCopy.splice(Math.floor(Math.random() * enemiesCopy.length), 1);
        enemiesToShoot.push(enemy);
    }
    for (const enemy of enemiesToShoot) {
        soak += enemy.shootProjectile();
    }
    time ++;
}


function testC() {
    var count = enemyShoot + 1;
    while (count--) {
        const shooter = enemies[Math.random() * enemies.length | 0];
        soak += shooter.shoot !== time ? (shooter.shoot = time, shooter.shootProjectile()) : 0;
    }
    time ++;
}



Shooters: 60. Shots per call: 10




Test Function
Mean time
Max diff
OPS
Relative Performance




TestA
26.669µs
±8.919µs
37,497
3%


TestB
2.293µs
±1.023µs
436,149
33%


TestC
0.762µs
±0.834µs
1,313,125
100%



Arrays the outcome
As you can see the results show that using arrays in this case to select items to perform and action is much slower than avoiding the need to hold shooters.
With that said arrays are not bad if used correctly.
Part of the performance penalty of arrays is the allocation and de allocation process.
As memory is currently very cheap compared to CPU cycles you can leave arrays populated and keep a separate counter to track how many active items it holds. This eliminates the need to allocate and de-allocate arrays each frame.
The overhead of iteration
Each time you iterate an array you add the overhead of the loop logic. A general rule is iterate twice only, once for update, once for display. Actions that you would normally do in a separate loop should be done in the update loop.
In this case as you would already be iterating each enemy to move them, this is where the logic to select a shooter should be.
I would not try for 2 shooters every 1 seconds, I would give all shooters odds to shoot that are equivalent. So if there are 60 enemy at 2 shoots total every 1 second the odds for any shooter shooting per frame is 2 / (60 * 60) = 5.556e-4
Thus in the main enemy loop you add Math.random() &lt; 5.556e-4 &amp;&amp; enemy.shootProjectile(); complete eliminating the extra code finding shooters, and in my view creating a far more realistic AI.
 All tests on Chrome 87. 64Bit i9 windows laptop, cooling set to active.