Создайте IDE с Godot

Создайте IDE с Godot

6 апреля 2022 г.

Я хотел создать игру, но создал базовую IDE для фэнтезийной консоли Click4.


Опубликовано 30 марта 2022 г. · 15 минут чтения


Я хотел создать игру, я написал компилятор.


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


В этой статье вы узнаете больше о фэнтезийных консолях, процессорах, ассемблере, компиляторах и Godot.


Никаких сложных деталей, обещаю.


ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Моя реализация, вероятно, очень наивна и далека от того, чем могли бы быть настоящий компилятор и IDE. Цель — понять основы.


Не стесняйтесь читать статью на моем личном сайте (https://darenn.github.io/en/click4_ide/).


Почему Click4 — это настоящий вызов.


Click4 использует виртуальные «картриджи» для хранения своих игр. Картридж на самом деле является изображением , файлом .png, который может прочитать консоль. Каждый пиксель этого изображения будет интерпретироваться консолью как инструкция, цвет, значение, звук.


Например, черный пиксель будет прочитан либо как «ничего не делать», либо как черный цвет, либо как значение 0. Значение каждого пикселя будет зависеть от предшествующих ему пикселей.


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


Создание игры прямо в консоли Click 4


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


  • 4-битный ЦП (16 возможных значений, от 0 до 15)

  • Только 13 операций на ассемблере, сложный язык, очень близкий к машине.

  • Память 4096 4-битных чисел, которая должна содержать все (код, оперативную память, спрайты).

  • Ни стека, ни кучи, поэтому сложное управление памятью.

Чтобы преуспеть в создании игры на этой консоли, я решил создать простую IDE. Да, это звучит немного преувеличенно, но это для развлечения.


Как преобразовать код в изображение?


Click4 считывает каждый пиксель изображения один за другим и переводит его в инструкцию сборки или значение. Как Click4 интерпретирует каждый пиксель


Сначала мой план был прост:


  1. Уметь запрограммировать игру, написав операции на ассемблере в текстовом редакторе.

  1. Преобразуйте этот текст в изображение, читаемое Click4. Мы хотим перейти от кода к пикселям

Просто напишите программу, которая считывает каждую строку кода, связывает ее с нужным цветом и рисует его на изображении. Назовем это парсером.


Но, как вы догадались из названия, я не просто так это сделаю...


Годо запрограммировать редактор?


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


На самом деле редактор Godot сам запрограммирован на Godot. Так что уже есть много виджетов и систем для управления графическим интерфейсом.


Всего за 1 час у меня уже было базовое приложение для ввода текста.


IDE Click4


Все автоматически реагирует благодаря системе контейнеров Godot. И уже есть нода TextEdit с множеством параметров (отображать номера строк, отображать миникарту кода, выделять текущую строку и т.д...).


Я не буду подробно описывать реализацию IDE в Godot. Важно отметить, что у меня не было проблем с выполнением всего, что мне было нужно, с Godot.


Как я пришел к созданию языка и его компилятора


Слишком много простых ошибок для обработки


Я делал много простых ошибок; забывая параметр, используя неправильный оператор и т.д...


Я решил улучшить свой парсер, чтобы он анализировал код и помогал быстро находить простые ошибки. Ошибки в отладчике


Сложно создавать условия или циклы


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


Но с Click4 все по-другому. Операция "jmp" принимает позицию (x, y) на изображении в качестве параметра.


Давайте еще немного усложним? Окончательное изображение имеет разрешение 64x64 пикселя. Таким образом, мы могли бы представить, например, что хотим перейти на позицию (35, 36).


Однако наш ЦП 4-битный, поэтому мы не можем представить число больше 15. Нам нужно использовать 2 блока памяти (2 4-битных числа) для представления числа больше 15.


Поэтому я не собираюсь читать вам лекцию о двоичном коде, но если мы хотим перейти к (35, 36), мы должны написать эту инструкцию.


джмп 2 3 2 4


И это еще не все. Как только мы добавляем инструкцию в наш код, мы должны обновить адрес перехода.


Позже мы увидим, как я реализовал метки для решения этой проблемы.


Недостаточно операций


Вот список из 13 операций сборки, доступных для Click4:


  • 0 nop : _no op_eration (ничего не делать)

  • 1 set &arg1 arg2 : Установить содержимое регистра, определенного ARG1, со значением ARG2.

  • 2 copy &arg1 &arg2 : Копировать содержимое регистра, определенного параметром ARG2, в содержимое регистра, определенного параметром ARG1.

  • 3 inc &arg1 : регистр приращения, определенный параметром ARG1.

  • 4 dec &arg : Регистр уменьшения, определенный параметром ARG1.

  • 5 nand &arg1 &arg2 &arg3 : NAND значения регистров, определенных ARG2 и ARG3, и сохранить в регистре, определенном ARG1.

  • 6 crsz &arg1 : Увеличение счетчика программы на 2, если регистр, определенный параметром ARG1, равен нулю.

  • 7 jmp &arg1 &arg2 &arg3 &arg4 : Изменить программный счетчик на позицию X(ARG1,ARG2) Y(ARG3,ARG4). Обратите внимание, что каждая координата состоит из двух кусочков, чтобы можно было перемещаться по всему экрану.

  • 8 rjmp arg1 : Увеличение счетчика программ на ARG1 плюс 1.

  • 9 загрузить: загрузить содержимое X(R1+R2), Y(R3+R4) в R0 (X и Y используют два полубайта).

  • 10 сохранить: сохранить содержимое R0 в X(R1+R2), Y(R3+R4).

  • 11 input &arg1 : Копировать значения WASD или Up, Right, Down, Left в регистр, определенный ARG1.

  • 12 draw : Рисование области экрана с помощью SourceX(R0+R1), SourceY(R2+R3), Width(R4) плюс 1, Height(R5) плюс 1, TargetX(R6+R7), TargetY( Р8+Р9)

  • 13 qsnd &arg1 : Ставить в очередь звук из регистра, определенного параметром ARG1.

Никаких сложений, делений, логических операций или способов перехода в соответствии с конкретными условиями.


Однако можно воссоздать эти функции из этих 13 операций. Например, вот код для сложения двух чисел, хранящихся в регистрах r0 и r1:


;; добавить r1 к r0 и сохранить результат в r0


@Добавить


crsz r1 ;; если r1 == 0 вернуть


ржмп 4 ;; в противном случае перейти к увеличению


джмп @endadd


вкл r0


декабрь r1


jmp @добавить


@endadd


Обратите внимание, что я использую метки для прыжков.


Только для того, чтобы выполнить сложение, требуется много кода и более двадцати блоков памяти. Я написал другую версию дополнения для 8-битных чисел. Он использует 42 блока памяти...


Код будет нечитаемым, и его будет сложно поддерживать, если вам придется каждый раз переписывать его. Должна быть возможность добавлять операции, например, «добавить». Анализатор распознает инструкцию добавления и заменяет ее соответствующим кодом.


С этого момента мы будем говорить не о парсере, а о компиляторе.


Создание языка и моего компилятора


Ключевые слова и комментарии


Я добавил систему комментариев, содержимое которой будет игнорироваться компилятором.


Я также добавил много ключевых слов для регистров, цветов, звуков, чисел и т. д. Затем они заменяются кодами операций (от 0 до 15) во время компиляции.


установить г0 0 ;; r0 будет заменен на число 0 во время компиляции


установить красный цвет r2 ;; красный будет заменен цифрой 1 во время компиляции


установить r4 С# ;; c# (музыкальная нота) тоже будет заменена


установить r6 b0010 ;; двоичная запись будет заменена на 2


Определяет


Я добавил способ связать значение с именем. Очень полезно создавать константы, которые можно легко изменить и использовать в нескольких местах кода.


define player_start_pos_x 10


определить other_const 2


определить начальное_HP 15


Ярлыки


Ярлыки существуют, чтобы решить проблему прыжка. Вот бесконечный цикл, увеличивающий регистр 0.


@mylabel


вкл r0


джмп @monlabel


Во время компиляции аргумент "@monlabel" для jmp заменяется правильной позицией на изображении.


@monlabel


вкл r0


джмп 0 0 0 0


Здесь важно для компилятора прочитать метки, как только мы получим все инструкции. Действительно, представьте, что у нас есть такой код:


jmp skip_line


вкл r0


@skip_line


Во время jmp существование метки skip_line еще не известно. Поэтому нам необходимо:


  1. Прочитайте все инструкции в первый раз

  1. Сохраните все существующие метки (и свяжите их с позицией в памяти)

  1. Замените метки во всех прыжках на правильные позиции.

Спрайты


Спрайты должны храниться в памяти. Эта память также содержит наш код. Мы должны быть осторожны, чтобы различать их и не перезаписывать наш код спрайтами и наоборот.


Я выделил область памяти спрайтам. Расположение спрайтов в памяти



Мы «рисуем» наши спрайты прямо в коде.


;; скинь код игры сюда


== спрайты


;; спрайт 1


0 0 0 0 0 0 0 0


0 0 0 1 1 0 0 0


0 0 0 1 1 0 0 0


0 1 1 1 1 1 1 0


0 1 1 1 1 1 1 0


0 0 0 1 1 0 0 0


0 0 0 1 1 0 0 0


0 0 0 0 0 0 0 0


;; спрайт 2


0 0 0 0 0 0 0 0


0 0 0 4 4 0 0 0


0 0 0 3 3 0 0 0


0 0 0 2 2 0 0 0


0 0 0 1 1 0 0 0


0 0 0 0 0 0 0 0


0 0 0 0 0 0 0 0


0 0 0 0 0 0 0 0


Компилятор автоматически поместит их в нашу зарезервированную память. Спрайты помещены в память


Затем мы можем использовать #define для хранения положения наших спрайтов и использовать операцию рисования Click4.


Куча


Click4 имеет 15 регистров для временного хранения значений. Чтобы хранить переменные в течение длительного времени, нам нужно хранить их в нашей памяти (которая также содержит код и спрайты).


Мы можем использовать инструкции сохранения и загрузки Click4. Давайте сохраним 5 в позиции (15,15) в памяти.


установить r0 5


сохранить 0 15 0 15


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


Я создал базовую кучу. Куча — это место в памяти, где вы можете хранить значения и изменять их. Когда вы вызываете new в C++, обычно для вас резервируется место в куче. Область памяти, зарезервированная для кучи


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


Вот пример того, как использовать мою кучу.


аллок player_pos_x 15


hload player_pos_x ;; загрузить значение, хранящееся в куче, в r0


дек r0


hsave player_pos_x ;; сохранить значение r0 в куче


При каждом #alloc компилятор резервирует блок памяти для хранения нашей переменной. Я выбрал начальную позицию для своей кучи (63, 55). При каждом новом распределении:


  1. Резервируем текущую позицию под переменную.

  1. Сохраняем его значение по умолчанию (15 в примере)

  1. Уменьшить позицию следующей переменной.

В окончательном коде операторы hload и hsave заменены на load и save.


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


Логические операторы


Мы хотим иметь возможность использовать логические операторы, такие как ИЛИ, НЕ, И и т. д.


У нас есть только NAND с Click4. NAND обладает особым свойством, называемым функциональной полнотой. Из NAND мы можем воссоздать все остальные логические операции!


Можно даже воссоздать компьютер только с NAND.


Пример с ИЛИ:


;; r0 ИЛИ r1 в r0


;; A NOR B <=> (A NAND A) NAND (B NAND B)


;; И-НЕ


;; (А-И-А) НЕ-И (В-НЕ-В)


и r0 r0 r0


и r1 r1 r1


и r0 r0 r1


и r0 r0 r0


Условия


В общем, мы используем логические операторы в условиях.


Click4 дает нам только crsz, который перемещается на 2 блока памяти, если r0 == 0 (в противном случае он переходит к следующему блоку памяти).


Что ж... этого достаточно для реализации всех остальных типов условий.


Мы собираемся реализовать две концепции, которые можно найти в других языках ассемблера:


  • смп

  • набор условных переходов

Операция cmp сравнивает значения в r0 и r1 и изменяет r15 в соответствии со сравнением.


Если r0 < r1, r15 = 1.


Если r0 == r1, r15 = 2.


Если r0 > r1, r15 = 0.


Затем мы можем реализовать набор условных переходов, которые будут использовать r15.


  • je (прыгать, если равны)

  • jne (перейти, если не равно)

  • jg (прыгать, если больше)

  • jge (прыгать, если больше или равно)

  • jl (прыжок, если ниже)

  • jle (прыгать, если меньше или равно)

Вы всегда должны вызывать cmp перед любым условным переходом, чтобы правильно обновить r15.


Вот пример использования, если r0 больше r1, то мы увеличиваем r1.


установить r0 5


установить r1 2


cmp


jg @r0_greater_than_r1


вкл r1


@r0_greater_than_r1


;; больше кода...


Арифметические операции


Вы можете увеличивать и уменьшать только с помощью Click4. Я переписал сложение и вычитание из этих двух инструкций.


Сложение двух чисел означает прибавление 1 несколько раз (и наоборот для вычитания).


Затем для деления и умножения та же система. Умножение на X означает добавление X несколько раз.


Алгоритмы были немного сложнее, поэтому, чтобы упростить себе задачу, я написал и протестировал их на питоне, прежде чем переводить на ассемблер.


Вот пример со сложением двух 8-битных чисел. Я создал класс int4, чтобы воспроизвести поведение 4-битного числа.


определение добавить (r0, r1, r2, r3):


при r2 > 0 или r3 > 0:


если r3 == 0:


г2 -= 1


г3 -= 1


г1 += 1


если r1 == 0:


r0 += 1


вернуть г0, г1


модульные тесты


assert(add(0, 0, 0, 0) == (0, 0)) # добавление нуля к нулю


assert(add(0, 0, 0, 1) == (0, 1)) # добавление 1 к нулю


assert(add(0, 1, 0, 0) == (0, 1)) # добавление нуля к 1


assert(add(0, 1, 0, 4) == (0, 5)) # без переноса


assert(add(0, 10, 0, 6) ==(1, 0)) # перенос


assert(add(1, 2, 0, 3) == (1, 5)) # перенос + non null r0


assert(add(0, 3, 1, 3) == (1, 6)) # перенос + non null r2


assert(add(1, 3, 1, 2) == (2, 5)) # перенос + ненулевые r0 и r2


Я стараюсь максимально использовать операции, доступные в Click4. Вот почему я делаю только инкременты, декременты и сравнения с 0.


Я также сделал несколько модульных тестов, чтобы убедиться, что это работает.


Вот 8-битная надстройка на ассемблере.


;; Добавьте два 8-битных числа


;; добавляет r2-r3 к r0-r1 в r0-r1


@8bitadd


;; в то время как r2 > 0 или r3 > 0 {


;; или r3 r2 в r4


и r4 r2 r2


нанд r5 r3 r3


нанд r4 r4 r5


crsz r4 ;; если r2 или r3 == 0, то вернуть


ржмп 4


jmp @end8bitadd


crsz r3 ;; если r3 переполняется, то убавляется и r2


ржмп 1


декабрь r2


декабрь r3


вкл r1


crsz r1 ;; если r1 переполняется, то добавим и r0


ржмп 1


вкл r0


jmp @8bitadd


@end8bitadd


Лучшее управление вводом


Я добавил более простой способ обработки ввода с клавиатуры.


Click4 имеет инструкцию input, которая сохраняет состояние клавиш направления клавиатуры в заданном регистре.


Лучше представлять его в двоичном виде.


Допустим, мы используем input :


вход r0


Если нажата клавиша вверх, r0 = 1 или b0001 в двоичном формате. Если нажата правая клавиша, r0 = 2 или b0010 в двоичном формате. Если нажаты клавиши вверх и вправо, r0 = 3 или b0011 в двоичном формате.


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


jmp_if_pressed key_right @right_pressed


jmp @right_not_pressed


@right_pressed


вкл r0


@right_not_pressed


;; делать вещи


Что бы я хотел добавить


Если бы у меня было больше времени, я бы реализовал:


  • Стек для создания функций и рекурсивных вызовов.

  • Массив фиксированного размера для хранения последовательности значений.

Как организована компиляция


Обычно мы используем термин компиляция, чтобы говорить о процессе преобразования кода в машинный язык, понятный процессору.


На самом деле шагов несколько, и компиляция — один из них.


Эти шаги вдохновили меня на создание моего компилятора. Идея состоит в том, чтобы разбить «компилятор» на несколько частей, каждая со своими обязанностями.


Несколько терминов для определения, чтобы понять:


  • Ассемблер Click 4 (C4ASM) : операции, включенные по умолчанию в Click 4.

  • Расширенный ассемблер Click 4 (EC4ASM): добавлены все операции и другие функции.

  • Скрипт Click 4 (.cs4) : файл, написанный на EC4ASM.

  • Мнемоника : имя операции, например set.

  • Opcode : Код, который должен быть передан ЦП для выполнения операции (для набора это 1).

Вот как работает мой компилятор:


  1. Препроцессор

  • Удаляет комментарии.

  • Заменяет все #define правильным значением.

  • Резервирует память для кучи.

  • Преобразует сценарий (строку) в более простую в использовании структуру данных для следующих шагов.

  1. Скомпилируйте

  • Замените все операции EC4ASM на C4ASM.

  • Регистрирует метки и заменяет их правильными адресами.

  • Проверяет валидность всех операций C4ASM и их аргументов.

  1. Соберите

  • Преобразование всего кода в машинный код (все мнемоники и ключевые слова заменяются их кодом операции).

  1. Ящик

  • Рисует операции над изображением.

  • Рисует спрайты на изображении.

  • Нарисуйте кучу на изображении.

А вот и финальная игра!


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


Я сделал клон игры Kaboom на Atari 2600. Но вы вместо бомб собираете котиков. Я выбрал кошек, потому что помимо музыкальных нот, Click4 также может воспроизводить мяуканье. Идет дождь из кошек!


Игра называется It's raining Cats, что происходит от выражения It's raining Cats and Dogs на английском языке.


Вы можете увидеть c4script на моем [github] (https://gist.github.com/Darenn/131bf842ad94ebbeeb8d16576e55621b).


Действия


  • Вот несколько каналов Youtube, где можно узнать больше о низкоуровневом программировании, стеке, куче и работе компьютера в целом.



  • Создайте игру для Click4 и пришлите мне. Даже при использовании IDE это по-прежнему сложно, но я уверен, что некоторые из вас смогут делать удивительные вещи.

  • Попробуйте создать свою собственную IDE и компилятор для другой фэнтезийной консоли. Есть много других, вот список.

  • Стать участником моего Patreon, чтобы получить доступ к эксклюзивному контенту, такому как исходный код IDE, мой повседневный прогресс в формате видео с множеством интересных деталей, бесплатные игры и многое другое.

Ресурсы


  • [IDE Click4] (https://darenn.itch.io/click4-ide)

  • [Консоль Click4 Fantasy] (https://josefnpat.itch.io/click4)

Благодарности


Спасибо @josefnpat за создание этой игрушки, а также за то, что помог мне понять, как она работает (он работает над отличной игрой. -голубой-планеты), зацените!).


Благодаря этим людям, которые поддерживают меня на Patreon и Ko-fi, я могу работать: Moey Mei, Rascal, Staren, Kel Pat, Poupipnou, les Tartines, Dysphobia.



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE