ITCS - Lua для игр и не только. Часть 2 - РАЗРАБОТКА КОМПЬЮТЕРНЫХ ИГР
Сегодня: Четверг, 08.12.2016, 01:09 (МСК)| Здравствуйте, Гость| Мой профиль | Регистрация | Вход | RSS

Домашние системы
3D-видения

Визуальная среда Flowstone

Blu-ray приводы для ПК

Google Chrome. Таким должен быть браузер

Adobe Audition 3. Лучшая в 2010-м
Главная » РАЗРАБОТКА КОМПЬЮТЕРНЫХ ИГР

Lua для игр и не только. Часть 2

01.08.2010
«Развалины Колизея — это доказательство великих строительных возможностей древних предков…»
Странная фраза, прозвучавшая по ТВ

Веселый вопрос мне задали на днях: «ты работаешь с искусственным интеллектом, а файлы с расширением *.ai к какому языку относятся?» — «К программе Adobe Illustrator» — «А причем здесь AI?». Как говорили в советские времена, которые я зацепил детством: «Ширше смотреть нужно».

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

Есть и другие моменты, например… В прошлом материале этой серии эпиграфом шла шутка: «Если вы не любите кошек, значит, не умеете их готовить». Эта фраза интересна тем, что при неправильном дословном переводе англичане или те же белорусы могут ее не так понять, потому как у них есть два отдельных слова «love»/«like», «кахаць»/«любiць» («калi вы не кахаеце катоу…»:))))). Тут смешно, но иногда и языки программирования взаимодействуют также. Кстати, это очень уместный пример для сегодняшнего обсуждения. 

Ну что же, вступление пройдено, к делу!


Лексические соглашения Lua


Здесь все стандартно, имена переменных не должны начинаться с цифр и повторять зарезервированные слова. Есть чувствительность к регистру. Список зарезервированных слов достаточно стандартен: and, break, do, else, elseif, end, false, for, function, if, in, local, nil, not, or, repeat, return, then, true, until, while. Как видите, не так много (по секрету скажем, что в ассемблере гораздо меньше:)). Дело в том, что Lua считается простым не только в силу высокоуровневой конструкции, которую он пытается представить, но и в облегчении языка. Где спросите case? Нет его, да, и не очень нужен. Куда девались классы? А зачем они в Lua? По сравнению с предыдущими версиями в зарезервированных словах добавлены true и false, но об этом позже:).  

По соглашению, имена, начинающиеся с символа подчеркивания и записанные в верхнем регистре (например, _VERSION), зарезервированы для использования в качестве внутренних глобальных переменных, используемых Lua.

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

Допустимые символы: «+», «-», «*», «/», «%»(остаток от деления), «^» (степень), «#» (операция определения длины, например, строки), «==», «~=» (не равно), «<=», «>=», «<», «>», «=», «(», «)», «{», «}», «[», «]», «;», «:», «,», «.», «..» (конкатенация), «...» (неявный аргумент).


Динамический контроль типов



Помню, когда я начал писать на ActionScript 2, маститые программисты, представляющие классическую С-школу, меня ругали: как это не указывать тип данных для переменной, сразу присваивая ей значение, а после и вообще меняя этот тип на другой. Например, в AS2 без ошибок сработает такой код:

A=true;
B=75;
C=A*B; (выдаст результат 1*75=75)
A= «Привет»;

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

Динамический контроль типов, а его разновидностью является автоматическое преобразование типов, в большинстве языков программирования сведен к минимуму (причина — ресурсоемкость и невысокая надежность динамического контроля). Основная роль отводится статическому контролю типов, т. е. контролю на этапе компиляции. Его суть заключается в том, что компилятор проверяет, не используются ли в операциях операнды недопустимых типов. Если подобное несоответствие обнаруживается, возвращается сообщение об ошибке. В рамках Lua динамический контроль типов решен своеобразно, в силу специфики как самого языка, так и собственно, самих типов.

Типы данных


Итак, перейдем к описанию типов данных Lua (возможности определения пользовательских типов данных отсутствуют), ведь по ним можно понять функциональную принадлежность того или иного языка. 

Этих типов восемь (в более ранних версиях языка было меньше, не было потоков и, многие удивятся — boolean, хотя логические операторы присутствовали). 
  • nil (неопределенный), 
  • boolean (логический), 
  • number (числовой), 
  • string (строковый), 
  • function (функция), 
  • userdata (пользовательские данные), 
  • thread (поток), 
  • table (таблица)
Nil - это особый тип значения, главное свойство которого — отличаться от всех остальных значений и обозначать отсутствие пригодного значения. Во многих языках есть аналог этого типа — undefined. 



Boolean — тип данных, известный всем. К этому типу относятся только две величины — true (истинно) и false (ложно). На самом деле булев тип… нужен он или нет, для расстановки флагов, конечно, полезен, да и интеграция с С получается более плотной:) Не будем долго распространяться на эту тему.  

Причем в рамках Lua nil и false воспринимаются (ассоциируются) как ложь, когда все другие варианты — как истина (в том числе ноль и пустая строка). Чтобы проиллюстрировать как это происходит, дадим пример кода (не забываем, что используем редактор/компилятор SciTE c http://scite.ruteam.ru):

function eq(...)
if (...) then print("да") else print("нет") end
end
eq(false) eq(nil) 
eq(nil==false)
eq(0) eq("Lua") 

Результатами будут: нет, нет, нет, да, да. Объясним код. Изначально мы создали функцию eq, которая получает в качестве параметров данные без определенного типа (то есть любого, «…» обозначает неявный аргумент), и если они соответствуют истине, то выводится ответ «да». Мы послали пять запросов (напомним, что разделение между командами, операциями и т.п. можно делать с помощью пробела). Третий запрос по поводу равенства nil и false, которые ассоциируются как ложь, также выдал ответ: нет. То есть, они ассоциируются, но не приравниваются. 

Я не спроста начал разговор с ActionSript, поскольку там представлена более свободная модель работы с типами данных. В нашем случае в рамках Lua сравнение на равенство (==) сначала сравнивает типы операндов и если они различны, выдается false. То есть, выражение «0» == 0 также выдаст false. Но при этом в рамках арифметических операций Lua обеспечивает автоматическое преобразование между строковыми и числовыми значениями в процессе выполнения, а в операциях сравнения(!) этого не происходит. Обойти данные тонкости можно различными путями (есть и базовые функции конвертирования tostring() и tonumber()). 

Number — числовой тип, подразумевающий вещественные числа двойной точности с плавающей запятой (тип double в других языках). Причем внутреннее представление чисел можно поменять, изменив определение в файле luaconf.h. 

Теперь пройдемся по классическим ошибкам… Добавьте к предыдущему коду следующее:

a=2
b=((a)^0.5)^2
eq(a==b)
print(a-b)

Мы присвоили a значение 2, а b у нас равен возведенному в квадрат корню из 2, то есть, по идее, b=2 (корень и возведение в степень можно также вычислять при использовании библиотеки math, записав строку: b= math.pow(math.sqrt(a),2) вместо b=((a)^0.5)^2). Что выдает программа? 

нет
-4.4408920985006e-016  

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

if math.abs(a-b)<0.0000001 then a=b end

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

String — строковый тип данных, то есть массив символов. Cтроки могут содержать любой 8-разрядный символ, включая вложенные нули ('\0').

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

Функции (Function) с одной стороны в Lua рассматриваются как значения первого класса (first-class values), к которым относятся и переменные. Это означает, что функции могут быть сохранены в переменных, переданы как параметры другим функциям и возвращены как результаты. Lua может вызывать и управлять как функциями, написанными на Lua, так и функциями, написанными на C. На данный момент функции рассматриваются и как объекты (подробности чуть ниже). Вообще функции достаточно сложно отнести к какому-либо типу данных, что очевидно проявляется во многих высокоуровневых языках, которые избегают создания типа function. Функция — это чрезвычайно многоликая структура. Она может быть сравнима и с подпрограммой, тогда представление типа является чрезвычайно сложным, потому как исполнимый код не является объектом данных в буквальном смысле. И, кстати, Lua — один из немногих языков, в котором функции представляются как отдельный тип.  

Userdata — особый тип данных, по существу принадлежащих ведущей программе. Lua не может их создавать или изменять в своих рамках, но при этом может производить ряд операций (присваивание, проверка на равенство). Другими словами, тип userdata обеспечивается, чтобы позволить произвольным C-указателям быть сохраненными в Lua-переменных. Над этими данными можно производить другие операции с помощью метатаблиц, о чем мы расскажем отдельно. 

Thread — независимый поток исполнения, используется при реализации механизма сопрограмм (coroutines). При этом не стоит его отождествлять с потоками операционной системы, есть ОС, в которых не реализована поддержка потоков. Lua поддерживает подпрограммы, эту технологию часто называют общей многопоточностью. Подпрограмма Lua представляет собой независимый поток выполнения. Об этом мы поговорим отдельно. 

Тип Table представляет собой единственный механизм структурирования данных в Lua и определяет ассоциативные массивы, которые могут заполнятся данными различных типов за исключением nil. То есть, они могут использоваться как обычные массивы, таблицы символов, множества, поля записей, деревья и так далее. Причем, поскольку функции относятся к значениям первого класса, поля таблицы могут содержать и функции. Таким образом, таблицы могут хранить методы.

Типы userdata, thread, table, function являются объектами, которые не содержат самих данных, а только ссылки на соответствующие объекты и значения. Переменные не содержат значения, только ссылаются на них. Назначение, обработка параметра и возврат всегда управляют ссылками и не подразумевают никакого вида копирования.  

Например, мы говорили об операциях сравнения, где числа и строки сравниваются обычным способом. Но с объектами ситуация иная. Таблицы, пользовательские данные, потоки и функции сравниваются по ссылке: два объекта считаются равными, только если они являются одним и тем же объектом, хотя можно изменить способы сравнения тех же таблиц и пользовательских данных с использованием метаметодов (об этом будет рассказано в следующих материалах). В документации сказано, что создаваемый объект (таблица, пользовательские данные, поток или функция) не может быть равен ни одному из уже существующих, это, конечно, почти так, но есть тонкости. 

А чтобы было более понятно вышесказанное, приведем тривиальный пример: 

function f(a,b) 
return a+b
end
--------
function g(a,b)
return f(a,b)
end
--------
print(f(1,2))
print(g(1,"2"))
------------
print(f)
print(g)
if (f ~= g) then print("да") end

Конечно же, объяснение займет больше времени, чем написание кода:). Итак, функция f(a,b) не содержит значений, а только ссылается на параметры, являющиеся локальными переменными, которые инициализированы входными значениями. В теле функции g(a,b) для вывода результатов мы просто расположили вызов функции f(a,b), сославшись на нее. Потом мы выводим результаты. print() по существу ведет себя также, как и другие функции, потому как 1 и 2 хранятся как аргументы, на которые ссылается print(), вызывая f(a,b) и передавая ссылки на эти значения. То есть, новые определения в документации по типу function являются более достоверными — функция является объектом, содержащим ссылки. 

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

Варианты print(f) и print(g) отобразят ссылки, соответствующие этим функциям. И если эти два объекта не равны, то выведется ответ «да».
Итак, нажимаем F5, результаты будут следующими:

3
3
function: 010342F8
function: 009CC450
да    

Объявление функции является выполняемым выражением, его результатом будет значение типа function, что нам демонстрируют результаты вывода после print(f) и print(g). Это конкретизирует правило: два объекта считаются равными, только если они являются одним и тем же объектом. Но еще не совсем.

Думается, что не все разобрались со ссылками, объектами и их равенством.

Допустим, мы модифицировали код:

function f(a,b)
return a+b 
end
g=f  
 s=g
c=s(2,3)
print(c) print(f)
print(g) print(s)

При выводе результатов вы увидите, что f(a,b), g, s — это один и тот же объект, то есть ссылка у всех идентична. Об этом и речь. Нет никакого копирования. Мы оперировали только присваиванием ссылок на объекты. И в данном случае сравнение s==f, g==f или g==s будет равно true.


Промежуточное завершение


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






Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Ассоциация боевых роботов
Рекомендуем...
Новости

Разделы

Опросы

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

Друзья

3D-кино






Найти на сайте:








Об авторе       Контакты      Вопрос-ответ        Хостинг от uCoz