An Idle Clicker
Learning Objectives
- You can build a complete idle-clicker game in Pyxel.
- You can hold a game’s whole state in a few instance variables.
- You can use
pyxel.frame_countto make something happen on a timer. - You can implement a simple “spend a resource to buy an upgrade” mechanic.
The first game in this part has no movement, no collisions, and nothing on the screen but numbers. It is an idle clicker — a game where a number goes up, partly because you press a button and partly on its own. Cookie Clicker is the famous example; ours will bake cookies too.
We start here because it strips the game loop down to its bones. There is nothing to draw but text, nothing to move, no physics. What is left is the pure skeleton: state lives in instance variables, the update step changes that state, the draw step shows it. Once that is solid, every later game just adds more state and more interesting update logic on top of the same frame.
The Plan
The whole game is one idea: the player has some cookies, and the number of cookies goes up. It goes up by one each time the player presses space (baking a cookie by hand), and it goes up on its own once per second (from the bakers they have hired). The player can spend cookies to hire another baker, which makes the per-second number larger.
So the state is a few numbers:
cookies— how many the player has now,per_click— how many a single space-press adds (we will keep this at 1),per_second— how many are added automatically each second,- and that is almost it.
Everything the game does is some operation on those numbers. This is the simplest kind of state there is: just a handful of integers.
A First Version: Clicking
Start with just the manual clicking and the display:
import pyxel
class App: def __init__(self): self.cookies = 0 pyxel.init(160, 120, title="Cookie Clicker") pyxel.run(self.update, self.draw)
def update(self): if pyxel.btnp(pyxel.KEY_SPACE): self.cookies = self.cookies + 1
def draw(self): pyxel.cls(1) pyxel.text(10, 10, f"Cookies: {self.cookies}", 7) pyxel.text(10, 100, "SPACE to bake", 13)
if __name__ == "__main__": App()This is already a complete, if dull, game. Run it and tap space, and the count climbs. Notice the use of btnp, not btn: we want one cookie per press, not one per frame the key is held. With btn, holding space would add thirty cookies a second, which is not the game we are making.
Showing text is done with pyxel.text(x, y, string, color). The first two arguments are the pixel coordinates of the top-left corner of the text. The third is a string, which can be an f-string to show numbers. The last is a color number from 0 to 15, which you can see in Pyxel’s Color Palette. The actual text is created with f-strings, just like you’ve done since Getting Started with Programming.
Adding the Idle Part
The “idle” in idle clicker means the number goes up even when you are not touching anything. We want per_second cookies added automatically every second. At 30 frames per second, “every second” means “every 30th frame”, which is exactly the frame_count timer from the previous chapter:
def update(self): if pyxel.btnp(pyxel.KEY_SPACE): self.cookies = self.cookies + 1
if pyxel.frame_count % 30 == 0: self.cookies = self.cookies + self.per_secondFor this to work, per_second has to exist, so add it to __init__:
def __init__(self): self.cookies = 0 self.per_second = 0 pyxel.init(160, 120, title="Cookie Clicker") pyxel.run(self.update, self.draw)It starts at 0, so at first nothing happens automatically — there are no bakers yet. The next step is to let the player hire some.
Spending Cookies on Upgrades
An upgrade is a trade: spend some cookies now, get a higher per_second forever. We let the player press U to hire a baker, which costs 10 cookies and adds 1 to per_second:
def update(self): if pyxel.btnp(pyxel.KEY_SPACE): self.cookies = self.cookies + 1
if pyxel.frame_count % 30 == 0: self.cookies = self.cookies + self.per_second
if pyxel.btnp(pyxel.KEY_U) and self.cookies >= 10: self.cookies = self.cookies - 10 self.per_second = self.per_second + 1The condition self.cookies >= 10 is the important part. Without it, a player with 3 cookies could still buy an upgrade and end up with -7 cookies. The check uses the same habit as Exceptions and Input Validation: before performing an action that requires a resource, confirm the resource is there.
The Complete Game
Putting it together, with a fuller display:
import pyxel
class App: def __init__(self): self.cookies = 0 self.per_second = 0 pyxel.init(160, 120, title="Cookie Clicker") pyxel.run(self.update, self.draw)
def update(self): # bake one by hand if pyxel.btnp(pyxel.KEY_SPACE): self.cookies = self.cookies + 1
# the bakers produce, once per second if pyxel.frame_count % 30 == 0: self.cookies = self.cookies + self.per_second
# hire a baker if you can afford it if pyxel.btnp(pyxel.KEY_U) and self.cookies >= 10: self.cookies = self.cookies - 10 self.per_second = self.per_second + 1
def draw(self): pyxel.cls(1) pyxel.text(10, 10, f"Cookies: {self.cookies}", 7) pyxel.text(10, 25, f"Per second: {self.per_second}", 6) pyxel.text(10, 95, "SPACE bake a cookie", 13) pyxel.text(10, 105, "U hire a baker (10)", 13)
if __name__ == "__main__": App()Twenty-odd lines, and it is a real game with a real loop: an early-game grind of tapping space, a turning point when you can afford your first baker, and the satisfying moment when the per-second number takes over and the cookies climb on their own. That loop — spend effort, buy something that reduces the effort, repeat — is the heart of a whole genre, and you have just built the smallest possible version of it.
What did the game loop give us here? The update/draw split, the frame_count timer, and a few integers of state. Every game from here on adds more, but none of it is more fundamental than this.
Idle clicker with classes
0 / 40 points
Refactor the idle clicker game from the chapter into a class-based Pyxel program.
The game should still work like the chapter version:
- pressing space bakes cookies by hand,
- pressing
Uhires a baker when the player can afford one, - bakers add cookies automatically once per second,
- and the screen shows the cookie count, production rate, and controls.
Game rules to implement:
- the player starts with
0cookies, - each manual bake adds
1cookie, - one baker costs
10cookies, - hiring a baker succeeds only when the player has at least
10cookies, - the baker cost stays
10; it does not increase after hiring, - each baker produces
1cookie per second, - one second is
30frames, so automatic cookies are added whenframe_count % 30 == 0, - and failed purchases must leave both the cookie count and baker count unchanged.
The important change is the internal design. The game mechanics must live in model classes that can be tested without opening a Pyxel window. The Pyxel App should only translate input into method calls and draw the current model state.
Required classes:
| Class | Contract |
|---|---|
Player(cookies=0, per_click=1) | stores cookies and per_click |
Player.bake(self) | adds per_click cookies |
Player.earn(self, amount) | adds amount cookies |
Player.spend(self, amount) | subtracts cookies and returns True when affordable; otherwise returns False and leaves cookies unchanged |
BakerCrew(count=0, cost=10) | stores baker count and hire cost |
BakerCrew.per_second(self) | returns one cookie per second for each baker |
BakerCrew.hire(self, player) | spends cost cookies from player; if successful, increases count and returns True |
ClickerGame(player=None, bakers=None) | owns self.player and self.bakers |
ClickerGame.manual_bake(self) | bakes once by calling the player object |
ClickerGame.hire_baker(self) | tries to hire one baker |
ClickerGame.tick(self, frame_count) | adds automatic cookies only when frame_count is divisible by 30 |
App | owns one ClickerGame, handles Pyxel input, and draws the game |
Pyxel integration requirements:
App.update()callsgame.manual_bake()whenpyxel.btnp(pyxel.KEY_SPACE)is true,App.update()callsgame.hire_baker()whenpyxel.btnp(pyxel.KEY_U)is true,App.update()callsgame.tick(pyxel.frame_count)every frame,App.draw()reads values fromgame.playerandgame.bakers,- do not call
pyxelfromPlayer,BakerCrew, orClickerGame, - do not start the app when the file is imported,
- and guard the launch with
if __name__ == "__main__": App().
You may start from the chapter code, but your final submission must use these classes rather than keeping all state directly on App.
Check Your Understanding
- Why does the manual-bake action use
pyxel.btnprather thanpyxel.btn? - The automatic cookies are added when
pyxel.frame_count % 30 == 0. What would change if you used% 15instead?% 60? - Why is the condition
self.cookies >= 10necessary on the upgrade? What state could the game reach without it? - The game’s entire state is two integers. Which part of the program reads them, and which part changes them?
- Where in the program would you add a second kind of upgrade — say, one that makes each hand-bake worth 2 cookies instead of 1?
AI Study Aid
Create a chapter diagram
We're looking into to what extent AI-generated diagrams could help with studying.
Use this study aid to generate an AI-generated visual summary of the material you just studied.
Each diagram style emphasizes the content in a different way, so you can choose the focus
that feels most useful.
Using the diagram generator is voluntary and does not affect your course progress. We encourage
you to try it out and see if it helps, but it's totally up to you! Your answers help us understand
how to make better study aids in the future.