WarCraft 3: Как это работает

ZINC

Содержание:

Препроцессор zinc..endzinc

Код написанный на Zinc и vJass должен храниться отдельно друг от друга. Один из способов, это использование препроцессорных команд, которые сообщают парсеру где начинается и заканчивается Zinc код. Я не предполагаю, что данный подход лучше импорта из внешних файлов, тем не менее, это должен быть хороший вариант для людей, которым не удается адаптироваться к работе вне редактора World Edit (и как следствие требует NewGen Pack).
Все довольно просто. Если вы пишите код на Zinc, вы должны использовать специальные препроцессорные команды //! zinc - где код начинается и //! endzinc где заканчивается:
//! zinc

library HelloWorld
{
    function onInit()
    {
         BJDebugMsg("Hello World");
    }
}

//! endzinc
Ожидается, что у читателя данного руководства установлен NewGen Pack (и JassHelper не ранее 0.9.Z.0 версии). Скопируйте представленный выше пример куда-нибудь в код вашей карты, затем сохраните ее и протестируйте. Вы увидите как работает ваша первая Zinc-программа.

Препроцессор импорта

Здравым подходом будет расширение препроцессора импорта vJass. Теперь его конструкция выглядит следующим образом:
[//! import [vjass/zinc] "filename"]
что подразумевает возможность не только импорта файла, но и указания спецификации импортируемого языка. Данная спецификация является опциональной и зависит от того что вы импортируете и откуда. Например, если вы импортируете код из vJass участка, подразумевается импорт vJass кода, если вы явно не укажите спецификацию Zinc. Соответственно, если команда импорта выполняется из участка Zinc кода, будет производится импорт кода на языке Zinc.
// Импорт файла main.zn из папки code с кодом на языке Zinc
//! import zinc "code/main.zn"

// Импорт файла libTimerUtils.j из папки code с кодом на языке vJass
//! import vjass "code/libs/libTimerUtils.j"

// Импорт файла spell.j из папки code с кодом на текущем языке
//! import "code/spell1.j"

Основы синтаксиса

В Zinc, каждая команда/оператор либо начинает новый блок кода, либо заканчивается символом ;
Точка с запятой знаменует завершение вашей текущей инструкции и служит разделителем между разными инструкциями. В других языках, подобных Jass, для этого потребуется переход на новую строку.
Поскольку разделителем служит именно точка с запятой, а не начало новой строки, у вас открывается возможность использовать множество удобных конструкций. Вы можете производить писать инструкций в одной строке или же наоборот, разделить слишком длинную инструкцию на несколько строк. Заметьте, что для этого потребуется определенный самоконтроль, а так же желательно придерживаться некоторого стандарта оформления кода, дабы избежать возможной неразберихи.
Блоки кода, это набор инструкций, ограниченных фигурными скобками, где { отмечает начало нового блока кода, а } его завершение. Все синтаксические конструкции требующие объявления блоков кода используют данный синтаксис.
В Zinc предусмотрено две конструкции комментариев, это уже привычный // и новый /*...*/.
// закомментирует всю строку до ее завершения.
/*...*/ закоментируют все что находится между ними, игнорируя начало и конец строки. Комментарии этой конструкции могут быть вложенными.

Библиотеки

Библиотеки, это основная и обязательная конструкция в Zinc. Библиотеки определяют блоки кода, переменных и структур, которые что-либо выполняют. Одни библиотеки могут использовать код других библиотек, для чего необходимо явно указывать имя требуемой библиотеки ключевым словом requires. Библиотеки так же являются способом выполнения вашего кода. Если библиотека содержит функцию с именем onInit, эта функция будет вызвана во время инициализации карты.
library CodeGoesHereo
{
    // code goes here:
       // * глобальные переменные
       // * функции
       // * структуры
       // * интерфейсы
       // * прочие типы (динамические массивы, указатели на функцию)

    function onInit()
    {
        // Ваша onInit() функция будет вызвана во время инициализации карты
    }
}
Если вы хотите использовать код из библиотеки A внутри некой библиотеки B, вам необходимо сказать компилятору, что библиотека B требует библиотеку A. Если ваша библиотека использует ресурсы другой библиотеки, функция инициализации onInit внутри затребованной библиотеки будет выполнена прежде функции инициализации вашей библиотеки. Код востребованных библиотек так же будет располагаться выше кода вашей библиотеки после компиляции.
library A
{
    public function AABoom()
    {
        BJDebugMsg("BOOM IN A");
    }
    function onInit()
    {
        BJDebugMsg("Library A");
    }
}

library B requires A
{
    function onInit()
    {
        BJDebugMsg("Library B");
        Boom();
        Boom();
    }
}
Одна библиотека так же может запрашивать несколько других библиотек одновременно. В этом случае, просто перечислите имена требуемых библиотек через запятую
library A requires B, C, D, ...
Требования к библиотекам могут быть указанны как опциональные, путем добавления ключевого слова optional прежде имени востребованной библиотеки. Суть заключается в том, что компилятор не выдаст ошибки, если не найдет в коде востребованную опциональную библиотеку. Эта возможность обычно используется в сочетании с конструкцией static if (о которой позже).

Приватные и публичные конструкции библиотек

Конструкции внутри библиотек (переменные, функции, структуры, интерфейсы, типы) могут быть публичными и приватными, для чего используются ключевые слова:

  • private: приватные конструкции библиотеки доступны только внутри библиотеки, в которой были объявлены. Другие библиотеки могут объявлять конструкции с таким же именем, это не имеет значения.
  • public: публичные конструкции библиотеки доступны другим библиотекам. Если 2 разные библиотеки объявляют в себе публичные конструкции с одинаковым именем, будет вызвана ошибка. Будьте осторожны и тщательно выбирайте имя для публичных конструкций.
При объявлении тех или иных конструкций внутри библиотеки, вы можете их расположить внутри public или private блоках, таким образом явно указав их спецификатор доступа. Если спецификатор доступа не указан явно, конструкция будет объявлена как private по умолчанию.
library PublicTest
{
    public
    {
        integer PublicTest1;
        integer PublicTest2;
        integer PublicTest3;
        // Все эти переменные публичные и доступны другим библиотекам
    }
}
Указывать спецификацию доступа можно без использования {}, таким образом делая приватной или публичной только одну конструкцию. Вам так же не обязательно указывать все публичные конструкции в пределах только одного блока. Следующий пример выглядит как vJass конструкция, но она эквивалентна примеру выше:
library PublicTest
{
    public integer PublicTest1;
    public integer PublicTest2;
    public integer PublicTest3;
}

Глобальные переменные

Глобальные переменные хранят в себе какие-либо значения и могут быть использованы внутри любой функции. Если переменная объявлена публичной, она может быть использована функциями извне библиотеки, в который была объявлена. Вы так же можете объявлять константы, поведение которых идентично глобальным переменным, за тем исключением, что значение констант не может быть изменено.
Синтаксис объявления глобальных переменных следующий:
( (constant) тип имя ( = значение) )
Добавление префикса constant сделает переменную константой, что запретит изменение ее значения во время выполнения, но в таком случае установка значения при инициализации переменной обязательна.
Вы так же можете объявлять массивы, чей синтаксис следующий
тип имя[размер]
Указание размера опционально и необязательно. Если размер не указывается, объявляется стандартный Jass массив размером в 8192 ячейки. Вы так же можете объявлять массивы размером более стандартного Jass массива (более 8192), однако при обращении к такому массиву, будет вызываться специально сгенерированная парсером функция.
Вы можете объявлять двумерные массивы
тип имя[размер1][размер2]
чей действительный размер будет равен размер1*размер2.
Массивы не могут быть инициализированы значением при объявлении.
Вы так же можете объявлять несколько переменных одного типа в одной строке, разделяя их имена запятой.
library PublicTest
{
    // Набор приватных констант
    constant integer SPELL_ID = 'A000';
    constant real    HEIGHT = 10.0;
    constant integer UNIT_COUNT = 7, ITEM_COUNT = UNIT_COUNT*6;
        
   // A, B, C, D глобальные переменные целочисленного типа,
   // объявленные как публичные. 
   // Переменные B и C инициализированы значением
   // Переменная D инициализирована значением переменной B
    public integer A;
    public integer B = 1;
    public integer C=2, D=B;

    // X: массив действительных чисел, стандартным размером 8192 ячейки
    // Y: массив действительных чисел, размером 16000 ячеек
    // Z: двумерный массив действительных чисел, размером 200*200
    // Все массивы приватные
    real X[], Y[16000];
    real Z[200][200];

    function onInit()
    {
        A = B;
        X[0] = A;
        X[1] = B + 1;
        D = B;
        D = 7;
        C = D*3;
        Y[10000] = D;
        Z[4][4] = 10;
    }

}

Функции

Функция, это блок когда, который может быть вызван многократно. Функция может принимать аргументы и возвращать какое-либо значение. Синтаксис объявления функции следующий:
function имя ([аргументы]) -> возвращаемыйТип { тело }
Аргументы функции, это список элементов, разеделенных запятой. Каждый элемент этого списка представляет из себя имя типа аргумента, за которым следует имя аргумента.
Если функция не возвращает никакого значения оператор -> не указывается. Если функция возвращает какое-либо значение, -> и следующий за ним тип возвращаемого значения обязательны.
Тело функции, это набор Zinc инструкций. Оно может включать в себя объявление локальных переменных, оператор return, условные блоки if-then-else, циклы while/for/break, присваивание значений, вызовы функций и методов.
Для вызова функции просто используйте имя функции и набор передаваемых значений в круглых скобках следом:
SomeFunctioon(values)
Список передаваемых значений аналогичен списку аргументов при объявлении функции, которые разделяются запятой и следуют порядку указанному при объявлении. Если функция возвращает какое-либо значение, вы можете использовать ее вызов в качестве присваиваемого или передаваемого значения.
Обратите внимание, что объявление функции должно происходить выше места ее вызова. В ином случае вы можете использовать метод .evaluate() и .execute(), о которых позже (в этом плане они действуют как в vJass)
Следуя этим правилам вы можете так же вызывать функции из common.j, blizzard.j и vJass библиотек.
library FunctionTest
{
    integer x = 0;

    function increaseX()
    {
        // Функция, которая просто увеличивает значение
        // глобальной переменной
        x = x + 1;
    }

    function sum3(integer a, integer b, integer c) -> integer
    {
       // Сумма 3х чисел
       return a+b+c;
    }

    function onInit()
    {
        increaseX();
        x = sum3( x, x, 7);
        increaseX();
        /* BJDebugMsg = blizzard.j функция и I2S = wc3 нативная  */
        BJDebugMsg( I2S(x) );

        // Выведет сообщение "10" в игре
    }
}

Присваивание

Для присваивания значения внутри функции/метода, просто установите это значение для переменной или массива. Синтаксис
(thing to assign) = (value)
Вы уже встречали примеры присваивания ранее или можете встретить ниже.

Условный оператор

Синтаксис условного оператора:
if (условие) { инструкции1 } else { инструкции2 }
Если условие истинно, будут выполнены инструкции1, иначе инструкции2. Обратите внимание, что объявление блоков кода посредством {} необходимо только в случае выполнения более одной инструкции, иначе они опциональны и могут быть опущены.
library IfTest
{
    integer x = 0;

    function onInit()
    {
        if(x==0)
        {
            BJDebugMsg("it is 0");
        }
        else
        {
            BJDebugMsg("it is not 0");
        }

        x = GetRandomInt(0,3);
            
        // Короткий вариант, {} не требуется, поскольку выполняется только 1 инструкция
        // (делает ровно то же самое что и предыдущий условный блок)
        if(x==0)  BJDebugMsg("it is 0");
        else      BJDebugMsg("it is not 0");

        x = GetRandomInt(0,6);
        if(x==5)
        {
            BJDebugMsg("Today is your lucky day, because you got a 5");
        }
        else if (x==0)
        {
            // В Zinc нет такой конструкции как elseif, но поскольку
            // после else стоит только 1 if инструкция, в обявлении
            // нового блока нет необходимости. В результате, мы выдим
            // поведение идентичное elseif.
            BJDebugMsg("Today is your unlucky day,")
            BJDebugMsg(" you got a 0");
        }
        else
        {
            BJDebugMsg("Normal day");
            // Вложенный условный оператор
            if( x==4) {
                BJDebugMsg("But hey, at least it is a 4, that is good");
            }
        }

    }

}
Условие подразумевает значение типа boolean, которое может принимать только значение true или false, либо выражение, результатом которого будет значение типа boolean.

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

Следующие операторы могут участвовать в операциях со значениями типа boolean
  • ! : оператор not (не), обращает логическое значение в обратное ( !true становится false и !false становится true)
  • && : оператор and (и), подразумевает, что все значения логического выражения являются true ( true && true равен true, true && false дают false)
  • ||: оператор or (или), подразумевает, что хотя бы одно из значений логического выражения является true ( false || false равен false, true || false дает true)

static if

static if - конструкция, близкая к обычному условному оператору, за тем исключением, что его логическое выражение может содержать только константы типа boolean и только логические операторы. В статичном условии могут быть использованы несуществующие логические константы, но вместо ошибки при компиляции, они будут заменены значением false.
Что самое главное - статичные условия выполняются на этапе компиляции кода. Блок кода, который удовлетворяет условию в static if конструкции будет сохранен в коде карты, не удовлетворяющий же условию блок кода будет удален из кода карты на этапе компиляции.
Что еще нужно знать, так это что на этапе компиляции, если компилятор обнаружит новую библиотеку, он автоматически создаст логическую константу с именем LIBRARY_имяБиблиотеки и установит ей значение true.
library OptionalCode requires optional UnitKiller
{
    constant boolean DO_KILL_LIB = true

    function fun()
    {
        unit u = GetTriggerUnit();
        // Следующий код используется для убийства юнита.
        // Однако, если в коде карты существует библиотека
        // UnitKiller и пользователь установил константе
        // DO_KILL_LIB значение ture, вместо стандартной
        // функции KillUnit будет вызвана функция UnitKiller
        // из этой библиотеки.
        // Все это проверяется на этапе компиляции, удаляя
        // не удовлетворяющий статичному условию код.
        static if(DO_KILL_LIB && LIBRARY_UnitKiller )
        {
            //static if because if the UnitKiller
            // library was not in the map, using a normal
            // if would not remove this line of code and
            // therefore it would cause a syntax error.
            // (unable to find function UnitKiller)
            UnitKiller(u);
        }
        else
        {
            KillUnit(u);
        }
    }

}

library UnitKiller
{
    function UnitKiller(unit u)
    {
        BJDebugMsg("Unit kill!");
        KillUnit(u);
    }
}

Цикл while

Помимо условных конструкция нам так же нужны циклы. Zinc предоставляет цикл while со следующим синтаксисом:
while (условие) {инструкции}
Выполнение тела цикла будет повторятся до тех пор, пока условие имеет значение true. В следующем примере на экран будет выведено значение переменной x, между 5 и 1, в порядке убывания:
library WhileTest
{
    function onInit()
    {
        integer x = 5;
        while( x > 0)
        {
            BJDebugMsg( I2S(x) );
            //decrease x
            x = x - 1;
        }
    }
}

Цикл for

В Zinc присутствует и другая конструкция, специализированная для итераций - цикл for. Предыдущий пример будет реализован лучше с его помощью. Синтаксис цикла:
for ( диапазон ) { инструкции }
Тело цикла будет повторятся в пределах указанного диапазона. Звучит немного обескураживающе.
Давайте представим: мы хотим чтобы некоторая переменная i принимала значения от 0 до n-1. То есть, нам нужно, чтобы набор инструкций повторялся, до тех пор, пока переменная i >= 0 и i < n. Другими словами, пока 0 <= i < n. Таким образом и работает цикл for. Обратите внимание, что цикл for может работать только с обычными переменными (не массивами и не членами структур).
library WhileTest
{
    function onInit()
    {
        integer x, n=7;
        // Числа от 0 до 9,
        // в порядке возрастания
        for ( 0 <= x < 10)
        {
            BJDebugMsg( I2S(x) );
        }

        // Числа от 9 до 0
        // в порядке убывания
        for ( 10 > x >= 0)
        {
            BJDebugMsg( I2S(x) );
        }

        // Числа от 1 до 10,
        // в порядке возрастания
        for( 1 <= x <= 10 )
        {
            BJDebugMsg( I2S(x) );
            //decrease x
            x = x - 1;
        }

        // Числа от 0 до n-1,
        // в порядке возрастания
        for ( 0 <= x < n )
        {
            BJDebugMsg( I2S(x) );
        }
    }
}

break

Если циклы for и while недостаточны гибки для вас, вы можете остановить выполнение текущего цикла используя ключевое слово break
Просто напишите в теле цикла break;

debug

Еще одна полезная конструкция, это синтаксис отладки
debug {statements }
Инструкции блока отладки будут добавлены в код только если JassHelper работает в режиме debug.
library Test
{
    function onInit()
    {
        debug
        {
            BJDebugMsg("Debug mode is enabled");
        }
    }
}

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

Локальные переменные подобны глобальным, они объявляются ровно с тем же синтаксисом. Единственное различие - локальные переменные объявляются внутри функции и могут быть доступны только внутри функции, внутри которой были объявлены. Аргументы функции, это тоже локальные переменные.
Вы должны объявлять локальные переменные в начале функции.
Вы можете объявлять локальные переменные-массивы, но не можете задавать им размерность или объявить такой массив как двумерный.

Структуры

Структуры... это долгая история, а я очень торопился выпустить Zinc. Давайте скажем, что они очень близки к vJass структурам, за исключением синтаксиса свойственного именно Zinc.
Чтобы объявить член структуры мы используем тот же синтаксис что и для глобальных/локальных конструкций, за тем исключением, что они могут быть статичными (static).
Обратите внимание, что в отличие от библиотек, конструкции внутри структуры по умолчанию публичные.
Существуют некоторые отличия в лимите хранения структур и массивах структур, с основным побочным эффектом - не нужно указывать слово array.
О структурах можно сказать многое, но времени мало, посему я просто сдобрю этот параграф обилием примеров:
library WhileTest
{
    // Структура
    struct A
    {
        integer x,y,z;
    }

    // Интерфейс
    interface B
    {
        method move();
    }

    // Структура на 20000 экземпляров
    struct[20000] C
    {
        real a,b,c;
        string s,t;
    }

    // Интерфейс на 20000 экземпляров
    interface[30000] D
    {
        integer meh;
        method transform(integer a) -> integer;
    }

    // "Структура-массив"
    struct myArr[]
    {
        static constant integer meh = 30004;
        integer a;
        integer b;
    }

    // Структура-массив в 10000 ячеек
    struct myErr[10000]
    {
        integer x,y,z;
    }

    interface F
    {
        method balls(integer x) = null; // Аналогичен 'defaults nothing' в vJass
        method bells(integer y) -> real = 0.0 ; // Аналогичен defaults 0.0 в vJass для return real

        // Статичный метод create для этого интерфейса
        static method create();
    }

    struct G
    {
        static method onCast()
        {
            KillUnit( GetTriggerUnit() );
        }

        static method onInit() {
            // Статичный метод onInit
            trigger t= CreateTrigger();

            // Супротив vJass в котором использовался бы синтаксис function G.onCast.
            TriggerAddAction(t, static method G.onCast);
        }
        integer x;

        // Перегрузка операторов:

        method operator< (G other) -> boolean
        {
            return (this.x < other.x)
        }

        method operator[](integer x) -> real
        {
            return x*2.5;
        }

        method operator readOnly() -> integer
        {
            return 0;
        }
    }

    module H
    {
        method kill() { BJDebugMsg("Kill it"); }
    }

    struct K
    {
        module H; // Реализация модуля H
        optional module XX; // Опциональная реализация модуля XX

        delegate G myg; // Делегат
    }

}

Динамические массивы

Синтаксис близок к vJass
library Test
{
    // Динамический целочисленный массив размерностью 5
    type myDinArray extends integer[5];

    // Динамический массив действительных значений, размером 6 и резервом в 10000
    type myDinArray2 extends real[6, 10000];
}

Указатель на функцию

Данное объявление сильно отличается от vJass
library Test
{
    // Функциональный тип с единственным аргументом типа юнит
    type unitFunc extends function(unit);

    // Функциональный тип с двумя аргументами целочисленного типа и возвращаемым
    // значением логического типа (использовать не будем, просто для демонстрации)
    type evFunction extends function(integer,integer) -> boolean;

    function TortureUnit(unit u)
    {
        BJDebugMsg(GetUnitName(u)+" is being tortured!");
        KillUnit(u);
    }

    function HealUnit(unit u)
    {
        SetWidgetLife(u, 1000);
    }

    /* Данная функция вызывает F(u), если юнит является союзником и G(u), если противником.
    Пример бессмысленный и сильно уступает по скорости выполнения обычному if-else ветвлению,
    но сгодится для демонстрации функционального типа. */
    function AllyEnemyFunctionPicker(player p, unit u, unitFunc F, unitFunc G)
    {
        if (IsUnitAlly(u, p)) {
            F.evaluate(u);
        } else {
            G.evaluate(u);
        }
    }

    function test(unit u)
    {
        // Используем функции в качестве аргументов здесь
        AllyEnemyFunctionPicker( Player(0), u,  TortureUnit, HealUnit );
        // Пытает союзника или исцеляет врага
    }

}

Анонимные функции

Множество BJ функций, встроенных функций, нативных функций и функций от других разработчиков, принимает в качестве аргумента другую функцию. Например, запуск таймера, добавления действия триггеру, обход группы через ForGroup, разного рода Condition и так далее.
Анонимные функции, это просто функции без имени. Они так же являются "выражением", которое вы можете использовать там, где это выражение требуется, подразумевая объявление новой функции внутри уже существующей функции... В результате компиляции, анонимные функции будут преобразованы в обычные Jass функции (если не имеют принимаемых аргументов) или объекты функционального типа из примера выше (если имеют список принимаемых аргументов). Zinc неявно даст этим функциям имя и расположит в подходящем для вас месте.
Обратите внимание, что анонимные функции не могут обратиться к локальным переменным родительской функции (это вызовет ошибку PJass). На данный момент, они используются только в качестве более быстрой и лучшей конструкции (можете вспомнить ранее разработанный пример CountUnitsInGroup).
library Test
{
    function test1()
    {
        TimerStart(CreateTimer(), 5.0, false, function() {
             BJDebugMsg("5 seconds since you called test1");
        });
    }


    type unitFunc extends function(unit);
    function AllyEnemyFunctionPicker(player p, unit u, unitFunc F, unitFunc G)
    {
        if (IsUnitAlly(u, p) ) {
            F.evaluate(u);
        } else {
            G.evaluate(u);
        }
    }

    /* Пример функционального типа */
    function test2(unit u)
    {
        unitFunc F,  G;

        F = function(unit u) {
            BJDebugMsg("We are torturing "+GetUnitName(u)+" again");
            KillUnit(u);
        };

        G = function(unit u) { SetWidgetLife(u, 100); };
            
        AllyEnemyFunctionPicker( Player(0), u, F, G);
        // Пытает союзника или исцеляет врага.
        // Обратите внимание, это другой вид реализации примера
        // из предыдущего пункта статьи.
    }

}

Просмотров: 325

Vitalik8 #1 - 1 неделю назад -1
Как использовать тут readonly и что такое опциональная реализация модуля?
GetLocalPlayer #2 - 6 дней назад (отредактировано ) 2
Как использовать тут readonly
Никак, вместо этого используется перегрузка операторов.
Vitalik8:
что такое опциональная реализация модуля?
Это означает, что модуля может не быть. То есть, при использовании модуля внутри структуры с указанием ключевого слова optional, при отсутствии этого модуля в коде карты, компиляция не прервется, ошибки выдано не будет.