Найти в Дзене

Заголовок: Сокобан на Python и Turtle: Как я заставил усатого грузчика страдать (и перекладывать ящики) целую вечность

Привет, мой юный повелитель клавиатуры! Помните те времена, когда графика в играх была настолько крутой, что квадратик с усами считался человеком, а круг — сокровищем? Добро пожаловать в эпоху ретро! Сегодня мы напишем «Сокобан» (или «Sokoban») — игру, которая научит вас ценить порядок. Вам предстоит взять на себя роль усатого грузчика (назовём его Аркадий), который пришёл на склад, а там бардак! Ящики не на местах. Ваша задача — поменять профессию с «программист» на «кладовщик» и расставить всё по полочкам, используя только стрелки и черепашью логику. Поехали! Будем кодить и страдать вместе с Аркадием. Нам понадобится: Как обычно, начинаем с призыва духов. import turtle Никаких random и time сегодня. Только хардкор, только геометрия и логика. Наш герой будет работать на захламлённом складе. Сделаем антураж: чёрный фон, чтобы никто не видел пыли. # Настройка окна
win = turtle.Screen()
win.title("Сокобан: Тяжелая жизнь грузчика Аркадия")
win.bgcolor("black")
win.setup(width=600, heig
Оглавление

Привет, мой юный повелитель клавиатуры!

Помните те времена, когда графика в играх была настолько крутой, что квадратик с усами считался человеком, а круг — сокровищем? Добро пожаловать в эпоху ретро!

Сегодня мы напишем «Сокобан» (или «Sokoban») — игру, которая научит вас ценить порядок. Вам предстоит взять на себя роль усатого грузчика (назовём его Аркадий), который пришёл на склад, а там бардак! Ящики не на местах.

Ваша задача — поменять профессию с «программист» на «кладовщик» и расставить всё по полочкам, используя только стрелки и черепашью логику.

Поехали! Будем кодить и страдать вместе с Аркадием.

Инвентарь грузчика

Нам понадобится:

  1. Python (версии, где черепашка ещё не ушла на пенсию).
  2. Модуль turtle (встроенный, хватать не надо).
  3. Чувство прекрасного (чтобы выбрать цвет ящиков).
  4. Терпение (ящики тяжелые, сами не двигаются).

Шаг 1. Импортируем всё и немного больше

Как обычно, начинаем с призыва духов.

import turtle

Никаких random и time сегодня. Только хардкор, только геометрия и логика.

Шаг 2. Создаём сцену (Склад имени "Рога и Копыта")

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

# Настройка окна
win = turtle.Screen()
win.title("Сокобан: Тяжелая жизнь грузчика Аркадия")
win.bgcolor("black")
win.setup(width=600, height=600)
win.tracer(0) # Отключаем автоматическое обновление, чтобы ящики не прыгали, как мячики

Шаг 3. Знакомьтесь, Аркадий (Наш Герой)

Нарисуем героя. Пусть он будет зелёным квадратиком с усами (ну, хотя бы квадратиком — усы мы дорисуем в воображении).

# Игрок (Аркадий)
player = turtle.Turtle()
player.speed(0)
player.shape("square")
player.color("lime green")
player.penup()
player.goto(0, 0) # Пока стоит в центре, чешет репу

Шаг 4. Ящики (Коробки с секретами)

Ящики будут серыми. Они тяжёлые, поэтому мы их будем пихать коленкой. В нашем коде ящики будут храниться в списке, чтобы мы могли их пихать по одному.

А ещё у нас будут точки-цели (хранилища), куда эти ящики нужно доставить.

# Список для ящиков
boxes = []

# Список для целей (хранилищ)
targets = []
Пока списки пустые. Мы их наполним позже.

Шаг 5. Рисуем стены (Где Аркадий будет биться головой)

Склад не бесконечный, там есть стены. Пусть стены будут синими и непреодолимыми (прямо как нежелание идти на работу в понедельник).

Создадим функцию для рисования стены.

# Функция создания стены
def create_wall(x, y):
wall = turtle.Turtle()
wall.speed(0)
wall.shape("square")
wall.color("blue")
wall.penup()
wall.goto(x, y)
return wall

# Стены будут храниться в списке
walls = []

Шаг 6. Создаём уровень (Карта барахолки)

Это самое сложное. Нам нужна карта. Давайте сделаем простой уровень 5x5, но через стены.

Я предлагаю схему:

  • Стены по краям.
  • Внутри Аркадий, пара ящиков и цели.

Мы будем использовать координаты. Так как шаг у нас 20 пикселей (размер квадратика), то и двигаться будем на 20.

Давайте вручную расставим объекты для теста.

# Создаем стены по периметру и пару внутренних
for i in range(-200, 220, 20):
# Верхняя и нижняя стена
if i != 0: # Чтобы оставить проход? Нет, сделаем глухой забор
walls.append(create_wall(i, 200))
walls.append(create_wall(i, -200))

# Левая и правая стена
for i in range(-200, 220, 20):
if i != 0:
walls.append(create_wall(-200, i))
walls.append(create_wall(200, i))

# Ставим игрока в центр
player.goto(0, 0)

# Создаем ящик (пусть будет справа от игрока)
box1 = turtle.Turtle()
box1.speed(0)
box1.shape("square")
box1.color("brown")
box1.penup()
box1.goto(40, 0)
boxes.append(box1)

# Создаем цель (место, куда нужно притащить ящик)
target1 = turtle.Turtle()
target1.speed(0)
target1.shape("circle") # Круглая, чтоб выделялась
target1.color("gold")
target1.penup()
target1.goto(80, 0)
targets.append(target1)

# Создадим второй ящик для интереса
box2 = turtle.Turtle()
box2.speed(0)
box2.shape("square")
box2.color("brown")
box2.penup()
box2.goto(40, 40)
boxes.append(box2)

target2 = turtle.Turtle()
target2.speed(0)
target2.shape("circle")
target2.color("gold")
target2.penup()
target2.goto(80, 40)
targets.append(target2)

Шаг 7. Функции движения (Аркадий, шевели булками!)

Теперь научим Аркадия двигаться. Но просто так ходить нельзя — он должен толкать ящики. А если ящик упирается в стену или другой ящик — стоять и материться.

Напишем функцию проверки стены.

def is_wall(x, y):
for wall in walls:
if wall.xcor() == x and wall.ycor() == y:
return True
return False

def is_box(x, y):
for box in boxes:
if box.xcor() == x and box.ycor() == y:
return box # Возвращаем сам ящик, если нашли
return None

Теперь функции движения. Они будут сложнее, чем в змейке.

def move_up():
# Куда хочет пойти игрок
new_x = player.xcor()
new_y = player.ycor() + 20

# Проверка на стену
if is_wall(new_x, new_y):
return # Аркадий бьется лбом

# Проверка на ящик
box = is_box(new_x, new_y)
if box:
# Куда поедет ящик
box_new_x = box.xcor()
box_new_y = box.ycor() + 20

# Если за ящиком стена или другой ящик - не двигаем
if is_wall(box_new_x, box_new_y) or is_box(box_new_x, box_new_y):
return # Ящик застрял, Аркадий пыхтит бесполезно

# Двигаем ящик
box.goto(box_new_x, box_new_y)

# Двигаем игрока
player.goto(new_x, new_y)

def move_down():
new_x = player.xcor()
new_y = player.ycor() - 20

if is_wall(new_x, new_y):
return

box = is_box(new_x, new_y)
if box:
box_new_x = box.xcor()
box_new_y = box.ycor() - 20
if is_wall(box_new_x, box_new_y) or is_box(box_new_x, box_new_y):
return
box.goto(box_new_x, box_new_y)

player.goto(new_x, new_y)

def move_left():
new_x = player.xcor() - 20
new_y = player.ycor()

if is_wall(new_x, new_y):
return

box = is_box(new_x, new_y)
if box:
box_new_x = box.xcor() - 20
box_new_y = box.ycor()
if is_wall(box_new_x, box_new_y) or is_box(box_new_x, box_new_y):
return
box.goto(box_new_x, box_new_y)

player.goto(new_x, new_y)

def move_right():
new_x = player.xcor() + 20
new_y = player.ycor()

if is_wall(new_x, new_y):
return

box = is_box(new_x, new_y)
if box:
box_new_x = box.xcor() + 20
box_new_y = box.ycor()
if is_wall(box_new_x, box_new_y) or is_box(box_new_x, box_new_y):
return
box.goto(box_new_x, box_new_y)

player.goto(new_x, new_y)

Шаг 8. Управление с клавиатуры (Кнопки пота и мозолей)

Подключаем стрелки.

python

win.listen()
win.onkeypress(move_up, "Up")
win.onkeypress(move_down, "Down")
win.onkeypress(move_left, "Left")
win.onkeypress(move_right, "Right")

Шаг 9. Проверка победы (Наконец-то домой!)

Нужно проверять, все ли ящики стоят на целях. Создадим функцию check_win().

def check_win():
for box in boxes:
box_on_target = False
for target in targets:
if box.xcor() == target.xcor() and box.ycor() == target.ycor():
box_on_target = True
break
if not box_on_target:
return False
return True

Шаг 10. Игровой цикл (Мучительно, но весело)

Теперь запускаем бесконечный цикл, где мы обновляем экран и проверяем, не победил ли Аркадий (и не пора ли ему на пенсию).

# Игровой цикл
while True:
win.update()

if check_win():
# Рисуем поздравление (ну, хоть какая-то радость)
congrats = turtle.Turtle()
congrats.color("white")
congrats.penup()
congrats.goto(0, 0)
congrats.write("УРА! АРКАДИЙ ИДЕТ ДОМОЙ!", align="center", font=("Arial", 24, "bold"))
break # Выходим из цикла, игра окончена

time.sleep(0.1) # Небольшая задержка, чтобы процессор не плакал

win.mainloop()

Подождите, мы забыли импортировать time! Давайте исправим.

import time

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

А вот что будет, когда Аркадий всё расставит по своим местам
А вот что будет, когда Аркадий всё расставит по своим местам

Типичные ошибки и их философия:

  1. Ящики проходят сквозь стены: Вы забыли проверить is_wall() для нового положения ящика. Аркадий теперь не грузчик, а фокусник. Но нам нужны реалии, а не цирк.
  2. Игрок проходит сквозь ящики: Вы проверили ящик, но забыли, что он не пускает игрока дальше. Игрок не должен стоять на одной клетке с ящиком, если только он не йог.
  3. Ящик проваливается сквозь пол: Проверьте, что у всех объектов вызван penup(). Иначе они рисуют линии, как пьяные художники.
  4. Победа не наступает: Возможно, ваши ящики стоят на целях, но вы проверяете не те координаты. Или цели стали невидимыми (ушли в подполье).

Эпилог

Поздравляю! Теперь вы знаете, как создавать головоломки, от которых у вас будет дёргаться глаз, но рука будет тянуться к стрелкам снова и снова.

Запускайте игру и попробуйте завести ящики в угол. Там они и останутся навечно. Прямо как те носки, которые вы теряете при стирке.

Если Аркадий начал разговаривать с ящиками — значит, вы пересидели за кодом. Срочно выключайте компьютер и идите на улицу! Там тоже есть стены, но они обычно не синие.

P.S. Если ваш Сокобан вдруг превратился в симулятор толкания стен — переустановите Python. Или отпустите Аркадия на волю.