Привет, мой юный повелитель клавиатуры!
Помните те времена, когда графика в играх была настолько крутой, что квадратик с усами считался человеком, а круг — сокровищем? Добро пожаловать в эпоху ретро!
Сегодня мы напишем «Сокобан» (или «Sokoban») — игру, которая научит вас ценить порядок. Вам предстоит взять на себя роль усатого грузчика (назовём его Аркадий), который пришёл на склад, а там бардак! Ящики не на местах.
Ваша задача — поменять профессию с «программист» на «кладовщик» и расставить всё по полочкам, используя только стрелки и черепашью логику.
Поехали! Будем кодить и страдать вместе с Аркадием.
Инвентарь грузчика
Нам понадобится:
- Python (версии, где черепашка ещё не ушла на пенсию).
- Модуль turtle (встроенный, хватать не надо).
- Чувство прекрасного (чтобы выбрать цвет ящиков).
- Терпение (ящики тяжелые, сами не двигаются).
Шаг 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
Если собрать всё вместе, получится игра. Но будьте готовы к тому, что Аркадий будет страдать, пока вы не расставите ящики по золотым кружочкам.
Типичные ошибки и их философия:
- Ящики проходят сквозь стены: Вы забыли проверить is_wall() для нового положения ящика. Аркадий теперь не грузчик, а фокусник. Но нам нужны реалии, а не цирк.
- Игрок проходит сквозь ящики: Вы проверили ящик, но забыли, что он не пускает игрока дальше. Игрок не должен стоять на одной клетке с ящиком, если только он не йог.
- Ящик проваливается сквозь пол: Проверьте, что у всех объектов вызван penup(). Иначе они рисуют линии, как пьяные художники.
- Победа не наступает: Возможно, ваши ящики стоят на целях, но вы проверяете не те координаты. Или цели стали невидимыми (ушли в подполье).
Эпилог
Поздравляю! Теперь вы знаете, как создавать головоломки, от которых у вас будет дёргаться глаз, но рука будет тянуться к стрелкам снова и снова.
Запускайте игру и попробуйте завести ящики в угол. Там они и останутся навечно. Прямо как те носки, которые вы теряете при стирке.
Если Аркадий начал разговаривать с ящиками — значит, вы пересидели за кодом. Срочно выключайте компьютер и идите на улицу! Там тоже есть стены, но они обычно не синие.
P.S. Если ваш Сокобан вдруг превратился в симулятор толкания стен — переустановите Python. Или отпустите Аркадия на волю.