<template>
  <div id="app" class="col col-md-6 col-sm-8">
    <div
      id="guess-number"
      class="align-items-center d-flex justify-content-center p-3"
    >
      <div id="quiz" v-if="!finish">
        <label for="stepRange" class="form-label">
          Current Step: {{ steps }}
        </label>
        <input
          class="form-range"
          id="stepRange"
          type="range"
          v-model="steps"
          min="3"
          max="7"
        />
        <div>
          <p>
            Mind a number between 1 and {{ 2 ** steps }}, then check is your
            number between numbers below {{ steps }} times, after that I'll tell
            your number!
          </p>
        </div>
        <div id="numbers">
          <ul class="d-flex flex-wrap p-0 m-0">
            <li v-for="number in previewNumbers" :key="number">{{ number }}</li>
          </ul>
        </div>
        <div class="input-group justify-content-center align-items-center">
          <button
            class="btn btn-outline-success form-control"
            type="button"
            @click="setPositive"
          >
            Yes
          </button>
          <button
            class="btn btn-outline-danger form-control"
            type="button"
            @click="setNegative"
          >
            No
          </button>
        </div>
      </div>
      <div id="result" class="text-center" v-else>
        <p>Your number is {{ numbers[0] }}!</p>
        <button type="button" class="btn btn-info" @click="start">
          Restart
        </button>
      </div>
    </div>
  </div>
</template>

<script type="module">
export default {
  name: "App",
  data() {
    return {
      steps: 6,
      numbers: [],
      wrongNumbers: [],
      finish: false
    };
  },
  mounted() {
    this.start();
    /**
    * @copyright 
    **/
    $("body").prepend(`
    <div class="bg">
    <ul class="parts">
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
</div>
    `).append(`
    <div id="footer">
    <small>
      Powered by
      <a target="blank" href="https://vuejs.org/">Vue.js</a>
    </small>
    <small><a target="blank" href="https://github.com/realSamy/guess-number">View on github</a></small>
    <small>
      © 2022
      <a target="blank" href="https://github.com/realSamy">realSamy</a>
    </small>
</div>`);
  },
  watch: {
    steps() {
      this.start();
    },
    numbers(value) {
      if (value.length === 1) {
        this.finish = true;
      }
    }
  },
  computed: {
    firstHalf() {
      let numbers = this.numbers.slice();
      return numbers.splice(0, this.middle);
    },
    lastHalf() {
      let numbers = this.numbers.slice();
      return numbers.splice(-this.middle);
    },
    middle() {
      return Math.ceil(this.numbersCount / 2);
    },
    numbersCount() {
      return this.numbers.length;
    },
    previewNumbers() {
      return this.lastHalf
        .slice()
        .concat(
          this.wrongNumbers.slice(2 ** this.steps / 2 - this.lastHalf.length)
        )
        .sort((a, b) => a - b);
    }
  },
  methods: {
    start() {
      this.finish = false;
      this.wrongNumbers = [];
      // noinspection JSVoidFunctionReturnValueUsed
      this.numbers = this.shuffle(this.range(2 ** this.steps, 1));
    },
    /**
     * @copyright https://stackoverflow.com/a/10050831/19113328
     * @static
     * @param {Number} size - Length of range
     * @param {Number} startAt  - Starting number of range
     * @returns {ReadonlyArray<Number>} - Readonly array of numbers
     */
    range(size, startAt = 0) {
      return [...Array(size).keys()].map((i) => i + startAt);
    },

    /**
     * @copyright https://stackoverflow.com/a/2450976/19113328
     * @static
     * @param {Array} array
     * @returns {Array}
     */
    shuffle(array) {
      let currentIndex = array.length,
        randomIndex;

      // While there remain elements to shuffle.
      while (currentIndex !== 0) {
        // Pick a remaining element.
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex--;

        // And swap it with the current element.
        [array[currentIndex], array[randomIndex]] = [
          array[randomIndex],
          array[currentIndex]
        ];
      }

      return array;
    },
    setNegative() {
      this.wrongNumbers = this.wrongNumbers.concat(this.lastHalf);
      this.numbers = this.firstHalf;
    },
    setPositive() {
      this.wrongNumbers = this.wrongNumbers.concat(this.firstHalf);
      this.numbers = this.lastHalf;
    }
  }
};
</script>

<style>
html,
body {
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

body {
  background-size: cover;
  background: url(https://realsamy.github.io/guess-number/img/bg.93db21e3.png) fixed;
}

/*noinspection ALL*/
#app {
  border: 1px solid rgb(255 255 255 / 30%);
  border-radius: 10px;
  box-shadow: 0 0 5px 0 #00000030;
  backdrop-filter: blur(20px) brightness(1.1);
}

#app * {
  user-select: none;
}

#guess-number {
  min-height: 200px;
}

#numbers {
  padding: 10px;
  background: #ffffff3b;
  border: 1px solid #ffffff42;
  border-radius: 10px;
  margin: 20px 0;
  box-shadow: 0 0 4px -2px;
}

#numbers li {
  width: 40px;
  height: 40px;
  display: flex;
  justify-content: center;
  align-items: center;
}
#footer {
  position: fixed;
  bottom: 0;
  border: 1px solid rgb(255 255 255 / 30%);
  border-radius: 10px 10px 0 0;
  box-shadow: 0 0 5px 0 #00000030;
  -webkit-backdrop-filter: blur(20px) brightness(1.1);
  backdrop-filter: blur(20px) brightness(1.1);
  width: 100%;
  display: flex;
  justify-content: space-around;
  padding: 10px;
}

#footer a {
  text-decoration: none;
}

.bg {
  position: absolute;
  width: 100%;
  height: 100vh;
}

.parts {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.parts li {
  position: absolute;
  display: block;
  list-style: none;
  width: 20px;
  height: 20px;
  -webkit-animation: animate 25s linear infinite;
  animation: animate 25s linear infinite;
  bottom: -150px;
  border: 1px solid rgb(255 255 255 / 30%);
  box-shadow: 0 0 5px 0 #00000030;
  border-radius: 10px;
  backdrop-filter: brightness(1.1);
}

.parts li:nth-child(1) {
  left: 25%;
  width: 80px;
  height: 80px;
  animation-delay: 0s;
}

.parts li:nth-child(2) {
  left: 10%;
  width: 20px;
  height: 20px;
  animation-delay: 2s;
  animation-duration: 12s;
}

.parts li:nth-child(3) {
  left: 70%;
  width: 20px;
  height: 20px;
  animation-delay: 4s;
}

.parts li:nth-child(4) {
  left: 40%;
  width: 60px;
  height: 60px;
  animation-delay: 0s;
  animation-duration: 18s;
}

.parts li:nth-child(5) {
  left: 65%;
  width: 20px;
  height: 20px;
  animation-delay: 0s;
}

.parts li:nth-child(6) {
  left: 75%;
  width: 110px;
  height: 110px;
  animation-delay: 3s;
}

.parts li:nth-child(7) {
  left: 35%;
  width: 150px;
  height: 150px;
  animation-delay: 7s;
}

.parts li:nth-child(8) {
  left: 50%;
  width: 25px;
  height: 25px;
  animation-delay: 15s;
  animation-duration: 45s;
}

.parts li:nth-child(9) {
  left: 20%;
  width: 15px;
  height: 15px;
  animation-delay: 2s;
  animation-duration: 35s;
}

.parts li:nth-child(10) {
  left: 85%;
  width: 150px;
  height: 150px;
  animation-delay: 0s;
  animation-duration: 11s;
}

@keyframes animate {
  0% {
    transform: translateY(0) rotate(0deg);
    opacity: 1;
    border-radius: 0;
  }

  100% {
    transform: translateY(-1000px) rotate(720deg);
    opacity: 0;
    border-radius: 50%;
  }
}
</style>

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.0.2/css/bootstrap.min.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.0/js/bootstrap.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js