How the Game Works

A guide to understanding the Alphabetty Number Game code

This guide walks through all the code behind the Alphabetty Number Game. The entire game lives in a single file called index.html, and it's built with three languages that work together: HTML, CSS, and JavaScript.

You can play the game right here before we dig in:

1. The three languages inside every web page

Every web page you've ever visited is built with the same three languages. Each one has a different job:

In bigger projects, these three languages usually live in separate files. But for a small project like this, we put them all in one file, which makes it easier to see everything in one place.

Why one file? When you're learning, it's helpful to see the HTML, CSS, and JavaScript all together. You don't have to jump between files to understand how a colour in the CSS connects to a heading in the HTML. For bigger projects, splitting things up keeps them organised — but for now, one file is simpler.

2. HTML: building the skeleton

HTML works with tags. A tag is a word wrapped in angle brackets, like <h1> or <div>. Most tags come in pairs — an opening tag and a closing tag — with content in between:

<h1>Alphabetty Number Game</h1>

That creates a big heading with the text "Alphabetty Number Game" inside it. The / in </h1> means "this is the end of the heading."

The three screens

Our game has three screens, but they're all in the same page. We use <div> tags (short for "division" — just a box that holds other things) to group each screen's content:

<!-- The start screen -->
<div id="start-screen" class="screen active">
  <h1>Alphabetty<br>Number Game</h1>
  <p>Type the letter or number you see!<br>Press SPACE to start.</p>
  <div class="high-scores" id="start-scores"></div>
</div>

<!-- The game screen -->
<div id="game-screen" class="screen">
  <span id="timer">Time: 60s</span>
  <span id="score">Score: 0</span>
  <div id="character">A</div>
</div>

<!-- The game-over screen -->
<div id="gameover-screen" class="screen">
  <h2>Time's Up!</h2>
  <div id="final-score">You scored: 0</div>
  ...
</div>

Notice a few things:

Think about it: Why do we put all three screens in the HTML at once, instead of loading a new page for each one? Because switching between web pages is slow — the browser has to fetch a whole new file from scratch. By keeping everything on one page and just showing and hiding sections, the game feels instant. This is what people mean when they say "single-page app."

3. CSS: making it look good

CSS is a list of rules. Each rule says: "find the things that match this pattern, and apply these styles to them." Here's one of ours:

body {
  background-color: #FFD700;
  color: #FF0000;
  font-family: 'Fredoka One', cursive;
  height: 100vh;
  overflow: hidden;
}

Let's break that down:

Showing and hiding screens

This is the most important CSS trick in the game:

.screen {
  display: none;     /* hidden by default */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.screen.active {
  display: flex;     /* visible when active */
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

The first rule says: every element with the class screen should be hidden (display: none) and stretched to fill the whole page. The dot in .screen means "look for this class name."

The second rule, .screen.active, says: if a screen also has the class active, show it. The display: flex part, together with align-items: center and justify-content: center, places the screen's content right in the middle — both horizontally and vertically. That's how the big letter ends up dead centre.

Why does this work? Because CSS rules are like layers of paint. The more specific rule wins. .screen.active (two classes) is more specific than .screen (one class), so its display: flex overrides the display: none.

Positioning the timer and score

#timer {
  position: absolute;
  top: 1rem;
  left: 1.5rem;
  font-size: 2rem;
}

#score {
  position: absolute;
  top: 1rem;
  right: 1.5rem;
  font-size: 2rem;
}

The # selector targets an id. position: absolute means "place me at an exact spot, regardless of where other things are." The timer goes top-and-left; the score goes top-and-right. They sit in their corners like a heads-up display in a video game.

Making the letter huge

#character {
  font-size: 20vw;
}

vw stands for "viewport width." 20vw means "20% of the screen's width." So on a 1000-pixel-wide screen, the letter would be 200 pixels tall. On a phone, it shrinks automatically. This is one simple line of CSS doing a lot of work — the letter looks great on any screen size without us having to write special rules for phones, tablets, and desktops.

4. JavaScript: making it do things

HTML and CSS are descriptive — they say what things are and how they look. JavaScript is imperative — it tells the computer what to do, step by step. All the action in our game — the timer ticking, the score going up, the letters changing — happens in JavaScript.

JavaScript code lives inside a <script> tag at the bottom of the HTML file. We put it at the bottom so that by the time the browser reads the JavaScript, it has already built all the HTML elements the code needs to find and change.

Our code is organised into sections, and we'll go through each one.

5. Keeping track of the game with variables

A variable is a named box that holds a piece of information. You can put something in the box, look at what's inside, or replace its contents. Here are the variables our game uses:

let currentScreen  = 'start';
let currentChar    = '';
let currentScore   = 0;
let timeRemaining  = 60;
let timerInterval  = null;
let initialsBuffer = '';
let enteringInitials = false;

The word let creates a new variable. After the = is the value we're putting in the box to start with. Let's look at each one:

There are also some constants — values that never change while the game runs:

const CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.split('');
const GAME_DURATION = 60;
const MAX_HIGH_SCORES = 10;

const works like let, except you can't change its value later. We write constant names in ALL_CAPS as a convention — a way of signalling to anyone reading the code "this is a fixed setting, not something that changes during gameplay."

What does .split('') do? The string 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' is just text. Calling .split('') on it chops it into an array (a list) of individual characters: ['A', 'B', 'C', ... '9']. We need a list so we can pick a random item from it later.
Think about it: Why do we keep track of currentScreen in a variable, when we could just check which HTML element has the active class? Either approach would work. But checking a simple variable is quicker to read and understand than searching through HTML elements. It's a trade-off: we have to remember to keep the variable in sync with the actual screen, but the rest of the code becomes cleaner.

6. Switching between screens

Remember how our CSS hides any screen that doesn't have the active class? Here's the JavaScript function that swaps which screen is active:

function showScreen(name) {
  document.querySelectorAll('.screen').forEach(function(el) {
    el.classList.remove('active');
  });
  document.getElementById(name + '-screen').classList.add('active');
  currentScreen = name;
}

A function is a reusable recipe. You give it a name (here, showScreen) and it does the same steps every time you call it. This one takes one ingredient — a name like 'start' or 'game' — and does three things:

  1. Find every screen using querySelectorAll('.screen') and remove the active class from all of them. Now they're all hidden.
  2. Find the one we want by building its id (e.g., 'game' + '-screen' = 'game-screen') and add the active class to it. Now just that one is visible.
  3. Update the variable currentScreen so the rest of our code knows where we are.

So when we write showScreen('game') anywhere in our code, the start screen disappears, the game screen appears, and everything stays in sync. One function, called from several places, doing the same reliable job each time.

7. Picking a random letter

function pickNewCharacter() {
  var candidates = CHARACTERS.filter(function(c) { return c !== currentChar; });
  var randomIndex = Math.floor(Math.random() * candidates.length);
  return candidates[randomIndex];
}

function showNewCharacter() {
  currentChar = pickNewCharacter();
  document.getElementById('character').textContent = currentChar;
}

pickNewCharacter does two things:

  1. It makes a new list called candidates that contains every character except the one currently on screen. The .filter() method goes through the list and keeps only the items that pass a test — here, the test is "is this character different from currentChar?"
  2. It picks a random item from that filtered list. Math.random() gives a random decimal number between 0 and 1 (like 0.73). Multiplying by the list's length and rounding down with Math.floor() turns that into a whole number we can use as a position in the list.
Think about it: Why do we filter out the current character? Imagine you're playing and the letter "M" appears. You type it correctly, and... "M" appears again. You wouldn't be sure if anything happened! By making sure the next character is always different, the player gets a clear visual signal that their key press worked.

showNewCharacter calls pickNewCharacter, saves the result in currentChar, and then puts it on screen by setting the textContent of the big centre element.

8. The countdown timer

The timer needs to do something once every second — subtract 1 from the time remaining and update the display. JavaScript has a built-in tool for this called setInterval:

function startTimer() {
  stopTimer();

  timeRemaining = GAME_DURATION;
  updateTimerDisplay();

  timerInterval = setInterval(function() {
    timeRemaining -= 1;
    updateTimerDisplay();

    if (timeRemaining <= 0) {
      endGame();
    }
  }, 1000);
}

setInterval(someFunction, 1000) says: "run this function every 1000 milliseconds (= 1 second)." Every tick, we subtract 1 from timeRemaining, update the display, and check if time has run out. If it has, we call endGame().

setInterval returns a number — a handle — that we store in timerInterval. We need this handle later so we can tell the browser to stop the timer:

function stopTimer() {
  if (timerInterval !== null) {
    clearInterval(timerInterval);
    timerInterval = null;
  }
}

clearInterval cancels the repeating timer. We also reset timerInterval to null (which means "nothing") so we know no timer is running.

Why call stopTimer() at the start of startTimer()? It's a safety measure. If a timer were somehow already running when we start a new game, we'd end up with two timers ticking at once — the clock would count down twice as fast! Stopping any existing timer first prevents that. This kind of careful thinking — "what if something unexpected is already happening?" — is an important habit in programming.

9. Listening for key presses

The game needs to respond when the player presses a key. We do this by setting up an event listener — we tell the browser "whenever a key is pressed, run this function":

document.addEventListener('keydown', function(event) {

  if (currentScreen === 'start') {
    if (event.key === ' ') {
      startGame();
    }

  } else if (currentScreen === 'game') {
    if (event.key.length > 1) return;

    var pressedKey = event.key.toUpperCase();
    if (pressedKey === currentChar) {
      currentScore += 1;
      updateScoreDisplay();
      showNewCharacter();
    }

  } else if (currentScreen === 'gameover') {
    // ... handle initials or restart
  }
});

This is the longest function in the game, but it follows a simple pattern. It checks which screen we're on, then decides what to do:

Think about it: Why do we require the space bar to start and restart, instead of accepting any key? Imagine you just finished a game and your fingers are still on the keyboard. If any key started a new game, you might accidentally begin one before you've even looked at your score! Requiring a specific key (space) means the player has to make a deliberate choice.

10. Saving high scores

Normally, when you close a web page, all the data disappears. But browsers have a feature called localStorage that lets a page save small amounts of data that sticks around — even if you close the browser and come back later. We use it to store the high-score table.

localStorage can only store text (strings), not complex data structures. So we need to convert our scores back and forth:

// Saving: turn the list of scores into a text string
localStorage.setItem('alphabettyHighScores', JSON.stringify(scores));

// Loading: turn the text string back into a list
var scores = JSON.parse(localStorage.getItem('alphabettyHighScores'));

JSON.stringify takes a JavaScript object or array and converts it to a text string. JSON.parse does the opposite — it reads a text string and rebuilds the object. JSON stands for JavaScript Object Notation, and it's one of the most common ways to store and exchange data on the web.

Checking for a high score

function isHighScore(newScore) {
  var scores = loadHighScores();
  if (scores.length < MAX_HIGH_SCORES) return true;
  return newScore > scores[scores.length - 1].score;
}

This function asks: "does this score deserve a spot on the table?" There are two ways to qualify:

  1. The table isn't full yet (fewer than 10 entries) — everyone gets in!
  2. The table is full, but this score is higher than the lowest one on the table.

Saving and sorting

function saveHighScore(initials, newScore) {
  var scores = loadHighScores();

  scores.push({
    initials: initials,
    score: newScore,
    date: new Date().toLocaleDateString()
  });

  scores.sort(function(a, b) { return b.score - a.score; });
  scores = scores.slice(0, MAX_HIGH_SCORES);

  localStorage.setItem(HIGH_SCORE_KEY, JSON.stringify(scores));
  return scores;
}

This function adds the new score to the list, sorts the list from highest to lowest, trims it to the top 10, and saves it. The .sort() function uses b.score - a.score to put bigger numbers first — if the result is positive, b comes before a.

11. How it all fits together

Here's the flow of the entire game, from start to finish:

Page loads
Start screen
Press SPACE
Game plays
Timer hits 0
Game over

Let's trace through the code for each step:

  1. Page loads. The browser reads the HTML and builds the three screens (all hidden except the start screen). It reads the CSS and applies the styles. It runs the JavaScript, which calls the init function. That function loads any saved high scores from localStorage and displays them on the start screen.
  2. Player presses SPACE. The keydown event listener fires. It sees we're on the 'start' screen and the key is a space, so it calls startGame().
  3. startGame() resets the score to 0, switches to the game screen, picks a random character, and starts the timer.
  4. Player types letters. Each key press triggers the event listener. It compares the pressed key to currentChar. If they match: score goes up, new character appears. If not: nothing happens.
  5. Timer ticks. Every second, the setInterval function subtracts 1 from timeRemaining and updates the display.
  6. Timer hits 0. The interval function calls endGame(), which stops the timer, shows the final score, and either prompts for initials (if it's a high score) or shows the score table directly.
  7. Player presses SPACE again. Back to step 2 — the whole cycle repeats.

That's the whole game. About 300 lines of code — and most of that is CSS styling and HTML structure. The actual game logic is maybe 100 lines of JavaScript. Simple games don't need complicated code.

12. Things to try yourself

The best way to learn is to change things and see what happens. Here are some ideas — start easy and work up:

  1. Change the colours. Find #FFD700 (yellow) and #FF0000 (red) in the CSS and replace them. Try #00FF00 (green) or #4444FF (blue). What happens if you make the text and background the same colour?
  2. Change the time limit. Find GAME_DURATION = 60 and change it to 30 for a faster game, or 120 for a longer one.
  3. Letters only. Change the CHARACTERS string to 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' — no numbers. What about numbers only?
  4. Add a sound. Can you play a short beep when the player gets one right? (Hint: search for "JavaScript play sound" — the Audio object is simpler than you might think.)
  5. Wrong key feedback. Right now, pressing the wrong key does nothing. Could you briefly flash the screen or change the letter's colour to show the mistake?
  6. Difficulty levels. What if the timer started at 30 seconds for hard mode and 90 seconds for easy mode? You'd need a way for the player to choose before the game starts.
Don't worry about breaking it. You can always undo your changes. The worst that can happen is the page shows an error or goes blank — and then you look at what you changed, figure out what went wrong, and fix it. That's not failure, that's programming.