Skip to main content

Command Palette

Search for a command to run...

Bloc Buster: A Retro-Style PWA Game

Updated
5 min read
Bloc Buster: A Retro-Style PWA Game

Overview

Bloc Buster is a browser-based block puzzle game built with HTML canvas and designed to work offline. It’s an homage to the well-known classic Tetris, but with my own twist. The idea came to me when I was stuck on the app store, trying to find something to play. My internet kept dropping, and every game that did load was either packed with ads, gated by logins, or felt like a stripped-down knockoff of the real thing.

Don’t get me wrong, the original is solid, but I wanted to take a crack at building something I could play offline. I had never made a game like this before (my past projects were mostly simple visual novels or tap games), so it was a fun and unique challenge.

Thanks to a few tutorials and a lot of tweaking, I finished the core game in a few days. But what made it shine was the polish: hand-crafted 8-bit SFX and background music, responsive design, and even a custom arcade cabinet UI that wraps around the game canvas.


UX + Design Decisions

  • Minimal friction: The game opens with a retro-style start screen. No menus, no loading screens.

  • Two control schemes:

    • Keyboard support for desktop (arrow keys to move, spacebar to drop)

    • Touch gestures for mobile (swipe to move/drop, tap to rotate, hold to soft drop)

  • Retro color palette: Features a dark background with cyan and magenta to really sell the “retro” look.

  • Responsive layout: Cabinet and screen scale dynamically based on screen size.

Design and Development Challenges

One of the biggest hurdles I ran into was getting the mobile swipe input to feel right. I tested multiple variations of swipe duration and direction logic. Sometimes it was too sensitive and triggered accidental movements, while at other times it lagged just enough to break the flow. I ended up testing several versions to get it to a place that felt fairly natural. It still might not be perfect, but it’s responsive enough to play without frustration.

Another challenge came later in development, when I decided to shift the game into an arcade cabinet layout. I don’t regret starting with the simpler version, but I do wish I had made the cabinet decision earlier. It would’ve saved me a lot of time in the long run. I ended up rewriting a big chunk of my CSS just to reposition elements and get the proportions right. The original version had a minimal layout (left image), but once I committed to the cabinet aesthetic, it required some redesign (right image). Still, I think it gave the project a lot more personality.

Core Features and Implementation

Arcade Cabinet Layout

The game is visually framed inside an arcade-style wrapper, with the canvas and UI elements housed inside a set of nested containers. The key element is the .arcade-window, which acts as the playable “screen” area. This window sits inside an .arcade-container, which handles responsive scaling across different screen sizes.

Below is an example of how the arcade screen is scaled proportionally in CSS based on the size of the cabinet image, which is 1024 × 1536. The .arcade-window is absolutely positioned and uses calc() with percentage-based values to stay centered and properly framed inside the cabinet design.

.arcade-window {
  top: calc(409 / 1536 * 100%);
  left: calc(192 / 1024 * 100%);
  width: calc(640 / 1024 * 100%);
  height: calc(767 / 1536 * 100%);
  z-index: 80;
  background: #090c0e;
  position: absolute;
}

Leaderboard

Bloc Buster includes a simple leaderboard that appears both on the index page and the game over screen. Both screens pull from the same core data but display the data slightly differently. The example below shows the leaderboard score that appears on the index page. If no score exists yet, it shows a default entry with a starting score of 6000.

let leaderboard = JSON.parse(localStorage.getItem("blocbuster_leaderboard") || "[]");

if (!leaderboard.length) {
  leaderboard = [{ name: "DOM", score: 6000 }];
}

leaderboard.sort((a, b) => b.score - a.score);
topScore = leaderboard[0];
highScoreElement.textContent = `HIGH SCORE ${String(topScore.score).padStart(6, "0")}`;

Pause on Tab Switch (and Basic Pausing)

Pausing the game is a core feature, but what I didn’t anticipate was how switching tabs would affect the game. When I came back, the game loop would speed up and break the flow. To fix this, I added a listener that automatically calls pauseGame() whenever the tab loses focus. The same function is reused when the player clicks the pause button, so both paths handle pausing gameplay, stopping the music, and showing the pause overlay. Below is an example of the function.

function pauseGame() {
  const pauseBtn = document.getElementById("pause-btn");
  const pauseOverlay = document.getElementById("pause-overlay");

  isPaused = !isPaused;

  pauseBtn.innerHTML = isPaused
    ? '<i class="fas fa-play"></i>'
    : '<i class="fas fa-pause"></i>';

  if (isPaused) {
    audio.music.pause();
    pauseOverlay.classList.remove("hidden");
  } else {
    audio.music.play().catch(() => {});
    pauseOverlay.classList.add("hidden");
    update();
  }
}

Final Takeaway

Although Bloc Buster is a small project on the surface, this project pushed me to figure out swipe controls and tackle responsive design in a way that’s very different from normal web design. It also allowed me to learn more nuanced aspects of Canvas game design using HTML and vanilla JavaScript.

Explore the Project

Try it out here: View Project
Check out the code: GitHub Repo
Watch the video: Coming soon!


— Dominique
Learning out loud, one project at a time.