Preventing Back-to-Back Repeat Random Array Values in Vue 3

William Schulte
Vue.js Developers
Published in
4 min readNov 13, 2022

Hey y’all! While I was working on my last article (Shuffling Array Values in Vue 3), I built a series of Random Number Generator demos that ultimately didn’t make the cut for the final post. One of the demos I had originally planned for the post was a Vue.js slot machine app that entailed cycling randomly through an array of values. While reviewing the demo later on, I discovered an issue: when cycling through a random assortment of slot values, the slots appeared to “stutter” at least once every other cycle. After taking a closer look at the log, it became clear that back-to-back repeating values were the culprits:

This bugged me…so I set out to fix it. After some trial and error and some consulting on StackOverflow, I believe I have at least one potential solution. Let’s check it out!

For this article, see the component titled IndexRepeatPrevention.vue in the following repo:

To begin, let’s talk about the component, which originally looked like this when I was starting out:

<template>
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div class="px-4 py-6 sm:px-0">
<div class="flex items-center justify-center py-16">

<QuestionMarkCircleIcon v-if="slots.length < 1" class="w-96 h-96"/>

<div v-else v-for="slot in slots" :key="slot.id" class="w-96 h-96 m-2">
<div class="flex justify-center items-center w-full h-full p-8 text-9xl rounded-md shadow-lg border-solid border-2 border-sky-500">
<!-- <img :src="slot.img"> -->
{{ slot }}
</div>
</div>
</div>

<div class="flex justify-center">
<button @click="randomize" class="group relative w-48 flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Randomize
</button>
</div>
</div>
</div>
</template>

<script setup>
import { ref } from "@vue/reactivity";
import { onMounted } from "@vue/runtime-core";
import { QuestionMarkCircleIcon } from "@heroicons/vue/outline"

const slots = ref([])

const numberArray = ref(Array.from({length: 9}, (e, i)=> i + 1))

const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}

const randomize = async () => {
const list = numberArray.value.sort(() => Math.random() - 0.5)

for (let i = 0; i < list.length; i++) {
const index = Math.floor(Math.random() * list.length)
await sleep(100)
slots.value = list.filter(r => r === (index + 1))
console.log(slots.value)
}
}
</script>

The randomize function above receives numberArray, then assigns it to list after sorting and randomizing. Then, inside the for-loop, we create a new random index on each cycle. The original list is then filtered using each newly-created index, then re-assigned to slots.value. Using a sleep function, we can slow down the loop cycle to see each individual result assigned to slots.value.

This functionality works in terms of generating random sequences, but it does not take into account the potential for back-to-back repeated random numbers. After some online digging and consulting, I refactored the randomize function as follows:

let prevIndex = null
const generateRandIndex = max => {
let index
do {
index = Math.floor(Math.random() * max)
} while (index === prevIndex)
prevIndex = index
return index
}

const randomize = async () => {
const list = numberArray.value.sort(() => Math.random() - 0.5)

for (let i = 0; i < 10; i++) {
const index = generateRandIndex(list.length)
await sleep(100)
slots.value = list.filter(r => r === index + 1)
}
}

Inside the randomize function, we’ll call another function named generateRandIndex(), which uses a do-while loop to re-generate the index if a value in a given cycle matches the one before it.

The final script setup should look like this:

<script setup>
import { ref } from "@vue/reactivity";
import { onMounted } from "@vue/runtime-core";
import { QuestionMarkCircleIcon } from "@heroicons/vue/outline"

const slots = ref([])

const numberArray = ref(Array.from({length: 9}, (e, i)=> i + 1))

const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}

let prevIndex = null
const generateRandIndex = max => {
let index
do {
index = Math.floor(Math.random() * max)
} while (index === prevIndex)
prevIndex = index
return index
}

const randomize = async () => {
const list = numberArray.value.sort(() => Math.random() - 0.5)

for (let i = 0; i < 10; i++) {
const index = generateRandIndex(list.length)
await sleep(100)
slots.value = list.filter(r => r === index + 1)
console.log(slots.value)
}
}
</script>

Let’s see what the output looks like now:

There you have it…no back-to-back repeat offenders!

Once again, check out this functionality in the IndexRepeatPrevention.vue component in the following repo:

That’ll do it for now with the RNG articles. I’m working on some Vue + Supabase demos next…so stay tuned!

--

--

William Schulte
Vue.js Developers

Mobile App Developer, Coding Educator, LDS, Retro-gamer 🎮