I’ve Made a Game!

I’ve been working with computer code–in one form or another–since typing BASIC commands to run games on my Commodore 64 back in the late 1980s. In elementary school, my friend Kyle and I would spend Saturday afternoons copying programs into QBasic from the back pages of 3-2-1 Contact magazine before moving on to designing and programming our own Zork-style text adventure games. In middle and high school, I moved into HTML and Flash, trying my hand at online interactivity. Eventually, I gave up on Windows and moved into the wonderful world of Linux, Bash, and Python.

Bringing it full circle, I’ve been taking a few coding boot camps, relearning the skills I haven’t used in earnest for years. I enjoy making things, and I miss building interactive things. So, for the first time in nearly two decades, I’ve built a game from scratch. It’s JavaScript, and it’s fairly simple, but it’s been a great exercise. Play it, have fun with it, and let me know what you think!

REACT!

REACT! screenshot

How To Place Boxes On The Screen In Random Places With HTML and JavaScript

To begin my grand plan for the VHS Time Capsule, we’ll need a way to place a couple of div containers in random places. I played with this idea a while back with my “React!” application, creating randomly-sized divs in random positions within the viewport. Let’s take a look at the code:

iteration1.js

function getRandomColor() {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    if (color == "#FFFFFF") {
        for (var i = 0; i < 6; i++ ) {
            color += letters[Math.floor(Math.random() * 16)];
        }
    } else {
        return color;}
}
            
function makebox() {
    var time=Math.random();
    time=5000*time;
    var top=Math.random();
    top=top*400;
    var left=Math.random();
    left=left*400;
    document.getElementById("box").style.top=top+"px";
    document.getElementById("box").style.left=left+"px";
    document.getElementById("box").style.backgroundColor=getRandomColor();
    document.getElementById("box").style.display="block";
    setTimeout(function() {
        makebox();
        }, time);
}

iteration1.html

<html>
<head>
<script type="text/javascript" src="iteration1.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<style>

#box {
    background-color: red;
    width: 200px;
    height: 200px;
    position: relative;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
$(document).ready(function(){
       makebox();
    });
</script>
</body>
</html>

The HTML document is fairly sparse, existing only to frame the application by defining the initial CSS parameters of the div containers to be used (for error detection and so the function can identify the div), creating that initial div in the HTML body, then calling the makebox() function using a simple jQuery string that detects when the page is loaded:

$(document).ready(function(){ makebox(); });

The makebox() function is the heart of the application, so let’s take a look at it–line by line:

var time=Math.random(); This line defines the variable time and assignes it a randomized value between 0 and 1.

time=5000*time; This multiplies time by 5000 and stores the new value as time. This will set the delay between random changes that we will invoke using setTimeout() at the end of the function.

var top=Math.random();
top=top400;
var left=Math.random();
left=left
400;

Just as with the time variable, we define top and left to be some random multiple of 400. Once we have all the properties defined, the next few lines of code re-define the CSS style of the div with ID box, moving any new divs created to the random positions defined by top and left:

document.getElementById("box").style.top=top+"px";
document.getElementById("box").style.left=left+"px";
document.getElementById("box").style.backgroundColor=getRandomColor();
document.getElementById("box").style.display="block";

At the end of the makebox() function, we invoke the setTimeout() function to create a delay as defined by time above before looping the makebox() function back to the beginning.

setTimeout(function() {
    makebox();
    }, time);

The last piece of the puzzle is the getRandomColor() function, which defines a random color using hexadecimal values:

function getRandomColor() {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    if (color == "#FFFFFF") {
        for (var i = 0; i < 6; i++ ) {
            color += letters[Math.floor(Math.random() * 16)];
        }
    } else {
        return color;}
}

The letters variable describes an array containing each of the possible hexadecimal values 0-A. The color variable is initially defined with the hashmark that precedes a hexadecimal color value. The for loop fills the 6 digits of our hexadecimal code:

for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
}

First, the function tests the value of the i variable to see how many digits have been placed. If the value is less than 6, the function proceeds to fill each digit as follows:

Math.random() * 16 Selecting a random value between 0-16
Math.floor(Math.random() * 16) Dropping the decimal from the random value, leaving a whole integer. letters[Math.floor(Math.random() * 16)] Using the randomly selected value to select the value of the array corresponding to that position. color += letters[Math.floor(Math.random() * 16)] The += operator appends the digit selected to the value of the colors variable.

if (color == "#FFFFFF") {
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }

If the value of color happens to end up as white (#FFFFFF), then the process repeats until the value is not white.

} else {
    return color;}

If the value of color is something other than white (#FFFFFF), then the function returns the value of color.

When we put all this together, we get something like the following: A 200×200 pixel square that is filled with a random color and appears in a random X,Y position at random intervals.

How To Animate A Box Flying Across The Screen using CSS

Now that we have random positioning and coloring settled, we need to start looking at a way to animate the box moving across the screen. Once upon a time, I would’ve just used Flash to make all this work, but vast improvements in HTML, CSS, and JavaScript have really (thankfully) eliminated the need to use Flash for this kind of work. There are now two main ways to animate objects on a web page: one using CSS and one using JavaScript (or jQuery). We’ll start by looking at the CSS method.

I made a few minor changes to the previous HTML & JavaScript files so that it draws 2 div containers, each a random color, and places them at some random Y position along the left side of the screen. For visual clarity, I made one square 300px and one 200px:

iteration2.js

function makebox() {
    var top1=Math.random();
    var top2=Math.random();
    top1=top1*100;
    top2=top2*100;
    document.getElementById("box1").style.top=top1+"px";
    document.getElementById("box1").style.backgroundColor=getRandomColor();
    document.getElementById("box1").style.display="block";
    \document.getElementById("box2").style.top=top2+"px";
    document.getElementById("box2").style.backgroundColor=getRandomColor();
    document.getElementById("box2").style.display="block";
}

iteration2.html

<html>
<head>
<script type="text/javascript" src="iteration2.js"></script>
<style type="text/css">

    div {
        animation-name: example;
        animation-duration: 4s;
        animation-iteration-count: infinite;
    }

    @keyframes example {
        0%   {left:0%;}
        100% {left:100%;}
    }

    #box1 {
        width:200px;
        height:200px;
        background-color:red;
        display:none;
        position:relative;
    }

    #box2 {
        width:300px;
        height:300px;
        background-color:red;
        display:none;
        position:relative;
    }
</style>
</head>

<body onload="makebox();">

<div id="box1"></div>
<div id="box2"></div>

</body>
</html>

In the CSS section of the HTML document, we see that div containers in this document will be animated and that animation will have a duration of 4 seconds, repeating infinitely. The @keyframes parameters define two keyframes for this 4 second animation: 0% (the beginning) and 100% (the ending). We could add other keyframes in between, but for simplicity, we’ll just stick with the requisite two.

0% {left:0%;} defines the first keyframe of the animation, placing the object to be animated at the left edge of the screen.

100% {left:100%;} declares that the last keyframe of the animation will see the object at the right edge of the screen.

Note that the animation is the same for every iteration of the loop. In order to see different combinations of colors and starting positions, you’ll need to refresh the page.

How To Animate A Box Flying Across The Screen with JavaScript

Right away, the problems with the CSS approach to animation become apparent: For one, the CSS animation applies to all div containers the same way at the same time. Granted, we could just apply different IDs to each div and animate them individually, but that seems like a lot of extraneous CSS and HTML code to brute-force the effect that we’re looking for. Secondly, the JavaScript would also start to get rather unwieldy rather quickly for similar reasons. The real answer here is to animate the objects using JavaScript instead. This will allows us to define random variables for speed and timing on the fly instead of using global parameters that would have to be overridden every time.

iteration3.js

function makebox() {
    var top=Math.random();
    var speed=Math.random();
    speed=speed*8000;
    top=top*600;
    document.getElementById("box1").style.top=top+"px";
    document.getElementById("box1").style.left="-50%";
    document.getElementById("box1").style.backgroundColor=getRandomColor();
    document.getElementById("box1").style.display="block";
    $("#box1").animate({left: "110%"}, speed);
}

We’re going to use the same HTML framework as before, only removing any CSS that defines animation and adding a script tag that calls the jQuery library. (jQuery makes animation a lot easier, reducing several lines of JavaScript to a single line.) Taking a look at the new JavaScript makebox() function, we’ll see a new variable, speed, that is defined as some random multiple of 8000. This value will be used to define how long the animation takes, effectively defining the speed of the div’s motion across the screen.

document.getElementById("box1").style.top=top+"px";
document.getElementById("box1").style.left="-50%";
document.getElementById("box1").style.backgroundColor=getRandomColor();
document.getElementById("box1").style.display="block";

This CSS block is effectively the same as we’ve seen previously, except that we’re now going to place the div with ID “box1” at a position off the left side of the screen. This -50% location is arbitrary for now, defining a spot where the div will (hopefully) be hidden to start (about 50% of the screen’s width beyond the left edge of the screen).

$("#box1").animate({left: "110%"}, speed);

This jQuery argument polls the HTML code for a div with the ID “box1”, then animates it from its starting position (defined in the preceding CSS block) to a position 110% the width of the screen from the left edge (or about 10% the width of the screen right of the right edge). Note that jQuery animations have to be defined relative to the initial state, otherwise unexpected things may happen depending on the way the browser chooses to render the result (in other words, you can’t/shouldn’t animate from a “left” position to a “right” position). The speed variable defines the duration of the animation, in miliseconds. For this example, the animation will take anywhere from 0-8 seconds.

Note that the animation in this iteration does not loop (we’ll address that in the next iteration), so you may need to refresh the page.

How To Animate Multiple Boxes Flying Across The Screen Using jQuery

This fourth iteration is where things are going to start coming together. We have the basic structure of a colored div container moving across the screen from different Y positions and at different speeds. Now, we’re going to clean up the code and start to customize it to our particular application.

iteration4.html

<html>
<head>
<script type="text/javascript" src="iteration4.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<style>
    body {
        overflow: hidden;
    }

    div.window {
        position: absolute;
        background-color: black;
        left: -427px;
        width: 427px;
        height: 320px;
    }

    div.window img {
        width: 100%;
        height: inherit
    }
</style>

</head>
<body>
<script>
$(document).ready(function(){
       makebox();
    });
</script>

<div class="window"></div>

</body>
</html>

In this HTML code, we’ve moved up from a 200px square to a 427x320px rectangle that will more closely fit the thumbnail images that we’re going to be using to fill the “flying windows”. We’ve also confined the viewport to just the visible size of the screen (no more scroll bar) and moved the starting position of the animated divs to just off the left side of the screen. Images in the “window” divs will take up the entire size of the container.

iteration4.js

function makebox() {
    $(".window:not(:animated)").each(function() {
        $(this).css({"top": Math.random() * window.innerHeight - 160,"left": "-500px","backgroundColor": getRandomColor});
        $(this).animate({left: "800px"}, Math.random() * 5000 + 5000, makebox);
    });
}

Thanks to some jQuery, we are able to reduce the length of the function by about half while adding some more sophisticated behavior!

$(".window:not(:animated)").each(function() This initial line tests whether or not each div container of class “window” is currently being animated. If it’s not being animated, then the attached function will run.

$(this).css({"top": Math.random() * window.innerHeight - 160,"left": "-500px","backgroundColor": getRandomColor}); The this jQuery object will apply the subsequent code to each matching object from the previous test. In this case, we’ll apply new CSS properties to the particular div: a starting Y position that’s some random fraction of the current viewport’s height (minus half of the height of the div to make sure it appears on the screen), a starting X position that’s 500 pixels left of the left edge, and a random background color (defined by the getRandomColor() function that hasn’t changed).

$(this).animate({left: "800px"}, Math.random() * 5000 + 5000, makebox); This version of the animate method will move the current div container across the screen to a position 800 pixels from the left edge (the right edge of the iframe). The animation will take some random time between 5 and 10 seconds to complete.

If we add more div containers to the HTML document, we get more windows flying across the screen:

Adding Random Images To Flying Windows Using jQuery

Now that we have the completed application skeleton, we need to populate the div containers with randomly selected thumbnails from the videos we’ll be viewing. The first thing we’ll need to do is add an array populated by the unique YouTube identifier locations to our JavaScript file. Again, I have omitted the getRandomColor() function because it has not changed.

iteration5.js

const addresses = ["9KuZj8zb0pQ",
"8amHoqOTsyI",
"g4ywxRCztuE",
"JMtzR9JpGFI",
"PdKB6opHjSM",
"bn9SLbfOEwo",
"IoIdD1p21o8",
"LusQqtxaCb8",
"Ds-Vp29bBcs",
"Q3fvTEsT0EI",
"fN-9U_9xaR4",
"HL_8u_qwIf0",
"ho5iDlsUSQE",
"gTHTsJfmY8k",
"Uegvzyl-wEU",
"b3G2QRE9qtY",
"rkFbx59lqEI",
"tFPuceqF37E",
"8wqAnj6Skjk",
"dsucNSCufJs",
"jh5lP3nmgC4",
"mNM0jy58gT8",
"rNkr_otHZUs",
"IDwWUHJ9PGk",
];
        
function makebox() {
    $(".window:not(:animated)").each(function() {
        addy = (Math.floor(Math.random() * addresses.length));
        $(this).css({"top": Math.random() * window.innerHeight - 160,"left": "-"+Math.random() * 150 -150+"%","background-image": "url('https://img.youtube.com/vi/"+addresses[addy]+"/0.jpg')", "border": "10px solid", "color": getRandomColor});
        $(this).animate({left: "150%"}, Math.random() * 5000 + 5000, makebox);
    });
}

Again, the makebox() function begins with a test to see which divs on screen are animated and then sets the initial position in CSS before animating the div to move across the screen from left to right. However, we have added the new variable addy which is to be populated by a random number between 0 and the amount of values in the addresses array (by using the Math.floor() function, we round the random number down to the nearest integer).

$(this).css({"top": Math.random() * window.innerHeight -
160,"left": "-"+Math.random() * 150 -150+"%","background-image":
"url('https://img.youtube.com/vi/"+addresses[addy]+"/0.jpg')", "border":
"10px solid", "color": getRandomColor});
We’re making three significant changes to this CSS object in this iteration: First, we’re changing the starting X position of the div to a random value between 150% and 300% of the screen width left of the left edge. Second, we’re adding a 10 pixel border to the div in a random color. Third, we’re adding a new background image to the div’s CSS parameters by calling the thumbnail image stored on YouTube’s server for the video located at the address string stored in the addresses array at position addy.

$(this).animate({left: "150%"}, Math.random() * 5000 + 5000, makebox); The last change in this version of the function is to call the makebox() function once the animation is finished.

iteration5.html

<html>
<head>

<script type="text/javascript" src="iteration5.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<style type="text/css">

    body {
        overflow: hidden;
    }

    div.window {
        position: absolute;
        background-color: black;
        left: -427px;
        width: 427px;
        height: 320px;
        justify-content: center;
        align-items: center;
    }

    div.window img {
        width: 95%;
        height: 95%;
    }

</style>
</head>

<body>

<script>
$(document).ready(function(){
       makebox();
    });
</script>

<div class="window"></div>

</body>
</html>

We’ve only made minor changes to the CSS section of the HTML file. Since we want the images centered in the div container, we add the justify-content: center and align-items: center parameters to the window class div stylesheet and we resize the img elements within the window class divs to 95% of their original size. Everything else runs as the previous iteration (I did omit the extra divs, but they can be added back in as needed to populate the screen more fully.

How To Link To A Popup Video Player On A Website

The VHS Time Capsule application is nearly complete; we just need to have some kind of popup video player that opens whenever a thumbnail is clicked. Of course, it also has to play the corresponding video. Thankfully, since we’re using videos hosted on YouTube, we only need one set of location strings. It’s just going to be a little tricky to embed them in the format we need.

iteration6.html

<html>
<head>
<script type="text/javascript" src="iteration6.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<style type="text/css">
    body {
        overflow: hidden;
    }

    div.window {
        position: absolute;
        background-color: black;
        left: -427px;
        width: 427px;
        height: 320px;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    #viewer {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 740px;
        height: 580px;
        visibility: hidden;
        background-color: black;
        z-index: 100;
    }

</style>
</head>

<body>

<script>
$(document).ready(function(){
       makebox();
    });
</script>

<div id="viewer"></div>

<div class="window"></div>

</body>
</html>

In the HTML file, we’ve added a new stylesheet for a div with the ID “viewer” that will appear in the center of the viewport. The transform: translate(-50%, -50%) parameter centers the div by moving its origin point (the top left corner) upward and leftward by 50% of the element’s height and width, respectively. The z-index defines the “distance off the paper” that the div will be drawn: higher numbers indicate closer to the viewer. At z-index 100, the viewer should appear on top of everything else being drawn. This new viewer div will start hidden and be made visible through JavaScript.

iteration6.js

function makebox() {
    $(".window").click(function () {
        color = $(this).css("color");
        $("#viewer").css({"visibility": "visible", "background-color": color});
        video = $(this).css("background-image");
        video = video.slice(32, -6);
        parent.document.getElementById("viewer").innerHTML=""<img src='close.png' style='position:absolute; right:25px; top: 25px; transform: translate(30%, -25%);' onclick='hideViewer()'><iframe width='640' height='480' style='position:absolute; top:50px; left:50px;' src='https://www.youtube.com/embed/"+video+"?controls=0&autoplay=1&fs=0&modestbranding=1&rel=0' title='YouTube video player' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' allowfullscreen></iframe>";
    });
    $(".window:not(:animated)").each(function() {
        addy = (Math.floor(Math.random() * addresses.length));
        $(this).css({"top": Math.random() * window.innerHeight - 160,"left": "-"+Math.random() * 150 -150+"%","background-image": "url('https://img.youtube.com/vi/"+addresses[addy]+"/0.jpg')", "border": "10px solid", "color": getRandomColor});
        $(this).animate({left: "150%"}, Math.random() * 15000 + 15000, makebox);
    });
}

function hideViewer() {
    $("#viewer").css({"visibility": "hidden"});
    parent.document.getElementById("viewer").innerHTML="";
}

Our trusty makebox() function has become quite complicated now, so let’s go over the new additions line-by-line:

$(".window").click(function () { When a div of class “window” is clicked, the embedded function will be invoked as follows:

color = $(this).css("color"); A new variable, color, is defined by the CSS color attribute for the particular div we’re working with. This color attribute will be set by the getRandomColor() function when the div is drawn.

$("#viewer").css({"visibility": "visible", "background-color": color}); This makes the viewer div visible and colors it with the same as the border of the div that was clicked.

video = $(this).css("background-image"); This defines a new variable video and defines it as the URL of the thumbnail populating the div that was clicked.

video = video.slice(32, -6); Redefines video by eliminating the first 32 characters and the last 6 characters of the value defined in the preceding line. This effectively reverts video back to the YouTube location identifier that would be found in the addresses array so that we can use it in the next line.

parent.document.getElementById("viewer").innerHTML="<img
src='close.png' style='position:absolute; right:25px; top: 25px;
transform: translate(30%, -25%);' onclick='hideViewer()'><iframe
width='640' height='480' style='position:absolute; top:50px; left:50px;'

src=’https://www.youtube.com/embed/”+video+”?controls=0&autoplay=1&fs=0&modestbranding=1&rel=0′
title=’YouTube video player’ frameborder=’0′ allow=’accelerometer;
autoplay; clipboard-write; encrypted-media; gyroscope;
picture-in-picture’ allowfullscreen></iframe>”;

This injects the defined HTML code between the tags for the “viewer” div. The code here is the boilerplate YouTube iframe embed code with a few optional parameters specified. The HTML also centers the YouTube iframe and renders a close button in the corner of the #viewer div that will run the hideViewer() function when clicked.

The rest of the makebox() function doesn’t change, but we do add a new hideViewer() function to the script.

function hideViewer() {
    $("#viewer").css({"visibility": "hidden"});
    parent.document.getElementById("viewer").innerHTML="";
}

This new function simply hides the viewer div by changing the CSS and stops any video playing by clearing the HTML inside the #viewer div tags.

How To Detect Browser User Agent And Add Mobile Website Support

There remains one major problem with the application as it stands right now: when viewed on a mobile browser, the animation breaks down and the viewer window causes undesirable effects such as rescaling the page. As such, we’ll need to tweak the operation just slightly for mobile browsers (this wasn’t such a problem in the 1990s and early 2000s, but with most web browsing done via mobile, it would be dumb not to account for the difference.

iteration7.html

<script>
$(document).ready(function(){
       detectUserAgent();
    });
</script>

In the main HTML file, the only change we’re going to make is calling a different function from our JS file. In this case, it’s a function that will detect the user agent, letting the application know whether to run the desktop or mobile version of the app.

iteration7.js

function detectUserAgent() {
    if ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) || (navigator.userAgent.match(/Android/i))) {
        location.replace("mobile.html");
    }
    startplayer();
    }

function startplayer() {
    $.getScript("videofiles.js", function() {
        });
    makebox();
    }

In the JavaScript, the new detectUserAgent() function queries the navigator.userAgent object to see if it contains the string “iPhone”, “iPad”, or “Android” which would indicate a mobile device and browser. If one of those strings is found in the object, then the current page is redirected to “mobile.html”. Otherwise, the startplayer() function is called.

In the startplayer() function, we use the jQuery getScript() method to load another JS file within the script as running. In this case, it will load the “videofiles.js” file that now contains the addresses array.

The “mobile.html” file works exactly like the main HTML file, except that it now refers to a slightly different JS file, “mobile.js” that contains the changes we need to make the mobile version work.

mobile.js

function makebox() {
    $(".window").click(function () {
        video = $(this).css("background-image");
        video = video.slice(32, -6);
        window.open("https://www.youtube.com/embed/"+video, '_blank');
    });
    $(".window:not(:animated)").each(function() {
        addy = (Math.floor(Math.random() * addresses.length));
        $(this).css({"top": Math.random() * window.innerHeight - 290+"px","left": "-"+Math.random() * 740 - 740+"px","background-image": "url('https://img.youtube.com/vi/"+addresses[addy]+"/0.jpg')", "border": "10px solid", "color": getRandomColor});
        $(this).animate({left: "2000px"}, Math.random() * 15000 + 15000, makebox);
    });
    }

We only need to make two major changes to the makebox() function to make it work on mobile: First, we need to make sure that we define all animations by pixel distances instead of screen widths. The percentage screen width method of measurement does not work correctly on some mobile browsers and can cause the flying windows to rescale themselves when they reach the right edge of the screen.

window.open("https://www.youtube.com/embed/"+video, '_blank');
Second, instead of a viewer div playing an embedded YouTube video, we’re going to open the video in a new tab. We’re still going to use the embed version of the video, though, to avoid having the full YouTube player page load (which would also be undesirable).

From this point, we just add an appropriate background video to the HTML file body using the background-video CSS property, some appropriate MIDI music using the MIDIjs JavaScript library (midijs.net), and a few navigation buttons, and the application is ready to go!