Frogger is a classic arcade game where you guide a frog across busy roads and dangerous rivers to reach safety on the other side. The rules are simple but the gameplay is addictive — dodge cars, ride logs, and reach the goal before time runs out. In this first part of a 6-part series, we cover the fundamentals: the grid and lane system, drawing the frog, handling keyboard input, rendering different lane types, and building the game loop.
Frogger is built on a grid where each row is a lane. Lanes have types: safe zones (grass), roads (cars), water (logs), and the goal row at the top. We define the grid dimensions and a lane map that describes each row.
const COLS = 13; // 13 columns wide
const ROWS = 13; // 13 rows tall
const CELL = 40; // each cell is 40px
const WIDTH = COLS * CELL; // 520px
const HEIGHT = ROWS * CELL; // 520px
const canvas = document.getElementById('game');
canvas.width = WIDTH;
canvas.height = HEIGHT;
const ctx = canvas.getContext('2d');
// Lane types: 'goal', 'water', 'safe', 'road'
const lanes = [
{ type: 'goal' }, // row 0 — top
{ type: 'water' }, // row 1
{ type: 'water' }, // row 2
{ type: 'water' }, // row 3
{ type: 'water' }, // row 4
{ type: 'water' }, // row 5
{ type: 'safe' }, // row 6 — middle safe zone
{ type: 'road' }, // row 7
{ type: 'road' }, // row 8
{ type: 'road' }, // row 9
{ type: 'road' }, // row 10
{ type: 'road' }, // row 11
{ type: 'safe' }, // row 12 — start (bottom)
];
The frog is a simple colored square on the grid. We track its position in grid coordinates (column and row) and draw it as a green rectangle with a slightly smaller size to create a visual gap between cells.
let frog = {
col: Math.floor(COLS / 2), // start center
row: ROWS - 1 // bottom row
};
function drawFrog() {
const x = frog.col * CELL;
const y = frog.row * CELL;
// Body
ctx.fillStyle = '#2ecc40';
ctx.fillRect(x + 3, y + 3, CELL - 6, CELL - 6);
// Eyes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(x + 12, y + 12, 4, 0, Math.PI * 2);
ctx.arc(x + CELL - 12, y + 12, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#111';
ctx.beginPath();
ctx.arc(x + 12, y + 12, 2, 0, Math.PI * 2);
ctx.arc(x + CELL - 12, y + 12, 2, 0, Math.PI * 2);
ctx.fill();
}
The frog is drawn with a 3px inset from the cell edges so it does not touch the grid borders. Two small white circles with black pupils give it a recognizable face.
Frogger uses tile-based movement — one key press moves the frog exactly one cell. We listen for arrow keys and update the frog's grid position, clamping to stay within bounds.
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowUp':
if (frog.row > 0) frog.row--;
break;
case 'ArrowDown':
if (frog.row < ROWS - 1) frog.row++;
break;
case 'ArrowLeft':
if (frog.col > 0) frog.col--;
break;
case 'ArrowRight':
if (frog.col < COLS - 1) frog.col++;
break;
}
});
Each lane type has a distinct color so the player can instantly tell what is safe and what is dangerous. We map lane types to colors and draw each row as a filled rectangle.
const LANE_COLORS = {
goal: '#f1c40f', // yellow — goal zone
water: '#2980b9', // blue — water (danger)
safe: '#27ae60', // green — grass (safe)
road: '#555' // gray — road (danger)
};
function drawLanes() {
lanes.forEach((lane, row) => {
ctx.fillStyle = LANE_COLORS[lane.type];
ctx.fillRect(0, row * CELL, WIDTH, CELL);
});
}
Drawing lanes first creates the background. Everything else — obstacles, logs, the frog — is drawn on top. The color coding is intuitive: green is safe, gray and blue are dangerous, yellow is the goal.
We use requestAnimationFrame for smooth rendering. The game loop clears the canvas, draws lanes, draws the frog, and repeats. Later we will add obstacles and collision detection inside this loop.
let lastTime = 0;
function gameLoop(timestamp) {
const delta = timestamp - lastTime;
lastTime = timestamp;
// Clear
ctx.clearRect(0, 0, WIDTH, HEIGHT);
// Draw background lanes
drawLanes();
// Draw frog on top
drawFrog();
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
setInterval, requestAnimationFrame syncs with the browser's refresh rate (usually 60fps). It also pauses automatically when the tab is not visible, saving CPU and battery. The delta value tells you how much time passed since the last frame — essential for smooth obstacle movement in Part 2.
Plain colored rectangles work, but adding simple decorations makes the game look much better. We draw dashed lane markings on roads and subtle wave lines on water.
function drawLaneDetails() {
lanes.forEach((lane, row) => {
const y = row * CELL;
if (lane.type === 'road') {
// Dashed center line
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.setLineDash([10, 10]);
ctx.beginPath();
ctx.moveTo(0, y + CELL / 2);
ctx.lineTo(WIDTH, y + CELL / 2);
ctx.stroke();
ctx.setLineDash([]);
}
if (lane.type === 'safe') {
// Grass tufts
ctx.fillStyle = '#1e8449';
for (let c = 0; c < COLS; c += 2) {
ctx.fillRect(
c * CELL + 8, y + CELL - 8, 6, 6
);
}
}
if (lane.type === 'goal') {
// Goal markers (lily pads)
ctx.fillStyle = '#27ae60';
for (let c = 1; c < COLS; c += 3) {
ctx.beginPath();
ctx.arc(
c * CELL + CELL / 2,
y + CELL / 2,
CELL / 3, 0, Math.PI * 2
);
ctx.fill();
}
}
});
}
Call drawLaneDetails() right after drawLanes() in the game loop. The dashed lines on roads mimic real road markings. Grass tufts add texture to safe zones. Green circles on the goal row represent lily pads where the frog needs to land.
Here is the complete game loop with all the drawing functions in the correct order:
function gameLoop(timestamp) {
const delta = timestamp - lastTime;
lastTime = timestamp;
ctx.clearRect(0, 0, WIDTH, HEIGHT);
drawLanes(); // 1. background colors
drawLaneDetails(); // 2. road lines, grass, goals
drawFrog(); // 3. frog on top
requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
The draw order matters: lanes first (background), then decorations, then the frog on top. In Part 2 we will add obstacles and logs between the lane details and the frog.
Here is how the pieces fit together so far:
Right now the frog can move around a colorful grid, but there is nothing to dodge or ride. In the next part, we add the core Frogger mechanics: cars driving across roads and logs floating on water.
Continue to Part 2: Traffic & Logs where we add moving obstacles and rideable platforms.