wynand1004 / snake_game.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
# Simple Snake Game in Python 3 for Beginners |
# By @TokyoEdTech |
import turtle |
import time |
import random |
delay = 0.1 |
# Score |
score = 0 |
high_score = 0 |
# Set up the screen |
wn = turtle . Screen () |
wn . title ( «Snake Game by @TokyoEdTech» ) |
wn . bgcolor ( «green» ) |
wn . setup ( width = 600 , height = 600 ) |
wn . tracer ( 0 ) # Turns off the screen updates |
# Snake head |
head = turtle . Turtle () |
head . speed ( 0 ) |
head . shape ( «square» ) |
head . color ( «black» ) |
head . penup () |
head . goto ( 0 , 0 ) |
head . direction = «stop» |
# Snake food |
food = turtle . Turtle () |
food . speed ( 0 ) |
food . shape ( «circle» ) |
food . color ( «red» ) |
food . penup () |
food . goto ( 0 , 100 ) |
segments = [] |
# Pen |
pen = turtle . Turtle () |
pen . speed ( 0 ) |
pen . shape ( «square» ) |
pen . color ( «white» ) |
pen . penup () |
pen . hideturtle () |
pen . goto ( 0 , 260 ) |
pen . write ( «Score: 0 High Score: 0» , align = «center» , font = ( «Courier» , 24 , «normal» )) |
# Functions |
def go_up (): |
if head . direction != «down» : |
head . direction = «up» |
def go_down (): |
if head . direction != «up» : |
head . direction = «down» |
def go_left (): |
if head . direction != «right» : |
head . direction = «left» |
def go_right (): |
if head . direction != «left» : |
head . direction = «right» |
def move (): |
if head . direction == «up» : |
y = head . ycor () |
head . sety ( y + 20 ) |
if head . direction == «down» : |
y = head . ycor () |
head . sety ( y — 20 ) |
if head . direction == «left» : |
x = head . xcor () |
head . setx ( x — 20 ) |
if head . direction == «right» : |
x = head . xcor () |
head . setx ( x + 20 ) |
# Keyboard bindings |
wn . listen () |
wn . onkeypress ( go_up , «w» ) |
wn . onkeypress ( go_down , «s» ) |
wn . onkeypress ( go_left , «a» ) |
wn . onkeypress ( go_right , «d» ) |
# Main game loop |
while True : |
wn . update () |
# Check for a collision with the border |
if head . xcor () > 290 or head . xcor () < - 290 or head . ycor () >290 or head . ycor () < - 290 : |
time . sleep ( 1 ) |
head . goto ( 0 , 0 ) |
head . direction = «stop» |
# Hide the segments |
for segment in segments : |
segment . goto ( 1000 , 1000 ) |
# Clear the segments list |
segments . clear () |
# Reset the score |
score = 0 |
# Reset the delay |
delay = 0.1 |
pen . clear () |
pen . write ( «Score: <> High Score: <>» . format ( score , high_score ), align = «center» , font = ( «Courier» , 24 , «normal» )) |
# Check for a collision with the food |
if head . distance ( food ) < 20 : |
# Move the food to a random spot |
x = random . randint ( — 290 , 290 ) |
y = random . randint ( — 290 , 290 ) |
food . goto ( x , y ) |
# Add a segment |
new_segment = turtle . Turtle () |
new_segment . speed ( 0 ) |
new_segment . shape ( «square» ) |
new_segment . color ( «grey» ) |
new_segment . penup () |
segments . append ( new_segment ) |
# Shorten the delay |
delay -= 0.001 |
# Increase the score |
score += 10 |
if score > high_score : |
high_score = score |
pen . clear () |
pen . write ( «Score: <> High Score: <>» . format ( score , high_score ), align = «center» , font = ( «Courier» , 24 , «normal» )) |
# Move the end segments first in reverse order |
for index in range ( len ( segments ) — 1 , 0 , — 1 ): |
x = segments [ index — 1 ]. xcor () |
y = segments [ index — 1 ]. ycor () |
segments [ index ]. goto ( x , y ) |
# Move segment 0 to where the head is |
if len ( segments ) > 0 : |
x = head . xcor () |
y = head . ycor () |
segments [ 0 ]. goto ( x , y ) |
move () |
# Check for head collision with the body segments |
for segment in segments : |
if segment . distance ( head ) < 20 : |
time . sleep ( 1 ) |
head . goto ( 0 , 0 ) |
head . direction = «stop» |
# Hide the segments |
for segment in segments : |
segment . goto ( 1000 , 1000 ) |
# Clear the segments list |
segments . clear () |
# Reset the score |
score = 0 |
# Reset the delay |
delay = 0.1 |
# Update the score display |
pen . clear () |
pen . write ( «Score: <> High Score: <>» . format ( score , high_score ), align = «center» , font = ( «Courier» , 24 , «normal» )) |
time . sleep ( delay ) |
wn . mainloop () |
Простейшая змейка на Python менее, чем в 100 строчек кода
На самом деле строчек кода с логикой игры будет гораздо меньше, добрую половину скрипта занимает подготовка игрового поля, рисование новорождённой змеи, назначение клавиш управления и ещё парочка мелочей. Публикация может быть полезна для таких же начинающих, как и я сам. И да, в коде могут быть ошибки, которые я в настоящий момент не вижу в силу своего небольшого опыта программирования, заканчивающегося на прочтении Марка Лутца и пока ещё недописанном телеграм боте, но змейка работает исправно и делает всё, что было задумано. Писать буду с использованием модуля turtle. Погнали.
import time import turtle from random import randrange BREAK_FLAG = False
Импортируем необходимые модули и задаём глобальную переменную. Модуль time понадобится для установки паузы в основном бесконечном цикле программы, turtle будет отвечать за графику в игре, из модуля random возьмём один метод randrange для генерации координат еды для нашей змеи. С помощью переменной BREAK_FLAG будем останавливать игру при укусе змейки самой себя, подробнее об этом позже.
# draw a window for the game screen = turtle.Screen() screen.title('Snake with turtle module') screen.bgcolor('orange') screen.setup(650, 650) screen.tracer(0)
Следующим шагом создаём окно игры, назначаем название, задаём цвет фона и размеры окна. Строка screen.tracer(0) отключает анимацию, рисовать кадры будем сами в основном цикле программы. Если этого не сделать вновь созданный сегмент змейки и новый элемент еды, после поедания добычи, будет появляться в центре поля и только потом перемещаться в заданные координаты и весь этот процесс мы будем видеть. Это особенность библиотеки turtle, каждый новый объект всегда появляется в центре координат.
# draw a game field border border = turtle.Turtle() border.hideturtle() border.penup() border.goto(-311, 311) border.pendown() border.goto(311, 311) border.goto(311, -311) border.goto(-311, -311) border.goto(-311, 311)
Для наглядности нарисуем границы игрового поля. Создаём объект черепашки border = turtle.Turtle(), делаем его невидимым border.hideturtle(), так как от него нам понадобится только линия, которую он чертит при перемещении. И опуская и поднимая перо border.penup(), border.pendown() перемещаем нашу черепашку по строго заданным координатам. На выходе получаем чёрный квадрат, границы которого нельзя будет пересекать нашей змее.
# draw a snake of three segments and # paint the head of the snake in black snake = [] for i in range(3): snake_segment = turtle.Turtle() snake_segment.shape('square') snake_segment.penup() if i > 0: snake_segment.color('gray') snake.append(snake_segment)
Создадим змейку. Наша вновь рождённая змея будет состоять из трёх сегментов каждый из которых будет являться новым экземпляром класса Turtle. Другими словами змея будет состоять из множества черепашек. Надеюсь контекст слова «черепашка» в этой публикации понятен всем без объяснений. Хранить змейку целиком будем в виде списка в переменной snake. Создаём змею в цикле for, который прогоняем три раза. Создаём новый сегмент snake_segment = turtle.Turtle(), задаём форму snake_segment.shape(‘square’) и поднимаем перо snake_segment.penup() так, как нам не надо, чтобы змейка оставляла после себя след. Условие if необходимо для окраски сегментов в серый цвет. Красятся все кроме первого, голова остаётся чёрной. В конце каждой итерации добавляем сегмент в список хранящий всю змею целиком snake.append(snake_segment).
# draw a food for the snake food = turtle.Turtle() food.shape('circle') food.penup() food.goto(randrange(-300, 300, 20), randrange(-300, 300, 20))
В этом блоке кода создаём объект еды, задаём ему круглую форму, генерируем координаты и перемещаем еду на определённое для неё место.
# snake control screen.onkeypress(lambda: snake[0].setheading(90), 'Up') screen.onkeypress(lambda: snake[0].setheading(270), 'Down') screen.onkeypress(lambda: snake[0].setheading(180), 'Left') screen.onkeypress(lambda: snake[0].setheading(0), 'Right') screen.listen()
Управлять змейкой будем кнопками навигации со стрелками. За привязку клавиш отвечает метод screen.onkeypress(). Методу необходимо передать в качестве аргументов функцию и имя кнопки, которая будет вызывать функцию. Метод setheading() задаёт направление движения объекта черепашки. Использовать его будем в таком виде snake[0].setheading(90), то есть голову змейки повернуть на 90 градусов относительно направления по умолчанию. Для нас это значит вверх. Оборачиваем метод в лямбда выражение, это отложит его вызов до момента нажатия на клавишу. Имя кнопки передаём в виде строки ‘Up’ повторяем процедуру для остальных направлений. Начинаем слушать события с клавиатуры screen.listen()
Ну и наконец самое интересное. Логика игры будет обрабатываться в бесконечном цикле. Для наглядности положу его полностью здесь под
while True: # creating a new segment of the snake # and redraw a food for the snake if snake[0].distance(food) < 10: food.goto(randrange(-300, 300, 20), randrange(-300, 300, 20)) snake_segment = turtle.Turtle() snake_segment.shape('square') snake_segment.color('gray') snake_segment.penup() snake.append(snake_segment) # snake body movement for i in range(len(snake)-1, 0, -1): x = snake[i-1].xcor() y = snake[i-1].ycor() snake[i].goto(x, y) # snake head movement snake[0].forward(20) screen.update() # snake collision with border x_cor = snake[0].xcor() y_cor = snake[0].ycor() if x_cor >300 or x_cor < -300: screen.bgcolor('red') break if y_cor >300 or y_cor < -300: screen.bgcolor('red') break # snake collision with itself for i in snake[1:]: i = i.position() if snake[0].distance(i) < 10: BREAK_FLAG = True if BREAK_FLAG: screen.bgcolor('red') break time.sleep(0.2)
# creating a new segment of the snake # and redraw a food for the snake if snake[0].distance(food) < 10: food.goto(randrange(-300, 300, 20), randrange(-300, 300, 20)) snake_segment = turtle.Turtle() snake_segment.shape('square') snake_segment.color('gray') snake_segment.penup() snake.append(snake_segment)
# snake body movement for i in range(len(snake)-1, 0, -1): x = snake[i-1].xcor() y = snake[i-1].ycor() snake[i].goto(x, y)
Этот блок кода отвечает за перемещение тела змеи. Перебираем в цикле for сегменты змеи начиная с хвоста. Получаем координаты x и y предпоследнего сегмента методами xcor() и ycor() перемещаем на их место последний сегмент snake[i].goto(x, y) и так двигаемся до самой головы.
# snake head movement snake[0].forward(20)
Саму же голову двигаем отдельно на 20 пикселей вперёд на каждой итерации бесконечного цикла while. Таким образом мы управляем только первым сегментом змейки, остальные просто повторяют его движение перебираясь в цикле for.
По правде говоря, с передвижением тела змеи у меня возникли некоторые проблемы. Текущую реализацию цикла for i in range(len(snake)-1, 0, -1) я подсмотрел на гитхабе. Но мне показалось не логичным начинать движение с хвоста. Попытки переписать цикл в обратном направлении(то есть начинать перемещение с головы) успехом не увенчались. Поэтому пользуясь случаем обращаюсь к опытным читателям показать возможную реализацию цикла в другом направлении. Если конечно в этом есть смысл, может мне просто кажется текущее решение не логичным.
Двигаемся дальше. screen.update() обновляет кадр, то есть по сути отвечает за анимацию, которую мы отключили в самом начале.
# snake collision with border x_cor = snake[0].xcor() y_cor = snake[0].ycor() if x_cor > 300 or x_cor < -300: screen.bgcolor('red') break if y_cor >300 or y_cor < -300: screen.bgcolor('red') break
Эти два условия проверяют расстояние от головы змеи до граничных координат. Если голова Выходит за предельные значение окрашиваем экран в красный цвет и прерываем работу главного цикла, Game Over короче.
# snake collision with itself for i in snake[1:]: i = i.position() if snake[0].distance(i) < 10: BREAK_FLAG = True if BREAK_FLAG: screen.bgcolor('red') break
В этом блоке кода реализовано поведение игры при укусе змейки самой себя. В цикле for перебираем все сегменты змеи кроме головы. Сравниваем расстояние от головы до текущего сегмента и если оно меньше 10 if snake[0].distance(i) < 10:, что равно укусу, передаём значение True глобальной переменной BREAK_FLAG. Далее проверяем на истинность BREAK_FLAG и если оно True красим экран в красный и останавливаем игру. Если False переходим к следующей строке.
Метод time.sleep(0.2) останавливает цикл на 20 мс, значением аргумента метода можно управлять скоростью игры.
screen.mainloop()
И последняя строка завершает код. Метод mainloop() должен всегда завершать программу написанную с использованием модуля turtle.
На этом всё, буду рад любой критике. Надеюсь было интересно и возможно для кого-то даже полезно. Исходный код змейки можно найти в моём github аккаунте. Программа написана в ОС Ubuntu 18.04, на Windows машине графика выглядит расплывчато и слишком крупно, но змейка вполне рабочая.