Завершение игры, когда черепаха достигает края сетки

#python #python-3.x #turtle-graphics

#python #python-3.x #черепаха-графика

Вопрос:

Я должен написать программу, которая реализует использование графики черепах. Я уже написал большую часть программы, которая включает в себя сетку и создание нескольких черепах, но мне трудно завершить игру, как только черепаха достигает края созданной мной сетки. Вот что у меня есть на данный момент:

 import turtle
import random

# Setting up Turtle Graphics Window
turtle.setup(800,600)
window = turtle.Screen()
window.title("Turtles Walking through Grid")
window.bgcolor("black")


# Making the turtle
grid = turtle.getturtle()
grid.shape("classic")
grid.color("white")
grid.speed(10)

# Creating the Grid (Relative Positioning)
grid.penup()
grid.setposition(-300,200)
grid.pendown()
grid.forward(600)
grid.right(90)
grid.forward(400)
grid.right(90)
grid.forward(600)
grid.right(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(400)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(400)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(400)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(400)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(400)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(400)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(400)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(400)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(600)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(600)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(600)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(600)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(600)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(600)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(600)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(600)
grid.right(90)
grid.forward(40)
grid.right(90)
grid.forward(600)
grid.left(90)
grid.forward(40)
grid.left(90)
grid.forward(600)

# User Input for Speed 
speed = int(input("Enter the speed of the turtles (1-10): "))

# Variable for choosing colors
all_colors =      ["red","white","blue","hotpink","purple","lightgreen","yellow"]

# Creating the turtles
def createTurtles(turtle_count):
    count = []
    for k in range(0, turtle_count):
        lil_guys = turtle.Turtle()
        lil_guys.shape("turtle")
        colors = random.choice(all_colors)
        lil_guys.color(colors)
        lil_guys.speed(speed)
        lil_guys.pendown()
        count.append(lil_guys)
    return count

# Determine where the Turtle should stop
def off_board(self):
    x = self.turtle.xcor()
    y = self.turtle.ycor()
    return x < -160 or 160 < x or y < -160 or 160 < y

# Set Turtle Amount to 5
count = createTurtles(5)
fun = True

while fun:
    for k in range(5):
        coin = random.randrange(0, 2)
        if coin == 0:
            count[k].left(90)
        else:
            count[k].right(90)

        count[k].forward(40)

# Exit on close window
turtle.exitonclick()
  

Предполагается, что программа завершится, как только одна из пяти черепах достигнет края созданной мной сетки.

Ответ №1:

Выход на краю сетки

Вы можете выйти, когда черепаха достигнет края сетки, следующим образом:

 while fun:
    for k in range(5):
        coin = random.randrange(0, 2)
        if coin == 0:
            count[k].left(90)
        else:
            count[k].right(90)

        count[k].forward(40)

        x = count[k].xcor()          # new lines
        y = count[k].ycor()          # |
                                     # |
        if x < -300 or 300 < x or   # |
           y < -200 or 200 < y:      # |
            fun = False              # |
            break                    # |
  

Вы на правильном пути, помещая эту логику в off_grid функцию, но эта функция не должна принимать self в качестве аргумента (это не экземпляр класса).


Предложения по дизайну

У меня есть несколько общих предложений по дизайну (простите за импровизированный обзор кода):

  • Избегайте глобальных переменных; используйте параметры для передачи информации в функции. Это обеспечивает многократное использование функций и их безопасность. Думайте о каждой функции как о черном ящике с регулируемыми ручками (параметрами); этот черный ящик должен работать независимо и не ломаться или работать по-другому, если внешнее состояние непредсказуемо меняется. Это уменьшает количество ошибок и упрощает анализ вашей программы.
  • Используйте точные имена переменных. count на самом деле это не count что-либо, а скорее список turtle ов. Осмысленные имена облегчают следование вашей логике и позволяют избежать ошибок и недоразумений. Переменная fun могла бы быть понятнее как running . Переменная k в for k in range(0, turtle_count): не используется и обычно записывается как _ на Python.
  • Предпочитайте snake_case для имен функций Python ( CamelCase используется для классов).
  • Вместо множества последовательных строк команд используйте цикл для рисования сетки и сохраняйте свой код СУХИМ (не повторяйтесь). Например:

     for _ in range(0, height   1, grid_size):
        turtle.pendown()
        turtle.forward(width)
        turtle.penup()
        turtle.right(90)
        turtle.forward(grid_size)
        turtle.right(90)
        turtle.forward(width)
        turtle.right(180)
      
  • Избегайте жесткого кодирования чисел и строк; разместите все эти переменные в верхней части вашей main программы и используйте их повсюду. В частности, в этой программе вам нужны height , width и grid_size параметры, которые будут определены в одном месте и будут управлять работой всей программы (включая определение того, когда черепаха покинула сетку). Теперь, если я решу, что мне нужен размер сетки 30, высота 200 и ширина 400, например, я могу изменить эти цифры в одном месте, и все просто заработает.

  • Используйте параметры Python по умолчанию или словари, чтобы уменьшить нагрузку избыточных параметров на функции. Разместите функции в верхней части вашего скрипта и отделите их от main .
  • Комментарии хороши, но комментарии, когда код уже очевиден, часто добавляют шума:

     # Exit on close window
    turtle.exitonclick()
      
  • Имейте в виду пользователя: я не знал, что мне нужно было вернуться к терминалу, чтобы ввести черепашью скорость после того, как сетка была нарисована. Я бы предпочел запросить у пользователя черепашью скорость, а затем запустить визуальную часть программы.


Возможный рефакторинг

Собрав все это вместе, вот предлагаемый первый рефакторинг (все еще есть много возможностей для улучшения дизайна, но это должно дать некоторую пищу для размышлений):

 import turtle
import random

def create_turtles(
    turtle, turtle_count, colors, speed=10, shape="turtle"
):
    turtles = []

    for _ in range(turtle_count):
        tur = turtle.Turtle()
        tur.shape(shape)
        tur.color(random.choice(colors))
        tur.speed(speed)
        tur.pendown()
        turtles.append(tur)

    return turtles

def draw_lines(turtle, turn, length_a, length_b, grid_size):
    for _ in range(0, length_a   1, grid_size):
        turtle.pendown()
        turtle.forward(length_b)
        turtle.penup()
        turn(90)
        turtle.forward(grid_size)
        turn(90)
        turtle.forward(length_b)
        turn(180)

def draw_grid(
    turtle, width=600, height=400, grid_size=40, 
    speed=100, shape="classic", color="white"
):
    tur = turtle.getturtle()
    tur.shape(shape)
    tur.color(color)
    tur.speed(speed)
    tur.penup()

    tur.setposition(-width // 2, height // 2)
    draw_lines(tur, tur.right, height, width, grid_size)

    tur.setposition(-width // 2, height // 2)
    tur.right(90)
    draw_lines(tur, tur.left, width, height, grid_size)

    turtle.penup()
    turtle.ht()

def off_grid(turtle, width, height):
    x = turtle.xcor()
    y = turtle.ycor()

    return x < -width // 2 or x > width // 2 or 
           y < -height // 2or y > height // 2


if __name__ == "__main__":
    grid_size = 40
    height = 400
    width = 600
    all_colors = [
        "red", "white", "blue", "hotpink", 
        "purple", "lightgreen", "yellow"
    ]

    speed = int(input("Enter the speed of the turtles (1-10): "))

    turtle.setup(800, 600)
    window = turtle.Screen()
    window.title("Turtles Walking through Grid")
    window.bgcolor("black")

    draw_grid(turtle, width, height, grid_size)
    turtles = create_turtles(turtle, 5, all_colors, speed)
    running = True

    while running:
        for tur in turtles:
            random.choice([tur.left, tur.right])(90)
            tur.forward(grid_size)

            if off_grid(tur, width, height):
                running = False
                break

    turtle.exitonclick()
  

Комментарии:

1. Как я отметил в своем ответе, я ценю совет, который вы дали OP ( 1). Один сбой, который вы наследуете от кода операционной системы, — это проблема с четностью. Т.е. черепахи ходят по линиям в одном измерении, между ними — в другом. Если вы измените ширину сетки на 400, то они полностью пройдут по линиям. Это действительно должно не зависеть от размера сетки, и я попытался учесть это в своем примере кода.

Ответ №2:

Я не собираюсь дублировать все отличные советы @ggorlen ( 1), а скорее укажу на некоторые другие проблемы:

  • Ваши черепахи ходят по линиям сетки в одном измерении и ходят между линиями сетки в другом. В моей доработке ниже они ходят по линиям сетки. Для этого требуется вычисление на основе (четности) размеров, которые вы выбираете для своей сетки.

  • В моей доработке движение останавливается, когда черепаха достигает края сетки, что становится более четким, поскольку они идут по линиям сетки.

  • Избегайте использования «white» в качестве цвета черепахи, если вы хотите, чтобы перо было опущено и линии сетки были белыми! То же самое для «черного» цвета черепахи.

  • Если / когда вы можете, не заставляйте пользователя выходить за пределы программы для ввода параметров. Как отмечает @ggorlen, выполнение input() перед вызовом turtle помогает. Но в своей доработке я использовал numinput() , новое в Python 3, чтобы сохранить все это в графическом интерфейсе.

  • Существует множество алгоритмов для рисования сетки в turtle, выберите один и используйте его!

Переработанный код:

 from turtle import Screen, Turtle
from random import choice

TURTLE_COUNT = 5

# Variable for choosing colors
ALL_COLORS = ['red', 'green', 'blue', 'magenta', 'yellow', 'cyan', 'purple']

WINDOW_WIDTH, WINDOW_HEIGHT = 800, 600
GRID_WIDTH, GRID_HEIGHT = 600, 400
CELL_SIZE = 40  # should be a divisor of GRID_WIDTH and GRID_HEIGHT, and probably no smaller than CURSOR_SIZE

CURSOR_SIZE = 20

# Creating the turtles
def create_turtles(turtle_count, speed):
    turtles = []

    for index in range(turtle_count):
        lil_guy = Turtle('turtle')
        lil_guy.color(ALL_COLORS[index % TURTLE_COUNT])
        lil_guy.speed(speed)
        lil_guy.penup()
        # to place a turtle cleanly on the grid lines, we have to consider the parity of the grid size
        lil_guy.goto((GRID_WIDTH / CELL_SIZE % 2) * CELL_SIZE/2, (GRID_HEIGHT / CELL_SIZE % 2) * CELL_SIZE/2)
        lil_guy.pendown()

        turtles.append(lil_guy)

    return turtles

# Determine where the Turtle should stop
def on_edge(turtle):
    x, y = turtle.position()
    return abs(x) >= (GRID_WIDTH/2 - CELL_SIZE/2) or abs(y) >= (GRID_HEIGHT/2 - CELL_SIZE/2)

# Setting up Turtle Graphics Window
window = Screen()
window.setup(WINDOW_WIDTH, WINDOW_HEIGHT)
window.title("Turtles Walking through Grid")
window.bgcolor('black')

# Create the grid via stamping
grid = Turtle(visible=False)
grid.speed('fastest')
grid.color('white')
grid.penup()

grid.setx(-GRID_WIDTH/2)
grid.shapesize(GRID_HEIGHT*2 / CURSOR_SIZE, 1/CURSOR_SIZE)

for _ in range(GRID_WIDTH // CELL_SIZE   1):
    grid.stamp()
    grid.forward(CELL_SIZE)

grid.setheading(90)

grid.setposition(0, -GRID_HEIGHT/2)
grid.shapesize(GRID_WIDTH*2 / CURSOR_SIZE, 1/CURSOR_SIZE)

for _ in range(GRID_HEIGHT // CELL_SIZE   1):
    grid.stamp()
    grid.forward(CELL_SIZE)

# User Input for Speed
user_speed = window.numinput("Turtle Speed", "Enter a value (1-10)", default=5, minval=1, maxval=10)

# Set Turtle Amount
turtles = create_turtles(TURTLE_COUNT, user_speed)

finished = False

while not finished:
    for k in range(5):
        angle = choice([90, -90])

        turtles[k].left(angle)
        turtles[k].forward(CELL_SIZE)

        finished = on_edge(turtles[k])
        if finished:
            break

# Exit on close window
window.exitonclick()