Home

Deep Miner

A Motherload-style mining game in the browser. Drill, collect, upgrade, repeat.

Motherload came out in 2004 and I still think about it. You pilot a little drill rig into the earth, fill your cargo hold with ore, fly back to the surface, sell it, buy better equipment, and go deeper. That is the whole game. It is perfect.

This is a version of that idea that runs right here, in your browser, on your phone or on your desktop. No install, no app store. Just a canvas element and about 500 lines of JavaScript.

• • •

The game

Drill downward through layers of earth. Collect ore, return to the surface station to sell it and refuel. Spend your earnings on upgrades: a stronger drill, bigger fuel tank, tougher hull, and more cargo space. See how deep you can go before your hull gives out.

Deep Miner v1.0
move  ·  / Space jetpack  ·  drill down  ·  P shop (at station)  ·  R restart

On mobile, two transparent d-pads appear in the bottom corners. Swipe the direction you want to go. Drive up to the station to enter the shop, or roll over the pump pad to refuel and sell your cargo on the move.

• • •

The layers

The world is 40 tiles wide and 200 tiles deep, divided into eight depth bands. Each layer has its own palette, its own ore distribution, and a couple have nasty surprises waiting for you.

Layer Depth Notes
Topsoil0–15mLoose dirt and roots. Coal and copper.
Bedrock15–35mBauxite, iron, the first silver.
Permafrost35–55mFrozen solid. Requires the Heated Drill upgrade or you cannot break a single tile. Methane ice deposits hide here.
Fossil layer55–80mAncient sediment. Amber with insects, trilobite fossils.
Deep crust80–110mCinnabar, gold, and the first uranium veins.
Magma veins110–145mGlowing red rock. Without a Heat Shield, your hull drains continuously while you are inside.
Crystal caves145–180mTanzanite, more diamonds. The walls glitter.
Mantle edge180–200mPainite and unobtanium. Even worse heat than the magma layer. Endgame only.

What is down there

Eighteen things to mine, give or take. Some are real, some are deliberately not. Where the real ones are concerned I have played a little fast and loose with relative values, but the general ordering — copper before silver before gold before tanzanite before painite — is roughly how the world works.

Ore Value Found in Notes
Coal$5Topsoil → bedrockCheap and common.
Copper$12Topsoil → bedrockStarter cash.
Bauxite$25BedrockAluminum ore.
Iron$35Bedrock → deep crustUbiquitous.
Pyrite$60BedrockFool's gold — looks valuable, isn't.
Silver$90Bedrock → deep crust
Methane ice$180PermafrostHeated drill required.
Cinnabar$140Deep crustMercury ore. Crystal clusters.
Gold$200Deep crustThe real thing.
Amber$350Fossil layerInsect trapped inside.
Trilobite$600Fossil layerHalf-billion-year-old arthropod.
Uranium$800Deep crustGlows. Requires drill Lv 3.
Obsidian$280Magma veinsVolcanic glass.
Emerald$900Fossil → crystal
Ruby$1,400Deep crust → crystal
Tanzanite$2,000Crystal cavesReal, found only in Tanzania.
Diamond$3,000Crystal cavesRequires drill Lv 4.
Painite$6,000Mantle edgeReal, briefly the world's rarest gem.
Unobtanium$12,000Mantle edgeNot real. Sue me.

Dirt and stone are not in this table because you do not collect them — they are rubble. You drill them, they disappear, you move on. The shop has six upgrades: drill, fuel, hull, cargo, plus a one-time Heated Drill purchase (gates the permafrost) and a two-tier Heat Shield (lets you survive the magma and mantle layers).

• • •

The stack

There is no stack, really. That is the point. One HTML file, one CSS file, one JavaScript file, and an <canvas> element. No build step, no bundler, no framework, no node_modules. The whole game is about 900 lines in an IIFE, served as a static file. If you can open it in a browser, you can run it.

Everything renders with the 2D canvas API — fillRect, createLinearGradient, arc, the usual suspects. State is plain objects. The game loop is requestAnimationFrame with a delta-time update(dt) followed by render(). Input is keyboard listeners on desktop and a virtual d-pad I draw onto the canvas itself for touch. That is the whole architecture.

• • •

Three pieces of code I like

Depth-biased ore generation

The world is generated once at startup. Every tile rolls against a list of candidate ores filtered by depth, with rarer ores getting slightly better odds the deeper you go. The trick is walking the candidate list from rarest to most common — that way diamond gets first dibs on the random number, and common dirt only gets picked when nothing rarer has claimed the roll.

function pickOre(depth) {
  var candidates = [];
  for (var i = 0; i < ORE_KEYS.length; i++) {
    var o = ORES[ORE_KEYS[i]];
    if (depth >= o.minDepth) candidates.push(ORE_KEYS[i]);
  }
  var r = Math.random();
  // Deeper = rarer ores more likely. Walk from rarest to common.
  var cumul = 0;
  for (var j = candidates.length - 1; j >= 0; j--) {
    var ore = ORES[candidates[j]];
    var adjustedChance = ore.chance * (1 + depth * 0.003);
    cumul += adjustedChance;
    if (r < cumul / (cumul + 1)) {
      return { type: candidates[j], hp: ore.hp };
    }
  }
  if (Math.random() < 0.12) return null; // air pocket
  return { type: 'dirt', hp: ORES.dirt.hp };
}

The cumul / (cumul + 1) bit is a soft normalization trick: it keeps the running probability bounded between 0 and 1 without having to know the total ahead of time, while still preserving the rare-first ordering. The depth multiplier (1 + depth * 0.003) is small but compounds — by 150 metres down, rare ores are getting a 45% bonus.

Collision against a tile grid

There is no physics engine. The whole world is a 2D array of tiles, and the player is an axis-aligned rectangle. To check if the player overlaps anything solid, I floor-divide its four corners by the tile size and look up every cell in the range:

function solidAt(px, py, w, h) {
  var c1 = Math.floor(px / TILE);
  var c2 = Math.floor((px + w - 1) / TILE);
  var r1 = Math.floor(py / TILE);
  var r2 = Math.floor((py + h - 1) / TILE);
  for (var r = r1; r <= r2; r++) {
    for (var c = c1; c <= c2; c++) {
      var t = tileAt(r, c);
      if (t === 'wall' || t !== null) return true;
    }
  }
  return false;
}

Movement then becomes: tentatively move on X, check; if blocked, don't move and zero the X velocity; do the same for Y. Separating the axes is what stops the player from slipping into corners diagonally, and it is what lets you slide along walls smoothly. The whole movement function is maybe 25 lines.

Touch d-pad as an angle sweep

On mobile, the d-pad is just two transparent circles drawn at the bottom corners of the canvas. There are no actual buttons. When a touch lands, I take the vector from the d-pad center to the touch and use atan2 to slice it into four directions:

function updateDpad(x, y) {
  dpad.left = dpad.right = dpad.up = dpad.down = false;
  var dx = x - DPAD_CX;
  var dy = y - DPAD_CY;
  var dist = Math.sqrt(dx * dx + dy * dy);
  if (dist < DPAD_SIZE * 0.85) {
    var a = Math.atan2(dy, dx);
    if (a > -0.7 && a < 0.7)              dpad.right = true;
    if (a >  2.4 || a < -2.4)             dpad.left  = true;
    if (a < -0.7 && a > -2.4)            dpad.up    = true;
    if (a >  0.7 && a <  2.4)            dpad.down  = true;
  }
}

atan2 returns an angle from to π. The 0.7 cutoffs (about 40°) carve the circle into four wedges with no dead zones between them. Drag your finger around inside the d-pad and the direction follows smoothly — there is no concept of "lifting off one button and pressing another." It feels much better than literal buttons, and it is six lines of math.

• • •

How it all fits together

The camera follows the player with a smooth lerp (cam.x += (target - cam.x) * 0.12). Drilling sets a timer on the target tile and subtracts hit points based on drill level — when HP hits zero, the tile is replaced with null and the ore goes into your cargo array. Every valuable ore gets a small white square drawn in the corner so you can spot it underground.

• • •

Strategy

Early game: drill straight down through topsoil, sell coal and copper constantly, and rush the fuel upgrade. Your starting tank barely gets you to 30 metres and back. Once you have fuel level three or four, you can start going after silver and gold. The pump pad outside the shop refuels and sells your cargo automatically when you drive across it — you never actually need to enter the shop just to refuel.

Mid game: hit the permafrost wall around 35 metres, head back up, and save for the Heated Drill. Without it you cannot break a single tile in that layer — not even dirt. Once you punch through, the fossil layer pays well: amber and trilobite fossils sell for hundreds. Uranium starts showing up around 80 metres but needs drill level 3 to chip at.

Late game: the magma layer at 110 metres is genuinely hostile. Without a Heat Shield, your hull drains every second you spend inside, regardless of whether you are drilling. Buy at least the Mk 1 shield before you commit. Below that, the crystal caves are mostly safe and full of tanzanite and diamond. The mantle edge at 180 metres is worse than the magma layer and holds painite and unobtanium — do not go down there without Mk 2 shield, max drill, and a full tank.

And cargo, always cargo. A single painite is worth $6,000 but if your hold only fits five items, you are making a lot of round trips. Max it out and each run to the deep zone pays for itself many times over.