You Might Also Like
Tank Arena Game — Free HTML CSS JS Snippet
Tank Arena Game · Games · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Tank Arena — Top-Down Combat with a 4-Way D-Pad, Enemy AI and Destructible Cover

A top-down tank game is a compact showcase of grid-based movement, tile collision, simple enemy AI, and a directional shooting system — and it maps perfectly onto touch, because a four-way D-pad plus a fire button is exactly the control set an arcade tank game needs. This snippet builds the whole thing on a single HTML5 <canvas>: you drive with an on-screen cross D-pad, tap FIRE to shoot in the direction you are facing, and battle waves of AI tanks across an arena of solid and destructible walls.
Tile arena with two kinds of wall
The arena is generated on a tile grid in buildWalls(). The border is *solid* (indestructible), while a scattered set of interior blocks is *destructible* with two hit points. Every wall is stored as a rectangle, and both movement and bullets test against that same wall list. Because destructible blocks are removed from the list once their hit points reach zero, cover genuinely erodes over a match — you can shoot your way through a wall to reach an enemy, and so can they.
Axis-separated movement so tanks slide along walls
Movement is resolved one axis at a time in moveTank(): the horizontal move is applied only if the destination doesn't collide, then the vertical move is checked independently. This axis-separation is what lets a tank slide along a wall it's pressed against instead of sticking — a small but important detail for a game where you're constantly threading between blocks. The same canMove() test also blocks tanks from driving through each other, so the arena stays physically consistent for both the player and the AI.
Directional firing on a cooldown
A tank always faces its last movement direction, stored as one of four unit vectors. shoot() spawns a bullet travelling along that vector from the barrel tip, so what you see (the barrel) is exactly where the shot goes. The player's fire is gated by a FIRE_CD timestamp cooldown so holding the FIRE button produces a controlled rate of fire rather than a stream, and each enemy carries its own independent fire timer.
Enemy AI and wave progression
Each enemy periodically re-picks a direction in update() — biased toward the player's current position but with a random component so they don't move in lockstep — and fires on its own cooldown. Player bullets destroy enemies for points; enemy bullets cost the player a life and respawn them at the start corner. Clearing every enemy advances the wave, which spawns more tanks, and the whole simulation runs on a fixed 1/60-second timestep so behaviour is identical across refresh rates. The best score persists to localStorage.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet's HTML, CSS and JS into an AI assistant like Claude and ask it to explain the axis-separated wall collision, the destructible-wall hit-point system, and the bias-plus-random enemy AI. It is a strong base to extend: ask for grid A* pathfinding so enemies route around walls, power-ups (rapid fire, shield, speed), a destructible home base you must protect, smarter aim that only fires when it has line of sight, or local two-player co-op on one keyboard. You could also ask it to add rotating turrets independent of the tank body, or to port the fixed-timestep loop into a React component with useRef and useEffect. Treat it as a working prototype to question and rebuild.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a mobile-friendly top-down tank battle game on an HTML5 canvas in plain HTML, CSS and JavaScript — no frameworks.
Requirements:
- A player tank on a tile-based arena that drives in four directions and shoots in its facing direction. Provide an on-screen four-way cross D-pad plus a FIRE button for touch, AND keyboard controls (arrows/WASD + Space). Both drive the same shared input state read each simulation step.
- Use Pointer Events for the buttons with press-and-hold, releasing on pointerup/pointercancel/pointerleave. Run the simulation on a fixed 1/60-second timestep so behaviour is frame-rate-independent.
- Generate the arena with indestructible border walls and scattered destructible cover blocks that take two hits before breaking. Store walls as rectangles and test both movement and bullets against them; remove destroyed blocks from the world.
- Resolve tank movement one axis at a time so tanks slide along walls instead of sticking, and prevent tanks from driving through each other.
- Bullets launch from the barrel along the tank facing vector; rate-limit the player fire with a timestamp cooldown. Add enemy tanks with simple AI that biases movement toward the player with a random component and fires on independent cooldowns.
- Player bullets destroy enemies for points; enemy bullets cost the player one of three lives and respawn them at a corner. Clearing all enemies advances the wave and spawns more. Persist the high score in localStorage with defensive try/catch, and show start / game-over overlays with particle burst effects.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1DeployTap "Deploy" to call startGame(), which builds the walled arena via buildWalls(), spawns the first wave of enemy tanks, and starts the fixed-timestep loop.
- 2Drive with the D-padHold the ▲ ◀ ▶ ▼ buttons (or arrow keys / WASD) to move. Your tank turns to face the direction it drives; movement is resolved per-axis so you slide along walls instead of sticking to them.
- 3Fire in your facing directionTap or hold FIRE (or Space) to shoot. The shell travels straight out of the barrel in the direction your tank is facing, rate-limited by a cooldown so holding gives controlled fire, not a stream.
- 4Use — and destroy — coverGrey border walls are indestructible; brown interior blocks take two hits and then break apart. Use them to block incoming fire, or blast through them to open a firing lane on an enemy.
- 5Survive the AIEnemy tanks hunt toward your position and fire on their own timers. An enemy shell costs one of your three lives and respawns you at the corner; losing all three ends the run.
- 6Clear waves and beat your bestDestroying every enemy advances the wave and spawns more tanks. Your high score persists in localStorage under tank-arena-best. Tune TANK/SPEED, fire cooldowns, and the wall layout in the JS.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Movement is resolved one axis at a time in moveTank(): the horizontal step is applied only if the new x position is clear, then the vertical step is checked separately. When you drive diagonally into a wall, the blocked axis is simply skipped while the free axis still moves, so the tank slides along the surface rather than stopping dead at the corner.
Each wall stores hit points. Border walls are solid with infinite hit points; interior cover blocks start with two. When a bullet overlaps a destructible wall its hit points drop and a spark burst plays, and once they reach zero the wall is flagged dead and filtered out of the walls array — so it no longer blocks movement or shots. Both your bullets and enemy bullets erode cover the same way.
On a timer, each enemy re-picks a direction inside update(). Half the time it chooses the axis that points most directly at the player (compare the horizontal and vertical distance and move toward the player on the larger one); the other half it picks a random direction. That mix makes enemies pursue you without all converging on the exact same path, and each fires on its own independent cooldown.
Holding FIRE sets keys.fire = true, which is read every simulation step. Without a limit that would emit a bullet every step. A FIRE_CD timestamp check only spawns a shell once enough real milliseconds have passed since the last one, giving a controlled, consistent rate of fire regardless of frame rate.
Yes. The best score is stored in localStorage under tank-arena-best and read back on load, so it survives reloads and browser restarts on the same browser and origin. It resets only if site data is cleared, or if you play in a different browser, device, or a private window.