Skip Navigation
pythontalk_ru
Наука и Технологии вчера
Telegram
Тетрис на Python: классика жанра или архитектурный хоррор?


Тетрис (ну и Змейка) — это «Hello World» в мире геймдева. Казалось бы, испортить его сложно, но автор этого репозитория очень старался. Разбираем проект, который преподносится как «обучающий материал для начинающих». Да, код рабочий, проект законченный, даже видео на YouTube есть. Но на деле там учат вредным привычкам.

1️⃣ Отсутствие точки входа
В main.py код просто навален в корень файла. Никакого if __name__ == "__main__":. Если вы попробуете импортировать что-то из этого файла (хотя зачем?), у вас сразу инициализируется Pygame и откроется окно.

2️⃣ Беда с пространством имен
В game.py мы видим прекрасное: from blocks import *.
Запомните: каждый раз, когда вы используете import *, вы забиваете пространство имен мусором. Какие классы прилетели? Откуда? Никто не знает.

3️⃣ Класс-оркестр
Класс Game — это и швец, и жнец. Он управляет логикой, считает очки, загружает звуки, проигрывает музыку и... отрисовывает блоки.
Нарушение SRP (Single Responsibility Principle) на лицо. Логика игры не должна знать о существовании pygame.mixer или о том, как рисовать прямоугольники.

# В недрах Game.__init__
self.rotate_sound = pygame.mixer.Sound("Sounds/rotate.ogg")
pygame.mixer.music.load("Sounds/music.ogg")

Хотите поменять звуковую библиотеку? Удачи переписывать всё ядро игры.

4️⃣ ООП головного мозга: Наследование ради... ничего
В blocks.py мы видим классическую ошибку: создание семи разных классов (LBlock, JBlock и т.д.), которые наследуются от Block только ради того, чтобы в __init__ вписать словарик с координатами.

Это классический оверинжиниринг. Все эти классы не имеют уникального поведения, только разные данные.

Как надо: Один класс Block, который принимает тип фигуры или конфиг при инициализации. Данные отдельно, логика отдельно.

5️⃣ Класс Position — зачем?
class Position:
def __init__(self, row, column):
self.row = row
self.column = column

Создавать целый класс для хранения двух целых чисел — это избыточно. В Python есть namedtuple, dataclasses или, в конце концов, просто кортежи (row, col).

6️⃣ Магические числа и хардкод
if self.next_block.id == 3:
self.next_block.draw(screen, 255, 290)
elif self.next_block.id == 4:
self.next_block.draw(screen, 255, 280)

Это «костыльный» UI в чистом виде. Вместо того чтобы вычислить центр области предпросмотра, автор просто подогнал координаты под конкретные ID блоков. Добавите новый блок — и вся верстка поплывет.

7️⃣ Обработка счета из эпохи мамонтов
В game.py мы видим это:

def update_score(self, lines_cleared, move_down_points):
if lines_cleared == 1:
self.score += 100
elif lines_cleared == 2:
self.score += 300
# ... и так далее


Как надо: Обычный словарь или список коэффициентов сделал бы этот код в одну строку. elif-цепочки для простых соответствий — верный признак того, что автор не умеет в структуры данных.

🧑‍⚖️ Вердикт:
Как учебный проект — пойдет. Если учитесь по таким туториалам, помните: их цель — показать результат за 20 минут видео, а не научить вас писать нормальный код. Не тащите эти паттерны в продакшен.

#жарим_код
pythontalk_ru
Наука и Технологии 23 февр
Telegram
@shadow_0771 попросил дать обратную связь по его коду в рамках изучения ООП
@shadow_0771 попросил дать обратную связь по его коду в рамках изучения ООП.

Ну чтож... #жарим_код!

RPG в консоли — это база. Код выполняет свою задачу, но написан так, что любая попытка его масштабировать вызовет БООООООООЛЬ. Разберем самые важные проблемы 👇

1️⃣ Ctrl+C, Ctrl+V наследование
Смотрим на иерархию брони:
class Armor(Item):
def __init__(self, name, category, strength=None, value_strength=None...): # и еще 100500 аргументов
super().__init__(name, category)
# ...

class Helmet(Armor):
def __init__(self, ...):
super().__init__(...)

class Chestplate(Armor):
# Копия Helmet

class Greaves(Armor):
# Копия Chestplate

Все эти классы (Helmet, Chestplate, Greaves, Boots) — абсолютно идентичны. Они не добавляют ни новых атрибутов, ни нового поведения. Они не делают ничего, кроме вызова super().__init__.

ООП создано не для того, чтобы описывать каждый физический предмет в мире отдельным классом. Если сущности отличаются только названием категории — это должен быть один класс Armor, у которого есть атрибут slot_type (в идеале через Enum).

2️⃣ Конструктор Франкенштейна
Посмотрите, как создается предмет:
crown = Helmet('Шлем Господства', 'Шлем', 'Сила', 5, 'Ловкость', 7, 'Интеллект', 3)

Никогда не хардкодьте названия статов в сигнатуру метода. Используйте словари.
Вместо этой простыни параметров, предмет должен принимать stats={'strength': 5, 'agility': 7, 'intellect': 3}.

3️⃣ Инцест классов
Класс Characteristic принимает в себя hero, а потом делает вот это:
for item in self._hero.slots_equipment.values():
if item:
if hasattr(item, 'value_strength') and item.value_strength:
self.attributes['strength'] += item.value_strength

Это называется "Tight Coupling" (жесткая связность). Класс характеристик лезет грязными руками в инвентарь героя, проверяет, есть ли там предметы, а потом через hasattr (что само по себе костыль в 99% случаев) пытается вытащить из них статы.

Герой должен сам опрашивать свою экипировку и передавать итоговые модификаторы в систему характеристик. А сейчас хвост виляет собакой.

4️⃣ Использование Exceptions для логики
В методе equip_armor видим такое:
try:
if key not in self.slots_equipment:
print('Нет такого слота.')
# ... логика ...
except KeyError:
print(f'Предмет не найден')

Во-первых, перехват широкого KeyError замаскирует вам реальные баги в коде (например, опечатку в словаре внутри try). Во-вторых, исключения — для исключительных ситуаций, а не для проверки наличия предмета в инвентаре. Для этого есть метод словаря .get().

В общем, для начала вместо 10 бесполезных классов брони и монструозных конструкторов можно сделать хотя бы так:

from dataclasses import dataclass
from enum import Enum

class EquipmentSlot(Enum):
HEAD = "Шлем"
CHEST = "Нагрудник"
WEAPON = "Оружие"

@dataclass
class Equipment:
name: str
slot: EquipmentSlot
stats_bonus: dict[str, int]

# Создание предмета:
crown = Equipment(
name='Шлем Господства',
slot=EquipmentSlot.HEAD,
stats_bonus={'strength': 5, 'agility': 7, 'intellect': 3}
)

И всё. Никаких дублей кода, защита от опечаток в слотах через Enum, и расширяемая система статов, куда можно завтра добавить хоть "Удачу", не переписывая __init__ у десятка классов.

ООП — это не когда у вас на каждую сущность во вселенной заведен отдельный класс. ООП — это про управление сложностью и состоянием.


Но для одного месяца обучения — это абсолютно нормальный этап эволюции.

📖 Читаем:
- Когда класс в Python — это зло: 6 случаев, когда вы усложняете себе жизнь
- Принципы SOLID в ООП с примерами на Python

Управлять можно с клавиатуры

Работает в лентах: голосование, переход между постами и разворот текста.

W
S
A
D
F
Топ авторов за месяц
Свежие комментарии