Расширенные возможности Lua

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

Оператор return

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

-- Lua - 0.005 s. (190.14 KB/s)
quest example begin state start begin when 9003.chat."Я хочу жениться " begin   --[[ pc.get_level() сообщает уровень игрока ]]   if pc.get_level() < 30 then say("Ваш уровень должен быть выше 30. ") return end   --[[ pc.get_gold() сообщает количество янг у игрока ]]   if pc.get_gold() < 100000 then say("Организация церемонии стоит 100.000 янг. ") return end   --[[ pc.get_sex() сообщает пол игрока (0 - мужчина; 1 - женщина) ]]   if pc.get_sex() != 0 then say("Организовать свадьбу может только мужчина. ") return end   say("Давайте же начнем приготовления к церемонии. ")   --[[ ... продолжение кода ... ]] end end end

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

-- Lua - 0.003 s. (243.89 KB/s)
quest example begin state start begin when 9003.chat."Я хочу жениться " begin if pc.get_level() < 30 then say("Ваш уровень должен быть выше 30. ") else if pc.get_gold() < 100000 then say("Организация церемонии стоит 100.000 янг. ") else if pc.get_sex() != 0 then say("Организовать свадьбу может только мужчина. ") else say("Давайте же начнем приготовления к церемонии. ")   --[[ ... продолжение кода ... ]] end end end end end end

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

Операторы and и or

Как уже упоминалось ранее, данные операторы имеют некоторую особенность. У обоих операторов по обе стороны есть два выражения: у оператора and по обе стороны должно быть true, а у оператора or true должно быть хотя бы с одной из сторон.

Внимательно посмотрите на пример:

-- Lua - 0.003 s. (189.45 KB/s)
quest example begin state start begin function foo() syschat("foo ")   return false end   function bar() syschat("bar ")   return true end   when login begin -- первая секция if example.foo() and example.bar() then syschat("Проверка оператора 'and' ") end  -- вторая секция if example.bar() or example.foo() then syschat("Проверка оператора 'or' ") end end end end

Внимательно посмотрите на обе функции. При выполнении каждая функция отправляет сообщение в чат со своим именем. При выполнении первой секции мы получим в чат только сообщение «foo». Т.к. первое выражение у оператора and вернуло false, то второе выражение уже не проверяется, поэтому функция bar() даже не запускается.

Во второй секции все аналогично. Только тут мы увидим 2 сообщения: «bar» и «Проверка оператора 'or'». Т.к. оператору or достаточно того, чтобы одно из выражений было истиной (а bar() возвращает true), то второе выражение данный оператор уже не проверяет.

Внутристрочное условие if

В Lua, как и в большинстве других языков программирования, можно делать внутристрочные условия if-else. Пример:

-- Lua - 0.002 s. (30.35 KB/s)
local a = 5   syschat(a == 5 and "пирожок " or "булочка ")

Данный пример выведет в чат слово «пирожок». Прочитать это можно так: вывести сообщение в чат (если 'а' = 5, то «пирожок», иначе «булочка»). Внутристрочные условия поддерживают только конструкции «если ... то ... иначе ...». Пример с конкатенацией строк:

-- Lua - 0.003 s. (41.88 KB/s)
local a = 3   syschat("У меня есть " .. (a == 5 and "пирожок " or "булочка ") .. "и стакан молока. ")

Выведет в чат «У меня есть булочка и стакан молока.». Если внутристрочное условие объединяется со строкой, то его следует заключить в скобки. Если условий несколько, то их тоже следует заключить в скобки:

-- Lua - 0.004 s. (57.52 KB/s)
local a = 3 local b = 10   syschat("У меня есть " .. ((a == 5 or b == 10) and "пирожок " or "булочка ") .. "и стакан молока. ") --> У меня есть пирожок и стакан молока.

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

-- Lua - 0.004 s. (66.63 KB/s)
local a = 3 local b = 10   syschat("У меня есть " .. (a == 3 and (b == 10 and "тортик " or "бутерброд ") or "булочка ") .. "и стакан молока. ") --> У меня есть тортик и стакан молока.

Есть еще такой вариант записи:

-- Lua - 0.003 s. (48.67 KB/s)
local a = 5   syschat("Стакан молока стоил " .. (a or 8) .. " рублей. ") --> Стакан молока стоил 5 рублей.

В данном примере возвращается первое условие, если оно не является nil или false. У нас первое условие равно 5, поэтому оно и вернулось. Еще пример:

-- Lua - 0.002 s. (94.22 KB/s)
-- Обратите внимание на то, что b мы нигде не задавали   syschat("Стакан молока стоил " .. (b or 8) .. " рублей. ") --> Стакан молока стоил 8 рублей.

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

-- Lua - 0.002 s. (100.12 KB/s)
-- Обратите внимание на то, что b мы нигде не задавали   syschat("Стакан молока стоил " .. (b and b or 8) .. " рублей. ") --> Стакан молока стоил 8 рублей.

Но тут b вызывается дважды, а в конструкции (b or 8) — всего один раз. Это важно, особенно если вместо переменной использовать какие-нибудь функции:

-- Lua - 0.003 s. (206.97 KB/s)
quest example begin state start begin function foo() syschat("foo ")   return 5 end   when login begin syschat("Стакан молока стоил " .. (example.foo() or 8) .. " рублей. ") -- Выведет два сообщения: --> foo --> Стакан молока стоил 5 рублей.   syschat("Стакан молока стоил " .. (example.foo() and example.foo() or 8) .. " рублей. ") -- Выведет три сообщения: --> foo --> foo --> Стакан молока стоил 5 рублей. end end end

Таблицы

Таблицы в Lua являются структурированным набором данных. Существует два типа таблиц: индексированные таблицы (от слова «индекс») и ассоциативные таблицы (от слова «ассоциация»). Для начала мы рассмотрим первый тип.

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

-- Lua - 0.002 s. (84.38 KB/s)
local all_months = {"Январь ", "Февраль ", "Март ", "Апрель ", "Май ", "Июнь ", "Июль ", "Август ", "Сентябрь ", "Октябрь ", "Ноябрь ", "Декабрь "}

Получить доступ к названию месяца можно по его порядковому номеру в таблице:

-- Lua - 0.003 s. (88.44 KB/s)
local all_months = {"Январь ", "Февраль ", "Март ", "Апрель ", "Май ", "Июнь ", "Июль ", "Август ", "Сентябрь ", "Октябрь ", "Ноябрь ", "Декабрь "}   syschat(all_months[2]) --> Февраль

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

-- Lua - 0.004 s. (303.82 KB/s)
quest example begin state start begin when login begin say("Введите порядковый номер месяца, ") say("в котором вы родились: ")   --[[ Функция input() выводит поле ввода текста Возвращает введенную пользоваетелем информацию ]]   local month = input()   --[[ Функция tonumber() превращает число типа string в тип numbеr ("123" --> 123) Если в функцию передать не число, то она вернет nil ]]   if not month or not tonumber(month) then syschat("Вы не ввели число. ") return end   month = tonumber(month)   if month < 1 or month > 12 then syschat("Число должно быть от 1 до 12. ") return end   local all_months = { "Январь ", "Февраль ", "Март ", "Апрель ", "Май ", "Июнь ", "Июль ", "Август ", "Сентябрь ", "Октябрь ", "Ноябрь ", "Декабрь " }   syschat("Месяц вашего рождения: " .. all_months[month]) end end end

Элементами таблицы могут быть данные любого формата (внутри квеста данные типа function не поддерживаются):

-- Lua - 0.004 s. (134.86 KB/s)
local a = {"Наполеон ", 123, true, false, nil, {"Яблоко ", 55}, "123"}   syschat(type(a[1])) --> string syschat(type(a[2])) --> number syschat(type(a[3])) --> boolean syschat(type(a[4])) --> boolean syschat(type(a[5])) --> nil syschat(type(a[6])) --> table syschat(type(a[7])) --> string   -- Обращение к вложенной таблице   syschat(type(a[6][1])) --> string syschat(type(a[6][2])) --> number   -- Индекс обязательно должен быть типа number   syschat(type(a["1"])) --> nil

Разумеется, значения таблицы можно менять:

-- Lua - 0.003 s. (47.31 KB/s)
local a = {"Наполеон "}   syschat(a[1]) --> Наполеон   a[1] = "Иван Грозный "   syschat(a[1]) --> Иван Грозный

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

-- Lua - 0.003 s. (96.17 KB/s)
local calories = { ["Big Mac"] = 540, ["Cheeseburger"] = 300, ["McChicken"] = 370 }   local burger = "Big Mac"   syschat(burger .. " содержит " .. calories[burger] .. " каллорий. ") --> Big Mac содержит 540 каллорий.

«Big Mac», «Cheeseburger» и «McChicken» — это всё называется «ключи таблицы». Количество каллорий — это значение ключа. Ключом таблицы может быть любой тип данных. Данные можно добавлять на лету:

-- Lua - 0.002 s. (111.65 KB/s)
local calories = { ["Big Mac"] = 540, ["Cheeseburger"] = 300, ["McChicken"] = 370 }   calories["Filet-O-Fish"] = 390   syschat("Filet-O-Fish содержит " .. calories["Filet-O-Fish"] .. " каллорий. ") --> Filet-O-Fish содержит 390 каллорий.

Можно даже сделать так:

-- Lua - 0.002 s. (116.82 KB/s)
local calories = {}   calories["Big Mac"] = 540 calories["Cheeseburger"] = 300 calories["McChicken"] = 370 calories["Filet-O-Fish"] = 390   syschat("Filet-O-Fish содержит " .. calories["Filet-O-Fish"] .. " каллорий. ") --> Filet-O-Fish содержит 390 каллорий.

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

-- Lua - 0.002 s. (89.41 KB/s)
local calories = { big_mac = 540, cheeseburger = 300, mcchicken = 370 }   syschat("Big Mac содержит " .. calories["big_mac"] .. " каллорий. ") --> Big Mac содержит 540 каллорий.

Тут big_mac — не переменная. Оно автоматически будет сконвертировано в ["big_mac"]. Но лучше забудьте об этом методе.

Таблицы могут иметь смешанный тип:

-- Lua - 0.002 s. (62.40 KB/s)
local example = { "Car", ["pyramid"] = 123, "Lego" }   syschat(example[1]) --> Car syschat(example["pyramid"]) --> 123 syschat(example[2]) --> Lego

Циклы

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

for ... do

Цикл for запускается определенное количество раз. Самый простой вариант такого цикла выглядит так:

-- Lua - 0.002 s. (56.96 KB/s)
for i = 1, 5, 1 do syschat("Цикл " .. i) end   --> Цикл 1 --> Цикл 2 --> Цикл 3 --> Цикл 4 --> Цикл 5

Первый аргумент цикла i = 1 задает локальную переменную, которая будет видна только внутри цикла. Данный аргумент выполняется только один раз перед началом цикла.

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

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

Еще несколько примеров:

-- Lua - 0.002 s. (57.42 KB/s)
for i = 5, 1, -1 do syschat("Цикл " .. i) end   --> Цикл 5 --> Цикл 4 --> Цикл 3 --> Цикл 2 --> Цикл 1
-- Lua - 0.002 s. (133.14 KB/s)
local countries = {"Russia", "USA", "Poland"}   --[[ Функция table.getn() возвращает количество элементов в таблице ]]   for i = 1, table.getn(countries), 1 do syschat("Страна: " .. countries[i]) end   --> Страна: Russia --> Страна: USA --> Страна: Poland

Существует оператор break, который позволяет прервать цикл. Работает аналогично оператору return, только прерывается не выполнение всего кода, а только цикла:

-- Lua - 0.002 s. (95.06 KB/s)
for i = 1, 5, 1 do if i >= 3 then break end   syschat("Цикл " .. i) end   syschat("Сообщение вне цикла ")   --> Цикл 1 --> Цикл 2 --> Цикл 3 --> Сообщение вне цикла

for ... in ... do

Расширенный цикл for использует оператор in. Он не похож на тот, что демонстрировался выше. Данный тип цикла сложно понять, поэтому вы можете просто перемотать дальше. Пример:

-- Lua - 0.002 s. (149.53 KB/s)
local calories = { ["Big Mac"] = 540, ["Cheeseburger"] = 300, ["McChicken"] = 370 }   for key, value in pairs(calories) do syschat(key .. " содержит " .. value .. " каллорий. ") end   --> Big Mac содержит 540 каллорий. --> Cheeseburger содержит 300 каллорий. --> McChicken содержит 370 каллорий.

Функция pairs() — это так называемая «функция-итератор». Она возвращает два значения: ключ и значение элемента таблицы. А оператор in переносит эти значения в переменные слева (ключ таблицы в переменную key, а значение — в переменную value). Цикл выполняется до тех пор, пока функция-итератор не вернет nil.

Если в функцию pairs() передать индексированную таблицу, то это будет выглядеть так:

-- Lua - 0.002 s. (114.16 KB/s)
local burgers = {"Big Mac", "Cheeseburger", "McChicken"}   for key, value in pairs(burgers) do syschat(key .. " имеет индекс " .. value) end   --> Big Mac имеет индекс 1 --> Cheeseburger имеет индекс 2 --> McChicken имеет индекс 3

В данном случае функция вместо ключей возвращает индекс элемента таблицы. Еще есть функция-итератор ipairs(), которая работает только с индексированными таблицами. Если в примере выше pairs() заменить на ipairs(), то результат не изменится. Если в ipairs() передать ассоциальную таблицу, то функция вернет nil и цикл не запустится.

while ... do

Цикл while самый простой из всех: он выполняется до тех пор, пока его условие равно true:

-- Lua - 0.002 s. (48.26 KB/s)
local i = 5   while i <= 10 do syschat(i)   i = i + 1 end   --> 5 --> 6 --> 7 --> 8 --> 9 --> 10
-- Lua - 0.002 s. (75.78 KB/s)
local burgers = {"Big Mac", "Cheeseburger", "McChicken"} local i = 1   while burgers[i] do syschat(burgers[i])   i = i + 1 end   --> Big Mac --> Cheeseburger --> McChicken

repeat ... until

Цикл repeat независимо от условия выполняется всегда минимум один раз и выполняется он до тех пор, пока его условие не будет равно true:

-- Lua - 0.002 s. (75.75 KB/s)
local burgers = {"Big Mac", "Cheeseburger", "McChicken"} local i = 1   repeat syschat(burgers[i])   i = i + 1 until burgers[i] == "McChicken"   --> Big Mac --> Cheeseburger

Цикл выполняется минимум один раз:

-- Lua - 0.002 s. (37.28 KB/s)
local a = 100 local b = 100   repeat syschat("Paris ") until a == b   --> Paris

Перехват возвращаемых значений функций

В будущем вы можете столкнуться с тем, что напишите функцию, которая будет возвращать неопределенное количество значений. Давайте представим, что вы написали функцию example(), которая возвращает от 1 до 10 значений. Такой пример не очень правильный:

-- Lua - 0.002 s. (142.93 KB/s)
local a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 = example()   if a1 then syschat("Переменная a1 существет и равна " .. a1) end   if a2 then syschat("Переменная a2 существет и равна " .. a2) end   -- ...   if a10 then syschat("Переменная a10 существет и равна " .. a10) end

Правильно делать вот так:

-- Lua - 0.002 s. (59.75 KB/s)
local a = {example()}   for key, value in pairs(a) do syschat("Переменная номер " .. key .. " равна " .. value) end

Заключая функцию, возвращающую несколько значений, в таблицу, на месте этой функции будет создано столько элементов таблицы, сколько эта функция и возвращает:

-- Lua - 0.002 s. (87.18 KB/s)
--[[ Функция example() в данном примере возвращает "Sand" и "Salt" ]]   local a = {"Sugar", example()}   syschat(a[1]) --> Sugar syschat(a[2]) --> Sand syschat(a[3]) --> Salt
230