Motion & Input

Still pictures were yesterday. Today the canvas gets a heartbeat — sixty frames a second — and you learn the one little number, dt, that makes motion smooth on every machine on Earth.

You will be able to…

The flip-book and the magic number

Animation is a flip-book: draw a picture, wait a sliver of a second, draw it again slightly different. Browsers flip roughly 60 pages per second. The Gamekit hands you the book via Anim.start — you give it a function, and that function gets called once per frame, forever, until Anim.stop ().

Each call receives one argument: dt, the number of seconds since the previous frame — about 0.016 at 60 fps. Why hand you that? Because frame rates lie. A gaming monitor flips 120 pages a second, a busy laptop maybe 45. If your square moves "5 pixels per frame", it moves twice as fast on the fancy monitor — same code, different game. The fix is one honest multiplication:

distance this frame = speed (pixels per second) × dt (seconds)

At 60 fps a speed of 300 px/s covers 300 × 1/60 = 5 px per frame; at 120 fps, 2.5 px per frame but twice as often. Same journey per second, everywhere, always. This is called frame-rate independence, and every serious game engine on the planet works this way.

A heartbeat on the canvas

Watch it breathe. This square glides right at 120 px/s and respawns on the left when it flies off the edge:

New machinery, honestly labelled: ref makes a mutable box — !x reads it, x := … overwrites it. Yes, after a lesson and a half of "names never change", we're changing one. A ref is the shortest path to motion today, so we take it — and in Phase 2 you'll learn the pure way, where the world isn't overwritten but flows, new world from old, every tick. Until then: refs stay small, and all the actual thinking goes into pure functions. Note the two conversions at the borders too: int_of_float turns our smooth float position into pixel coordinates, and Draw.clear () starts each frame fresh.

Now the actual work. The glide's maths — "position, speed, time in; new position out" — deserves to be a pure function: no refs, no drawing, just an answer. Pure maths is what the Check button can test, and in Phase 2 it's what entire games are made of.

Write move : float -> float -> float -> float, taking a position, a speed (px/s) and dt (seconds), and returning the new position. One line of frame-rate-independent physics.

Distance covered this frame is speed *. dt.
All floats, so dotted operators: pos +. speed *. dt.

Bouncing off the walls

Respawning at the edge is fine for screensavers; games bounce. Our square is 20 px wide on a 400-px canvas, so its x may roam from 0. (touching the left wall) to 380. (its right side touching the right wall — 400 minus 20). A bounce is a pure decision: given a position and a velocity, return the corrected pair. Past or on a wall? Clamp the position onto the wall and point the velocity back into the field — Float.abs vx forces it rightward, -. Float.abs vx leftward. Mid-air? Change nothing.

Why not just flip the sign? Because a square sitting exactly on a wall with an unlucky velocity would flip forever — trapped, vibrating. Pointing the velocity away from the wall can't be wrong twice. Wall-clamp logic like this guards every paddle, ball and camel you'll ever ship.

Write bounce : float -> float -> float * float. Given x and velocity vx, return the pair (x, vx) corrected for walls at 0. and 380.: touching or past a wall (x <= 0. or x >= 380.) means clamp x to that wall and send the velocity back into the field; otherwise return both unchanged.

Three cases: on/past the left wall (x <= 0.), on/past the right wall (x >= 380.), or free air.
A pair is built with parentheses and a comma: (0., Float.abs vx). Float.abs makes the speed point right; -. Float.abs vx makes it point left.
if x <= 0. then (0., Float.abs vx) else if x >= 380. then (380., -. Float.abs vx) else (x, vx)

Two tested, trustworthy functions. Time to put them on stage — the wiring below is written for you and calls your move and bounce every frame.

Bring in your move and bounce (replace the two TODO stand-ins). The provided Anim.start wiring does the rest. Check verifies the maths, then press Run to watch it bounce between the walls.

Copy your green-checked move and bounce over the TODO stand-ins — the wiring already calls them.
Square sails off-screen on Run? Your bounce isn't clamping. Snail-paced? Your move forgot *. dt's partner, speed.

Notice the shape of what you built: refs and drawing form a thin, boring shell, while every decision lives in pure, tested functions. Hold that thought — it's the architecture of everything to come.

Grab the controls

A game you can't steer is a movie. Input.is_down key answers true while a key is held, false otherwise — ask it fresh every frame. Keys go by their browser names: "ArrowLeft", "ArrowRight", letters like "a", and " " for the space bar.

Checkpoint quiz

Loot collected

That's Phase 1 cleared — engine lit, pixels painted, HUD styled, square bouncing. In Phase 2 the refs go away: worlds become values, and your game state gets a shape of its own. Onward.