Тетріс для початківців)

Що стало причиною для написання цієї статті?
1 Питання, що ставляться користувачів цього сайту про те як написати тетріс.
2 Нещодавно написаний мною незрозуміло з яких причин і для яких цілей тетріс. У дитинстві у мене не були спроб писати тетріси. Трохи подорослішавши вирішив реалізувати дитячу мрію :)
3 Банальне бажання показати себе і всьому світові який я чудовий програміст і так я круто вмію писати тетріси :)
Все хороший тріпатися перейдемо до справи.

Для початку нам потрібно представити з яких частин буде складатися наша гра. У тетрісі є фігурки (class Figure) і поле (class Field). Ще нам потрібен мир (class World) він буде містити поле, 2 фігури (падаючу і наступну), окуляри, рівень і ще дещо яку інформацію.

Давайте подумаємо з чого складатиметься наша фігурка. Є кілька варіантів. Ми виберемо простіше. Візьмемо за основу що наша фігурка це якась матриця розмірності N на M містить порожні і заповнені клітини. Тепер потрібно визначиться що з себе представляє клітина. По суті від неї нам потрібно знати заповнена вона чи ні. Значить для подання осередку нам досить типу «BOOL». Так само фігурка містить інформацію про колір і позицію на ігровому полі. З усього сказаного ось яка структура у нас виходить:

Що тут нам не подобається? По-перше сама матриця. Оскільки при такому підході нам потрібно робити її такого розміру, що б в неї помістилася найбільша фігурка. Найбільшою фігуркою у нас буде складатися в довжину з 4 клітинок і в ширину з 1. В тетрісі є можливість перевертає фігурки звідси ми отримуємо квадратну матрицю розмірністю 4 на 4. Недоліком такої матриці є «складність» перевірки при вертикальних і горизонтальних переміщеннях. Це пов'язано з тим що частина фігури буде виходити за межі нашого поля. Виходячи з цього переробимо нашу матрицю з статичної в динамічну.

В цьому варіанті ми замінили тип BOOL на UNSIGNED INT це зроблено було бо STL специфічно реалізовує STD :: VECTOR (Про це ви почитаєте де ні будь в іншому місці). Наша матриця стала одновимірної. Так як конструкція STD :: VECTOR> Виглядає трохи «загрозливо» у нас буде вона одновимірної. Створилися незручності з отриманням потрібного нам елементу матриці вирішуються дуже просто всього лише додаванням однієї маленької функції:

Усе. Ми знову можемо працювати з нашою матрицею як з двовимірним масивом.
У класі Figure відбулися ще дещо які зміни. Пропав колір нашої фігури. Про це ми поговоримо в іншому розділі.

І так! Наше поле має розмірність N на M власне як і фігура. Як і фігура воно повинно містити інформацію про те порожня клітка чи ні. А так же колір цієї клітини. Колір клітини ми будемо кодувати в unsigned int (0xAARRGGBB). Тепер ніша клітина зберігати в собі і колір і інформацію про те порожня вони чи ні. Якщо в клітці не нуль означає вона заповнена якщо нуль то порожня. Ось так виглядає наше поле:

Ви скажете що воно мало чим відрізняється від фігури. І будете абсолютно праві. Пропоную все загальне з наших 2 класів об'єднати в одну сутність:

Тепер ми бачимо що наше поле і фігура успадковуються від GameObj. А в класах Figure і Field залишилися тільки ті речі які їх від кардинально відрізняють один від одного. З останнього лістингу класу Figure, Field видно що в них «чарівним» чином ми додали більше деталей. Давайте поговоримо тепер про них.

Докладний розбір Figure

Думаю тут пояснювати особливо нічого і так все зрозуміло. Хочу лише зауважити, що клас фігурці містить покажчик на клас поля. Цей покажчик він отримує в конструкторі. При такому підході ми легко можемо викликати потрібні нам функції класу Field що власне ми з успіхом і робимо.
RotateLeft функція повертає нашу фігурку в ліво.

Як це працює. Створюємо нову фігурку Figure newFigure (field_, height_, width_). Заносимо в нову фігуру дані з нашої матриці з урахуванням повороту. Встановлюємо позицію і перевіряємо на колізію з полем. Якщо сталася колізія, то повернути фігурку неможливо. Якщо немає, то з чистою совістю міняємо дані нашої фігурки на дані поверненою.
Update тут ми рухаємо нашу фігурку по горизонталі і вертикалі а так само перевіряємо на можливість її подальшого падіння. Якщо падати більше нікуди, то дані з нашої фігурки переносяться на поле і фігурка позначається як впала "fly_ = false;"

Докладний розбір Field

В цьому класі розповідати особливо нема про що. Просто перерахую найцікавіші функції і розповім що вони роблять.
Hit - перевіряє чи є куди падати фігурці або під нею вже зайняте простору
Collision - перевірка на перетин з уже зайнятими клітинами поля і фігурки.
SetFigure - переносить дані фігурки на поле.
KillLine - перевіряє на можливість зняття лінії. Якщо струмовий є знімає лінію, обсипає верхні клітинки вниз, додає окуляри і збільшує лічильник знятих ліній.

Власне ми до сих пір не торкнулися класу World. Ось він:

Чи не чого цікавого в цьому класі немає. Виняток функція GenerateNext.

Вивчивши цю функцію можна помітити що тут випадково створюються і не започатковано 5 класів у поминання про які ніде не було. Давайте розглянемо що це за класи. Ось один з них:

Ми бачимо, що він успадковується від класу Figure з яким ми вже добре знайомі і складається з одного конструктора. Ось що відбувається в конструкторі:

У цьому конструкторі і створюється наша фігурка. Ті клітини які заповнені нулем будуть порожніми інші будуть, відображається на екрані. Створюючи такі класи ми створюємо різні фігури. Ось і всі хлопці!

Написаний тут тетріс є не закінченим продуктом. Він містить баги і деякі недоробки. Я звичайно ж міг усе виправити але подумав що б читачеві не було так нудно і він міг розважити себе покласти на нього це завдання якщо звичайно йому це треба :) Так що дерзайте!
Складно сказати чи зрозумілий процес створення гри з цієї статі але якщо не зрозумілий на цей випадок додаються вихідні.
Дякую всім до дочитав до сюди і удачі на нелегкій ниві;)

Схожі статті