Ping-Pong на Python (turtle)

в 8:15, , рубрики: Ping-Pong, turtle

Начало

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

Ping-Pong на Python (turtle) - 1

Инициализируем библиотеки 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")
Ping-Pong на Python (turtle) - 3

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

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)

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

Автор: Eralv1lle

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js