4,7K подписчиков

Пишем Сапёра на C + SDL #2: Структура программы

591 прочитал

Первая часть:

Код для этой части находится в ветке init на github. Вы можете смотреть там все файлы онлайн и также скачать зип-архив всей ветки.

Традиционно я начинаю разработку игры с её подробного описания. Но не в этом случае. Сошлёмся на то, что Сапёр – простая и всем известная игра, так что дизайн-документ для неё я составлять не буду.

Первая часть: Код для этой части находится в ветке init на github. Вы можете смотреть там все файлы онлайн и также скачать зип-архив всей ветки.

Я хочу в этом цикле сделать знакомство с самим языком C, но тогда получится, что я пишу не про разработку игры. Поэтому я решил сделать так: начало материала посвящается разбору языка, а конец – разработке игры. Если что – напишите, как лучше.

Рассмотрим, из чего состоит программа на языке C, и чем она отличается от других языков, таких как Python, Java, Javascript, PHP.

В проекте есть один файл – minesweeper.c, который является скелетом программы. Его мы и рассмотрим.

(К сожалению, #Дзен-идиот превращает знак "решётка" в тэг, поэтому придётся напрячь воображение.)

Самой первой мы видим инструкцию #define

С её помощью можно объявлять константы. Примерно то же можно делать в PHP и JavaScript с помощью const, но define умеет гораздо больше. С ней можно писать целые макросы, вплоть до изменения операторов самого языка.

В нашем простейшем случае мы объявляем константу SDL_MAIN_HANDLED, у которой даже нет значения. Она просто есть, как логический флаг. Она никак не участвует в самой программе и предназначена для управления компиляцией. В данном случае она сообщает, что компиляция должна вестись под Windows определённым способом. Детали нам пока совсем не важны, лишь бы работало. Подчеркну – это чисто заморочка SDL, она нужна только для SDL, только один раз в жизни, поэтому на ней задерживаться не будем.

Далее идут инструкции #include

Это аналог import в Python и Java, или include в PHP.

С помощью include мы включаем в свою программу описания функций из внешних библиотек, которые будем в ней использовать. Эти описания находятся в т.н. заголовочных файлах с расширением .h (header).

В данном случае мы включили в программу описания функций библиотеки SDL2, что вполне естественно, и описания из библиотеки stdio (стандартный ввод-вывод).

Имена .h-файлов написаны в угловых скобках <>. Это значит, что компилятор будет искать их в стандартном каталоге include, путь к которому мы настроили в предыдущей части. Если же мы хотим указать файл .h из нестандартного каталога, то путь к нему надо записывать в кавычках.

Наконец, начинается функция:

int main(int argc, char* argv[])

В Python, JavaScript и PHP программу можно начать писать сразу же, но в C (и отдалённо похоже в Java) программа начинается с функции main.

Это нужно потому, что скомпилированная программа в машинных кодах загружается непосредственно операционной системой непосредственно в оперативную память, и ОС после того, как загрузила программу, должна её собственно запустить, то есть передать в процессор адрес памяти, с которого нужно начать работать. Так вот main() – это и есть точка запуска, где начинает работать именно наш код.

Далее, в C и переменные, и функции имеют тип.

int main() – это значит, что функция должна возвращать результат в виде целого числа (int). Так как эта функция вызывается операционной системой, то результат вернётся в операционную систему и может быть обработан уже после того, как программа завершится (например, в bat-файле).

Кроме возвращаемого результата, main() имеет и входные параметры.

Это int argc (argument count) – целочисленный параметр, который сообщает число аргументов, и – сейчас будет сложновато, но разберёмся потом – массив символьных указателей char* argv[] (argument values).

Что это за аргументы?

Программу из командной строки можно запустить с различными настроечными ключами, например так (от балды совершенно):

minesweeper.exe -timer=no -sound=no -level=pro

Всё это и попадает из командной строки в массив argv[] функции main(). Первый аргумент это всегда сама программа, т.е. minesweeper.exe. Второй аргумент это -timer=no, третий это -sound=no, и т.д. Мы можем их записать и так: /timer, /sound, это вообще всем по барабану, кроме нас самих. Аргументы попадут в main() в таком виде, в каком мы их написали, а дальше мы разбираем их самостоятельно и как хотим.

К счастью, нам это не нужно, так что argc и argv можно полностью игнорировать.

Далее, создаются две переменные:

SDL_Window *window;
SDL_Event event;

Переменная-указатель (*) window имеет тип SDL_Window, а переменная event имеет тип SDL_Event.

Что это за типы, откуда они берутся?

В C нет классов и объектов, но есть структуры. Структура это просто кусок памяти, логически поделённый на поля разной (или одинаковой, как угодно) длины, и каждому полю назначено своё имя. То есть это нечто вроде объекта. А имя всей структуры и является типом.

Структура с именем SDL_Window описывает группу полей графического окна. Структура с именем SDL_Event описывает группу полей события. Эти структуры описаны в файле SDL.h, который мы ранее включили в программу.

Таком образом, переменная window будет хранить (ссылку на) информацию об окне, а переменная event – информацию о событиях. Они уже созданы, под них выделена память, но они пока бездействуют.

Далее по тексту вы можете заметить, что многие имена начинаются с SDL_. Это делается для того, чтобы при включении разных библиотек названия их функций случайно не пересеклись. Приставка SDL_ делает имена типов и функций из библиотеки SDL уникальными.

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

Представление данных

Поле Сапёра очевидно представлено целочисленной матрицей (двумерным массивом), с которой мы уже хорошо знакомы по таким проектам, как Robots и Питон на Питоне.

Каждый элемент матрицы может находиться в следующих состояниях:

  1. Есть бомба / Нет бомбы
  2. Закрыт / Открыт
  3. Нет флажка / Стоит флажок / Стоит вопрос / Неправильный флажок

Эти состояния могут существовать одновременно. Например: клетка с бомбой, закрыта, и на ней стоит флажок.

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

  1. 0: пусто, 1: бомба
  2. 0: открыто, 2: закрыто
  3. 0: нет флажка, 4: стоит флажок, 8: стоит вопрос, 16: неправильный флажок

Посмотрим на примере, что получается:

  • Клетка с бомбой: 1
  • Закрыта: 2
  • Стоит флажок: 4

1 + 2 + 4 = 7. Это будет значение клетки.

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

Первая часть: Код для этой части находится в ветке init на github. Вы можете смотреть там все файлы онлайн и также скачать зип-архив всей ветки.-2

Все эти состояния занимают 5 бит, то есть мы можем записать любую комбинацию в 1 байт данных, и останутся ещё 3 бита в запасе.

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

Значит, один элемент игрового поля будет типа char (символьный тип, размер 1 байт).

Далее, игровое поле имеет размер. Например, 10*10. Но в игре Сапёр есть несколько размеров – чем больше размер, тем сложнее игра. В любой момент времени игрок может выбрать новый размер.

На Python, JavaScript, Java, PHP мы могли бы просто пересоздавать массив каждый раз с новым размером. При этом ранее созданный массив просто удалялся бы сборщиком мусора.

В языке C нет сборщика мусора. Каждый раз, когда мы создаём переменную, мы несём персональную ответcтвенность за состояние памяти. Есть следующие случаи, которые необходимо рассмотреть – статически выделенная память, куча и стек. Что мы и сделаем в следующем выпуске, попутно с продолжением разработки игры.

Читайте дальше:

Читайте также: