BASIC Games Reborn

The astounding success of Wordle had me thinking about the BASIC program that it very closely resembles. In my copy of BASIC Computer Games from 1970, it is simply called Word. A player guesses a five-letter word and the computer tells the player which letters are matches and whether they are in the correct position. An individual named Charles Reid is credited with creating this program, over 50 years ago.

Of course, there was no easy way and no good reason to share the results of a simple BASIC game. Turning this simple game into a social phenomenon is quite impressive. Using the coloured square emojis is a pretty clever way to share without relying on images. And limiting all users to play once per day with the same word turns this simple game into a shared experience.

Description, code and sample execution of BASIC Word game from 1970

Wordle’s evolution from a BASIC program you in a book to popular web application had me thinking about one of the other programs in that book. A couple of years ago, I rebuilt a program called Reverse in Unity, a popular game development application. It was an interesting learning experience but I found that publishing had a few barriers. It wasn’t quite the right fit, and I never went too far with development.

Choosing A Framework

I decided to rebuild Reverse as a web application. After researching a few possible game development frameworks, I decided that a simple web app with a few custom styles would keep the application focused on the functionality and the necessary styles.

For this app, I decided to go with Vuejs. This is my favourite javascript framework. I think the developer Evan You managed to balance ease of use with functionality. Every language or framework has a feeling to it. Manually configure XML files for a Tomcat server, and you might have some unpleasant feelings. The best languages and frameworks do what you need them to quickly and easily.

BASIC Microcomputer Games

The BASIC code couldn’t be used in any direct way but the logic behind the application is clear from the description and example execution. Like the original, it displays the number of moves. I also added a timer, which is a lot simpler and more reliable than counting CPU cycles.

BASIC Computer Games book cover
Description and sample execution of BASIC Reverse game from 1970
BASIC code of Reverse game from 1970

Building The Components

There is very little to the application, I just needed a few components. I created one for the individual number tile and another one for the reverse component itself. The number tile component is fairly simple. The reverse component holds the main game logic.

The code is in a public BitBucket repository. The code below shows the majority of the functionality.

NumberTile (without styles)

<script>
export default {
  props: ["title", "index"],
};
</script>

<template>
  <div class="tilecolumn">
    <div class="tile">
      <h4>{{ title }}</h4>
    </div>
    <div class="keytile">
      {{ index + 1 }}
    </div>
  </div>
</template>

ReverseTiles (without styles)

<script>
import NumberTile from "./NumberTile.vue";

export default {
  components: {
    NumberTile,
  },
  data() {
    return {
      tiles: [
        { id: 1, value: 1 },
        { id: 2, value: 2 },
        { id: 3, value: 3 },
        { id: 4, value: 4 },
        { id: 5, value: 5 },
        { id: 6, value: 6 },
        { id: 7, value: 7 },
        { id: 8, value: 8 },
        { id: 9, value: 9 },
      ],
      moves: 0,
      match: false,
      startTime: null,
      endTime: null,
      runningTime: "",
      clipText: "",
      copied: false,
    };
  },
  mounted() {
    this.restart();
    this.updateRunningTime();
  },
  methods: {
    randomizeTiles() {
      const tiles = this.shuffle(this.tiles);
      const tileList = tiles.map(function (tile) {
          return tile.value;
      });
      this.clipText = '[' + tileList + ']';
    },
    updateRunningTime() {
      setInterval(() => {
        let runningTimeText = "";
        if (this.startTime === null) {
          return;
        }
        let startTime = this.startTime.getTime();
        let endTime = new Date().getTime();
        if (this.endTime !== null) {
          endTime = this.endTime.getTime();
        }

        let timeDiff = Math.abs(endTime - startTime);
        let seconds = Math.floor(timeDiff / 1000);
        if (timeDiff < 1000 * 60) {
          this.runningTime = seconds + " seconds";
        } else if (timeDiff < 1000 * 60 * 60) {
          let minutes = Math.floor(timeDiff / 1000 / 60);
          this.runningTime =
            minutes + " minutes " + (seconds - minutes * 60) + " seconds";
        }
      }, 200);
    },
    shuffle(array) {
      let currentIndex = array.length,
        randomIndex;

      while (currentIndex != 0) {
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex--;

        [array[currentIndex], array[randomIndex]] = [
          array[randomIndex],
          array[currentIndex],
        ];
      }

      return array;
    },
    reverse(tileIndex) {
      let arrayStart = this.tiles.slice(0, tileIndex + 1).reverse();
      let arrayEnd = this.tiles.slice(tileIndex + 1);
      this.tiles = [...arrayStart, ...arrayEnd];
      this.checkOrder();
      this.moves++;
    },
    checkOrder() {
      let matchOrder = true;
      for (let i = 0; i < this.tiles.length; i++) {
        if (this.tiles[i].value !== i + 1) {
          matchOrder = false;
          continue;
        }
      }
      if (matchOrder) {
        this.endTime = new Date();
      }
      this.match = matchOrder;
    },
    restart() {
      this.moves = 0;
      this.match = false;
      this.randomizeTiles();
      this.startTime = new Date();
      this.endTime = null;
      this.copied = false;
    },
    share() {
      let self = this;
      this.clipText += ' in ' + this.moves + ' moves';
      navigator.clipboard.writeText(this.clipText).then(function() {
        self.copied = true;
      });
    },
    keyUp(ev) {
      const keyboardZero = 48;
      const numpadZero = 96;

      if (ev.keyCode >= keyboardZero && ev.keyCode < keyboardZero + 10) {
        this.reverse(ev.keyCode - keyboardZero - 1);
      } else if (ev.keyCode >= numpadZero && ev.keyCode < numpadZero + 10) {
        this.reverse(ev.keyCode - numpadZero - 1);
      } else if (
        this.match &&
        (ev.keyCode === 32 || ev.keyCode === 13 || ev.keyCode === 10)
      ) {
        this.restart();
      }
    },
  },
  created: function () {
    document.addEventListener("keyup", this.keyUp);
  },
  unmounted: function () {
    document.removeEventListener("keyup", this.keyUp);
  },
};
</script>

<template>
  <div @keyup.enter="keyUp">
    <div class="scoreboard">
      <div class="logo">
        <h1>Reverse</h1>
      </div>
      <div class="time">
        {{ this.runningTime }}
      </div>
      <div class="moves">{{ this.moves }} moves</div>
    </div>
    <div class="tiles">
      <NumberTile
        v-for="(tile, index) in tiles"
        :key="tile.id"
        :title="tile.value"
        :index="index"
        @click="reverse(index)"
      ></NumberTile>
    </div>
    <div class="message" v-show="this.match">
      <p class="status">Sorted in {{ this.moves }} moves</p>
      <div class="buttons">
        <button @click="restart()">New Game</button>
        <button @click="share()">Copy &amp; Share</button>
      </div>
    </div>
    <div class="message" v-show="this.moves === 0">
      <p class="status">Order tiles from smallest to largest</p>
      <p class="status">Select a tile or type a number to sort from the left</p>
    </div>
    <div class="message" v-show="this.copied">
      <p class="status">Copied to clipboard</p>
    </div>
  </div>
</template>

Playing The Game

The game supports mouse and keyboard input. You can click on or type the position of the number to reverse. The objective is to sort the number from 1-9 in as few moves as possible. To play, visit reverse.noahjstewart.com.

Reverse javascript game start
Reverse javascript game end
Play Reverse