Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
611 lines (448 sloc) 25.3 KB

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

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

Оператор return

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

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

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

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 должно быть хотя бы с одной из сторон.

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

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), то второе выражение данный оператор уже не проверяет.

Тернарный оператор

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

local a = 5

syschat(a == 5 and "пирожок " or "булочка ")

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

local a = 3

syschat("У меня есть " .. (a == 5 and "пирожок " or "булочка ") .. "и стакан молока. ")

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

local a = 3
local b = 10

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

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

local a = 3
local b = 10

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

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

local a = 5

syschat("Стакан молока стоил " .. (a or 8) .. " рублей. ")
	--> Стакан молока стоил 5 рублей.

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

-- Обратите внимание на то, что b мы нигде не задавали

syschat("Стакан молока стоил " .. (b or 8) .. " рублей. ")
	--> Стакан молока стоил 8 рублей.

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

-- Обратите внимание на то, что b мы нигде не задавали

syschat("Стакан молока стоил " .. (b and b or 8) .. " рублей. ")
	--> Стакан молока стоил 8 рублей.

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

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 являются структурированным набором данных. Существует два типа таблиц: индексированные таблицы (от слова «индекс») и ассоциативные таблицы (от слова «ассоциация»). Для начала мы рассмотрим первый тип.

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

local all_months = {"Январь ", "Февраль ", "Март ", "Апрель ", "Май ", "Июнь ", "Июль ", "Август ", "Сентябрь ", "Октябрь ", "Ноябрь ", "Декабрь "}

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

local all_months = {"Январь ", "Февраль ", "Март ", "Апрель ", "Май ", "Июнь ", "Июль ", "Август ", "Сентябрь ", "Октябрь ", "Ноябрь ", "Декабрь "}

syschat(all_months[2]) --> Февраль

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

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 not month or month < 1 or month > 12 then
				syschat("Число должно быть от 1 до 12. ")
				return
			end

			local all_months = {
				"Январь ",  "Февраль ", "Март ",
				"Апрель ",  "Май ",     "Июнь ",
				"Июль ",    "Август ",  "Сентябрь ",
				"Октябрь ", "Ноябрь ",  "Декабрь "
			}

			syschat("Месяц вашего рождения: " .. all_months[month])
		end
	end
end

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

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

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

local a = {"Наполеон "}

syschat(a[1]) --> Наполеон

a[1] = "Иван Грозный "

syschat(a[1]) --> Иван Грозный

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

local calories = {
	["Big Mac"] = 540,
	["Cheeseburger"] = 300,
	["McChicken"] = 370
}

local burger = "Big Mac"

syschat(burger .. " содержит " .. calories[burger] .. " каллорий. ")
	--> Big Mac содержит 540 каллорий.

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

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 каллорий.

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

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 каллорий.

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

local calories = {
	big_mac = 540,
	cheeseburger = 300,
	mcchicken = 370
}

syschat("Big Mac содержит " .. calories["big_mac"] .. " каллорий. ")
	--> Big Mac содержит 540 каллорий.

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

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

local example = {
	"Car",
	["pyramid"] = 123,
	"Lego"
}

syschat(example[1]) --> Car
syschat(example["pyramid"]) --> 123
syschat(example[2]) --> Lego

Глобальные переменные и суперглобальная переменная _G

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

emoji_sucks = 100
_G["emoji_sucks"] = 100

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

Циклы

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

for ... do

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

for i = 1, 5, 1 do
	syschat("Цикл " .. i)
end

	--> Цикл 1
	--> Цикл 2
	--> Цикл 3
	--> Цикл 4
	--> Цикл 5

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

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

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

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

for i = 5, 1, -1 do
	syschat("Цикл " .. i)
end

	--> Цикл 5
	--> Цикл 4
	--> Цикл 3
	--> Цикл 2
	--> Цикл 1
local countries = {"Russia", "USA", "Poland"}

--[[
	Функция table.getn() возвращает количество элементов в таблице
]]

for i = 1, table.getn(countries), 1 do
	syschat("Страна: " .. countries[i])
end

	--> Страна: Russia
	--> Страна: USA
	--> Страна: Poland

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

for i = 1, 5, 1 do
	if i >= 3 then
		break
	end

	syschat("Цикл " .. i)
end

syschat("Сообщение вне цикла ")

	--> Цикл 1
	--> Цикл 2
	--> Цикл 3
	--> Сообщение вне цикла

for ... in ... do

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

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() передать индексированную таблицу, то это будет выглядеть так:

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:

local i = 5

while i <= 10 do
	syschat(i)

	i = i + 1
end

	--> 5
	--> 6
	--> 7
	--> 8
	--> 9
	--> 10
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:

local burgers = {"Big Mac", "Cheeseburger", "McChicken"}
local i = 1

repeat
	syschat(burgers[i])

	i = i + 1
until burgers[i] == "McChicken"

	--> Big Mac
	--> Cheeseburger

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

local a = 100
local b = 100

repeat
	syschat("Paris ")
until a == b

	--> Paris

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

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

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

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

local a = {example()}

for key, value in pairs(a) do
	syschat("Переменная номер " .. key .. " равна " .. value)
end

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

--[[
	Функция example() в данном примере возвращает "Sand" и "Salt"
]]

local a = {"Sugar", example()}

syschat(a[1]) --> Sugar
syschat(a[2]) --> Sand
syschat(a[3]) --> Salt