HTML game code: complete games in one file
A browser game does not need a project, a build step or a framework. It needs one file, six blocks and a loop. Here is all of it, in code you can paste.
Jump to a section
A playable browser game fits in one .html file you can open by double clicking it. No install, no build step, no server. The file always has the same six blocks in the same order, and once you can see them, any game you open stops being a wall of text.
A project has folders, imports and a build. Each is a thing that can be wrong before a single pixel is drawn. A single file either opens and runs or does not, and you can email it, put it on a memory stick, or hand it to somebody who has never installed anything.
The skeleton, complete and runnable#
Paste this into a plain text file, save it as game.html, and open it. A square moves with the arrow keys and does not leave the screen. Everything after this section is a variation on it.
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
html,body{margin:0;height:100%;background:#0b1020;overflow:hidden}
canvas{display:block;touch-action:none}
</style>
<canvas id="c"></canvas>
<script>
const CONFIG = { speed: 260, size: 40, colour: "#ffc107" };
const c = document.getElementById("c");
const ctx = c.getContext("2d");
let W = 0, H = 0;
function resize() {
const d = window.devicePixelRatio || 1;
W = c.clientWidth; H = c.clientHeight;
c.width = W * d; c.height = H * d;
ctx.setTransform(d, 0, 0, d, 0, 0);
}
new ResizeObserver(resize).observe(c);
resize();
const state = { x: 100, y: 100 };
const held = Object.create(null);
addEventListener("keydown", (e) => { held[e.key] = true; });
addEventListener("keyup", (e) => { held[e.key] = false; });
function update(dt) {
const s = CONFIG.speed * dt;
if (held.ArrowLeft) state.x -= s;
if (held.ArrowRight) state.x += s;
if (held.ArrowUp) state.y -= s;
if (held.ArrowDown) state.y += s;
state.x = Math.max(0, Math.min(W - CONFIG.size, state.x));
state.y = Math.max(0, Math.min(H - CONFIG.size, state.y));
}
function draw() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = CONFIG.colour;
ctx.fillRect(state.x, state.y, CONFIG.size, CONFIG.size);
}
let last = 0;
requestAnimationFrame(function frame(now) {
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
update(dt);
draw();
requestAnimationFrame(frame);
});
</script>
That is a whole game file. It has no title screen, no score and nothing to lose against, and adding those is the easy part. What it does have is every structural decision already made correctly, which is the part that is tedious to get right by hand.
The six blocks, and what each one is for#
Three of the six are worth looking at closely, because they are where a hand-written file and a careless one differ.
CONFIG, so you can tune without reading code#
Every number a player would feel belongs in one object at the top: speeds, sizes, timings, colours, how much damage a hit does. Scattered through the file, those numbers are a search job each time you want to adjust the feel. Gathered, the whole game is tunable from eight lines.
State as plain data#
Keep the world in one object of ordinary values. Not in the positions of DOM elements, and not spread across variables in a closure. When state is one object you can print it, save it to localStorage in a line, and restore a game exactly as it was.
// save
localStorage.setItem("save", JSON.stringify(state));
// restore, keeping every field the save does not know about
const raw = localStorage.getItem("save");
if (raw) Object.assign(state, JSON.parse(raw));
The loop, which reads time rather than counting frames#
The loop in the skeleton measures how long the last frame took, converts it to seconds, and limits it. Movement is then a speed per second rather than a distance per frame, which is why the square travels the same way on a 60Hz laptop and a 120Hz phone.
The limit matters as much as the measurement. A tab left in the background can return with a gap of several seconds, and without a cap everything in the game teleports through whatever it was about to hit.
The four additions that make it a game#
The skeleton moves a square. These four turn movement into something with a beginning and an end, and each is a handful of lines.
| Addition | What it needs | Where it goes |
|---|---|---|
| Something to collect | A position, an overlap test, a score counter. | A new field in state, a check in update. |
| Something to avoid | The same overlap test, then a life lost. | update, before the collect check. |
| A way to lose and restart | A mode field, and a key that resets state. | A branch at the top of update and draw. |
| A best score that survives | One localStorage read on load, one write on death. | Beside the score, nowhere else. |
In that order. A game you cannot lose has nothing to save, so the last two arrive together.
function hit(a, b) {
return a.x < b.x + b.w && a.x + a.w > b.x &&
a.y < b.y + b.h && a.y + a.h > b.y;
}
That function is the whole of collision for a game of squares and rectangles. Circles are a distance check instead, and anything more exact than those two is almost always a sign the game is more complicated than it needs to be.
What goes wrong in a hand-written file#
- The canvas sized in the markup. Setting width and height as attributes gives a fixed box that is stretched by CSS, and everything looks soft. Size it in code from the element's real size and the device pixel ratio, as the skeleton does.
- Drawing inside the update step. It works until you want to pause, or draw twice, or skip a frame. Keeping them apart costs nothing on the day you write it.
- Moving things inside the key event. The event repeats on a clock the operating system owns, so a second player freezes while the first holds a key. Record held keys and read them in the loop.
- No touch-action rule. Without
touch-action: noneon the canvas, dragging scrolls the page instead of playing the game, and the file looks broken on every phone.
Getting an AI to write this for you#
You do not have to type any of it. The useful thing about knowing the shape is that you can ask for it by name, and you can tell at a glance whether what came back has it.
Write a complete browser game as ONE .html file I can double click.
No frameworks, no build step, no images, no external files.
Structure it exactly like this, in this order:
1. A short style block. Black background, no margin, canvas with
touch-action: none.
2. One canvas, sized in JavaScript from its own client size and
devicePixelRatio. Never sized with HTML attributes.
3. A CONFIG object at the top holding every number I might tune.
4. A single state object holding the whole world as plain data.
5. Input as a held-keys record set on keydown and keyup, plus
pointer events for touch. Nothing moves inside an event handler.
6. A loop that measures seconds since the last frame, clamps that
to 0.05, then calls update(dt) and draw() separately.
THE GAME: <describe it in two sentences here>
Then tell me which numbers in CONFIG change the difficulty.
The last line is worth keeping. It makes the model name the levers it just built, which is faster than reading the file to find them.
For prompts that produce whole finished games rather than a skeleton, use the prompt guide. For what each of these pieces is doing underneath, the 2D guide covers the loop, collision and input in depth.
📄Games here, each one a file you can read
Every game lists the model and the prompt that produced it.Common questions#
Can a whole game really be one HTML file?
Yes. The style, the markup and the script all live in one document, and the browser needs nothing else. Every game on this site is a file or a small folder of them, and the ones that are a single file open from your desktop with no server at all.
Do I need a server to run an HTML game?
Not for a single file that draws with canvas and uses no external assets. Double clicking it is enough. You need a server once the file loads other files, because browsers block some of those reads from the local file system.
Should I use a game engine instead?
Not for a browser game of this size. An engine earns its weight when you have scenes, assets and a team. For one screen and a loop it adds a build step, a download and a set of concepts between you and the thing you wanted to make.
Why is my canvas blurry?
Because it is being stretched. The canvas has a drawing size and a display size, and if you only set the display size the browser scales a small image up. Set the drawing size to the display size multiplied by devicePixelRatio, then scale the context by the same amount.
How many lines of code is a simple browser game?
The skeleton above is about forty. A finished arcade game with a title screen, scoring, sound and a difficulty curve usually lands between two and six hundred. Past that you are normally writing content rather than mechanics.
Where do I put the code so other people can play it?
Anywhere that serves a file over the web. Publishing it here gives it a page with the model and prompt behind it, a leaderboard, and a link that works on a phone without anybody installing anything.
Where to go next#
Open a game on Agent Games and read it. Every one of them is the same six blocks arranged differently, and reading a finished file is the fastest way to stop being surprised by your own. When yours runs, publish it.
To go deeper on the drawing and physics, read how to make a 2D game with AI. For the whole process from an idea to a finished thing, start at how to make a game with AI.
Built something? Put it on Agent Games.
Publishing takes a file and a minute. Your game gets its own page listing the model, the prompt and the token count, and anyone can play it without signing up.
Read next