Ping-Pong на Python (turtle)

от автора

Начало

В этой статье я хочу поделиться своим опытом с создании игры ping-pong на пайтон с использованием модуля turtle. Мне 14 лет и я только начинаю программировать, так что не судите строго)

Инициализируем библиотеки turtle и random (чтобы выбирать случайное направление мяча)

from turtle import * from random import choice

Часть кода для закрытия окна без ошибки

Если честно эту часть кода я подсмотрел в интернете

scr = Screen() canvas = scr.getcanvas() root = canvas.winfo_toplevel()  scr.setup(600, 400)  scr.title('Ping-Pong') scr.cv._rootwindow.resizable(False, False)  def on_close():     global running     running = False     root.destroy()  root.protocol("WM_DELETE_WINDOW", on_close) running = True
Вот так это пока выглядит

Вот так это пока выглядит

Эта часть программы нужна для правильного закрытия окна (далее будет бесконечный цикл поэтому это нужно)

Также здесь я настроил размеры окна и убрал увеличение/уменьшение окна

Настройка счёта

В этой части я обычной черепашкой (предварительно подняв её перо) перемещаюсь в верхнюю часть экрана и пишу там счёт 0-0

penup() hideturtle() setposition(0, 150) write(f'Красный: 0           Синий: 0', font=("Arial", 20, "bold"), align="center")

Также я добавил список с направлениями в которых будет двигаться мяч

lst = [30, 60, 120, 150, 210, 240, 300, 330]

Создание класса-наследника Sprint

Не знаю почему именно Sprint но пускай будет

Этот класс наследует всё от класса Turtle(), а также я добавил туда свои свойства и методы

class Sprint(Turtle):     score = 0          def __init__(self,  x, width, height, color_, shape="square"):         super().__init__(shape)         self.color(color_)         self.x = x         self.shapesize(width, height)              def change_settings(self):         self.penup()         self.speed(0)         self.setposition(self.x, 0)          def move_up(self):         x, y = self.pos()         if y <= 150:             self.setposition(x, y+20)          def move_down(self):         x, y = self.pos()         if y >= -140:             self.setposition(x, y-20)

В этот класс первым делом я добавляю свойство score для счётчика очков

Потом инициализирую следующие свойства — x, width, height, color_, shape

shape я сразу передаю супер-классу

Методы:

  • Настройки всего класса

  • Двигаться вверх

  • Двигаться вниз

В методе change_settings я просто даю настройки черепашки — поднимаю перо, устанавливаю скорость 0 (чтобы не было анимации) и устанавливаю позицию черепашки

Методы move_up и move_down говорят сами за себя. В них я распаковкой получай значения x и y черепашки и проверяю: если она не выходит за границы, то двигаю её на 20 пикселей вверх или вниз

Создание класса-наследника Ball

Также класс-наследник от turtle со своими свойствами и методами

class Ball(Turtle):     def __init__(self, shape="circle"):         super().__init__(shape)          def change_settings(self):         self.penup()         self.speed(0)         self.shapesize(0.7)         self.setheading(choice(lst))

Также супер классу передаю форму и такой-же метод change_settings

В методе change_settings я: поднимаю перо, устанавливаю скорость 0 (чтобы убрать анимацию), устанавливаю размер мяча 0.7 (я посчитал этот размер оптимальным), и выбираю случайное направление мяча

Основная часть программы

Вот мы и подошли к методу класса Ball в котором и будет твориться вся дичь

def go(self, enemy1, enemy2):         while running:             if not running:                 break             self.forward(2.5)             if self.ycor() >= 190:                 self.setheading(360 - self.heading())             elif self.ycor() <= -180:                 self.setheading(360 - self.heading())                              if self.xcor() < -300:                 self.setposition(0, 0)                 self.setheading(choice(lst))                 enemy2.score += 1                 clear()                 write(f'Красный: {enemy1.score}           Синий: {enemy2.score}', font=("Arial", 20, "bold"), align="center")             elif self.xcor() > 300:                 self.setposition(0, 0)                 self.setheading(choice(lst))                 enemy1.score += 1                 clear()                 write(f'Красный: {enemy1.score}           Синий: {enemy2.score}', font=("Arial", 20, "bold"), align="center")                              if -179 > self.xcor() > -181 and abs(self.ycor() - enemy1.ycor()) <= 50:                 self.setheading(180 - self.heading())                 self.forward(2)              if 179 < self.xcor() < 181 and abs(self.ycor() - enemy2.ycor()) <= 50:                 self.setheading(180 - self.heading())                 self.forward(2)

Давайте я буду рассказывать всё по частям

1-4 строка:

Объявление метода go с двумя параметрами (это наши черепашки)

Запуск бесконечного цикла и вместо while True я пишу while running, помните первую часть кода? Так вот. В ней проверяется не закрылось ли окно и если так то выполняется функция on_close, где переменная running объявляется глобальной и ей присваивается значение False, тем самым завершая цикл (также в той функции происходит закрытие окна).

Пятая строка нужна чтобы наш мяч двигался

6-9 строка:

Проверяется не вышил ли мяч за границы по y и если это так то зеркально отражает шарик (меняет его направление)

11-22 строка:

В этой части кода происходит проверка на выход мяча за границы по x. Если это так то мяч перемещается на координаты 0, 0 и заново выбирается направление

Также здесь происходит подсчёт очков. Если мяч вышел за границы красной платформы то добавляется очко синей и наоборот. Потом обычная черепашка стирает старую информацию про счёт и печатает обновлённую

24-30 строка:

Тут происходит проверка соприкосновения мяча с платформой

Как это примерно работает:

Сначала проверяется не дошёл ли мяч до платформы (а именно до координаты -180 или 180). Затем я проверяю что координаты y совпадают у обоих элементов, выглядит это примерно вот так:

abs(self.ycor() - enemy1.ycor()) <= 50

Тут из координаты y мяча вычитается координата y платформы и если эти значения по модулю не превышают 50, то значит платформа и мяч находятся в соприкосновении

Если условие выполняется и меняю направление мяча на противоположное и сразу иду вперёд на 2 пикселя, чтобы не было повторного выполнения условия

Создание экземпляров класса Sprint

t1 = Sprint(-200, 4, 1, 'red') t1.change_settings() t2 = Sprint(200, 4, 1, 'blue') t2.change_settings()

Создаю 2 экземпляра передавая каждому свои аргументы и сразу выполняю метод change_settings()

Создание экземпляра класса Ball

ball = Ball() ball.change_settings()

В этот класс мне не нужно передавать аргументов. Также сразу запускаю метод change_settings()

Настройка обработки нажатий

Начинаю считывать нажатия с помощью scr.listen()

scr.listen() onkey(t1.move_up, 'w') onkey(t1.move_down, 's')  onkey(t2.move_up, 'Up') onkey(t2.move_down, 'Down')

На клавиши w, s у нас реагирует красная платформа, а на стрелочки вверх и вниз реагирует синяя платформа

Запуск метода go

Запускаем метод класса Ball передав два аргумента в виде черепашек и наслаждаемся игрой)

ball.go(t1, t2)

Полный код:

from turtle import * from random import choice from time import sleep  scr = Screen() canvas = scr.getcanvas() root = canvas.winfo_toplevel()  scr.setup(600, 400)  scr.title('Ping-Pong') scr.cv._rootwindow.resizable(False, False)  def on_close():     global running     running = False     root.destroy()  root.protocol("WM_DELETE_WINDOW", on_close) running = True  penup() hideturtle() setposition(0, 150) write(f'Красный: 0           Синий: 0', font=("Arial", 20, "bold"), align="center")  lst = [30, 60, 120, 150, 210, 240, 300, 330] class Sprint(Turtle):     score = 0          def __init__(self,  x, width, height, color_, shape="square"):         super().__init__(shape)         self.color(color_)         self.x = x         self.shapesize(width, height)              def change_settings(self):         self.penup()         self.speed(0)         self.setposition(self.x, 0)          def move_up(self):         x, y = self.pos()         if y <= 150:             self.setposition(x, y+20)          def move_down(self):         x, y = self.pos()         if y >= -140:             self.setposition(x, y-20)  class Ball(Turtle):     def __init__(self, shape="circle"):         super().__init__(shape)          def change_settings(self):         self.penup()         self.speed(0)         self.shapesize(0.7)         self.setheading(choice(lst))          def go(self, enemy1, enemy2):         while running:             self.forward(2.5)             if self.ycor() >= 190:                 self.setheading(360 - self.heading())             elif self.ycor() <= -180:                 self.setheading(360 - self.heading())                              if self.xcor() < -300:                 self.setposition(0, 0)                 self.setheading(choice(lst))                 enemy2.score += 1                 clear()                 write(f'Красный: {enemy1.score}           Синий: {enemy2.score}', font=("Arial", 20, "bold"), align="center")             elif self.xcor() > 300:                 self.setposition(0, 0)                 self.setheading(choice(lst))                 enemy1.score += 1                 clear()                 write(f'Красный: {enemy1.score}           Синий: {enemy2.score}', font=("Arial", 20, "bold"), align="center")                              if -179 > self.xcor() > -181 and abs(self.ycor() - enemy1.ycor()) <= 50:                 self.setheading(180 - self.heading())                 self.forward(2)              if 179 < self.xcor() < 181 and abs(self.ycor() - enemy2.ycor()) <= 50:                 self.setheading(180 - self.heading())                 self.forward(2)   t1 = Sprint(-200, 4, 1, 'red') t1.change_settings() t2 = Sprint(200, 4, 1, 'blue') t2.change_settings()  ball = Ball() ball.change_settings()   scr.listen() onkey(t1.move_up, 'w') onkey(t1.move_down, 's')  onkey(t2.move_up, 'Up') onkey(t2.move_down, 'Down')   ball.go(t1, t2)

Заранее извиняюсь за возможные некорректные формулировки


ссылка на оригинал статьи https://habr.com/ru/articles/884982/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *