WeBlog · Games
Using AI to write Python games is fine. Knowing the functions is what makes it work.
Two programs below draw the same picture. Both are correct Python, and an AI assistant could easily write either one. One of them slows to 2 frames a second. The other draws a quarter of a million dots without slowing down. Telling them apart doesn't take a better AI. It takes knowing what the functions do.
Yes, you can use AI to help you code
Lots of people now write code with an AI assistant like ChatGPT, Claude or Copilot open beside them. Professional programmers do it every day. It's quick at the boring parts, it's good at explaining an error message, and it can get you from a blank page to something that runs. That's a fair way to use a tool.
But the AI doesn't know what you're trying to make, and it doesn't have to live with the result. It writes code that looks right. Whether it is right for your game is up to you, and you can only judge that if you understand two things:
- What the code does, line by line. If you can't explain it, you can't fix it when it breaks.
- What the library can do. You need to know which tools exist, so you can ask for the right one and notice when you get the wrong one.
The second one is easy to skip, and it's what this post is about. Here's an example where it makes all the difference.
At school? Your teacher decides when AI tools are allowed, and for an assessment the answer may well be no. Check first.
One triangle, two programs
The Sierpinski triangle is a famous fractal: a triangle made of three smaller triangles, each made of three smaller ones, forever. There's a surprising way to draw it, called the chaos game:
- Pick one of the triangle's three corners at random.
- Move halfway from where you are towards that corner.
- Put a dot there. Go back to step 1.
It feels like it should make a random mess. It doesn't. After a few thousand dots the triangle appears, holes and all. The colour of each dot shows which corner it just jumped towards.
Both programs below do exactly this, 20 or 200 dots each frame, with a label at the top that counts the dots and measures the frames per second (fps) using time.time(). Apart from how many dots they draw each frame, the difference is one line inside the loop.
Version A: a sprite for every dot
import game
import random
import time
game.window(480, 360, background="#1b1030")
corners = [(240, 44), (30, 340), (450, 340)]
colours = ["#ff4d6d", "#ffd43b", "#4dabf7"]
x, y = 240, 200
info = game.label("counting...", 240, 18, 16, "#ffffff", "#000000")
dots = 0
frames = 0
start = time.time()
while game.playing():
for i in range(20):
c = random.randint(0, 2)
x = (x + corners[c][0]) / 2
y = (y + corners[c][1]) / 2
game.box(x, y, 2, 2, colours[c]) # a new sprite for every dot
dots = dots + 1
frames = frames + 1
if time.time() - start >= 1:
fps = frames / (time.time() - start)
info.content = str(dots) + " sprites, " + str(round(fps)) + " fps"
frames = 0
start = time.time()
game.frame()
Open in the Playground Opens with this code already in the editor.
game.box() makes a sprite: a plain coloured rectangle, here 2 pixels square. game.sprite(), game.circle() and game.label() all make sprites too, and they all behave the same way. A sprite is something you can move, spin, check for collisions or remove later, so the game keeps track of every one.
Version B: ink with game.plot()
import game
import random
import time
game.window(480, 360, background="#1b1030")
corners = [(240, 44), (30, 340), (450, 340)]
colours = ["#ff4d6d", "#ffd43b", "#4dabf7"]
x, y = 240, 200
info = game.label("counting...", 240, 18, 16, "#ffffff", "#000000")
dots = 0
frames = 0
start = time.time()
while game.playing():
for i in range(200):
c = random.randint(0, 2)
x = (x + corners[c][0]) / 2
y = (y + corners[c][1]) / 2
game.plot(x, y, colours[c]) # a dot of ink, stamped once
dots = dots + 1
frames = frames + 1
if time.time() - start >= 1:
fps = frames / (time.time() - start)
info.content = str(dots) + " dots, " + str(round(fps)) + " fps"
frames = 0
start = time.time()
game.frame()
Open in the Playground Opens with this code already in the editor.
game.plot(x, y, color) stamps a dot of ink onto the window. Ink isn't a sprite. You can't move it or check it with touches(). It just stays where you put it, like paint.
The numbers
We left both programs running and wrote down what their labels said:
- Version A (sprites): 19 fps at 400 sprites, 8 to 9 fps at 1,000, 4 fps at 3,000, and 2 fps at 5,000. By then the window updates twice a second, and it keeps getting worse.
- Version B (plot): 21 fps at 4,200 dots, 22 fps at 52,000 dots, and 20 fps at 251,000 dots. Flat the whole way.
These were measured in Microsoft Edge on a fast desktop computer. A school laptop or a phone will give lower numbers, and version A will hit the wall sooner, but the shape of the story is the same everywhere.
Why 21 and not 30? game.frame() aims for 30 frames a second by waiting about a thirtieth of a second after each frame. Version B does 200 dots of maths before that wait, so it settles at about 21. What matters is that the number stays put while the dots pile up.
Why one crawls and the other doesn't
Sprites are made to move, so the game redraws them from scratch every frame. Each time game.frame() runs, it sends every sprite (its position, size, colour, angle and more) from Python to the page, which wipes the window and draws them all again. Version A adds 20 sprites a frame and never removes any, so every frame has more work than the one before. At 5,000 sprites, each frame redraws 5,000 rectangles that haven't moved at all.
Ink is stamped once onto its own layer, and that layer is never wiped. Each frame only sends the dots that are new, and after that they're just pixels. Dot number 251,000 costs exactly as much as dot number 1. That's why version B can draw ten times as many dots per frame and still win easily.
Neither program is wrong. There's no error message, and both make a triangle. If you asked an AI for "a Sierpinski triangle with the PyWebLib game library", you could get either one. The person who knows that game.plot() exists, and what it's for, is the one who can ask for it, or spot the problem and fix it in one line.
It works the other way too. Ink is the wrong tool for a player, an enemy or a coin, because those need to move, collide and disappear. Things that move or interact are sprites. Pictures that build up are ink. Knowing that rule is worth more than any single program.
Read the docs, sometimes
Nobody reads documentation for fun, and you don't need to read all of it. But when you're about to use a function for the first time, or something is slow or acting strangely, five minutes on its page can save you an hour of guessing.
The game.plot() page has a section called Why not just use sprites? It explains everything in this post in four short paragraphs, and it was sitting there before anyone wrote a line of the slow version.
Tip for using an AI assistant: AI tools often guess that import game means pygame, and write code that doesn't work here. PyWebLib has a plain-text version of its whole reference made for AI assistants to read: play.pyweblib.org/llms-full.txt. Paste that link into your chat (or the text itself, if your assistant can't open links) and ask it to use PyWebLib's real functions. Its answers get much better.
A leaderboard in one line
Here's another feature that's easy to miss if you don't know it exists. Every published game can have its own leaderboard, and you don't have to build it. You write one line:
if frames == 0:
game.submit_score(score) # onto the leaderboard
game.game_over("Time's up! Score: " + str(score), retry=True)
Publish the game, and if its code contains game.submit_score, its page in the gallery gets a scoreboard with the top 10 players, one line each with their best score. In the Playground the line quietly does nothing, so you can test as much as you like without filling the board with practice runs.
An AI assistant that doesn't know PyWebLib might build you a whole scoreboard by hand, or tell you that you need a server and a database. You don't. The game.submit_score() page has a full example game, and the reasons a board might not show up (the most common: writing submit_score(score) without the game. in front).
The functions worth knowing
You don't need to memorise the library. You need to know these exist, so you know what to ask for. Each one has its own page with examples:
game.window(): open the game window. Always first.game.sprite(): a character, enemy or object you can move around.game.frame()andgame.playing(): the game loop. Draw a frame, wait, repeat.game.pressed(): is a key being held down?a.touches(b): are two sprites overlapping? Collisions in one line.game.label(): words on the screen, like a score or a timer.game.button(): a real clickable button, for menus and "Play again".game.clicked(): true once per click or tap, so it works on phones too.game.game_over(): end the game with a message, and let players tap to try again.game.sound(): ten built-in arcade sounds, no files needed.game.save()andgame.load(): remember coins, levels or high scores between runs.sprite.anchor: the point a sprite stands on and spins around, for feet on the ground and swinging hammers.game.submit_score(): your game's own leaderboard, in one line.game.plot(): ink that stays, for fractals, trails and paintings. Hundreds of thousands of dots at full speed.
The game library guide puts them all together in one game, step by step.
Use the tools. Know the tools.
Open the slow version, change one line to game.plot(), and watch it fly. Then make something of your own, with or without an AI beside you, and share it to the gallery.