Написание игры Змейка: код на Python и альтернативы

Раздел: 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).

Код игры Змейка на Python - comments

En
код змейки на python (python)