Codetoy — Python API Documentation
Codetoy runs Python via Pyodide in the browser. Your script is executed once, then the runtime calls your exported functions every frame and on input events.
The sample program below is a complete working sketch — a good starting point:
from codetoy.canvas import fill, rect, circle
from codetoy.screen import width, height, centerX, centerY
from codetoy.time import deltaTime
from codetoy._input import mouseX, mouseY
import math
t = 0
def update():
global t
t += deltaTime()
fill(20, 20, 30)
rect(0, 0, width(), height())
r = 60 + math.sin(t * 2) * 20
fill(100, 180, 255)
circle(centerX(), centerY(), r)
fill(255, 200, 50)
circle(mouseX(), mouseY(), 12)
How it works
When you run your script, the runtime:
- Executes the entire file top-to-bottom (module-level code runs once — good for setup, world generation, class definitions, etc.)
- Looks for functions with specific names in your global scope (
update,keyDown,mouseDown, etc.) and calls them at the appropriate times.
You never register callbacks. Just define the function at the top level with the right name and the runtime will find it.
Exported functions
These are the names the runtime looks for. Define any you need; omit the ones you don't.
def update() -> None
Called every frame. Put all drawing and game logic here.
from codetoy.canvas import fill, rect
from codetoy.screen import width, height
def update():
fill(30, 30, 40)
rect(0, 0, width(), height())
def resize(w: float, h: float) -> None
Called whenever the canvas is resized. Use this to cache width() / height() instead of calling them every frame.
from codetoy.screen import width, height
cx = width() / 2
cy = height() / 2
def resize(w, h):
global cx, cy
cx = w / 2
cy = h / 2
def keyDown(key: str) -> None
Called once when a key is pressed. key matches the MDN KeyboardEvent.key values: "a", "A", "ArrowLeft", " " (space), "Enter", etc.
Use this for actions that should fire once per press (jumping, toggling, slot selection). For movement that should continue while held, use isKeyDown() inside update() instead.
jumping = False
def keyDown(key):
global jumping
if key == " ":
jumping = True
if key == "r":
reset_game()
def keyUp(key: str) -> None
Called once when a key is released.
def keyUp(key):
if key == "Shift":
stop_sprinting()
def mouseDown(button: int) -> None
Called once when a mouse button is pressed. button: 0 = left, 1 = middle, 2 = right.
Use this for actions that should fire on click (not while held). For held-button logic, use isMouseDown() inside update().
just_clicked = False
def mouseDown(button):
global just_clicked
if button == 0:
just_clicked = True
def mouseUp(button: int) -> None
Called once when a mouse button is released.
def mouseUp(button):
if button == 0:
finish_drag()
def touchStart(id: int, x: float, y: float) -> None
Called when a finger touches the screen. id is a unique identifier for that touch point.
def touchStart(id, x, y):
spawn_particle(x, y)
def touchEnd(id: int) -> None
Called when a finger is lifted.
def touchEnd(id):
end_swipe(id)
def touchMove(id: int, x: float, y: float) -> None
Called when a finger moves on the screen.
def touchMove(id, x, y):
drag_to(id, x, y)
Imports
codetoy.canvas
from codetoy.canvas import fill, rect, circle # named imports
import codetoy.canvas as canvas # namespace import
codetoy.screen
from codetoy.screen import width, height, centerX, centerY
codetoy.time
from codetoy.time import deltaTime, elapsedTime
codetoy._input
from codetoy._input import mouseX, mouseY, isMouseDown, isKeyDown
API Reference
codetoy.canvas
Color state
fill(r, g, b, a=1.0)
Sets the fill color for subsequent drawing. r, g, b are 0–255; a is 0.0–1.0.
fill(255, 100, 0) # opaque orange
fill(0, 150, 255, 0.5) # semi-transparent blue
stroke(r, g, b, a=1.0)
Sets the stroke (outline) color.
stroke(255, 255, 255)
noFill() / noStroke()
Disables fill or stroke for subsequent shapes.
noStroke()
noFill()
fillStyle(color: str) / strokeStyle(color: str)
Sets fill/stroke using any CSS color string.
fillStyle("#ff6600")
fillStyle("hsl(200, 80%, 60%)")
strokeStyle("rgba(255,0,0,0.5)")
Line state
lineWidth(width: float)
Sets the line/stroke thickness in pixels.
lineWidth(3)
lineJoin(join: str)
"round", "bevel", or "miter".
lineCap(cap: str)
"butt", "round", or "square".
lineMiterLimit(limit: float)
Shape drawing
rect(x, y, w, h, r=0)
Draws a rectangle. r is the corner radius.
fill(0, 150, 255)
rect(100, 100, 200, 150) # sharp corners
rect(100, 100, 200, 150, 12) # rounded corners
circle(x, y, radius)
Draws a circle at (x, y).
fill(255, 80, 80)
circle(400, 300, 50)
ellipse(x, y, w, h)
Draws an ellipse. w and h are the horizontal and vertical radii.
ellipse(centerX(), centerY(), 100, 40)
line(x1, y1, x2, y2)
Draws a line segment.
stroke(255, 255, 0)
lineWidth(2)
line(0, 0, 400, 300)
triangle(x1, y1, x2, y2, x3, y3)
Draws a filled triangle.
fill(100, 200, 100)
triangle(200, 50, 100, 200, 300, 200)
polygon(points: list)
Draws a filled polygon from a flat list of [x1, y1, x2, y2, ...] coordinates.
fill(180, 100, 255)
polygon([100, 50, 200, 50, 250, 150, 50, 150])
Note: Pass a plain Python list. The runtime converts it to JS automatically.
Text
font(family: str, size: float, weight: str = "normal")
Sets the font before drawing text. Call this before every text() call if the font may have changed.
font("Arial", 24, "bold")
font("Courier", 14)
text(content: str, x: float, y: float)
Draws text at (x, y) using the current font and fill color.
fill(255, 255, 255)
font("Arial", 18)
text("Score: 42", 10, 30)
measureText(content: str) -> float
Returns the pixel width of a string in the current font. Useful for centering.
font("Arial", 20, "bold")
label = "Game Over"
text(label, centerX() - measureText(label) / 2, centerY())
Transform
translate(x, y) / rotate(radians) / scale(x, y)
Standard 2D canvas transforms applied to subsequent drawing.
import math
translate(centerX(), centerY())
rotate(math.pi / 4)
rect(-25, -25, 50, 50) # drawn at center, rotated 45°
resetTransform()
resetTransform()
Resets to the identity transform.
push() / pop()
Save and restore the full canvas state (transform + color + line settings).
push()
translate(100, 100)
rotate(0.5)
fill(255, 0, 0)
rect(0, 0, 40, 40)
pop()
# back to previous state
reset()
Clears the entire screen and resets all canvas state.
Advanced path drawing
beginPath()
moveTo(100, 100)
lineTo(200, 100)
bezierCurveTo(250, 100, 250, 200, 200, 200)
lineTo(100, 200)
closePath()
fill(80, 160, 255)
fillPath()
stroke(255, 255, 255)
strokePath()
beginPath()— starts a new pathmoveTo(x, y)— moves the pen without drawinglineTo(x, y)— adds a straight line segmentbezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)— cubic bezierquadraticCurveTo(cpx, cpy, x, y)— quadratic bezierclosePath()— closes the path back to the startfillPath()— fills the current pathstrokePath()— strokes the current path
codetoy.screen
width() -> float / height() -> float
Current canvas dimensions in pixels.
centerX() -> float / centerY() -> float
Equivalent to width() / 2 and height() / 2.
frameCount() -> int
Total frames rendered since the program started.
fps() -> float
Current frames per second.
from codetoy.screen import fps, frameCount
from codetoy.canvas import fill, font, text
def update():
fill(255, 255, 255)
font("Arial", 12)
text(f"FPS: {fps():.0f} Frame: {frameCount()}", 8, 20)
codetoy.time
deltaTime() -> float
Seconds elapsed since the last frame. Always use this to make animations frame-rate independent.
from codetoy.time import deltaTime
x = 0.0
speed = 150.0 # pixels per second
def update():
global x
x += speed * deltaTime()
elapsedTime() -> float
Total seconds since the program started.
from codetoy.time import elapsedTime
import math
def update():
t = elapsedTime()
y = centerY() + math.sin(t * 3) * 80
codetoy._input
Mouse
mouseX() -> float / mouseY() -> float
Current mouse position in canvas coordinates.
isMouseDown(button: int) -> bool
Returns True while a mouse button is held. 0 = left, 1 = middle, 2 = right.
Use this inside update() for continuous held-button behavior (e.g. hold to paint). Use mouseDown() for one-shot click detection.
from codetoy._input import isMouseDown, mouseX, mouseY
from codetoy.canvas import fill, circle
def update():
if isMouseDown(0):
fill(255, 100, 0)
circle(mouseX(), mouseY(), 20)
Keyboard
isKeyDown(key: str) -> bool
Returns True while a key is held. Call inside update() for continuous movement.
from codetoy._input import isKeyDown
def update():
global player_x
if isKeyDown("ArrowLeft") or isKeyDown("a"):
player_x -= 200 * deltaTime()
if isKeyDown("ArrowRight") or isKeyDown("d"):
player_x += 200 * deltaTime()
Touch
isTouchDown(id: int = -1) -> bool
Returns True if any touch is active (no argument), or if a specific touch id is active.
touchCount() -> int
Number of active touch points.
touchX(id: int) -> float / touchY(id: int) -> float
Position of a touch point by its id. Returns -1 if the id is not active.
Patterns and gotchas
Global state
Python does not automatically write to globals from inside functions. Always declare global for any module-level variable you modify:
score = 0
def update():
global score # required — otherwise this creates a local variable
score += 1
One-shot vs. continuous input
| Situation | Use |
|---|---|
| Jump on keypress | def keyDown(key) |
| Move while key held | isKeyDown(key) in update() |
| Click to place block | def mouseDown(button) + a flag |
| Paint while mouse held | isMouseDown(0) in update() |
# One-shot click detection
clicked = False
def mouseDown(button):
global clicked
if button == 0:
clicked = True
def update():
global clicked
if clicked:
place_block()
clicked = False
Frame-rate independence
Never move things by a fixed amount per frame. Always multiply by deltaTime():
# Bad — speed depends on frame rate
x += 5
# Good — 300 pixels per second regardless of FPS
x += 300 * deltaTime()
Module-level setup
Code outside any function runs exactly once when your script loads. Use it for world generation, initial state, and class definitions:
from codetoy.canvas import fill, rect
from codetoy.screen import width, height
import random
# Runs once
tiles = [[random.randint(0, 3) for _ in range(20)] for _ in range(15)]
player_x = 100.0
player_y = 100.0
def update():
# Runs every frame
draw_tiles()
draw_player()
Example programs
Bouncing ball
from codetoy.canvas import fill, rect, circle
from codetoy.screen import width, height
from codetoy.time import deltaTime
x, y = 200.0, 150.0
vx, vy = 180.0, 220.0
def update():
global x, y, vx, vy
dt = deltaTime()
x += vx * dt
y += vy * dt
if x < 20 or x > width() - 20: vx = -vx
if y < 20 or y > height() - 20: vy = -vy
fill(20, 20, 30)
rect(0, 0, width(), height())
fill(100, 200, 255)
circle(x, y, 20)
Keyboard movement
from codetoy.canvas import fill, rect
from codetoy.screen import width, height, centerX, centerY
from codetoy.time import deltaTime
from codetoy._input import isKeyDown
px, py = centerX(), centerY()
def update():
global px, py
spd = 200 * deltaTime()
if isKeyDown("ArrowLeft") or isKeyDown("a"): px -= spd
if isKeyDown("ArrowRight") or isKeyDown("d"): px += spd
if isKeyDown("ArrowUp") or isKeyDown("w"): py -= spd
if isKeyDown("ArrowDown") or isKeyDown("s"): py += spd
fill(20, 20, 30)
rect(0, 0, width(), height())
fill(100, 220, 120)
rect(px - 16, py - 16, 32, 32, 4)
Click to spawn
from codetoy.canvas import fill, rect, circle
from codetoy.screen import width, height
import random
dots = []
spawn = False
def mouseDown(button):
global spawn
if button == 0:
spawn = True
def update():
global spawn
from codetoy._input import mouseX, mouseY
if spawn:
dots.append((mouseX(), mouseY(),
random.randint(6, 20),
(random.randint(100,255), random.randint(100,255), random.randint(100,255))))
spawn = False
fill(15, 15, 25)
rect(0, 0, width(), height())
for x, y, r, (dr, dg, db) in dots:
fill(dr, dg, db)
circle(x, y, r)
Animated sine wave
from codetoy.canvas import fill, rect, circle, stroke, lineWidth, beginPath, moveTo, lineTo, strokePath, noFill
from codetoy.screen import width, height
from codetoy.time import elapsedTime
import math
def update():
t = elapsedTime()
fill(15, 15, 30)
rect(0, 0, width(), height())
noFill()
stroke(100, 200, 255)
lineWidth(2)
beginPath()
steps = int(width())
for i in range(steps + 1):
x = float(i)
y = height() / 2 + math.sin(x * 0.03 + t * 3) * 80
if i == 0:
moveTo(x, y)
else:
lineTo(x, y)
strokePath()
Multi-touch paint
from codetoy.canvas import fill, rect, circle
from codetoy.screen import width, height
from codetoy._input import touchCount, touchX, touchY, isTouchDown
import random
touches = {} # id -> color
def touchStart(id, x, y):
touches[id] = (random.randint(100,255), random.randint(100,255), random.randint(100,255))
def touchEnd(id):
touches.pop(id, None)
def update():
fill(20, 20, 30, 0.15) # trail fade
rect(0, 0, width(), height())
for id, (r, g, b) in list(touches.items()):
if isTouchDown(id):
fill(r, g, b)
circle(touchX(id), touchY(id), 24)