Games and Programming in the Wild

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_count to 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_second

For 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 + 1

The 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 U hires 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 0 cookies,
  • each manual bake adds 1 cookie,
  • one baker costs 10 cookies,
  • hiring a baker succeeds only when the player has at least 10 cookies,
  • the baker cost stays 10; it does not increase after hiring,
  • each baker produces 1 cookie per second,
  • one second is 30 frames, so automatic cookies are added when frame_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:

ClassContract
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
Appowns one ClickerGame, handles Pyxel input, and draws the game

Pyxel integration requirements:

  • App.update() calls game.manual_bake() when pyxel.btnp(pyxel.KEY_SPACE) is true,
  • App.update() calls game.hire_baker() when pyxel.btnp(pyxel.KEY_U) is true,
  • App.update() calls game.tick(pyxel.frame_count) every frame,
  • App.draw() reads values from game.player and game.bakers,
  • do not call pyxel from Player, BakerCrew, or ClickerGame,
  • 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

  1. Why does the manual-bake action use pyxel.btnp rather than pyxel.btn?
  2. The automatic cookies are added when pyxel.frame_count % 30 == 0. What would change if you used % 15 instead? % 60?
  3. Why is the condition self.cookies >= 10 necessary on the upgrade? What state could the game reach without it?
  4. The game’s entire state is two integers. Which part of the program reads them, and which part changes them?
  5. 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.

Diagrams tried: 0