Skip to main content

Переход с QSP 5.7.0 на 5.9.x

· 23 min read
Aleks Versus
Разработчик JAD_for_QSP

Многие авторы, начинавшие разработку игр для плееров версии 5.7.0, боятся переводить проекты на более свежие версии из-за обилия изменений. Байт активно обновляет основную библиотеку QSP уже пять лет, и количество введённых изменений действительно может пугать.

Но на самом деле всё не так страшно.

Список по-настоящему критических изменений весьма скромен, и эта статья поможет безболезненно перевести проект на более новый и быстрый плеер.

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

Все нововведения уже отражены в онлайн-справке: wiki.qsp.org

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

Инструменты

Для правки игры вам потребуются следующие инструменты:

  1. Утилита TXT2GAM — конвертирует игру в формат текстового файла и обратно.

  2. Анализатор QSP-кода — быстро сканирует игру в формате текстового файла и предоставляет список возможных ошибок, имена переменных, локаций, действий, предметов и прочее.

    • Скачать можно по ссылке из темы на форуме, посвящённой Анализатору.
    • Или из архива на меге "QSP/Программы". ← Смотрите папку "QSP-Analyser". Для первого раза лучше выбрать последнюю версию без модификаций.
    • Краткая информация доступна на форуме и в архиве статей.
  3. Для просмотра игры в формате текстовых файлов подойдёт любой текстовый редактор. Рекомендуем Sublime Text или VS Code — они поддерживают подсветку синтаксиса QSP.

Если вы используете системы контроля версий (например, Git), рекомендуем фиксировать каждое масштабное изменение в игре.

Первый этап переноса. Критичные изменения

Версия библиотеки QSP 5.8.0 получила несколько критически значимых изменений, которые сломали обратную совместимость. Игра, написанная для плеера 5.7.0, иногда будет некорректно работать на плеерах версии 5.8.0 и выше.

Поправить такую игру достаточно легко.

[Прежде всего!]

Сделайте полную копию вашего проекта на тот случай, если что-то пойдёт не так.

Анализ кода игры для QSP 5.7.0

  1. Сначала вам нужно сконвертировать игру в формат "qsps". Это делается с помощью утилиты txt2gam, или, если утилита подключена к Quest Generator, с помощью пункта меню "Экспорт → Текстовый файл формата TXT2GAM" в QGen.
    Формат "qsps" и "TXT2GAM" — это одно и то же.
  2. Далее нужно запустить Анализатор и "скормить ему игру":
    • Запускаете утилиту и нажимаете кнопку с тремя точками в правом верхнем углу.
    • Находите и выбираете текстовый файл с вашей игрой.
    • Утилита автоматически запустит чтение файла и на вкладке "Анализ" в самом нижнем поле вы увидите список ошибок и предупреждений.
    • В той же вкладке нажмите кнопку "Переменные". В случае успешного чтения файла в трёх полях с полосами прокрутки отобразятся списки используемых в игре переменных.

Это всё, что понадобится нам от утилиты "Анализатор". Не закрывайте окно, вам понадобится информация из него.

analyzer

Разнотипные значения в массивах

Одно из самых масштабных изменений в QSP: больше нельзя хранить в одном массиве под одним индексом и числовое, и текстовое значение. Подробно это изменение описано в статье "Массивы уже не те" .

Если вы использовали старое поведение массивов как фичу (например, хранили названия и здоровье юнитов под одним индексом), в плеерах версии 5.8.0 и выше это сломает работу игры.

! $unit - название, unit - здоровье
$unit[0] = "пехотинец" & unit[0] = 300
$unit[1] = "гвардеец" & unit[1] = 670
$unit[2] = "лучник" & unit[2] = 1500
$unit[3] = "артиллерист" & unit[3] = 10

В примере выше числовые значения затрут текстовые, а в плеерах версии 5.7.0 оба значения хранятся одновременно.

Решение простое. Чтобы починить игру, сломавшуюся из-за подобного использования массивов, мы конвертировали QSP-файл в текстовый формат и "скормили" его Анализатору.

Нужно сделать следующее:

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

    • Можно, например, вставить эти списки в Excel, отсортировать по алфавиту и сопоставить вручную.
  2. Если в обоих списках присутствуют одноимённые массивы (например, $unit и unit), переименуйте текстовые массивы во всей игре. (Для приведённого примера $unit можно переименовать в $unit_name.)

  3. Массивы $args/args и $result/result переименовывать не нужно — это специальные массивы. Однако во всех локациях, где используются $args/args, первой строчкой обязательно проводите инициализацию (например, args[9] = args[9]), если используете текстовые индексы для элементов массива $args/args.

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

Когда не останется одноимённых текстовых и числовых массивов (за исключением выше обозначенных случаев), игра в плане хранения данных в массивах станет совместима и с плеерами версии 5.7.0, и с плеерами версий 5.8.0 и выше.

Изменения в работе функций INSTR, ARRCOMP, ARRPOS

Необязательные аргументы функций INSTR, ARRCOMP и ARRPOS в плеерах 5.8.0 и выше переставлены в конец. В плеерах версии 5.7.0 и ниже эти аргументы шли в начале.

Например, в плеере 5.7.0 поиск подстроки в строке мог выглядеть так:

instr(7, "В корзине 23 красных и 47 синих яблок.", "красн") & ! 14

Если вы полностью переходите на новые версии плеера и совместимость с версией 5.7.0 вас не волнует, найдите все вхождения instr, arrcomp и arrpos в игре и переставьте необязательный аргумент в конец:

instr("В корзине 23 красных и 47 синих яблок.", "красн", 7) & ! 14

Если нужно сохранить совместимость с плеерами версии 5.7.0, сделайте следующее:

  1. Проверьте значения необязательных аргументов. Если для instr эти значения везде равны 1, а для arrpos и arrcomp0, достаточно просто опустить эти значения, и игра в плане работы этих функций станет совместима и с 5.7.0, и с 5.8.0 и выше.
  2. Если значения необязательных аргументов отличаются от указанных выше, напишите функции-обёртки для instr, arrcomp и arrpos и замените все встроенные функции QSP в игре на свои. Пример для функции instr ниже.
# test
! будет работать во всех плеерах
func("myInstr", "В корзине 23 красных и 47 синих яблок.", "красн", 7) & ! 14
-- test

# myInstr
$args[0] = $args[0] & ! строка, по которой производим поиск
$args[1] = $args[1] & ! подстрока, которую ищем
args[2] = iif(args[2]>0, args[2], 1) & ! с какого элемента начинать поиск
if $QSPVER >= '5.8.0':
result = instr($args[0], $args[1], args[2])
else:
result = instr(args[2], $args[0], $args[1])
end
-- myInstr

Как видно из примера, в нашей функции мы всё равно изменили порядок аргументов, переставив необязательный аргумент в конец. Все строки с оригинальным instr придётся заменить на нашу функцию. Такое решение обеспечит совместимость и с плеерами версии 5.7.0, и с более новыми плеерами.

[Совет]

Поиск и замену этих функций удобнее всего делать в текстовом редакторе типа Sublime Text или VS Code — там можно использовать регулярные выражения для поиска и замены. Это избавит от ручной обработки каждого вхождения, и вы сможете в полуавтоматическом режиме заменить все сомнительные функции на исправленные за считанные минуты.

DISABLESUBEX не работает

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

Если раньше вы писали такой код:

disablesubex = 1
'<<disablesubex>>'
disablesubex = 0

На экран без изменений выводилась строка "<<disablesubex>>". Подвыражение не раскрывалось.

Теперь переменная DISABLESUBEX больше не отключает раскрытие подвыражений, поэтому на экране вы увидите "1".

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

'<'+'<disablesubex>>'

Эта правка сохранит совместимость с плеерами версии 5.7.0.

ADDQST → INCLIB, KILLQST → FREELIB

В новых версиях плееров операторы ADDQST и KILLQST заменены на INCLIB и FREELIB.

Всё очень просто.

Переименуйте эти операторы, если не требуется сохранять совместимость с 5.7.0.

Если совместимость требуется, проверяйте версию плеера перед вызовом оператора:

if $QSPVER >= '5.8.0':
INCLIB 'my_lib.qsp'
else:
ADDQST 'my_lib.qsp'
end

Работать всё будет одинаково — это те же самые операторы, просто переименованные.

Изменения в работе логических операторов и функций

В QSP нет булевых значений (True и False). В плеерах версии 5.7.0 вместо них использовались числа 0 и -1.

0 означало Ложь (False), а -1Правду (True). Соответственно все логические операции возвращали эти значения:

! 5.7.0
*pl (3>2 and 4>3) & ! -1
*pl (3>2 or 4>3) & ! -1
*pl no 0 & ! -1
*pl (3<2 and 4>3) & ! 0
*pl (3<2 or 4<3) & ! 0
*pl no -1 & ! 0

Операторы AND, OR, NO были битовыми — они не просто сверяли истинность или ложность значений, а вычисляли их битовый результат:

! 5.7.0
*pl (3 and 2) & ! 2
*pl (4 or 6) & ! 6
*pl no 7 & ! -8

_В плеерах начиная с версии 5.8.0 операции сравнения, логические операторы и функции можно полноправно назвать логическими.

AND, OR и NO теперь всегда возвращают числовые значения 0 (эквивалент False) или 1 (эквивалент True). То же самое и с операциями сравнения.

! >= 5.8.0
*pl (3>2 and 4>3) & ! 1
*pl (3>2 or 4>3) & ! 1
*pl no 0 & ! 1
*pl (3<2 and 4>3) & ! 0
*pl (3<2 or 4<3) & ! 0
*pl no 1 & ! 0

*pl (3 and 2) & ! 1
*pl (4 or 6) & ! 1
*pl no 7 & ! 0

Обратите внимание: и в старых, и в новых версиях плееров ЛЮБОЕ число, отличное от нуля, воспринимается как True. Только 0 является эквивалентом False. Когда логический оператор получает любое отличное от нуля число в качестве операнда, он работает с ним, как если бы это была единица. Поэтому no 7 возвращает 0, что более правильно, чем в 5.7.0.

Логические функции ISNUM, LOC, ISPLAY и другие раньше возвращали -1 как эквивалент True и 0 как эквивалент False. Начиная с версии 5.8.0 они возвращают 1 и 0 соответственно, как и операции сравнения.

isnum('123') & ! 1
isnum('12d') & ! 0

Операция OBJ, которая в плеерах версии 5.7.0 возвращала -1 при наличии предмета в окне предметов, претерпела изменения.

В плеерах начиная с версии 5.8.0 она, как и прочие логические функции, стала возвращать 1 или 0. Однако начиная с версии 5.9.4 она возвращает ЧИСЛО предметов с указанным названием в инвентаре.

addobj 'Отвёртка'
addobj 'Отвёртка'
addobj 'Отвёртка'

*pl OBJ('Отвёртка') & ! в 5.7.0 ===> -1
& ! в 5.8.0 ===> 1
& ! в 5.9.0 ===> 3

Как видно, в QSP произошло много изменений в логических операторах и функциях, и всё это может повлиять на работоспособность игры.

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

Если вы использовали побитовые операции в игре именно для вычислений каких-то значений, а не просто для проверки условий, вам придётся либо остаться на 5.7.0, либо написать собственные локации-функции, имитирующие побитовые операции.

Если вы просто использовали OR, AND, NO для проверки условий, ничего исправлять не нужно — всё продолжит работать так же, как работало раньше.

Чтобы сохранить совместимость с 5.7.0 и более новыми плеерами, уберите явное сравнение с -1 для логических функций.

Пример:

! совместимо только с плеерами версии 5.7.0
:input_age
$age = $input('Сколько вам лет?')
if isnum($age) = -1:
hero_age = val($age)
else:
jump 'input_age'
end
! совместимо с плеерами любых версий
:input_age
$age = $input('Сколько вам лет?')
if isnum($age):
hero_age = val($age)
else:
jump 'input_age'
end

Также следует учесть, что в плеерах версии 5.7.0 (и ниже) у функций LOC и OBJ приоритет был ниже, чем у операций сравнения. Это могло быть неочевидным для выражений такого рода:

(obj 'Отвёртка' = obj 'Верёвка')

Кажется, что это выражение должно выполняться так: проверяется наличие предмета "Отвёртка", проверяется наличие предмета "Верёвка", и лишь потом значения сравниваются. Однако в 5.7.0 у операции сравнения приоритет выше, чем у OBJ. Поэтому сначала выполняется операция сравнения, и лишь потом функция OBJ. Таким образом в плеерах версии 5.7.0 это выражение всегда возвращает 0.

Начиная с версии 5.8.0 приоритет для LOC и OBJ стал таким же, как и для других функций типа isnum и isplay, из-за чего некоторые сложные условия могут теперь работать неправильно. Чтобы исправить это и сохранить совместимость со всеми версиями плееров, расставьте скобки во всех условиях с такими функциями:

if (obj 'Отвёртка') = (obj 'Верёвка'):
...

Это будет прочитано одинаково и в плеере версии 5.7.0, и в плеерах версий 5.8.0 и выше.

Аргумент по умолчанию для функции RAND

В плеерах версии 5.7.0 и ниже второй параметр функции RAND по умолчанию был 0. Например, если вы указывали число 100 в качестве аргумента функции RAND, она возвращала случайное число от 0 до 100. В плеерах версии 5.8.0 и выше, а также в Quest Navigator, второй параметр по умолчанию равен 1. То есть если вы укажете лишь одно число, например 100, функция RAND вернёт случайное значение от 1 до 100.

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

! вместо:
RAND(100)
! пишем:
RAND(0, 100)

В текстовых редакторах типа VS Code и Sublime Text это легко делается с помощью регулярных выражений.

Изменения в работе неявного оператора

Неявный оператор — это оператор, который мы не указываем. В 5.7.0 он делал примерно то же, что оператор *pl — выводил на экран значение, добавляя после него перевод строки:

! работает в плеерах любых версий  
*pl 456
*pl "text"

! эквивалентно:

456 & ! здесь для вывода используется неявный оператор
"text" & ! и здесь для вывода используется неявный оператор

Если мы вызывали какую-то функцию, но она не возвращала результат, неявный оператор, как и оператор *pl, выводил на экран пустую строку и добавлял к ней перевод строки:

istmt1

Начиная с версии 5.8.0, если функция не возвращает значение, неявный оператор будет просто игнорировать такую функцию.

istmt2

Чтобы исправить это и сохранить совместимость с плеерами версии 5.7.0, вместо неявного оператора везде напишите явный оператор *pl.

*pl "Строка текста"
*pl func('foo.3')
*pl "Строка текста"

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

Чтобы разбивать длинные строки на несколько (для удобства чтения), в QSP используется сочетание символов " _" (пробел и символ нижнего подчёркивания).
В плеерах версии 5.7.0 и ниже при разборе этой конструкции движок оставлял строки как есть. Для примера возьмём такую конструкцию:

if t _  
or _
t:

В плеерах версии 5.7.0 символы преформатирования будут исключены при интерпретации, а строка, разбитая с помощью " _", будет объединена как есть, то есть будет равнозначна строке:

if tort:

В плеерах версии 5.8.0 и выше эта строка будет объединена с добавлением пробела вместо каждого сочетания " _", то есть будет равнозначна строке:

if t or t:

Унифицировать поведение для всех версий плееров очень легко — достаточно перед " _" поставить дополнительный пробел, то есть в конце строки должно получиться "  _" (два пробела и символ подчёркивания).

Небольшое резюме по критическим изменениям

Как видно, все критические несовместимости при переходе на новые версии плееров легко исправляются так, чтобы работать и на старых, и на новых версиях. Да, придётся потратить прилично времени, если у вас большая игра, но в целом здесь нет ничего сложного.

Мы предложили варианты с сохранением совместимости для тех, кто хочет продолжать писать игры на 5.7.0, но при этом запускать их на последних версиях QSP. Однако настоятельно рекомендуем подумать над тем, чтобы полностью перевести игру на плееры последних версий с потерей совместимости со старыми плеерами — новые версии QSP обзавелись множеством новых фич, которые не только делают код более читаемым, но и облегчают разработку и ускоряют различные процессы в играх.

Синтаксический сахар, циклы и прочее

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

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

Множественное присваивание

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

Вместо:

mass=45 & daz=65 & zaz=79

Теперь можно писать:

mass, daz, zaz = 45, 65, 79

Как видно, множественное присваивание делает код более читаемым.

Также множественное присваивание позволяет поменять местами значения в двух переменных без использования третьей. Например, раньше было так:

temp = new
new = old
old = temp

Теперь это делается одной строчкой:

new, old = old, new

Множественное присваивание работает и с оператором SET:

set mass, daz, zaz = 45, 65, 79

Новый тип значения "кортеж"

Кортеж — это такой тип значения, в котором можно хранить несколько значений другого типа. Например, можно упаковать в кортеж два числовых и одно строковое значение:

%pers = [187, 26, 'Петя']

Как видно из примера, для кортежей введён специальный префикс типа "%". Кортежи — это новый полноценный тип данных (например кортеж %A), наравне со строками ($A) и числовым типом (A).

Поскольку кортежи — это отдельный тип данных, вы можете вкладывать одни кортежи в другие:

%unit = ['лучник', [123, 90], [234, 300], 'агрессивен']

Вот ещё несколько примеров создания кортежей:

%q = [4, 6, 7]  
%q = ['sdfsd', 6]
%q = [34]
%q = [] & ! пустой кортеж
%q = [56, [32,'sdsd'], 3]

Можно не только помещать данные в кортеж, но и извлекать их обратно. Процесс размещения данных в кортеже называется упаковка, а извлечение данных из кортежа — распаковка.

! упаковка:
%q = [56, [32,'sdsd'], 3]
! распаковка:
a, %b, c = %q

Подробнее о кортежах читайте на нашей вики: wiki.qsp.org/кортежи.

Многомерные массивы

Чтобы организовать многомерный массив, в плеерах версии 5.7.0 (и более ранних) приходилось использовать текстовые индексы. Например:

! работает в плеерах любых версий:  
$unit_coords["3,1"]="Пехотинец"
$unit_coords["2,7"]="Артиллерист"
$unit_coords["10,0"]="Танк"

В новых версиях плеера (начиная с 5.8.0 и выше) можно не использовать текстовые индексы, а указывать несколько нужных значений через запятую:

! версия 5.8.0 и выше  
$unit_coords[3,1]="Пехотинец"
$unit_coords[2,7]="Артиллерист"
$unit_coords[10,0]="Танк"

Это намного упрощает работу с многомерными массивами, при этом вы можете работать и с текстовыми индексами.

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

! многомерный массив:
$map[1,2] = 'space'
$map[1,3] = 'space'
$map[5,7] = 'tree'
$map[0,-1] = 'hero'
*pl $map[x,y]

! можно, но не рекомендуется,
! писать так:
$map[[1,2]] = 'space'
$map[[1,3]] = 'space'
$map[[5,7]] = 'tree'
$map[[0,-1]] = 'hero'
*pl $map[[x,y]]

Поскольку многомерный индекс — это кортеж, его значение можно хранить в переменной:

%coords = [0, 3, -19]
$map[%coords] = 'hero'

Локальные переменные, оператор local

В плеерах версии 5.8.0 и выше появился новый оператор, который позволяет объявить указанные переменные локальными для отдельного блока кода (локации, действия, циклы, код в DYNAMIC/DYNEVAL). После выполнения блока кода значения переменных восстанавливаются к предыдущим:

5.8.0 (qSpider)

Можно объявить локальную переменную и сразу присвоить ей значение:

local i=45

Можно объявить сразу несколько локальных переменных:

! объявляем локальные переменные  
local i, j, k

! объявляем локальные переменные и присваиваем им значения
local f, d, $g = 123, 45, 'string'

Подробнее про локальные переменные написано в справке.

Приведение всех типов данных к булевому типу

Теперь не только числовые значения могут использоваться как условные True и False, но также и строки, и кортежи.

  • Любое число, отличное от нуля считается True, а ноль — False;
  • Любая непустая строка считается True, а пустая — False;
  • Любой непустой кортеж считается True (даже кортеж содержащий пустой кортеж, или пустую строку), а пустой — False;

См. также подраздел "Правда или Ложь?" в справке.

Циклы

Долгое время циклы в QSP приходилось делать костыльным способом с использованием меток. Сейчас в QSP есть настоящие циклы, которые работают быстрее циклов на метках и гораздо лучше читаются.

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

! заполнение массива значениями

! 5.7.0
i = 0
:loop
if i < 10:
mass[i] = i
i += 1
jump 'loop'
end

! 5.8.0 и выше
loop local i = 0 while i < 10 step i += 1:
mass[i] = i
end

Обратите внимание: счётчик цикла объявлен как локальная переменная. Он будет существовать только внутри цикла. Больше не нужно следить за значениями счётчика, как это было раньше.

! Проверка ввода имени игроком

! 5.7.0
:name_enter
$name = $input('Как вас зовут?')
if $name = '': jump 'name_enter'

! 5.8.0 и выше
loop while no $name: $name = $input('Как вас зовут?')

Подсчёт числа предметов с указанным названием в инвентаре:

$item = 'Отвёртка'

! 5.7.0
counter = 0

i = countobj()
:for
if i:
if $getobj(i) = $item: counter += 1
i -= 1
jump 'for'
end

! 5.8.0 и выше
counter = 0

loop local i = countobj() while i step i -= 1:
if $getobj(i) = $item: counter += 1
end

Доступ к ARGS локаций в гиперссылках

Массив ARGS — это специальный массив, который заново создаётся на каждой локации. На каждой локации существует свой собственный массив ARGS, и значения в этом массиве не пересекаются со значениями массивов ARGS, созданных на других локациях. Более того, собственный уникальный массив ARGS создаётся и в выполняемых с помощью DYNAMIC/DYNEVAL блоках кода.

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

Если с массивами ARGS в коде локаций, вызываемых по GOSUB и FUNC, а также с массивами ARGS в коде, выполняемом с помощью DYNAMIC и DYNEVAL, всё понятно — эти массивы существуют только пока выполняется код, а потом уничтожаются, то с массивами ARGS в коде локаций, на которые был осуществлён переход с помощью GOTO или XGOTO, не всё так однозначно.

Технически такая локация продолжает выполняться, и массив ARGS продолжает существовать, пока игрок не покинет локацию.

Изначально доступ к этому массиву мы могли получить только внутри действий, теперь массив ARGS стал доступен и из кода гиперссылок.

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

Так мы можем, например, помещать в ARGS многострочный код в виде текста, а потом выполнять его из ссылки с помощью DYNAMIC.

$args[21] = {
if no деньги<100:
addobj 'Кружка имбирного эля'
кружка_эля += 1
деньги -= 100
*pl "Я приобрёл кружку имбирного эля."
else
*pl "Мне не хватает денег на эль."
end
}
*pl "<a href='exec:dynamic $args[21]'>Купить кружку имбирного эля</a>"

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

Неявный вызов пользовательских функций и процедур

Это синтаксический сахар над функцией FUNC и оператором GOSUB, который сильно упрощает написание и чтение кода.

Вот как раньше выглядел такой код:

! код в формате QSPS/TXT2GAM
# start
gosub 'proceed'
*pl func('foo.3', 23, 45)
-- start

# proceed
act 'ДЕЙСТВИЕ':
*pl func('foo.3', 12, 12)
end
-- proceed

# foo.3
$result = args[0]*args[1]/(args[0]+args[1])
-- foo.3

Здесь мы явно прописывали и оператор GOSUB, и функцию FUNC, что немного усложняло чтение кода.

Теперь мы не указываем оператор GOSUB явно, а пишем @@, затем сразу без пробелов — название локации, потом ставим пробел и, если нужно, перечисляем аргументы через запятую. Для нашего примера это выглядит так:

@@proceed

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

@@proceed 'text', 23, $var

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

@foo.3(23, 45)

Таким образом мы существенно сокращаем запись вызовов собственных функций и процедур.

! код в формате QSPS/TXT2GAM
# start
@@proceed
*pl @foo.3(23, 45)
-- start

# proceed
act 'ДЕЙСТВИЕ':
*pl @foo.3(12, 12)
end
-- proceed

# foo.3
$result = args[0]*args[1]/(args[0]+args[1])
-- foo.3

Подробнее об этом нововведении можно прочитать в разделе "Пользовательские функции и процедуры" справки.

Больше аргументов для локаций и функций

Теперь можно передавать до двадцати аргументов локациям и функциям.

Кроме встроенных функций MAX и MIN, ни одна другая функция или оператор не принимают такое количество аргументов.

Если говорить о локациях, то при работе с ними мы используем операторы GOSUB, GOTO, XGOTO и другие, а также функцию FUNC, и при передаче аргументов всегда нужно учитывать, что одним из аргументов является название локации. Для оператора DYNAMIC или функции DYNEVAL первым аргументом является текст выполняемого кода. То есть в данном случае мы можем передавать в код локации или "динамика" не более девятнадцати аргументов.

Помимо прочего, если мы создаём кортеж, мы не можем перечислить в квадратных скобках более двадцати значений. Если вам нужен более длинный кортеж, создавайте его частями, а части объединяйте с помощью операции & (конкатенация):

%tpl = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
%tpl = (%tpl & [21, 22, 23])

Усовершенствование killvar

Теперь KILLVAR может уничтожать элемент массива по строковому индексу или кортежу.

! удаление по числовому индексу
killvar 'яблоко',3
! удаление элемента по строковому индексу
killvar '$item_loc','палка'
! удаление элемента по многомерному индексу
killvar '$space',[0,-2,9]

Комментарии после двоеточий в многострочных операторах

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

act "Взять яблоко": ! многострочное действие
addobj "Яблоко"
яблоко += 1
end

Улучшение сообщений об ошибках

Сообщения об ошибках стали более информативны. Теперь они не только показывают номер строки с ошибкой, но и её содержимое.

error_view

Поддержка многострочности внутри круглых и квадратных скобок

%test = [
[ 'key1', 'value1' ],
[ 'key2', 'value2' ],
[ 'key3', 'value3' ],
]

max(
'val1',
'val2',
'val3',
'val4'
)

pl (a +
(b * 2) -
(c + 2)
) * 2

$arr[
'key1',
'key2',
$key3
] = 'value'

pl $arr['key1',
'key2',
$key3]

if ($color = "жёлтый" or
$color = "красный" or
$color = "зелёный"): "По-прежнему однострочное условие."

Список новых операторов и функций, а также новые возможности старых

  • ARRITEM — возвращает значение элемента массива под указанным индексом.
  • SCANSTR — поиск в строке непересекающихся вхождений, соответствующих шаблону, и помещение этих вхождений в массив.
  • SORTARR — сортировка указанного массива.
  • $CUROBJS — возвращает список выведенных на экран предметов в виде QSP-кода.
  • SETVAR — присваивает значение переменной или ячейке массива.
  • ARRTYPE — возвращает тип значения, хранящегося в переменной, или указанной ячейке массива.
  • ARRPACK — упаковывает массив в кортеж.
  • UNPACKARR — распаковка кортежа в указанный массив.
  • RAND — возвращает случайное число между двумя указанными числами. Добавлен новый параметр: Мода.
  • REPLACE — замена текста в строке. Добавлен новый параметр: Число замен.
  • DELOBJ — удаление предмета из инвентаря по названию (если такой предмет существует). Добавлен новый параметр: Число удаляемых предметов.
  • MODOBJ, RESETOBJ — управление отображением предметов на экране

Заключение

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

Успехов в творчестве, и релиза без багов!

Обновление документации по ключевым словам до 5.9.0

· One min read
Aleks Versus
Разработчик JAD_for_QSP

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

Тут, конечно, играет роль моя привычка полностью перевычитывать весь текст, что отнимает время. И не спасает от ошибок.

Что я успел сделать за три месяца:

  • обновил информацию на вики;
  • обновил информацию в разделе ключевых слов здесь;
  • перевёл подсветку синтаксиса для обсидиан на Prism (теперь по сути синтаксис для обсидиан и синтаксис для докузаурус работают на одном механизме, с небольшими отличиями, что позволяет мне на лету исправлять ошибки и обновлять синтаксис, пока я пользуюсь обсидианом, или пишу для докузаурус, и сразу закидывать обновлённый синтаксис в любой другой источник).

Ну, и чего я не успел:

  • овердофига всего.

Но придётся продолжать в том же темпе.

Ключевые слова, системные переменные и спецсимволы QSP

· 2 min read
Aleks Versus
Разработчик JAD_for_QSP

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

Это раздел, в котором собраны все операторы, функции, системные переменные, а так же спецсимволы и синтаксемы языка QSP.

Важно помнить:

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

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

Чтобы комфотно работать в Обсидиане и не терять функциональность ссылок в Докузаурус, необходимо следовать некоторым правилам:

  • Имена файлов должны начинаться с буквы, но ни в коем случае не с цифры.
    Потому что:

    Докузаурус опускает числа в начале имён файлов. Из-за этого ссылки валидные в Обсидиане не будут работать в Докузаурусе. (Соответственно порядок размещения статей должен определяться через поле sidebar_position в yml-заголовке документа).

  • Необходимо смириться с тем, что ссылки на index.md раздела не будут работать в докузаурусе, либо, если их "обрезать" для докузауруса, не будут работать в обсидиане. Лучше избегать создания разделов index.md для комфортной работы и там и там.
  • Ссылки на файлы статей должны быть относительными (чтобы работать и там и там), и включать так же расширение файлов (.md).
    ../language/qsp-keywords/qsp-keywords-functions.md
    Потому что:

    Докузаурус опускает расширение .md в ссылках и таким образом в процессе работы ссылки поддерживаются и в обсидиане и в докузаурусе. После сборки ссылки во всех статьях приобретают вид валидный для докузауруса, но невалидный для Обсидиана.
    https://dev.qsp.org/docs/language/qsp-keywords/qsp-keywords-statements

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

Подсветка синтаксиса QSP в Docusaurus

· 3 min read
Aleks Versus
Разработчик JAD_for_QSP

Добавил в документацию Docusaurus подсветку синтаксиса для кода QSP. Она весьма проста, и умеет подсвечивать синтаксис на самом примитивном уровне. Тем не менее работает уже довольно сносно, не считая некоторых артефактов.

Например:

Действие по условию
if ваза_на_столе=1:
! если переменная-маркер ваза_на_столе равна 1

! создаём действие
act "Взять вазу со стола":
! в действии изменяем значение переменной-маркера
ваза_на_столе=0
addobj "Ваза"
goto $curloc
end
end

Другой пример:

Код в формате qsps с двумя локациями
# start
result = 123 & ! записываем число в переменную result
N=24
*pl func('square') & ! выведет на экран 576
*pl result & ! выведет на экран 123
- start -

vase_on_desk = 0

# square
result = N * N
- square -

И ещё пример:

# em.summ
local $_, $res
if $args[0]='': exit & !@ если не указано имя массива. Защита от дурака
if $args[1]='':
!@ если аргумент не указан, в качестве разделителя используется пробел
$args[1]=' '
elseif $args[1]='/se':
!@ если аргументом указан ключ, разделитель не используется
$args[1]=''
end
!@ если указан текстовый массив, помечаем в переменной-маркере
if instr($args[0],'$')=1: $_='$'
loop local i,size=0,arrsize($args[0]) while i<size step i+=1:
if $_='$':
!@ для текстовых массивов раюотает конкатенация
$res+=$dyneval("$result=<<$args[0]>>[<<i>>]")+$args[1]
else
!@ для числовых суммирование
res+=dyneval("result=<<$args[0]>>[<<i>>]")
end
end
!@ возвращение результата
if $_='$':
$result=$res
else
result=res
end
--- em.summ ---------------------------------
Проверяйте окончания строк

LineEndings при использовании подсветки в markdown-файлах для Docusaurus должны быть строго LF! Не CRLF, иначе вылезают артефакты в виде лишних отступов.

Установка подсветки в ваш Docusaurus весьма проста:

  1. Используйте swizzle для того, чтобы добавить неподдерживаемые языки, введя команду в терминале:

    npm run swizzle @docusaurus/theme-classic prism-include-languages

    При этом будет создан файл "src/theme/prism-include-languages.ts" (или .js).

  2. В папке "src/theme" создайте папку qsp-syntax и скопируйте в неё файл "prism-qsp.js", например, отсюда.

  3. В файле "src/theme/prism-include-languages.ts" отредактируйте функцию prismIncludeLanguages:

    export default function prismIncludeLanguages(PrismObject: typeof PrismNamespace,): void {
    // ...
    additionalLanguages.forEach((lang) => {
    // ...
    require(`prismjs/components/prism-${lang}`);
    });
    require('./qsp-syntax/prism-qsp.js');
    // ...
    }
  4. Далее вы можете создать собственный файл стилей, положить его рядом с common.css и прописать путь в docusaurus.config.ts:

    presets: [
    [
    'classic',
    {
    docs: {
    // ...
    },
    blog: {
    // ...
    },
    theme: {
    customCss: [
    './src/css/custom.css',
    './src/css/qsp-syntax.css'
    ]
    },
    // ...

    Или вы можете скачать готовую цветовую схему отсюда, и разместить её точно так же.

После сохранения и перезапуска сервера, или при сборке проекта, подсветка QSP-кода подхватится.

Больше примеров работы подсветки можно посмотреть в онлайн-версии справочника по самым часто задаваемым вопросам о QSP, на котором собственно и производились опыты.

tip

Параллельно была написана подсветка для Obsidian: qsp-syntax-obsidian.

Разработка документации

· 4 min read
Fering
Разработчик Qsp.FSharp

Документация как центральный орган

Главная, как по мне, проблема Куспа, — это его децентрализованность. Это значит, что одна рука не ведает, что творит другая, а потом выясняется, что это уже давно всё сделано; а ноги вообще идут своим каким-то непонятным путем.

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

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

Когда документация выходит на сцену в разработке?

Вот к примеру, Байт хочет ввести тип для кортежей. Он пишет в #qsp_platform_dev свои планы, и начинается обсуждение в духе: "Какой символ использовать?", "Какие проблемы решает новый тип?" и т.д. На все вопросы рано или поздно находятся ответы, обсуждение к чему-то приходит, но всё это сейчас находится в чатике в виде разрозненных сообщений.

Обобщение напрашивается само по себе, но вот куда его писать? Прямо в документацию? Пока рано, потому что оно сырое и его нужно уточнять. Поэтому в документации у нас существует блог (да, в документации есть документация и блог, пусть это вас не смущает), куда можно писать свои мюсли в произвольном виде (вот, к примеру, как этот пост).

Я вот, к примеру, пишу пост-обобщение про тип кортежей, затем показываю его участникам в чате. Всё это дело правится, одобряется, пересматривается и в конце концов пост отправляется в документацию (скорее всего, материал пойдет в раздел языка QSP, где ему самое место).

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

Каким языком должна писаться документация?

Писать ли документацию в более техническом стиле или, наоборот, писать в стиле научно-популярном — вопрос тяжелый. Чтобы попытаться на него ответить, нужно определить аудиторию.

У нас имеются:

  • разработчики самого Куспа
  • разработчики игр на Куспе

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

Порой диву даешься, как им удается выжимать из такого скудного набора инструментом такие большие проекты! Да и обидно становится, что разрабатываешь всякий инструментарий, а никто не пользуется. Поэтому к разработчикам игр нужно как-то доносить всё это современно ноу-хау.

А бывает так, что и сами разработчики игр сами создают инструменты. Я почему-то уверен, что ребята из GLife были простыми разработчиками игр на QSP, а потом как-то умудрились додуматься до контроля версий GIT, что надо всю эту махину растаскивать по отдельным исходникам, даже расширение придумали для них — .qsrc. Я вот, к примеру, не знал об этом, и вляпал свое .qsps для расширения VS Code. В итоге у нас идет два разрозненных стандарта расширений для исходников, и какой из них "правильнее" — кто его знает. Скорее всего, придется поддерживать два типа.

Документация должна отсекать такие случаи и приводить к общему знаменателю. А чтобы в документации разобраться, нужно писать ее как можно более понятнее. А что такое "более понятнее" — фиг его знает!

Возможно, научпоп надо отдать на откуп евангелистам (Aleks Versus, к примеру, занимается этим на своем канале) и не сушить себе голову, а может, надо немного уменьшить градус техничности. Истина где-то рядом.

Структура документации

Сейчас написано мало и толком непонятно, как всё это структурировать.

Предлагаю сделать структуру пока что такой:

  • QSP Foundation — о том, что это всё такое, зачем оно надо и т.д. Возможно, стоит вклинить туда этот пост
  • Совместная разработка — статья о том, как заполнять документацию, чтобы не наступить друг другу на ногу. Скорее всего, название неудачное и надо его сменить
  • Общий взгляд — сейчас это называется "Всё вместе". Здесь нужно показать, как все сущности стыкуются между собой
  • язык QSP — спецификация языка, т.е. то, с чего по-идеи должен начинаться QSP и от чего наследуются остальные сущности
    • синтаксис — по-идеи, тут надо изложить полное синтаксическое дерево от значений до локаций в каком-то всем понятном формате. Я поглядываю в сторону EBNF с визуализацией, но это надо обсуждать
    • семантика — предопределенные переменные, процедуры, функции и т.п., что они означают и как работают
  • Парсеры — раздел с парсерами
  • Интерпретаторы (движки? Т.е. прослойка между парсерами и плеерами)
  • Компиляторы (штуки, который преобразуют код QSP в нечто другое) — пока что мне известен только компилятор qsp-to-js Garret'а и qsp-to-twine ребят из GLife, но, возможно, их больше
  • Плееры
  • Разработка
    • Редакторы — краткое описание QGen, к примеру
    • Расширения для редакторов — краткое описание JAD и Qsp.FSharp.VsCode
    • Анализаторы
      • вот тот анализатор на C#
      • какой-то неизвестный анализатор на OCaml
  • Персоналии — раздел, в котором перечислены разработчики экосистемы, кто чем занимается и к кому в случае чего обращаться. Друзей надо знать в лицо! Что-то вроде персоналии в IFWiki, но с разработчиками