Написание игры Змейка: код на Python и альтернативы
В этом материале рассматриваются несколько подходов к созданию игры Змейка на Python. Основной вариант использует библиотеку Pygame, затем показаны альтернативы с Tkinter, консолью и облегченной версией Pygame Zero. Для каждого решения приведены примеры кода, пояснения и типичные ошибки.
Основной вариант: использование библиотеки Pygame
Pygame предоставляет удобный интерфейс для работы с графикой, звуком и событиями. Это самый распространенный способ создания 2D игр на Python. Ниже приведен полный код простой змейки.
import pygame
import random
pygame.init()
WIDTH, HEIGHT = 600, 400
CELL_SIZE = 20
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Змейка")
WHITE = (255, 255, 255)
GREEN = (0, 200, 0)
RED = (200, 0, 0)
BLACK = (0, 0, 0)
clock = pygame.time.Clock()
snake = [(WIDTH // 2, HEIGHT // 2)]
direction = (CELL_SIZE, 0)
food = (random.randrange(0, WIDTH, CELL_SIZE),
random.randrange(0, HEIGHT, CELL_SIZE))
score = 0
speed = 10
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and direction != (0, CELL_SIZE):
direction = (0, -CELL_SIZE)
elif event.key == pygame.K_DOWN and direction != (0, -CELL_SIZE):
direction = (0, CELL_SIZE)
elif event.key == pygame.K_LEFT and direction != (CELL_SIZE, 0):
direction = (-CELL_SIZE, 0)
elif event.key == pygame.K_RIGHT and direction != (-CELL_SIZE, 0):
direction = (CELL_SIZE, 0)
head = (snake[0][0] + direction[0], snake[0][1] + direction[1])
if head[0] < 0 or head[0] >= WIDTH or head[1] < 0 or head[1] >= HEIGHT:
break
if head in snake:
break
snake.insert(0, head)
if head == food:
score += 1
food = (random.randrange(0, WIDTH, CELL_SIZE),
random.randrange(0, HEIGHT, CELL_SIZE))
else:
snake.pop()
screen.fill(BLACK)
for segment in snake:
pygame.draw.rect(screen, GREEN, (segment[0], segment[1], CELL_SIZE, CELL_SIZE))
pygame.draw.rect(screen, RED, (food[0], food[1], CELL_SIZE, CELL_SIZE))
pygame.display.flip()
clock.tick(speed)
pygame.quit()
код змейки на python (код игры змейка на python)
Пояснение шагов:
- Инициализация Pygame и создание окна размером 600x400 пикселей.
- Ячейки размером 20x20, змейка и еда располагаются с шагом CELL_SIZE.
- Начальное положение змейки – центр, направление – вправо.
- В основном цикле обрабатываются события (закрытие окна, нажатие клавиш).
- Перед перемещением запрещается разворот на 180 градусов (проверка direction != ...).
- При столкновении со стеной или собственным телом игра завершается.
- Змейка растет, если голова совпадает с координатами еды.
- Отрисовка всех объектов с помощью pygame.draw.rect.
- Скорость регулируется clock.tick(speed).
Возможные проблемы и их решения:
- Задержка ввода. При быстром нажатии нескольких клавиш за один тик может быть потеряна реакция. Решение – обрабатывать события в цикле for event, а не использовать pygame.key.get_pressed().
- Изменение направления внутри себя. Если нажать две клавиши за один кадр, змейка может «сложиться». Решение – хранить только последнее направление, а не список.
- Несоответствие размера окна и ячеек. Если WIDTH и HEIGHT не кратны CELL_SIZE, еда может появиться за пределами сетки. Решение – использовать randrange с шагом CELL_SIZE и убедиться, что размеры кратны.
Как создать змейку в консоли без графики?
Консольная версия полезна для отладки логики и изучения алгоритмов без лишней графики. Используется модуль curses (на Unix) или библиотека keyboard (Windows). Ниже приведен пример с curses.
import curses
import random
stdscr = curses.initscr()
curses.curs_set(0)
sh, sw = stdscr.getmaxyx()
w = curses.newwin(sh, sw, 0, 0)
w.keypad(1)
w.timeout(100)
snake_x = sw // 4
snake_y = sh // 2
snake = [
[snake_y, snake_x],
[snake_y, snake_x - 1],
[snake_y, snake_x - 2]
]
food = [sh // 2, sw // 2]
w.addch(food[0], food[1], curses.ACS_PI)
key = curses.KEY_RIGHT
score = 0
while True:
next_key = w.getch()
if next_key == -1:
key = key
else:
key = next_key
new_head = [snake[0][0], snake[0][1]]
if key == curses.KEY_DOWN:
new_head[0] += 1
elif key == curses.KEY_UP:
new_head[0] -= 1
elif key == curses.KEY_LEFT:
new_head[1] -= 1
elif key == curses.KEY_RIGHT:
new_head[1] += 1
if (new_head[0] in [0, sh] or
new_head[1] in [0, sw] or
new_head in snake):
break
snake.insert(0, new_head)
if new_head == food:
w.addch(food[0], food[1], curses.ACS_BLOCK)
food = None
while food is None:
nf = [
random.randint(1, sh - 1),
random.randint(1, sw - 1)
]
if nf not in snake:
food = nf
w.addch(food[0], food[1], curses.ACS_PI)
score += 1
else:
tail = snake.pop()
w.addch(tail[0], tail[1], ' ')
w.addch(snake[0][0], snake[0][1], curses.ACS_CKBOARD)
curses.endwin()
print("Очки:", score)
Типичные ошибки:
- Ошибка инициализации curses в Windows. В Windows curses не входит в стандартную поставку. Альтернатива – установка windows-curses через pip.
- Проблема с размером терминала. Если окно слишком мало, змейка не помещается. Желательно проверять sh и sw перед стартом.
- Неверная обработка клавиш. curses.getch() возвращает код клавиши, но преобразование в символ может отличаться. Используйте константы curses.KEY_*.
Как реализовать змейку на Tkinter?
Tkinter – стандартная библиотека для GUI. Проект не требует установки дополнительных пакетов. Работа с холстом Canvas позволяет рисовать прямоугольники. Ниже представлен код.
import tkinter as tk
import random
WIDTH = 600
HEIGHT = 400
CELL = 20
root = tk.Tk()
canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg='black')
canvas.pack()
snake = [(WIDTH//2, HEIGHT//2)]
dx, dy = CELL, 0
food = (random.randrange(0, WIDTH, CELL), random.randrange(0, HEIGHT, CELL))
score = 0
speed = 150
def move():
global dx, dy, food, score
head = (snake[0][0] + dx, snake[0][1] + dy)
if (head[0] < 0 or head[0] >= WIDTH or
head[1] < 0 or head[1] >= HEIGHT or
head in snake):
return
snake.insert(0, head)
if head == food:
score += 1
food = (random.randrange(0, WIDTH, CELL),
random.randrange(0, HEIGHT, CELL))
else:
snake.pop()
draw()
root.after(speed, move)
def draw():
canvas.delete('all')
for seg in snake:
x, y = seg
canvas.create_rectangle(x, y, x+CELL, y+CELL, fill='green')
fx, fy = food
canvas.create_rectangle(fx, fy, fx+CELL, fy+CELL, fill='red')
canvas.create_text(50, 10, text=f"Score: {score}", fill='white', anchor='nw')
def change_direction(event):
global dx, dy
if event.keysym == 'Up' and dy == 0:
dx, dy = 0, -CELL
elif event.keysym == 'Down' and dy == 0:
dx, dy = 0, CELL
elif event.keysym == 'Left' and dx == 0:
dx, dy = -CELL, 0
elif event.keysym == 'Right' and dx == 0:
dx, dy = CELL, 0
root.bind('', change_direction)
root.after(speed, move)
root.mainloop()
Распространенные сложности:
- Задержка после окончания игры. В коде не предусмотрен выход из цикла after. Можно добавить флаг running и в move проверять его.
- Пропуск нажатий клавиш. Если нажать клавишу между вызовами move, смена направления не будет обработана мгновенно. Tkinter обрабатывает события асинхронно, но этого обычно достаточно.
- Размер окна. Tkinter не поддерживает изменение размера через .resizable(False, False) в самом начале.
Как упростить код с Pygame Zero?
Pygame Zero – это надстройка над Pygame, предназначенная для обучения и быстрой разработки. Она автоматически создает окно, игровой цикл и обрабатывает события. Код становится лаконичнее.
import pgzrun
import random
WIDTH = 600
HEIGHT = 400
CELL = 20
snake = [(WIDTH//2, HEIGHT//2)]
direction = (CELL, 0)
food = (random.randrange(0, WIDTH, CELL), random.randrange(0, HEIGHT, CELL))
speed = 10
timer = 0
def update(dt):
global timer, food, direction
timer += dt
if timer < 1.0/speed:
return
timer = 0
head = (snake[0][0] + direction[0], snake[0][1] + direction[1])
if head[0] < 0 or head[0] >= WIDTH or head[1] < 0 or head[1] >= HEIGHT or head in snake:
exit()
snake.insert(0, head)
if head == food:
food = (random.randrange(0, WIDTH, CELL),
random.randrange(0, HEIGHT, CELL))
else:
snake.pop()
def draw():
screen.clear()
for seg in snake:
screen.draw.filled_rect(Rect(seg[0], seg[1], CELL, CELL), 'green')
screen.draw.filled_rect(Rect(food[0], food[1], CELL, CELL), 'red')
def on_key_down(key):
global direction
if key == keys.UP and direction[1] == 0:
direction = (0, -CELL)
elif key == keys.DOWN and direction[1] == 0:
direction = (0, CELL)
elif key == keys.LEFT and direction[0] == 0:
direction = (-CELL, 0)
elif key == keys.RIGHT and direction[0] == 0:
direction = (CELL, 0)
pgzrun.go()
Ограничения Pygame Zero:
- Отсутствие прямого управления окном. Невозможно задать заголовок через код, только через глобальную переменную title.
- Отсутствие встроенной поддержки коллизий. Все проверки приходится писать вручную.
- Устаревшая версия. PGZero редко обновляется, некоторые функции могут быть недокументированными.
Как добавить рекорды и уровни сложности?
Усложненная версия на Pygame включает сохранение рекорда в файл, ускорение с каждым съеденным яблоком и случайное появление препятствий. Пример расширения основного кода:
# Вставка в основной цикл после счетчика
if head == food:
score += 1
if score % 5 == 0: # каждые 5 очков ускорение
speed = min(speed + 2, 30)
# сохранение рекорда
if score > high_score:
with open('record.txt', 'w') as f:
f.write(str(score))
# генерация препятствия с вероятностью 10%
if random.random() < 0.1:
obstacle = (random.randrange(0, WIDTH, CELL),
random.randrange(0, HEIGHT, CELL))
if obstacle not in snake and obstacle != food:
obstacles.append(obstacle)
Сложности при добавлении препятствий:
- Пересечение с змейкой. Необходимо проверять, чтобы препятствие не накладывалось на тело змейки или еду.
- Столкновение змейки с препятствием. В игровом цикле нужно добавить проверку head in obstacles.
Общие проблемы всех версий:
- Зависание при быстром нажатии. Решение – использовать буфер направлений (очередь) или проверять, что последнее направление не равно нулю.
- Размер ячеек не кратен размеру окна. Всегда выбирайте размер окна кратным CELL_SIZE.
- Непредсказуемое поведение при нулевом размере змейки. Минимальная длина змейки должна быть хотя бы 1.
Дополнительные расширенные примеры с кодом и результатами.
Змейка с анимированной едой (мигание)
Еда меняет цвет каждые 0.5 секунды, привлекая внимание. Результат – динамичная игра.
import pygame
import random
pygame.init()
# ... (основные настройки как в базовом варианте)
food_color = RED
timer = 0
while running:
dt = clock.tick(speed) / 1000.0
timer += dt
if timer > 0.5:
timer = 0
if food_color == RED:
food_color = (255, 255, 0) # желтый
else:
food_color = RED
# ... остальная логика
pygame.draw.rect(screen, food_color, (food[0], food[1], CELL_SIZE, CELL_SIZE))
Еда мигает красно-желтым с интервалом 0.5 сек.
Змейка с движущимися препятствиями
Препятствия медленно перемещаются по вертикали, усложняя игру. Результат появляется случайный движущийся блок.
obstacles = [{'x': 300, 'y': 200, 'dy': 2}]
# В основном цикле:
for obs in obstacles:
obs['y'] += obs['dy']
if obs['y'] <= 0 or obs['y'] >= HEIGHT - CELL_SIZE:
obs['dy'] *= -1
head_collide = head[0] == obs['x'] and head[1] == obs['y']
if head_collide:
running = False
pygame.draw.rect(screen, (100,100,100), (obs['x'], obs['y'], CELL_SIZE, CELL_SIZE))
Серые препятствия перемещаются вверх-вниз. При столкновении игра заканчивается.
Сохранение рекорда в JSON с датой
Каждый рекорд сохраняется в файл records.json, что позволяет отслеживать прогресс по дням. Результат – файл с историей.
import json
from datetime import datetime
def save_record(score):
try:
with open('records.json', 'r') as f:
records = json.load(f)
except:
records = []
records.append({'date': str(datetime.now()), 'score': score})
with open('records.json', 'w') as f:
json.dump(records, f, indent=2)
# Вызов после завершения игры
save_record(score)
Создается/обновляется records.json. Пример содержимого:
[
{
"date": "2025-04-10 14:32:15.123456",
"score": 12
}
]
Змейка с двумя игроками на одной клавиатуре
Одна змейка управляется стрелками, вторая – клавишами WASD. Результат – соревнование двух игроков.
snake1 = [(100,100)]
dir1 = (CELL,0)
snake2 = [(500,300)]
dir2 = (-CELL,0)
# В обработке событий:
if event.key == pygame.K_UP and dir1[1]==0:
dir1 = (0,-CELL)
elif event.key == pygame.K_w and dir2[1]==0:
dir2 = (0,-CELL)
# ... аналогично для других направлений
# В основном цикле обновляются оба списка
head1 = (snake1[0][0]+dir1[0], snake1[0][1]+dir1[1])
head2 = (snake2[0][0]+dir2[0], snake2[0][1]+dir2[1])
# проверка столкновений между змейками
if head1 == head2 or head1 in snake2 or head2 in snake1:
running = False
Две змейки разных цветов (зеленая и синяя) управляются независимо. Столкновение любой змейки с другой или с границей завершает игру.
Змейка с псевдографикой в терминале (shutil и time)
Без использования сторонних модулей, только стандартные print и time. Вывод в одной строке с очисткой экрана (os.system('cls')). Результат – анимация в консоли.
import os, time, random
W, H = 30, 15
snake = [(W//2, H//2)]
dx, dy = 1, 0
food = (random.randint(0,W-1), random.randint(0,H-1))
score=0
while True:
os.system('cls')
# рисование поля
for y in range(H):
line = ''
for x in range(W):
if (x,y) == snake[0]:
line += 'O'
elif (x,y) in snake[1:]:
line += 'o'
elif (x,y) == food:
line += '@'
else:
line += '.'
print(line)
print('Score:',score)
# ввод направления (простой, без немедленной реакции)
# упрощенно - направление меняется случайно
time.sleep(0.3)
head = (snake[0][0]+dx, snake[0][1]+dy)
if head[0] < 0 or head[0]>=W or head[1]<0 or head[1]>=H or head in snake:
break
snake.insert(0, head)
if head == food:
score+=1
food = (random.randint(0,W-1), random.randint(0,H-1))
else:
snake.pop()
Консоль выводит поле 30x15 с символами. Змейка из 'o' и 'O', еда '@'. Скорость 0.3 сек. Для полноценного управления требуется дополнительная обработка ввода (например, через msvcrt).