Вот мы и подошли к заключительной части нашей серии уроков по созданию Тетриса средствами C++\SFML (первая часть здесь, а вторая здесь). Сегодня мы рассмотрим реализацию механизма проверки тетрамино на выход за пределы игрового поля, сделаем генерацию рандомных типов фигурок и их цветов, добавим возможность ускорить падение тетрамино, будем уничтожать полученную из тетрамино линию и установим рамки игрового поля вместе с фоном.
Проверка границ и случайная генерация тетрамино
Функция проверки тетрамино на выход за границы игрового поля довольно-таки простая и состоит из двух условий if():
первое условие if() проверяет, не вышли ли мы за границы поля слева, справа и снизу. Т.к. мы движемся сверху вниз, то выйти за границу сверху мы не можем, соответственно, проверять её нет смысла.
второе условие if() проверяет, свободна ли сейчас ячейка, расположенная на пути движения, или занята другими тетрамино.
Код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// Массив фигурок-тетрамино int figures[7][4] = { 1,3,5,7, // I 2,4,5,7, // Z 3,5,4,6, // S 3,5,4,7, // T 2,3,5,7, // L 3,5,7,6, // J 2,3,4,5, // O }; // Проверка на выход за границы игрового поля bool check() { for (int i = 0; i < 4; i++) if (a[i].x < 0 || a[i].x >= N || a[i].y >= M) return 0; else if (field[a[i].y][a[i].x]) return 0; return 1; } int main() { ... } |
Теперь реализуем рандомную генерацию тетрамино с помощью генератора случайных чисел. Для этого подключаем заголовочный файл time.h:
1 |
#include <time.h> |
А в теле функции main() дописываем следующую строку:
1 |
srand(time(0)); |
Далее переходим к секции кода, отвечающей за горизонтальное перемещение тетрамино. Здесь нам потребуется добавить вызов функции проверки границ, которую мы описали ранее:
1 2 3 4 5 6 7 8 9 10 11 |
// Горизонтальное перемещение for (int i = 0; i < 4; i++) { b[i] = a[i]; a[i].x += dx; } // Если вышли за пределы поля после перемещения, то возвращаем старые координаты if (!check()) { for (int i = 0; i < 4; i++) a[i] = b[i]; } |
Рассмотрим этот код детально. Сначала мы перемещаем координаты массива a[]
во вспомогательный массив b[]
:
1 |
b[i] = a[i]; |
Далее на игровой доске делаем «шаг влево» или «шаг вправо»:
1 |
a[i].x += dx; |
А затем проверяем, не вышли ли мы за пределы игрового поля. Если вышли, то возвращаем старые координаты из вспомогательного массива b[]
:
1 2 3 4 |
if (!check()) { for (int i = 0; i < 4; i++) a[i] = b[i]; } |
То же самое проделываем и с секцией «Вращение»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Вращение if (rotate) { Point p = a[1]; // указываем центр вращения for (int i = 0; i < 4; i++) { int x = a[i].y - p.y; //y-y0 int y = a[i].x - p.x; //x-x0 a[i].x = p.x - x; a[i].y = p.y + y; } // Если вышли за пределы поля после поворота, то возвращаем старые координаты if (!check()) { for (int i = 0; i < 4; i++) a[i] = b[i]; } } |
А теперь секция «Тик таймера». Здесь мы добавим цикл for, в котором, при помощи генератора случайных чисел, будем задавать тип и цвет тетрамино:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Движение тетрамино вниз (тик таймера) if (timer > delay) { for (int i = 0; i < 4; i++) { b[i] = a[i]; a[i].y += 1; } if (!check()) { for (int i = 0; i < 4; i++) field[b[i].y][b[i].x] = colorNum; colorNum = 1 + rand() % 7; int n = rand() % 7; for (int i = 0; i < 4; i++) { a[i].x = figures[n][i] % 2; a[i].y = figures[n][i] / 2; } } timer = 0; } dx = 0; rotate = 0; |
Не забываем, что нужно объявить переменные, которые будут определять горизонтальное перемещение, вращение и цвет тетрамино:
1 2 3 |
int dx = 0; // горизонтальное перемещение bool rotate = 0; // вращение int colorNum = 1; // цвет |
Ловим баг
Далее у автора видео идет довольно спорный момент. Он, видимо, посчитав, что алгоритм первоначального задания тетрамино теперь перенесен в секцию «Движение тетрамино вниз (Тик таймера)», легким взмахом руки удаляет из проекта следующий кусок кода:
1 2 3 4 5 6 7 8 9 |
int n = 3; // указываем тип тетрамино // Первое появление тетрамино на поле? if (a[0].x == 0) for (int i = 0; i < 4; i++) { a[i].x = figures[n][i] % 2; a[i].y = figures[n][i] / 2; } |
Из-за чего мы получаем неприятный баг, о котором я расскажу ниже. А пока, продолжая объяснение видеоролика, отредактируем нижеследующую секцию кода «Отрисовка»:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
dx = 0; rotate = 0; //----ОТРИСОВКА----// // Указываем цвет фона - белый window.clear(Color::White); for(int i=0;i<M;i++) for (int j = 0; j < N; j++) { if (field[i][j] == 0) continue; sprite.setPosition(j * 18, i * 18); window.draw(sprite); } |
Наконец, после всех проделанных действий, мы можем скомпилировать и запустить нашу программу, и увидеть тот баг, о котором я говорил чуть выше:
Багом является одинокий квадратик, который постоянно присутствует на поле. Данная ошибка возникает из-за того, что в самом начале игры, при создании первой фигурки-тетрамино, не выполняется код, который отвечает за следующее:
1 2 |
a[i].x = figures[n][i] % 2; a[i].y = figures[n][i] / 2; |
Для начала, чтобы исправить этот баг, нам нужно создать новую переменную beginGame
. И сюда же перенесем инициализацию переменной n
, при помощи которой задается тип тетрамино:
1 2 3 4 5 6 |
// Переменные для горизонтального перемещения и вращения int dx = 0; bool rotate = 0; int colorNum = 1; bool beginGame = true; int n = rand() % 7; |
Далее частично продублируем секцию кода «Движение тетрамино вниз (Тик таймера)», чтобы появление первой фигурки-тетрамино происходило корректно. Для этого сделаем следующие изменения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// Движение тетрамино вниз (Тик таймера) if (timer > delay) { for (int i = 0; i < 4; i++) { b[i] = a[i]; a[i].y += 1; } if (!check()) { for (int i = 0; i < 4; i++) field[b[i].y][b[i].x] = colorNum; colorNum = 1 + rand() % 7; n = rand() % 7; for (int i = 0; i < 4; i++) { a[i].x = figures[n][i] % 2; a[i].y = figures[n][i] / 2; } } timer = 0; } // Первое появление тетрамино на поле? if (beginGame) { beginGame = false; n = rand() % 7; for (int i = 0; i < 4; i++) { a[i].x = figures[n][i] % 2; a[i].y = figures[n][i] / 2; } } dx = 0; rotate = 0; //----ОТРИСОВКА----// // Указываем цвет фона - белый window.clear(Color::White); |
Код, расположенный внутри блока if(beginGame)
, вызывается только один раз при старте приложения. Благодаря ему задается первоначальный тип тетрамино. В результате этих действий стартовый тетрамино отображается корректно, и бага больше нет:
Ускоряем движение тетрамино вниз
Если вы уже знаете, куда поставить тетрамино, и не хотите ждать, пока оно своим ходом доберется до этого положения, то логичным будет ускорить падение фигурки по нажатию на кнопку «стрелка вниз», как это и реализовано в многочисленных вариантах тетриса. Для этого добавим проверку на нажатие соответствующей клавиши на клавиатуре. И, если условие выполняется, уменьшим задержку между «тиками» до 0.05 секунды, тем самым заставив тетрамино падать быстрее:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// … ну тогда может это стрелка вправо? else if (event.key.code == Keyboard::Right) dx = 1; } // Нажали на кнопку "Вниз"? Ускоряем падение тетрамино if (Keyboard::isKeyPressed(Keyboard::Down)) delay = 0.05; // Горизонтальное перемещение for (int i = 0; i < 4; i++) { b[i] = a[i]; a[i].x += dx; } // Если вышли за пределы поля после перемещения, то возвращаем старые координаты if (!check()) { for (int i = 0; i < 4; i++) a[i] = b[i]; } |
При этом нужно не забыть вернуть первоначальное значение задержки, иначе новые тетрамино также будут двигаться вниз с ускорением:
1 2 3 4 5 6 7 8 |
if (beginGame) { //**********ЧАСТЬ ПРОПУЩЕНА**********// } dx = 0; rotate = 0; delay = 0.3; |
Разукрашиваем тетрамино
До этого момента все тетрамино имели одинаковый цвет. Пришло время их немного разукрасить:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//----ОТРИСОВКА----// // Указываем цвет фона - белый window.clear(Color::White); for (int i = 0; i < M; i++) for (int j = 0; j < N; j++) { if (field[i][j] == 0) continue; sprite.setTextureRect(IntRect(field[i][j] * 18, 0, 18, 18)); sprite.setPosition(j * 18, i * 18); window.draw(sprite); } for (int i = 0; i < 4; i++) { // Разукрашиваем тетрамино sprite.setTextureRect(IntRect(colorNum * 18, 0, 18, 18)); // Устанавливаем позицию каждого кусочка тетрамино sprite.setPosition(a[i].x * 18, a[i].y * 18); // Отрисовка спрайта window.draw(sprite); } |
Результат выполнения программы:
Уничтожение полученной из тетрамино линии
Рассмотрим вопрос о реализации функции проверки на наличие заполненной тетрамино линии, которую по правилам тетриса нужно удалять. В этом нам поможет цикл for, с помощью которого мы снизу вверх пройдемся по массиву field[][]
, который представляет наше игровое поле, и проверим каждый элемент массива на равенство нулю.
Если элемент field[i][j]
не равен нулю (т.е. эта позиция поля уже занята), то увеличиваем счетчик на единицу (count++
) и присваиваем данный элемент самому себе (field[k][j] = field[i][j];
). При этом, мы как бы с двух сторон ползем вверх по массиву field[][]
, увеличивая сначала индекс i
и, если у нас есть пробелы в текущей строке (count < N
), индекс k
. И опять присваиваем каждый элемент строки самому себе. Тем самым текущая строка не изменяется.
Но если условие if (count < N)
не выполняется (т.е. в данной строке все места заняты), то индекс k
не меняется. И тогда, при очередном присваивании field[k][j] = field[i][j];
, индекс k
не будет равен индексу i
. А это означает, что элементы верхней строки будут присвоены элементам нижней строки. Тем самым наше игровое поле «сдвинется» вниз на одну строку:
1 2 3 4 5 6 7 8 9 10 11 12 |
//----ПРОВЕРКА ЛИНИИ----// int k = M - 1; for (int i = M - 1; i > 0; i--) { int count = 0; for (int j = 0; j < N; j++) { if (field[i][j]) count++; field[k][j] = field[i][j]; } if (count < N) k--; } |
Картинка для понимания процесса:
Результат выполнения программы:
Установка текстуры фона
Осталось дело за малым: установить фоновую текстуру и рамку игрового поля. Для этого создадим две дополнительные переменные текстур: texture_background
и texture_frame
; и соответствующие им две переменные спрайтов: sprite_background
и sprite_frame
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
srand(time(0)); RenderWindow window(VideoMode(320, 480), "The Game!"); // Создание и загрузка текстур Texture texture, texture_background, texture_frame; texture.loadFromFile("C:\\dev\\SFML_Tutorial\\images\\tiles.png"); texture_background.loadFromFile("C:\\dev\\SFML_Tutorial\\images\\background.png"); texture_frame.loadFromFile("C:\\dev\\SFML_Tutorial\\images\\frame.png"); // Создание спрайтов Sprite sprite(texture), sprite_background(texture_background), sprite_frame(texture_frame); // Вырезаем из спрайта отдельный квадратик размером 18х18 пикселей sprite.setTextureRect(IntRect(0, 0, 18, 18)); // Переменные для горизонтального перемещения и вращения int dx = 0; bool rotate = 0; int colorNum = 1; bool beginGame = true; int n = rand() % 7; |
Добавим отрисовку новых спрайтов в секцию «Отрисовка». Т.к. наши фигуры появляются от края окна, а текстура фона немного смещена к центру, то нужно немного сместить тетрамино, чтобы всё выглядело красиво. Для этого воспользуемся функцией sprite.move(28, 31)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
//----ОТРИСОВКА----// // Указываем цвет фона - белый window.clear(Color::White); // Отрисовка фона window.draw(sprite_background); for (int i = 0; i < M; i++) for (int j = 0; j < N; j++) { if (field[i][j] == 0) continue; sprite.setTextureRect(IntRect(field[i][j] * 18, 0, 18, 18)); sprite.setPosition(j * 18, i * 18); sprite.move(28, 31); // смещение window.draw(sprite); } for (int i = 0; i < 4; i++) { // Разукрашиваем тетрамино sprite.setTextureRect(IntRect(colorNum * 18, 0, 18, 18)); // Устанавливаем позицию каждого кусочка тетрамино sprite.setPosition(a[i].x * 18, a[i].y * 18); sprite.move(28, 31); // смещение // Отрисовка спрайта window.draw(sprite); } // Отрисовка фрейма window.draw(sprite_frame); // Отрисовка окна window.display(); |
Окончательная версия нашего Тетриса:
GitHub / Часть №3: Создание игры «Тетрис» на С++/SFML — Исходный код
Заключение
Ну вот и всё, друзья! Наше приложение готово! Конечно, здесь можно еще много чего реализовать, например: отображение игровых очков, функцию подсчета очков, музыкальное сопровождение, разные уровни сложности с динамическим увеличением скорости тетрамино и т.д. Но это уже вы можете попробовать сделать сами, добавляя новый функционал или изменяя текущий. Я же хочу сказать вам большое спасибо за ваше внимание и интерес к моим статьям.
И до скорых встреч!
Дмитрий, большое спасибо за урок!
Хорошо, что акцент сделан именно на функциональности библиотеки SFML а не на ООП (классы, прегрузки и т.д.). Все кто внимательно изучил курс, сейчас вполне смогут перестроить программу в стиль ООП. Но нужно ли это именно для текущего проекта? К тому же в мире много критики ООП.
«Чем быстрее вы забудете ООП, тем лучше для вас и ваших программ» 😉
Очень хорошо, что не все шаги описаны пунктуально-идеально, иначе создание программы сводится к конструктору лего, а подумать стоит. Для всех кто изучает курс очень советую читать коментарии других учеников.
Следующий шаг, наверное, совместить SFML и MFC для установки дополнительных настроек.
Единственный удручающий момент по использованию SMFL только один — каждое изменение состояния влечет за собой ПОЛНУЮ перерисовку экрана. Но, видимо, для такой простой программы это не существенно. Ресурсы современных копьютеров позволяют. Или всё не так однозначно?
Большое спасибо за уроки.
Не в обиду автору кода, не все мне в нем понравилось. Поэтому решил сделать все по своему с самого начала и добавил "конец игры".
main.cpp
Tetramino.h
Tetramino.cpp
И еще такой момент, что при условии когда тетрамино пересекает координату M внизу, то оно не остается внизу а респаунится в точке 0, вместо того чтобы оставаться внизу и появляться другой фигуре одновременно
Подскажите пожалуйста, не могу понять почему при возвращении 0 функции чек, не выполняются условия к возвращению к предыдущим координатам?
А возможно ли увеличить размер фигур тетриса без изменения масштаба окна? Если да то как это сделать?
Здравствуйте! Очень понравился ваша игра и решил сделать свою. Пишу первый раз такой проект (игру Тетрис) . Я имею одну проблему, не могу понять как можна увеличить игровое поле для тетромина .
Пытался изменить координаты поля
Пробовал что-то изменять в массиве и в функциях которые отвечают за пределы выхода поля тетрамина , ничего не помогло. Буду очень благодарен за помощь 🙂
Здравствуйте! Очень хороший урок, а Вы не подскажите, как сделать так, что когда окошко растягиваешь, все внутри не растягивалась?
Можно добавить в цикл while (window.pollEvent(event)) строчки:
Спасибо большое.
Добрый день,
Понравился урок и Ваша реализация тетриса.
Решил несколько модифицировать код в учебных целях и сделал автоповорот фигур по таймеру во время падения. Получилось уже достаточно сложно и забавно 🙂
Поставил перед собой следующую задачу сделать более плавный автоповорот падающих фигур (как пример на 30*), но не нашёл способ этого сделать. Добавление функции split.Rotation(30) после
Не приводит к нужному результату, так как все 4 splite поворачиваются по отдельности на 30 градусов. Меня же интересует поворот этой фигуры целиком на 30 градусов.
По объединению splite никакой информации не нашёл. Есть ли какой-либо способ это реализовать?
P.S.: Я понимаю, что фигуры не должны падать на поле под углом 30, 60 и т.д. градусов, но в процессе их падения хотелось бы сделать вот такую "анимацию". В полёте фигуры вращаются с шагом в 30 градусов, а падают всегда в правильной ориентации.
Как это можно реализовать?
В общем переписывал программу в своём стиле, чтоб легче было ориентироваться, полностью под ООП не переписывал, но класс для tetramino добавил( только для своего удобства). По ходу решил такие проблемы: подпрыгивание tetramino при нарушении боковых границ, возврат в начало поля при вращении tetramino у левой границы, добавил конец игры(можно переписать под обновление поля), избавился от "магических чисел". В любом случае код сырой, но главное что стабильный и работает.
main.cpp
Tetramino.h
Tetramino.cpp
А как ты добавил конец игры?
Добрый день, подскажите пожалуйста, куда обратить внимание, чтоб понять почему у нас не исчезают уже упавшие блоки? Все серии перечитал, но видимо глаза уже "замылились" от чего не замечаю 🙂
Если мне не изменяет память, то за установку блоков отвечает код из самого конца нашей программы:
Но перед этим, в коде есть несколько проверок на выход за границы игрового поля при помощи функции check(). Если проверка не проходит, то возвращаются старые координаты a[i].x и a[i].y. Вроде так 🙂
Здравствуйте!
Как я понял в игре нет конца игры. И получается, что если не двигать тетрамино, они сначала ставятся друг на друга, а потом накладываются друг на дружку. При попытке вывести тетрамино вправо, вывести можно только "палку" (4 вертикальный блока). Можно конечно каждый раз перезапускать, но огрешность есть. Не могли бы Вы написать недостающий код?
Ось метод для перевірки кінця гри:
Обновлено:
Не очень понятно куда и как его надо вставить(
>> Т.к. мы движемся сверху-вниз, то выйти за границу сверху мы не можем, соответственно, проверять её нет смысла.
Но ведь выход за границу сверху это конец игры, и в текущем варианте фигуры просто будут бесконечно появляться поверх уже имеющихся.
И как тогда будет выглядеть условие проигрыша, а то что-то сходу так и не могу это осознать?
Большое спасибо за статьи. Тетрис работает.
Огромное спасибо вам. Все просто и ясно, прям спасли меня.
Но вот этот момент не совсем понятен
>if (field[i][j] == 0) continue;
>sprite.setPosition(j * 18, i * 18);
>window.draw(sprite);
В логической модели нашего игрового поля пустая клетка имеет нулевое значение. Это значит, что на никакого спрайта на ней не должно находиться. Поэтому, если мы попали на такую клетку, то просто пропускаем итерацию. Если же это не пустая клетка — то с помощью sprite.SetPosition() устанавливаем на нее спрайт и отрисовываем его.
P.S.: И вам большое спасибо за уделенное внимание 🙂
Здравствуйте. Попробую объяснить покороче, что мне не понравилось:
1) Нет пояснения, что проверку if (!check) нужно добавить и в блоке с тиком таймера. Вертикальные координаты меняются как раз там. В коде проверка есть, но пояснения этому не наблюдается.
2) В коде тика таймера мы также сначала присваиваем координаты кубиков вспомогательному массиву (если не ошибаюсь, этот код тоже оставлен без пояснений). Но нигде потом не делаем обратное присваивание. Но зато используем вспомогательные. Всё работает, но как-то это некрасиво. Или хотя бы пояснение этому моменту нужно.
3) Очень не хватает нормального пояснения изменению блока "Отрисовка". Я имею в виду появление блока
в котором, насколько я поняла, мы рисуем уже приземлившиеся фигуры. При первом появлении этого куска кода, вторая часть кода с циклом for (прорисовка текущего тетрамино?) исчезла. Если её не прописать, то будет просто белый экран. Из-за отсутствия пояснений этим делам, я сначала так и получила белый экран, пришлось разбираться вдумчиво. В следующей демонстрации кода прорисовки (для разукрашивания тетрамино) пропущенный кусок опять появился.
4) В пояснении про проверку полной линии ничего не сказано про индекс k, из-за этого всё пояснение трудно понять (но я справилась). Почему начинаем с k=M-1, а не с k=M тоже не совсем очевидно.
У меня в итоге всё получилось, как и в статье, но из-за этих упущений осталось смазанное впечатление. Вроде бы и работает, но нет уверенного понимания назначения и порядка некоторых строк кода, а значит и нет ощущения, что я бы смогла написать такое самостоятельно. Здесь мало комментариев, поэтому могу предположить, что я такая не одна. Скопировать и вставить код можно, а вот разобраться полностью, чтобы потом при случае повторить — совсем другое дело. Мне не хватило пояснений.
Добрый день.
1)В самом начале статьи идет подробное описание алгоритма и назначение функции check(). Что она нужна для проверки выхода тетрамино за границы игрового поля.
Далее, рассматривается добавление функции check() в секцию кода "горизонтальное перемещение" (с добавлением необходимых комментариев). Что вполне логично.
Затем идет указание, что аналогичное добавление нужно сделать и для следующей секции (дословно — "То же самое проделываем с секцией «Вращение»"). Что также вполне объяснимо и понятно, т.к. при вращении фигуры тетрамино может выйти за границы поля.
Следующая секция — "Движение тетрамино вниз (тик таймера)". Снова встречается функция check(). И тут читатель перестаёт понимать, зачем мы функцию check() и в эту секцию добавили? Ну такое себе…
2)Чуть позже добавлю в статью более подробное описание алгоритма данного пункта.
3)Насчет "исчезновения" второй части кода с циклом for. Она как была там, так и осталась. Никуда не исчезала.
Сам двойной цикл for() довольно простой, ведь в нем всего 3 строчки кода, две из которых — это уже известные функции, а третья — сравнение текущего элемента с 0. Не понимаю, в чем могла возникнуть сложность при разборе с данным моментом. Но, если требуется, я добавлю описания к этому куску кода.
4)Насчет индекса k=M-1 и проверки линии. Мы же начинаем со дна нашего игрового поля и поднимаемся вверх (см. соответствующие картинки в этом пункте). Если, например, есть вот такое объявление массива — int array[M] размера M, то array[M-1] — это будет последний его элемент (т.к., в C++ нумерация элементов массива начинается с 0, а не с 1). Аналогичная ситуация и с двумерными массивами (читать — таблицами). Если есть объявление двумерного массива — int array[M][N], то мы получим таблицу из M строк и N столбцов. Как обратиться к элементам последней строки такой таблицы? Да так же, как и в случае с одномерным массивом, а именно — array[M-1][i], где 0<= i < N. Вот откуда и берется индекс k=M-1. Подобные моменты относятся к базовым (и самым простым) понятиям C++.
(Советую еще раз посмотреть уроки #75, #76, #78 на Ravesli.com)
>>но я справилась
Это похвально.
——————————————————————————
В итоге:
>>У меня в итоге всё получилось, как и в статье, но из-за этих упущений осталось смазанное впечатление.Вроде бы и работает, но нет уверенного понимания назначения и порядка некоторых строк кода, а значит и нет ощущения, что я бы смогла написать такое самостоятельно.
Да нет здесь каких-то таких уж криминальных упущений. Есть некоторые моменты, которые, в принципе, можно было бы и расписать несколько подробнее. Есть моменты, связанные с ограниченными возможностями средств для вёрстки статьи. Были моменты, в которых вы не смогли разобраться из-за недостатка опыта. Одно наложилось на другое, в итоге получили то, что получили. И это нормально.
Здравствуйте. У меня возникла небольшая проблема: при повороте фигуры у левого края, она возвращается в начальное положение (0,0).
В других частях стакана работает все хорошо. Как думаете в чем может быть проблема? Кстати, у меня изначально(до добавления функции check() ) фигура не проходила за левую границу стакана, а через правую проходила.
Я разобрался с этой проблемой))
Так опишите хотя бы в двух словах: в чем была проблема и как решили? 🙂
Я подозреваю, что этот баг вызван недостаточно полной проверкой первого появления тетрамино. Это скорее актуально для второй части статьи, так как при появлении beginGame всё меняется. Попробуйте запустить код из ваших исходников для части 2 и сделать пару вращений — Вы увидите, что тетрамино вернётся наверх так как выполнится (a[0].x == 0), мне пришлось временно добавлять дополнительную проверку (a[0].x == 0 || a[0].y == 0) или что-то в таком духе.