Кликер можно собрать на Tkinter без игрового движка. За 15 секунд игрок набирает очки, а программа обновляет таймер, блокирует кнопку после финала и сохраняет лучший результат.
Главная задача кликера не в большой кнопке, а в управлении состоянием. До старта клики не считаются, во время раунда каждый клик добавляет одно очко, а после нуля кнопка отключается. Флаг running явно отделяет эти три фазы.
Обнуляем score, ставим 15 секунд и включаем кнопку.
Обработчик click добавляет очко только при running.
after вызывает tick каждую секунду, а в конце сохраняет best.
Из каких состояний состоит кликер
В окне нужны Label с очками, Label с остатком времени, большая кнопка клика и маленькая кнопка старта. Числа хранятся в обычных атрибутах класса, а StringVar используются только для подписей. Рекорд лежит в JSON рядом с файлом игры.
| Фаза | running | Кнопка |
|---|---|---|
| До старта | false | disabled |
| Раунд | true | normal |
| После 0 | false | disabled |
| Новый старт | true | normal |
После этого шага у каждой части есть одна роль. Интерфейс собирает действие, логика меняет состояние, а вывод показывает признак успеха. Это проще отлаживать, чем один большой обработчик.

Три фазы и один флаг running. Новый раунд можно начать сразу после нуля
Пишем окно, таймер и счётчик
Tkinter нельзя замораживать time.sleep, иначе окно перестанет рисоваться и принимать клики. Метод after ставит tick в очередь событий через 1000 мс. После каждого tick окно снова готово к клику.
import json
import tkinter as tk
from pathlib import Path
SAVE_FILE = Path("clicker_record.json")
def load_best():
try:
return int(json.loads(SAVE_FILE.read_text())["best"])
except (FileNotFoundError, KeyError, ValueError, json.JSONDecodeError):
return 0
class Clicker:
def __init__(self, root):
self.root = root
self.score = 0
self.seconds = 15
self.best = load_best()
self.running = False
self.info = tk.StringVar()
self.click = tk.Button(root, text="КЛИК!", font=("Arial", 28),
width=12, height=4, command=self.add_point,
state="disabled")
self.click.pack(padx=30, pady=20)
tk.Label(root, textvariable=self.info, font=("Arial", 16)).pack()
tk.Button(root, text="Начать 15 секунд",
command=self.start).pack(pady=16)
self.render()
def render(self):
self.info.set(f"Очки: {self.score} | Время: {self.seconds} | Рекорд: {self.best}")
def start(self):
if self.running: return
self.score, self.seconds, self.running = 0, 15, True
self.click.config(state="normal")
self.render(); self.root.after(1000, self.tick)
def add_point(self):
if self.running:
self.score += 1; self.render()
def tick(self):
if not self.running: return
self.seconds -= 1; self.render()
if self.seconds > 0:
self.root.after(1000, self.tick)
else:
self.running = False; self.click.config(state="disabled")
if self.score > self.best:
self.best = self.score
SAVE_FILE.write_text(json.dumps({"best": self.best}))
self.render()
root = tk.Tk(); root.title("Кликер")
Clicker(root); root.mainloop()after сохраняет отзывчивость окна. running не даёт запустить два таймера. Кнопка не считает очки до старта и после финала. Битый или отсутствующий JSON даёт безопасный рекорд 0.

Для GUI ответ один. Сравнение показывает, какой вариант легче поддерживать и проверять.
Как after обновляет игру без зависания
after не гарантирует идеальную точность до миллисекунды: обработчик попадёт в очередь не раньше указанного времени. Для учебного кликера этого достаточно. Для соревновательной игры считайте дедлайн по monotonic-времени, а не только число тиков.
| Событие | Меняет | Последствие |
|---|---|---|
| start | score, seconds, running | Новый раунд |
| add_point | score | Новая подпись |
| tick | seconds | Следующий after |
| Финал | running, best | Блокировка и JSON |
Сохраняйте в одном месте то, что может измениться. Если одно и то же значение скопировано в три функции, после первой правки они начнут расходиться. Один объект состояния и одна функция отрисовки снимают эту проблему.
Практика: проверяем раунд и рекорд
Таймер легко ломается двойным стартом. Если кнопку «Начать» нажать трижды, без running появятся три независимых цепочки after, и секунды полетят втрое быстрее. Проверьте этот сценарий, а затем испортите JSON вручную. Игра должна восстановиться с нулевым рекордом.
- Запустите clicker.py и нажмите большую кнопку до старта.
- Начните раунд и наберите не меньше 10 очков.
- Нажмите «Начать» ещё раз во время раунда.
- Дождитесь нуля и попробуйте добавить очко.
- Перезапустите файл и сверьте рекорд.
- Замените содержимое JSON на мусор и запустите игру.
Очки считаются только во время раунда. Таймер уменьшается раз в секунду и не ускоряется. Кнопка отключается на нуле. Рекорд переживает перезапуск и битый JSON не роняет игру.

До того, как добавлять улучшения. Пройдите все четыре сценария перед публикацией проекта.
Доводим кликер до готовой мини-игры
Когда базовый цикл работает, добавьте цену клика, улучшение или комбо-серию. Добавляйте по одному правилу и оставляйте один источник истины для score. Если кнопка и клавиша Space добавляют очки, оба события должны вызывать add_point, а не копировать её логику.
Окно зависнет. Используйте after.
Без running появятся несколько таймеров.
Отключайте Button и дополнительно проверяйте running.
Первый запуск или обрыв записи уронят программу.
Что получится в итоге

Готовая мини-игра показывает очки, 15-секундный таймер и рекорд, а большая кнопка работает только во время раунда.
Почему не time.sleep?
Он блокирует главный цикл Tkinter, и окно перестаёт отвечать.
Зачем running?
Флаг не даёт запустить два раунда и не считает клики вне игры.
Где хранится рекорд?
В маленьком JSON-файле рядом с игрой.
Как понять, что проект готов?
Он повторяет целевой сценарий несколько раз подряд, понятно реагирует на ошибочный ввод и не теряет состояние при обычных действиях.
Можно ли копировать код целиком?
Можно как стартовую точку, но сначала замените значения из примера, затем запустите проект по шагам. Если вы не можете объяснить одну строку, её лучше разобрать до следующего шага.
Классы, файлы и события можно закрепить в курсе Python. Если игра не находит JSON, сверьте случай со статьё про FileNotFoundError.
Сравните игровой цикл с крестиками-ноликами и камнем, ножницами, бумагой. В разделе «Практика» Кодика можно хранить каждую версию мини-игры отдельно.