Shuffling Array Values in Vue 3

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

Lately I’ve been a little obsessed with using JavaScript array shuffling methods to create Random Number Generators (RNGs), so I thought I’d share with y’all what those methods look like in Vue.js!

In this article, we’ll set up some Vue.js demos with the following configurations:

  1. RNG with custom-sorting shuffle array
  2. RNG with Fisher-Yates algorithm

To finish it out, we’ll also create a mini-game that prompts the user to input 3 numbers before loading the RNG deck. If at least one of the inputted numbers matches one of the final RNG deck numbers, the user gets 5 points. If none of the numbers match, the user loses 5 points.

Let’s get started!

TailwindCSS Setup

Let’s kick off a new Vue 3 Tailwind CSS + Vite build. Run the following in the terminal:

npm init vite my-tailwind-project
cd my-tailwind-project

Next, install and initialize Tailwind CSS in the new project:

npm install -D tailwindcss
npx tailwindcss init -p

Add the following to tailwind.config.js:

module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

Create a new index.css file in ./src and add the following:

@tailwind base;
@tailwind components;
@tailwind utilities;

Update main.js to import index.css:

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
createApp(App).mount('#app')

Install the @headlessui/vue in order to access the transition animation component:

npm install @headlessui/vue

Run the app:

npm run dev

Project Setup

We’ll Create a new component called CustomSorting.vue. We’ll then add the following template to our new component:

<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">
{{ 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">
Randomize
</button>
</div>
</div>
</div>
</template>

The project template will display three numbers in the RNG deck, all of which correspond to the first three indices in our randomized array. The “randomize” button (below the three slots) calls the function to randomize the RNG deck numbers.

We’ll then add the following to the component, in order to handle the RNG deck functionality:

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

const inputValueOne = ref(null)
const inputValueTwo = ref(null)
const inputValueThree = ref(null)
const score = ref(0)
const slots = ref([])

const randomize = async () => {
//
}
</script>

Now let’s look at two ways to set up the randomize method to handle randomization of the RNG deck!

1. RNG with custom sorting shuffle array

The simplest way to set up RNGs is to create a custom sorting function. In this setup, we’ll use a .sort()method to shuffle an array of 10 digits, before using a .slice()method to return the first 3 indices in the array. Update the .randomize() function with the following:

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

const randomize = async () => {
for (let i = 0; i < numberArray.value.length; i++) {
slots.value = numberArray.value.sort((a, b) => 0.5 - Math.random()).slice(0, 3);
}
}

The above code block uses a for-loop to cycle through 10 iterations of a sequentialized number array (0–9), with each iteration returning a randomized variation of the original array.

To slow down the loop iteration, we’ll implement a .sleep()function above the .randomize()function. We’ll then call the .sleep() function inside the .randomize() function.

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

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

const randomize = async () => {
for (let i = 0; i < numberArray.value.length; i++) {
await sleep(200)
slots.value = numberArray.value.sort((a, b) => 0.5 - Math.random()).slice(0, 3);
}
}

Our script setup should now look like the following:

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

const inputValueOne = ref(null)
const inputValueTwo = ref(null)
const inputValueThree = ref(null)
const score = ref(0)
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 () => {
for (let i = 0; i < numberArray.value.length; i++) {
await sleep(200)
slots.value = numberArray.value.sort((a, b) => 0.5 - Math.random()).slice(0, 3);
}
}
</script>

2. RNG with Fisher-Yates algorithm

Another approach we can take is to loop through the array from start to end, pick a random item from the array, and swap it with the item in the current iteration:

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

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

const randomize = async () => {
for (let i = numberArray.value.length - 1; i > 0; i--) {
await sleep(100)
const j = Math.floor(Math.random() * (i + 1));
const temp = numberArray.value[i];
numberArray.value[i] = numberArray.value[j];
numberArray.value[j] = temp;
slots.value = numberArray.value.slice(0, 3);
}
}

The above method is known as a Fisher-Yates algorithm, which can be used to achieve a truly random distribution of items. Another way to write this function is as the following:

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

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

const randomize = async () => {
for (let i = numberArray.value.length - 1; i > 0; i--) {
await sleep(100)
const j = Math.floor(Math.random() * (i + 1));
[numberArray.value[i], numberArray.value[j]] = [numberArray.value[j], numberArray.value[i]];
slots.value = numberArray.value.slice(0, 3);
}
}

The above is the (slightly) shorter ES6/ECMAScript 2015 approach to carrying out the Fisher-Yates algorithm, which allows for the assignment (and ultimately the swapping) of two variables. To learn more about JavaScript arrays with the Fisher-Yates algorithm, check out the following: https://sebhastian.com/fisher-yates-shuffle-javascript/

Game Time!

We’ll now create a mini game that prompts the user to input 3 numbers before loading the RNG deck. If at least one of the inputted numbers matches one of the final RNG deck numbers, the user gets 5 points. If none of the numbers match, the user loses 5 points.

Update the template with the following:

<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">
<span class="flex items-center justify-center mb-2 text-3xl font-bold">
{{ score }} Points!
</span>

<span class="flex items-center justify-center text-md font-bold">
<span>Match at least 1 value per spin to get 5 points!</span>
</span>

<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">
{{ slot }}
</div>
</div>
</div>

<div class="flex items-center justify-center mb-12">
<input v-model="inputValueOne" type="number" class="mt-4 m-2 border-2 border-solid border-sky-500" />
<input v-model="inputValueTwo" type="number" class="mt-4 m-2 border-2 border-solid border-sky-500" />
<input v-model="inputValueThree" type="number" class="mt-4 m-2 border-2 border-solid border-sky-500" />
</div>

<div class="flex justify-center">
<button @click="randomize" :disabled="disableButton" :class="[disableButton === true ? 'cursor-not-allowed' : 'cursor-pointer']" 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">
Randomize
</button>
</div>
</div>
</div>
</template>

The template now includes:

  • Fields for the user to input their 3 guesses
  • Score board (at the top)
  • disable/enable handling on the button to prevent the user from clicking “randomize” before entering the 3 inputs

Below the numberArray declaration in the script, add the following computed property to handle disabling:

const disableButton = computed(() => {
if (inputValueOne.value === null
|| inputValueTwo.value === null
|| inputValueThree.value === null) {
return true
}
})

Inside the .randomize() function (below the for-loop), add the following functionality to handle the guess-matching:

const randomize = async () => {
for (let i = 0; i < numberArray.value.length; i++) {
await sleep(200)
slots.value = numberArray.value.sort((a, b) => 0.5 - Math.random()).slice(0, 3);
}

//***
const guessArray = [inputValueOne.value, inputValueTwo.value, inputValueThree.value]

const found = guessArray.some(r => [...slots.value].includes(r))

if (found === true) {
score.value += 5
} else {
score.value -= 5
}
//***
}

Our script setup should now look like the following:

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

const inputValueOne = ref(null)
const inputValueTwo = ref(null)
const inputValueThree = ref(null)
const score = ref(0)
const slots = ref([])

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

const disableButton = computed(() => {
if (inputValueOne.value === null
|| inputValueTwo.value === null
|| inputValueThree.value === null) {
return true
}
})

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

const randomize = async () => {
for (let i = 0; i < numberArray.value.length; i++) {
await sleep(200)
slots.value = numberArray.value.sort((a, b) => 0.5 - Math.random()).slice(0, 3);
}

const guessArray = [inputValueOne.value, inputValueTwo.value, inputValueThree.value]

const found = guessArray.some(r => [...slots.value].includes(r))

if (found === true) {
score.value += 5
} else {
score.value -= 5
}
}
</script>

And there it is! You’ve learned at least two ways to handle random number generating with array shuffling in Vue.js!

Here is the GitHub link to the project. See CustomSorting.vue and FisherYateSorting.vue for the examples covered above:

For more information on the TailwindCSS, see the following link:

Stay tuned for part 2 of this series, covering the prevention of repeated indices in cycled arrays!

--

--

William Schulte
Vue.js Developers

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