Игра "Прыг-скок" на 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

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