Как написать змейку на PyGame: от поля из клеток до счёта и рестарта

Змейка на PyGame за 155 строк: поле из клеток вместо пикселей, тело как очередь deque, запрет разворота на 180°, еда, которая не появляется внутри змейки, счёт, ускорение и рестарт без рекурсии.

Каждый кусок кода запущен на pygame 2.6.1, отдельно разобраны пять ошибок из чужих уроков.

Статью написал:
Ваня Буявец, продюсер, основатель Checkroi
Ваня Буявец
Основатель Checkroi, продюсер, эксперт в выборе онлайн-курсов
Все 3048 статей автора Подписаться на Телеграм-канал
Одобрено экспертом:
Наташа Буявец, основатель Checkroi, эксперт по онлайн-курсам
Наташа Буявец
Основательница Checkroi, продюсер Youtube-каналов, эксперт по онлайн-курсам
Все 3702 экспертных мнения Подписаться на Телеграм-канал
Обложка: Как написать змейку на PyGame: от поля из клеток до счёта и рестарта

Чтобы написать змейку на PyGame, нужно четыре вещи: поле из клеток вместо пикселей, змейка как очередь координат deque, один ход по таймеру раз в 150 мс и проверка двух столкновений, со стеной и с собственным телом. Ниже я собираю игру по шагам от пустого окна до счёта, ускорения и рестарта, каждый кусок кода запущен на pygame 2.6.1 и Python 3.12.

Если вы ещё не делали ни одной игры на этой библиотеке, начните с разбора игры на PyGame с нуля: там подробно про игровой цикл, а здесь я на нём не останавливаюсь.

Как устроена змейка и что понадобится

Рой пишет змейку на PyGame за ноутбуком

Змейка обманчиво простая: у неё нет ни физики, ни спрайтов. Вся игра строится на одной идее: поле поделено на клетки, и змейка живёт в координатах клеток, а не пикселей. Голова с координатой (15, 10) значит «пятнадцатая клетка слева, десятая сверху», а в пиксели это переводится только при отрисовке.

Из этого следует всё остальное:

  • ход змейки это сдвиг головы на одну клетку и удаление хвоста, тело хранить отдельно не нужно;
  • еда занимает ровно одну клетку, а значит проверка «съели» это сравнение двух кортежей;
  • столкновение с собой это вопрос «есть ли новая клетка головы в списке клеток тела».

Понадобится Python 3 и установленный PyGame 2, я проверял на Python 3.12 и pygame 2.6.1. Если import pygame падает с ModuleNotFoundError, загляните в инструкцию по установке PyGame, там разобраны все типовые причины. Я собираю игру в одном файле snake.py, без классов: для первого проекта функции и словарь состояния читаются проще, а переложить на классы вы сможете потом за полчаса.

Шаг 1: поле, константы и окно

Все размеры задаём в клетках и только один раз переводим в пиксели. Клетка 20 пикселей, поле 30 на 20 клеток, значит окно 600 на 400. Захотите поле больше, поменяете две цифры, и ни одна другая строка не пострадает.

import random
from collections import deque

import pygame

CELL = 20                 # размер клетки в пикселях
COLS, ROWS = 30, 20       # поле 30 × 20 клеток
WIDTH, HEIGHT = COLS * CELL, ROWS * CELL   # 600 × 400
FPS = 60
START_STEP_MS = 150       # пауза между ходами в начале игры
MIN_STEP_MS = 60          # быстрее этого змейка не ходит

BG = (24, 26, 32)
GRID = (36, 39, 46)
HEAD = (120, 220, 120)
TAIL = (30, 110, 60)
FOOD = (230, 80, 80)
TEXT = (235, 235, 235)

Обратите внимание на две пары констант. FPS это частота кадров, с которой мы перерисовываем экран и опрашиваем клавиатуру. START_STEP_MS это пауза между ходами змейки. Кадры и ходы разведены специально: экран обновляется 60 раз в секунду, а змейка ползёт 6–7 раз в секунду. Если привязать ход к кадру, как делают многие уроки через clock.tick(15), ускорить змейку можно только вместе с частотой кадров, а два быстрых нажатия попадают в один кадр, и первое перезаписывается вторым.

Окно создаём стандартно, как в разборе первого окна PyGame. Шрифт беру системный: SysFont("arial", 22) найдёт Arial на Windows и macOS, а на Linux без него подставит встроенный freesansbold, кириллицу оба показывают.

def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Змейка")
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("arial", 22)
Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединиться

Шаг 2: змейка как очередь клеток

Игровое поле змейки как доска с кубиками

Тело змейки это упорядоченный список клеток, где первый элемент голова. На каждом ходу мы добавляем клетку в начало и убираем с конца. Обычный список для этого плох: insert(0, x) сдвигает все элементы и стоит O(n). collections.deque делает appendleft() и pop() за O(1) с обоих концов, это прямо написано в документации Python. На поле 600 клеток разницу вы не почувствуете, но привычка выбирать структуру под операцию дороже любой змейки.

Всё состояние партии я храню в одном словаре, который создаёт функция new_game(). Так рестарт превращается в одну строку: вызвали функцию ещё раз, получили чистую партию.

def new_game():
    """Состояние новой партии: змейка из трёх клеток в центре, смотрит вправо."""
    cx, cy = COLS // 2, ROWS // 2
    snake = deque([(cx, cy), (cx - 1, cy), (cx - 2, cy)])
    return {
        "snake": snake,
        "direction": RIGHT,
        "next_direction": RIGHT,
        "food": spawn_food(snake),
        "score": 0,
        "alive": True,
        "paused": False,
    }

Змейка стартует из трёх клеток в центре и смотрит вправо. Кортеж направления это сдвиг по осям: RIGHT = (1, 0), UP = (0, -1). Минус у «вверх» не опечатка, в PyGame ось Y растёт вниз, как и во всех оконных библиотеках.

Отрисовка тоже простая: клетка переводится в прямоугольник умножением на CELL. Сегменты я крашу градиентом от светлой головы к тёмному хвосту, это две строки и сразу видно, куда змейка ползёт. Функция lerp смешивает два цвета в пропорции от 0 до 1.

def lerp(a, b, t):
    return tuple(int(a[i] + (b[i] - a[i]) * t) for i in range(3))

def draw(screen, font, state):
    screen.fill(BG)
    for x in range(0, WIDTH, CELL):
        pygame.draw.line(screen, GRID, (x, 0), (x, HEIGHT))
    for y in range(0, HEIGHT, CELL):
        pygame.draw.line(screen, GRID, (0, y), (WIDTH, y))

    fx, fy = state["food"]
    pygame.draw.rect(screen, FOOD, (fx * CELL, fy * CELL, CELL, CELL), border_radius=6)

    n = len(state["snake"])
    for i, (x, y) in enumerate(state["snake"]):
        color = lerp(HEAD, TAIL, i / max(n - 1, 1))
        pygame.draw.rect(screen, color, (x * CELL + 1, y * CELL + 1, CELL - 2, CELL - 2), border_radius=4)

    screen.blit(font.render(f"Счёт: {state['score']}", True, TEXT), (10, 8))

border_radius у pygame.draw.rect скругляет углы, параметр есть с версии 2.0. Отступ в один пиксель по краям клетки даёт зазор между сегментами, без него змейка сливается в сплошную полосу.

Шаг 3: движение и запрет разворота на 180°

Управление стрелками и WASD сводится к словарю «клавиша → направление». Но тут же прячется самая частая ошибка змейки, из-за которой игрок умирает от одного нажатия: змейка ползёт вправо, игрок жмёт «влево», голова идёт во вторую клетку тела, игра окончена. Разворот на 180° надо запрещать.

Запрещать нужно правильно. Направление я храню в двух полях: direction это куда змейка сделала последний ход, next_direction это что нажал игрок. Проверка разворота сравнивает нажатие с direction, а не с предыдущим нажатием.

UP, DOWN, LEFT, RIGHT = (0, -1), (0, 1), (-1, 0), (1, 0)
KEYS = {
    pygame.K_UP: UP, pygame.K_w: UP,
    pygame.K_DOWN: DOWN, pygame.K_s: DOWN,
    pygame.K_LEFT: LEFT, pygame.K_a: LEFT,
    pygame.K_RIGHT: RIGHT, pygame.K_d: RIGHT,
}

def turn(state, new_dir):
    """Запоминаем поворот, но запрещаем разворот на 180°."""
    dx, dy = state["direction"]
    if new_dir == (-dx, -dy):
        return
    state["next_direction"] = new_dir

Почему не сравнивать с next_direction? Змейка ползёт вправо, игрок за один ход успевает нажать «вверх» и «влево». Если проверять против последнего нажатия, «влево» пройдёт, ведь оно не противоположно «вверх». Но ход ещё не сделан, змейка всё ещё смотрит вправо, и на следующем ходу она развернётся в себя. У быстрых игроков это случается постоянно и выглядит как «игра глючит».

Сам ход начинается с того, что direction принимает накопленное нажатие, и только потом считается новая голова:

def step(state):
    """Один ход змейки. Возвращает True, если змейка жива."""
    state["direction"] = state["next_direction"]
    dx, dy = state["direction"]
    hx, hy = state["snake"][0]

Шаг 4: еда, рост и подсчёт очков

Еда это одна клетка. Спавнить её через randrange по всему полю нельзя: рано или поздно яблоко появится внутри змейки, и игрок не сможет его взять. Правильный способ: собрать список свободных клеток и выбрать случайную из них.

Рост делается бесплатно за счёт очереди. На каждом ходу голова добавляется в начало. Если голова попала на еду, хвост мы не удаляем, и змейка становится на клетку длиннее. Если не попала, хвост уходит, длина сохраняется. Никаких отдельных счётчиков «сколько ещё расти».

def spawn_food(snake):
    """Случайная СВОБОДНАЯ клетка: еда никогда не появится на теле."""
    free = [(x, y) for x in range(COLS) for y in range(ROWS) if (x, y) not in snake]
    return random.choice(free)

    state["snake"].appendleft(new_head)
    if new_head == state["food"]:
        state["score"] += 1
        state["food"] = spawn_food(state["snake"])
    else:
        state["snake"].pop()
    return True

Счёт это число съеденных яблок, оно лежит в том же словаре состояния и выводится в углу через font.render. Если хотите вывести его крупнее или добавить рекорд, механика та же, что в любом цикле while на Python: одна переменная, одно место, где она меняется.

Шаг 5: столкновения со стеной и с собой

Со стеной всё очевидно: новая голова должна лежать в диапазоне от 0 до COLS - 1 по X и от 0 до ROWS - 1 по Y. В клетках эта проверка занимает одну строку, в пикселях пришлось бы возиться с rect.right и rect.bottom, как в разборе Rect и столкновений в PyGame.

С собой чуть тоньше. Проверять надо не всё тело, а тело без последней клетки: хвост в этот же ход освободит свою клетку, и заход на неё законен. Если проверять всё тело целиком, змейка, свернувшаяся плотным кольцом, будет умирать там, где должна была проскочить.

    new_head = (hx + dx, hy + dy)

    hit_wall = not (0 <= new_head[0] < COLS and 0 <= new_head[1] < ROWS)
    # хвост в этот ход уйдёт с клетки, поэтому его из проверки исключаем
    body = list(state["snake"])[:-1]
    if hit_wall or new_head in body:
        state["alive"] = False
        return False

Проверку я сделал до того, как голова добавлена в очередь, поэтому при проигрыше змейка остаётся на экране целой, а не «въехавшей» в стену. Ситуацию с хвостом я прогнал отдельным тестом: змейка из четырёх клеток поворачивает на клетку уходящего хвоста и остаётся жива, а та же змейка длиной семь, где клетка занята серединой тела, погибает.

CheckroiCheckroiПодборка курсов по Python569 курсов • 51 школаСравните цены, школы, программу и найдите выгодные предложения по обучениюСравнить→ Ваня БуявецКанал основателя Checkroi Вани БуявцаЗабирайте промпты и обучение по нейросетям в моём Телеграм-каналеБольше 3 700 человек уже применяют Claude Code, ChatGPT и другие нейросети в работе, учёбе, бизнесе и жизниПерейти в канал

Шаг 6: скорость, пауза, конец игры и рестарт

Змейка, которая ползёт с одной скоростью, надоедает через минуту. Каждое яблоко срезает 3 мс с паузы между ходами: старт 150 мс, к тридцатому яблоку 60 мс, дальше не ускоряется. Это и есть выигрыш от того, что ход отвязан от кадра: скорость меняется одной функцией, а клавиатура по-прежнему опрашивается 60 раз в секунду.

Таймер ходов строится на clock.tick(FPS): метод возвращает миллисекунды с прошлого кадра, мы их накапливаем, и как только накопилось больше паузы, делаем ход и обнуляем счётчик.

Рестарт это state = new_game() по клавише R. Ни одной глобальной переменной сбрасывать не нужно, потому что всё состояние живёт в словаре. Пауза это флаг, при котором ходы не считаются, а экран продолжает рисоваться.

def step_ms(score):
    """Пауза между ходами: каждое яблоко ускоряет змейку на 3 мс."""
    return max(MIN_STEP_MS, START_STEP_MS - score * 3)


    if state["paused"] and state["alive"]:
        msg = font.render("Пауза — P, чтобы продолжить", True, TEXT)
        screen.blit(msg, msg.get_rect(center=(WIDTH // 2, HEIGHT // 2)))
    if not state["alive"]:
        msg = font.render(f"Игра окончена, счёт {state['score']}. R — заново, Esc — выход", True, TEXT)
        screen.blit(msg, msg.get_rect(center=(WIDTH // 2, HEIGHT // 2)))

    pygame.display.flip()

    state = new_game()
    since_move = 0
    running = True
    while running:
        dt = clock.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_r and not state["alive"]:
                    state = new_game()
                    since_move = 0
                elif event.key == pygame.K_p:
                    state["paused"] = not state["paused"]
                elif event.key in KEYS:
                    turn(state, KEYS[event.key])

        if state["alive"] and not state["paused"]:
            since_move += dt
            if since_move >= step_ms(state["score"]):
                since_move = 0
                step(state)

        draw(screen, font, state)

    pygame.quit()


if __name__ == "__main__":
    main()

Клавиша R работает только когда змейка мертва, иначе случайное нажатие в разгар партии обнулит счёт. Esc закрывает игру в любой момент, крестик окна тоже, за него отвечает событие QUIT.

Полный код игры

Вот весь snake.py целиком, 155 строк. Сохраните в файл (как завести и запустить его, показано в инструкции про файл Python в VS Code), запустите python snake.py, управление стрелками или WASD, P пауза, R новая партия, Esc выход. Если строк слишком много, чтобы разобрать их в одиночку, такой код удобно проходить с наставником на курсах по Python: змейка есть в программе почти каждого из них как первый проект.

import random
from collections import deque

import pygame

CELL = 20                 # размер клетки в пикселях
COLS, ROWS = 30, 20       # поле 30 × 20 клеток
WIDTH, HEIGHT = COLS * CELL, ROWS * CELL   # 600 × 400
FPS = 60
START_STEP_MS = 150       # пауза между ходами в начале игры
MIN_STEP_MS = 60          # быстрее этого змейка не ходит

BG = (24, 26, 32)
GRID = (36, 39, 46)
HEAD = (120, 220, 120)
TAIL = (30, 110, 60)
FOOD = (230, 80, 80)
TEXT = (235, 235, 235)

UP, DOWN, LEFT, RIGHT = (0, -1), (0, 1), (-1, 0), (1, 0)
KEYS = {
    pygame.K_UP: UP, pygame.K_w: UP,
    pygame.K_DOWN: DOWN, pygame.K_s: DOWN,
    pygame.K_LEFT: LEFT, pygame.K_a: LEFT,
    pygame.K_RIGHT: RIGHT, pygame.K_d: RIGHT,
}


def new_game():
    """Состояние новой партии: змейка из трёх клеток в центре, смотрит вправо."""
    cx, cy = COLS // 2, ROWS // 2
    snake = deque([(cx, cy), (cx - 1, cy), (cx - 2, cy)])
    return {
        "snake": snake,
        "direction": RIGHT,
        "next_direction": RIGHT,
        "food": spawn_food(snake),
        "score": 0,
        "alive": True,
        "paused": False,
    }


def spawn_food(snake):
    """Случайная СВОБОДНАЯ клетка: еда никогда не появится на теле."""
    free = [(x, y) for x in range(COLS) for y in range(ROWS) if (x, y) not in snake]
    return random.choice(free)


def turn(state, new_dir):
    """Запоминаем поворот, но запрещаем разворот на 180°."""
    dx, dy = state["direction"]
    if new_dir == (-dx, -dy):
        return
    state["next_direction"] = new_dir


def step(state):
    """Один ход змейки. Возвращает True, если змейка жива."""
    state["direction"] = state["next_direction"]
    dx, dy = state["direction"]
    hx, hy = state["snake"][0]
    new_head = (hx + dx, hy + dy)

    hit_wall = not (0 <= new_head[0] < COLS and 0 <= new_head[1] < ROWS)
    # хвост в этот ход уйдёт с клетки, поэтому его из проверки исключаем
    body = list(state["snake"])[:-1]
    if hit_wall or new_head in body:
        state["alive"] = False
        return False

    state["snake"].appendleft(new_head)
    if new_head == state["food"]:
        state["score"] += 1
        state["food"] = spawn_food(state["snake"])
    else:
        state["snake"].pop()
    return True


def step_ms(score):
    """Пауза между ходами: каждое яблоко ускоряет змейку на 3 мс."""
    return max(MIN_STEP_MS, START_STEP_MS - score * 3)


def lerp(a, b, t):
    return tuple(int(a[i] + (b[i] - a[i]) * t) for i in range(3))


def draw(screen, font, state):
    screen.fill(BG)
    for x in range(0, WIDTH, CELL):
        pygame.draw.line(screen, GRID, (x, 0), (x, HEIGHT))
    for y in range(0, HEIGHT, CELL):
        pygame.draw.line(screen, GRID, (0, y), (WIDTH, y))

    fx, fy = state["food"]
    pygame.draw.rect(screen, FOOD, (fx * CELL, fy * CELL, CELL, CELL), border_radius=6)

    n = len(state["snake"])
    for i, (x, y) in enumerate(state["snake"]):
        color = lerp(HEAD, TAIL, i / max(n - 1, 1))
        pygame.draw.rect(screen, color, (x * CELL + 1, y * CELL + 1, CELL - 2, CELL - 2), border_radius=4)

    screen.blit(font.render(f"Счёт: {state['score']}", True, TEXT), (10, 8))

    if state["paused"] and state["alive"]:
        msg = font.render("Пауза — P, чтобы продолжить", True, TEXT)
        screen.blit(msg, msg.get_rect(center=(WIDTH // 2, HEIGHT // 2)))
    if not state["alive"]:
        msg = font.render(f"Игра окончена, счёт {state['score']}. R — заново, Esc — выход", True, TEXT)
        screen.blit(msg, msg.get_rect(center=(WIDTH // 2, HEIGHT // 2)))

    pygame.display.flip()


def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Змейка")
    clock = pygame.time.Clock()
    font = pygame.font.SysFont("arial", 22)

    state = new_game()
    since_move = 0
    running = True
    while running:
        dt = clock.tick(FPS)
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_r and not state["alive"]:
                    state = new_game()
                    since_move = 0
                elif event.key == pygame.K_p:
                    state["paused"] = not state["paused"]
                elif event.key in KEYS:
                    turn(state, KEYS[event.key])

        if state["alive"] and not state["paused"]:
            since_move += dt
            if since_move >= step_ms(state["score"]):
                since_move = 0
                step(state)

        draw(screen, font, state)

    pygame.quit()


if __name__ == "__main__":
    main()

Что я проверял перед публикацией, чтобы вы не проверяли сами: разворот на 180° игнорируется, еда ни разу не появилась на теле, съеденное яблоко удлиняет змейку ровно на одну клетку, выход за стену и заход в середину тела заканчивают партию, R создаёт чистое состояние. Полный цикл прогнан с заскриптованными событиями: поворот, пауза, снятие паузы, второй поворот, рестарт, выход.

Частые ошибки и что с ними делать

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

Змейка умирает от одного нажатия

Разворот на 180° не запрещён или запрещён неправильно, против последнего нажатия вместо последнего хода:

# так делать нельзя: сравниваем с последним НАЖАТИЕМ, а не с последним ходом
if new_dir != (-next_direction[0], -next_direction[1]):
    next_direction = new_dir

Лечение из шага 3: два поля направления, проверка против direction.

Яблоко появляется внутри змейки

# еда может появиться внутри змейки
food = (random.randrange(COLS), random.randrange(ROWS))

Со змейкой в три клетки это случается редко, с длинной постоянно. Выбирайте из списка свободных клеток, как в spawn_food.

После нескольких рестартов игра тормозит или падает с RecursionError

# рекурсия: каждая новая партия вкладывается в предыдущую
def game_loop():
    ...
    if key == pygame.K_r:
        game_loop()

Каждая новая партия открывает ещё один уровень вложенности, и старые циклы никуда не деваются. Рестарт это пересоздание состояния внутри того же цикла, а не повторный вызов функции с циклом.

Змейку нельзя ускорить, а быстрые повороты не срабатывают

Ход привязан к кадру через clock.tick(10). Скорость змейки и частота опроса клавиатуры это одно число, и два нажатия за один кадр схлопываются в последнее. Разделите частоту кадров и паузу между ходами, как в шаге 1.

Змейка идёт вверх при нажатии «вниз»

Перепутан знак по оси Y. Вниз это (0, 1), вверх (0, -1): начало координат в левом верхнем углу окна.

Кириллица в счёте выводится квадратами

Загружен шрифт без русских символов, обычно через pygame.font.Font("какой-то.ttf"). SysFont("arial") и встроенный Font(None, 28) кириллицу поддерживают, я проверил обе строки в этой игре.

Что добавить, когда змейка работает

Рой радуется работающей змейке

База в 155 строк это фундамент, на котором удобно пробовать идеи. Три доработки, каждая на 10–20 минут.

Доработка Что менять Сложность
Проход сквозь стены Одна строка в step(): голова считается по модулю размера поля, проверка стены убирается Простая
Препятствия на поле Множество клеток-стен, они рисуются как сегменты и добавляются в проверку столкновений и в исключения при спавне еды Средняя
Таблица рекордов Читать и писать число в файл рядом со скриптом, показывать рядом со счётом Простая
Звук при поедании pygame.mixer.Sound и вызов play() в ветке, где растёт счёт Простая
Второй игрок Второй словарь состояния, вторая раскладка клавиш, проверка столкновений между двумя телами Сложная

Проход сквозь стены выглядит так:

new_head = ((hx + dx) % COLS, (hy + dy) % ROWS)   # вышли справа, вошли слева

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

from pathlib import Path

RECORD = Path("record.txt")
best = int(RECORD.read_text()) if RECORD.exists() else 0
...
if not state["alive"] and state["score"] > best:
    RECORD.write_text(str(state["score"]))

Препятствия чуть длиннее, но идея та же: клетки-стены живут в множестве и участвуют в проверке рядом с телом.

walls = {(5, y) for y in range(3, 12)} | {(x, 15) for x in range(10, 24)}
...
if hit_wall or new_head in body or new_head in walls:

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

Где освоить Python и разработку игр системно

Змейка учит трём вещам: хранить состояние в одном месте, отделять логику от отрисовки и проверять граничные случаи вроде уходящего хвоста. Дальше по этой дороге лежат структуры данных, ООП и настоящие движки, и здесь уже удобнее идти по программе, а не по разрозненным урокам. В каталоге собраны курсы по Python с проектами в портфолио, а для тех, кто нацелен именно на игры, отдельная подборка курсов по разработке игр.

КурсШколаСтоимость со скидкойВ рассрочкуДлитель­ностьОбзор курса от Checkroi
Профессия «Python-разработчик»
Перейти на сайт курса
SkillboxSkillbox157 335 ₽5987 ₽/мес.10 месяцевОбзор курса
Fullstack-разработчик на Python
Перейти на сайт курса
НетологияНетология137 000 ₽7125 ₽/мес.21 месяцОбзор курса
ДО Профессия Python-разработчик
Перейти на сайт курса
GeekBrainsGeekBrains149 001 ₽3167 ₽/мес.10 месяцевОбзор курса
Автоматизированное тестирование на Python
Перейти на сайт курса
SkillboxSkillbox122 226 ₽4108 ₽/мес.9 месяцевОбзор курса
Профессия Python-разработчик
Перейти на сайт курса
SkillFactorySkillFactory154 044 ₽4279 ₽/мес.10 месяцевОбзор курса

Больше программ — в полном каталоге курсов по Python

Часто задаваемые вопросы

Почему змейка умирает при быстром нажатии стрелок?

Скорее всего, в игре нет запрета на разворот на 180° или он сравнивает нажатие с предыдущим нажатием, а не с последним ходом. Змейка ползёт вправо, вы быстро жмёте «вверх» и «влево», и на следующем ходу голова идёт в собственное тело. Храните два поля: направление последнего хода и следующее нажатие, и запрещайте разворот относительно первого.

Как сделать, чтобы змейка проходила сквозь стены?

Считайте новую голову по модулю размера поля: ((hx + dx) % COLS, (hy + dy) % ROWS), и уберите проверку выхода за границы. Змейка, ушедшая за правый край, появится слева. Проверку столкновения с собственным телом оставьте.

Почему еда появляется внутри змейки?

Потому что координаты выбираются случайно по всему полю через randrange. Соберите список свободных клеток, которых нет в теле змейки, и выбирайте случайную из этого списка. На поле 30 на 20 клеток это 600 элементов, выбор занимает доли миллисекунды.

Как ускорять змейку по мере роста счёта?

Отвяжите ход от кадра. Экран рисуйте 60 раз в секунду через clock.tick(60), а ход делайте, когда накопленное время превысило паузу между ходами. Паузу считайте от счёта, например 150 мс минус 3 мс за каждое яблоко, с нижней границей 60 мс.

Как сделать рестарт без перезапуска программы?

Держите всё состояние партии в одном объекте, который создаёт функция вроде new_game(), и по клавише R вызывайте её ещё раз внутри того же игрового цикла. Повторный вызов функции с циклом внутри обработчика клавиши даёт вложенные циклы и со временем RecursionError.

Можно ли написать змейку на Python без PyGame?

Да, на tkinter или curses из стандартной библиотеки. Логика та же: поле из клеток, очередь координат, ход по таймеру и две проверки столкновений. Меняется только отрисовка и способ читать клавиши. Подробнее про выбор библиотек для игр в обзоре языков и движков для разработки игр.

Читайте Checkroi первым в Google
Добавьте Checkroi в избранные источники — и наши разборы курсов и обзоры школ будут показываться выше в вашей выдаче Google.
Оставить комментарий
0 комментариев
Форма комментария

Оставьте комментарий

Напишите, что думаете. Нам важно ваше мнение!