Перейти к основному содержимому

Игра "Прыг-скок" на Python

Родителям и детям
Откуда материал

Проект из глав 13–14 книги Джейсона Бриггса "Python для детей" (пер. С. Ломакина, 2017). Перед стартом полезны Программа на Python, Классы и объекты и базовые циклы.

Перед стартом

Установите Python 3 с галочкой Add Python to PATH. Редактор — IDLE или VS Code. Онлайн-интерпретатор Trinket подходит для простых скриптов; для tkinter нужен локальный Python.

"Прыг-скок!" — первая полноценная игра в самоучителе Бриггса. Красный мяч отскакивает от стен, синяя ракетка двигается стрелками ← →. Если мяч упал ниже ракетки — игра останавливается.

tkinter — стандартная библиотека Python для окон. Canvas (холст) — прямоугольная область, на которой рисуют фигуры и двигают их каждый кадр.


Идеи, которые Вы освоите

ИдеяГде встречается в коде
Окно и холстTk(), Canvas(...)
Классы Ball и Paddleмяч и ракетка как объекты
Игровой циклwhile True + update + sleep
Столкновенияметод hit_paddle, функция coords()
Клавиатураbind_all для стрелок

Шаг 1. Холст и окно

Создайте файл pryg_skok.py:

from tkinter import *
import random
import time

tk = Tk()
tk.title("Прыг-скок!")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)

canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
tk.update()

Параметры окна:

  • resizable(0, 0) — нельзя растянуть мышью
  • -topmost — окно поверх других (удобно при отладке)
  • bd=0, highlightthickness=0 — без рамки вокруг поля

Шаг 2. Класс мяча

class Ball:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
self.x = starts[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()

def draw(self):
self.canvas.move(self.id, self.x, self.y)
  • create_oval(x1, y1, x2, y2) рисует эллипс в прямоугольнике
  • move(id, dx, dy) сдвигает фигуру на dx и dy пикселей
  • random.shuffle(starts) перемешивает список — мяч стартует в случайном направлении по горизонтали

self.x и self.yскорость (сколько пикселей добавлять за один кадр).


Шаг 3. Отскок от стен

Добавьте в draw проверку границ:

def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.y = -3
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3

coords(id) возвращает список [x1, y1, x2, y2] — координаты ограничивающего прямоугольника фигуры.

  • pos[1] — верхний край; удар о верх → меняем self.y
  • pos[3] — нижний край
  • pos[0] и pos[2] — левый и правый края

Шаг 4. Главный цикл

В конце файла:

ball = Ball(canvas, 'red')

while True:
ball.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)

Запустите (F5 в IDLE). Мяч летает по полю.

Игровой цикл — бесконечный while, который каждый кадр:

  • двигает объекты
  • обновляет экран (update)
  • делает короткую паузу (sleep(0.01) ≈ 10 мс)

Без паузы движение будет слишком быстрым.


Шаг 5. Класс ракетки

class Paddle:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)
self.canvas.move(self.id, 200, 300)
self.x = 0
self.canvas_width = self.canvas.winfo_width()
self.canvas.bind_all('<KeyPress-Left>', self.turn_left)
self.canvas.bind_all('<KeyPress-Right>', self.turn_right)

def draw(self):
self.canvas.move(self.id, self.x, 0)
pos = self.canvas.coords(self.id)
if pos[0] <= 0:
self.x = 0
elif pos[2] >= self.canvas_width:
self.x = 0

def turn_left(self, evt):
self.x = -2

def turn_right(self, evt):
self.x = 2

bind_all привязывает нажатие клавиши к методу. evt — объект события (здесь не используется, но параметр обязателен).

Кликните по окну игры, затем жмите стрелки — иначе фокус может остаться в редакторе.

В цикле добавьте:

paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, 'red')

while True:
ball.draw()
paddle.draw()
...

Шаг 6. Столкновение мяча с ракеткой

В конструкторе Ball добавьте аргумент paddle и строку self.paddle = paddle.

Метод проверки:

def hit_paddle(self, pos):
paddle_pos = self.canvas.coords(self.paddle.id)
if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
return True
return False

Логика: нижняя точка мяча (pos[3]) попала в прямоугольник ракетки по горизонтали и вертикали.

В draw после движения:

if self.hit_paddle(pos):
self.y = -3

Создание объектов:

paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, paddle, 'red')

Шаг 7. Проигрыш

В __init__ мяча добавьте self.hit_bottom = False.

В draw, когда низ мяча коснулся пола:

if pos[3] >= self.canvas_height:
self.hit_bottom = True

Главный цикл:

while True:
if not ball.hit_bottom:
ball.draw()
paddle.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)

Мяч проскочил мимо ракетки — анимация останавливается.


Полный код (шпаргалка)

Развернуть готовую программу
from tkinter import *
import random
import time


class Ball:
def __init__(self, canvas, paddle, color):
self.canvas = canvas
self.paddle = paddle
self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
self.canvas.move(self.id, 245, 100)
starts = [-3, -2, -1, 1, 2, 3]
random.shuffle(starts)
self.x = starts[0]
self.y = -3
self.canvas_height = self.canvas.winfo_height()
self.canvas_width = self.canvas.winfo_width()
self.hit_bottom = False

def hit_paddle(self, pos):
paddle_pos = self.canvas.coords(self.paddle.id)
if pos[2] >= paddle_pos[0] and pos[0] <= paddle_pos[2]:
if pos[3] >= paddle_pos[1] and pos[3] <= paddle_pos[3]:
return True
return False

def draw(self):
self.canvas.move(self.id, self.x, self.y)
pos = self.canvas.coords(self.id)
if pos[1] <= 0:
self.y = 3
if pos[3] >= self.canvas_height:
self.hit_bottom = True
if self.hit_paddle(pos):
self.y = -3
if pos[0] <= 0:
self.x = 3
if pos[2] >= self.canvas_width:
self.x = -3


class Paddle:
def __init__(self, canvas, color):
self.canvas = canvas
self.id = canvas.create_rectangle(0, 0, 100, 10, fill=color)
self.canvas.move(self.id, 200, 300)
self.x = 0
self.canvas_width = self.canvas.winfo_width()
self.canvas.bind_all('<KeyPress-Left>', self.turn_left)
self.canvas.bind_all('<KeyPress-Right>', self.turn_right)

def draw(self):
self.canvas.move(self.id, self.x, 0)
pos = self.canvas.coords(self.id)
if pos[0] <= 0:
self.x = 0
elif pos[2] >= self.canvas_width:
self.x = 0

def turn_left(self, evt):
self.x = -2

def turn_right(self, evt):
self.x = 2


tk = Tk()
tk.title("Прыг-скок!")
tk.resizable(0, 0)
tk.wm_attributes("-topmost", 1)
canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()
tk.update()

paddle = Paddle(canvas, 'blue')
ball = Ball(canvas, paddle, 'red')

while True:
if not ball.hit_bottom:
ball.draw()
paddle.draw()
tk.update_idletasks()
tk.update()
time.sleep(0.01)

Улучшения из книги

  1. Старт по клику — привязка к <Button-1>, игра ждёт клика по полю
  2. Надпись "Конец игры"canvas.create_text(...) и itemconfig(..., state='hidden')
  3. Счёт очков — +1 при отскоке от ракетки, текст в углу экрана
  4. Ускорение мяча — менять self.x / self.y после каждого удара

Следующий проект в книге — Человечек спешит к выходу с GIF-спрайтами и платформами.


Частые ошибки

СимптомПричина
Окно мигнуло и закрылосьНет цикла while или синтаксическая ошибка — смотрите текст в Shell
Стрелки не работаютНе кликнули по окну игры
Мяч проходит сквозь ракеткуШаг 3 px велик; проверяйте пересечение по всей высоте ракетки
TclError при закрытииЦикл ещё крутится, окно уже закрыто — остановите через Ctrl+C

Связанные материалы