Select elements randomly from an array without repeating

Asked

Viewed 185 times

1

First of all, I searched several websites about this problem and found numerous examples, but unfortunately none of these examples worked in solving my problem.

Anyway, I’m developing a quiz based on the game CS:GO, where a photo will be shown and the user needs to match the position name for the photo (based on the game).
For this, I created an array that has objects, which in turn have the image and the name of the position that will be shown. Because of that I need those objects with those hints. are chosen randomly without repetition, until the array has no more options available.
I managed to get close to this goal through this code:

const maps = [
  {
    mapName: "dust ii",
    positions: [
      {
        posicao: "fundo",
        posicaoImg: "#",
      },
      {
        posicao: "escuro baixo",
        posicaoImg: "#",
      },
      {
        posicao: "base tr",
        posicaoImg: "#",
      },
      {
        posicao: "base ct",
        posicaoImg: "#",
      },
    ],
  },
  {
    mapName: "mirage",
    positions: [
      {
        posicao: "fundomirage",
        posicaoImg: "#",
      },
      {
        posicao: "escuro baixomirage",
        posicaoImg: "#",
      },
    ],
  },
];

let selectElementMap = document.getElementById("mapSelector");
let valueSelectedMap;
let positionAlreadyChosen = [];

function getSelectedValuesIndex() {
  valueSelectedMap =
    selectElementMap.options[selectElementMap.selectedIndex].value; // pegando o mapa através do "<select>" no html

  let positions = maps[valueSelectedMap].positions; // pega o array que contem o nome das posições e a imagem

  let randomArrayPos = Math.floor(
    Math.random() * maps[valueSelectedMap].positions.length
  );

  return (showPosition = positions[randomArrayPos].posicao); // pega uma posição aleatória do mapa
}

function basicLogic() {
  getSelectedValuesIndex();

  // verificar se a posição gerada já foi selecionada
  if (!positionAlreadyChosen.includes(showPosition)) { 
    positionAlreadyChosen.push(showPosition);
  }
  console.log(positionAlreadyChosen);
}
<div class="container">
  <main class="content">
    <div class="selects-area">
      <select name="mapSelector" id="mapSelector">
        <option value="" disabled selected>Selecione um mapa</option>
        <option value="0">Dust II</option>
        <option value="1">Mirage</option>
        <option value="2">Cache</option>
        <!-- <option value="...">___</option> -->
      </select>
    </div>

    <button class="play" onclick="basicLogic();">Jogar</button>
  </main>
</div>

So with this code I get the following result when I click to get the elements Random:

https://i.stack.imgur.com/4lQha.png

With everything explained, my doubt is the following: There is an easier way to do this process, without having to click 9x (as in the example) for all positions to be chosen?

1 answer

2


Have you ever thought of just creating a copy of your array and sort randomly?

const desordenar = (arr) => {
  const copia = [...arr];

  for (let i = copia.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1));
    [copia[i], copia[j]] = [copia[j], copia[i]];
  }
  
  return copia;
};

const desordenado = desordenar(['fundo', 'base tr', 'base ct']);
console.log(desordenado.shift()); // Pega o primeiro da lista
console.log(desordenado.shift()); // Pega o segundo da lista
console.log(desordenado.shift()); // Pega o terceiro da lista


The above function is based on the algorithm Fisher-Yates shuffle which consist of placing all elements in one array and, backwards, exchange its position with some random element.


In the case of your code the suggestion would be as follows:

const maps = [{
    mapName: "dust ii",
    positions: [{
        posicao: "fundo",
        posicaoImg: "#",
      },
      {
        posicao: "escuro baixo",
        posicaoImg: "#",
      },
      {
        posicao: "base tr",
        posicaoImg: "#",
      },
      {
        posicao: "base ct",
        posicaoImg: "#",
      },
    ],
  },
  {
    mapName: "mirage",
    positions: [{
        posicao: "fundomirage",
        posicaoImg: "#",
      },
      {
        posicao: "escuro baixomirage",
        posicaoImg: "#",
      },
    ],
  },
];

const desordenados = {};

let selectElementMap = document.getElementById("mapSelector");

const desordenar = (arr) => {
  const copia = [...arr];

  for (let i = copia.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [copia[i], copia[j]] = [copia[j], copia[i]];
  }

  return copia;
};

function basicLogic() {
  const valueSelectedMap = selectElementMap.options[selectElementMap.selectedIndex].value;
  const desordenado = desordenados[valueSelectedMap]?.length ? desordenados[valueSelectedMap] :  desordenar(maps[valueSelectedMap].positions); // o array que será randomizado com base no <select> do usuario
  desordenados[valueSelectedMap] = desordenado;
  console.log(desordenado.shift()); // Pega o primeiro da lista
}
<div class="container">
  <main class="content">
    <div class="selects-area">
      <select name="mapSelector" id="mapSelector">
        <option value="" disabled selected>Selecione um mapa</option>
        <option value="0">Dust II</option>
        <option value="1">Mirage</option>
        <option value="2">Cache</option>
        <!-- <option value="...">___</option> -->
      </select>
    </div>

    <button class="play" onclick="basicLogic();">Jogar</button>
  </main>
</div>


Reference: Shuffle an array.

  • I tried to use this answer but it is returning all elements of the array. The intention is that each time the user clicks on the button, a position of maps[X].positions randomly, and this cannot be chosen again. Then when you click again the process repeats. The output on the console: (4) [{…}, {…}, {…}, {…}]

  • @Gabrielg.Pereira then, after cluttering the array just take one element at a time with the pop() or shift function()

  • So, apparently this method has a good chance to work in my own code. The problem is that it ended up being repeated a position (a base ct) array also (something that was better than with my code, since to select all positions I had to click about 9x). On the.log console looks like this: [Consolelog] (https://prnt.sc/vrcds6)

  • @Gabrielg.Pereira only repeated if you implemented something wrong. Present all your code using this solution and I’ll tell you what’s wrong.

  • I don’t know the best way to send a complete code in Stackoverflow responses, so I put it in Jsfiddle: Jsfiddle

  • Thanks, the code worked as it should! ? desordenados[valueSelectedMap]?.length ? desordenados[valueSelectedMap] : desordenar(maps[valueSelectedMap].positions);&#Most of all, I appreciate your help.

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.