Строка 6: |
Строка 6: |
| | | |
| == Основы == | | == Основы == |
− |
| |
− | Здравствуйте, сегодня мы будем создавать объекты, переменные, методы и изучать основы логики.
| |
− |
| |
− | Начнём!
| |
| | | |
| '''ООП''' (''Объектно-Ориентированное Программирование'') - основа BYOND. Это не процедурный и далеко не императивный язык, здесь всё по большей части закручено именно на объектах, так что примем '''объект''' за низшую единицу счисления. Что же такое объект? Аирлок, плата в компьютере, "око" ИИ и т.д. Объект обладает чётко выраженными свойствами(переменные) и возможностями(методы). Но что я говорю, давайте же попробуем создать объект! | | '''ООП''' (''Объектно-Ориентированное Программирование'') - основа BYOND. Это не процедурный и далеко не императивный язык, здесь всё по большей части закручено именно на объектах, так что примем '''объект''' за низшую единицу счисления. Что же такое объект? Аирлок, плата в компьютере, "око" ИИ и т.д. Объект обладает чётко выраженными свойствами(переменные) и возможностями(методы). Но что я говорю, давайте же попробуем создать объект! |
Строка 97: |
Строка 93: |
| | | |
| Что за '''return'''? Это оператор возврата значения. Если его прописать без значения - как в нашем случае, он возвращает ''null'', хотя это мы обсудим в другом уроке. В данном случае ''return'' работает как прерыватель исполнения кода. То есть после выполнения ''return'', исполнение данного метода дальше не идёт. | | Что за '''return'''? Это оператор возврата значения. Если его прописать без значения - как в нашем случае, он возвращает ''null'', хотя это мы обсудим в другом уроке. В данном случае ''return'' работает как прерыватель исполнения кода. То есть после выполнения ''return'', исполнение данного метода дальше не идёт. |
| + | |
| + | == Наследование == |
| + | |
| + | В прошлом параграфе мы рассмотрели создание переменных и методов, объектов и использование процедур, в этом же мы глубже копнём методы, изучим основные возможности самого универсального цикла бьёнда и действий ("''verb''"), наследование, а также подружим два разных объекта. |
| + | |
| + | Что такое метод, мы видели в предыдущем параграфе, но у него ещё множество интересных возможностей. Вспомните фонарь, он нам ещё сослужит службу. На данный момент он выглядит так: |
| + | |
| + | /obj/flashlight |
| + | var/on = 0 |
| + | var/charge = 1000 |
| + | |
| + | proc/Switch() |
| + | if(!charge && !on) |
| + | return |
| + | on = !on |
| + | |
| + | proc/Use(var/t = 3) |
| + | charge = max(0, charge - t) |
| + | if(!charge) |
| + | Switch() |
| + | |
| + | Сделаем для фонаря лампочку. Конечно, столь малый код проще сделать прямо в фонаре, но если мы собираемся использовать лампочку где-то ещё или просто хотим отделить одно от другого, то лучше создать новый объект. |
| + | Лампочка будет обладать параметром "''glower''", который отвечает за состояние нити накаливания и "''glass''" за состояние стекла лампы. Конечно, вообще такие сложности ни к чему, но ради примера можно. |
| + | |
| + | /obj/bulb |
| + | //Status |
| + | var/glower = 0 |
| + | var/glass = 0 |
| + | |
| + | Иногда метод гораздо удобнее, чем проверка каждой переменной, поэтому сделаем такой для проверки: |
| + | |
| + | proc/Broken() |
| + | if(glower || glass) |
| + | return 1 |
| + | return 0 |
| + | |
| + | proc/AnotherBroken() |
| + | return glower && glass |
| + | |
| + | Здесь можно увидеть два примера - в первом переменные "складываются", во втором "умножаются". Любой из них будет работать одинаково хорошо, но я возьму первый. Как ранее говорилось, "''return''" отвечает за то, что возвращает метод - в нашем случае он отвечает, сломана ли лампочка. Сделаем эту лампочку тёплой и ламповой, пускай она греет людям сердца :3 |
| + | |
| + | proc/Emit(var/r) |
| + | if(Broken()) return |
| + | for(var/mob/m in range(r)) m << "You feel some heat." |
| + | |
| + | С первой строкой, я думаю, всё понятно, но во второй "''return''" на той же строке, что и "''if''", разве так можно? Да, если после системного оператора(''for'', ''if'', ''while'' и прочее) вам хватает лишь одной строки, то можно поставить код прямо на той же строке, точно также мы можем написать "''for(var/mob/m in range®) m << "You feel some heat."''" в одну строку. Но не переусердствуйте с этим, обычно так лаконично смотрится лишь простой код. |
| + | |
| + | Следующая строка с ''for'' это цикл, довольно сложная строчка, я скажу, поэтому разберём её по кусочкам с конца: ''range(n)'' это системный метод бьёнда, который возвращает связный список "''list''" (далее). "''in''" это также системный оператор, но немного другой. Он сверяет список справа с условием слева и возвращает уже фильтрованный список. Вообще данная строка читается как "Для (''for'') всех объектов типа ''/mob(var/mob/m)'' в (''in'') радиусе 3 (''range(3)'')". Внимательные уже заметили "''var''" и, возможно, поняли, что мы создали переменную в цикле. Но чего мы этим добиваемся? Для этого надо рассмотреть работу "''for''" - он начинает листать список: берёт первый элемент списка, присваивает его укзанной и созданной специально для этого переменной, указанной в самом "''for'''е", исполняет код, указанный ниже него, потом повторяет, пока не кончатся объекты или не встретится оператор "''break''", который как "''return''" прерывает исполнение, но не метода, а конкретного цикла. Это самый сложный к описанию элемент и если вы его поняли - вы поймёте и дальше. |
| + | |
| + | Переменной ''m'' будет присваиваться моб. "''<<''" - оператор вывода. В контексте бьёнда оператор вывода посылает файл, сообщение или что-то ещё прямо на текстовое поле всех клиентов, подключённых к данному объекту. К примеру, если двое человек управляют мобом, то им обоим пришлётся сообщение, указанное справа. |
| + | |
| + | Теперь наша тёплая, ламповая лампочка светит тёплым, ламповым светом, но ведь её ещё нужно засунуть в фонарик, не так ли? Для этого мы будем использовать ссылку ("''reference''"/"''ref''") на объект. Ссылки создаются точно также, как и переменные. Так как в бьёнде используются нетипизированные переменные, то есть не важно, будет ли это строка, объект, число, то всё сокращается до лаконичного: |
| + | |
| + | /obj/flashlight |
| + | var/on = 0 |
| + | var/charge = 1000 |
| + | var/light .. |
| + | |
| + | Но тут-то и возникает главное отличие ссылки от обычной переменной - ссылка не создаёт объект, она лишь указывает. То есть мы можем создать десять ссылок в разных местах, указать на один объект, и изменение через ссылку объекта отразится на всех ссылках. |
| + | |
| + | Оператор создания нового объекта в бьёнде это "''new()''". Зачем же скобки? В них мы можем указать нужные для создания переменные, к примеру для всех стандартных классов бьёнда нужно указывать место, где появляется объект. |
| + | |
| + | New() |
| + | ..() |
| + | light = new /obj/bulb(src) |
| + | l2 = light |
| + | l3 = light |
| + | |
| + | Здесь ''light'', ''l2'' и ''l3'' будут полностью идентичны, они ссылаются на один объект. К тому же здесь мы впервые используем переопределение метода, но об этом позже. Далее, после new вы можете увидеть путь к объекту, я думаю, это очевидно, а в скобках "''src''". "''src''" это ссылка на объект, в котором выполняется метод, то есть мы создали новый объект типа ''/obj/bulb'' в фонаре, в котором и присутствует данный код. |
| + | Вызов методов, как многие уже знают, осуществляется через "''ProcName(args)''", но как нам вызвать его у лампочки? Для этого существует оператор доступа ".", который позволяет нам получить доступ к переменным, ссылкам и методам объекта. |
| + | |
| + | proc/Use(var/t = 3) |
| + | charge = max(0, charge - t) |
| + | |
| + | light.Emit(3) |
| + | |
| + | if(!charge) |
| + | Switch() |
| + | |
| + | Но тут возникает главная проблема - бьёнд не видит данного метода, так как мы не сообщили ему, на класс какого типа ссылается объект. Исправим это: |
| + | |
| + | /obj/flashlight |
| + | var/on = 0 |
| + | var/charge = 1000 |
| + | var/obj/bulb/light |
| + | |
| + | Здесь мы видим, что переменная обзавелась путём, типом объекта в середине. Почему именно так? Скорее всего потому что это действительно удобный способ, отличающийся полнотой информации и отсутствием необходимости запоминать множество классов, а так же оставляющий возможность повторяемости названий. Вообщем удобно. Соберём всё вместе: слева, в самом начале, мы указываем, что это переменная("''var''"), дальше мы сообщаем тип объекта. Если мы этого не сделаем - бьёнд будет обращаться с ней, как с универсальной переменной и не даст нам доступа к методам и переменным. Закрывается же всё названием переменной. Также существует оператор "мягкого" доступа, который не выдаёт ошибку, если метод отсутствует, но реальная необходимость возникает редко, а глупо используют часто, поэтому я лишь сказал про наличие такой возможности. |
| + | |
| + | Наследование классов и переопределение методов, это базис ООП, который позволяет слегка подправивить или полностью изменить объект, добавить новый функционал или изменить старый. Вы уже видели первый пример переопределения с системным методом ''New()'', которая есть у абсолютно всех классов, но я приведу и другой пример. А вот текущей листинг кода. |
| + | |
| + | /obj/flashlight |
| + | var/on = 0 |
| + | var/charge = 1000 |
| + | var/obj/bulb/light |
| + | |
| + | New() |
| + | ..() |
| + | light = new /obj/bulb(src) |
| + | |
| + | proc/Switch() |
| + | if(!charge && !on) |
| + | return |
| + | on = !on |
| + | |
| + | proc/Use(var/t = 3) |
| + | charge = max(0, charge - t) |
| + | light.Emit(3) |
| + | if(!charge) |
| + | Switch() |
| + | |
| + | /obj/bulb |
| + | var/glower = 0 |
| + | var/glass = 0 |
| + | |
| + | proc/Broken() |
| + | if(glower || glass) |
| + | return 1 |
| + | return 0 |
| + | |
| + | proc/Emit(var/r) |
| + | if(Broken()) return |
| + | for(var/mob/m in range(r)) m << "You feel some heat." |
| + | |
| + | Допустим, нам понадобился фонарик с двумя лампочками, но нам нужен и старый с одной лампочкой. Что нам поможет? Наследование! Совершим наследование от фонаря и добавим новую переменную! |
| + | |
| + | /obj/flashlight/powerful |
| + | var/obj/bulb/anotherLight |
| + | |
| + | New() |
| + | ..() |
| + | anotherLight = new /obj/bulb(src) |
| + | |
| + | Что мы здесь видим? Во-первых, изменился путь/тип, но если быть точнее, то он не изменился, а расширился. Остался старый "''/obj/flashlight''", но к нему добавился "''/powerful''". Что это даёт? У данного класса остаются все старые переменные, ссылки (далее просто переменные) и методы, но мы можем добавить новые, не затрагивая старых. Что мы и делаем в следующей строке, объявляя новую лампочку уже известным способом. Ещё можно заметить, что мы снова переопределяем "''New()''", добавляя в него создание новой лампочки и странный метод "''..()''". Этот метод отвечает за вызов аналогичного метода, но в интерпретации класса-родителя. Коротко говоря, он вызывает "''New()''" класса ''/obj/flashlight''. Зачем нам это? Чтобы не переписывать создание первой лампочки, разумеется. |
| + | |
| + | Но что это за сильный фонарь, если он греет так же недалеко? Исправляем! |
| + | |
| + | Use(var/t = 6) |
| + | charge = max(0, charge - t) |
| + | light.Emit(8) |
| + | if(!charge) |
| + | Switch() |
| + | |
| + | Увы, частями методы не переопределить, поэтому приходится иногда переписывать их полностью ради изменения пары строчек. Хочу вдолбить вам в голову, хотя многие не понимают, пользу наследования и переопределения. |
| + | |
| + | /mob/flashlighter_monster |
| + | name = "FLASHLIGHTEEEEER!!!!!!" |
| + | |
| + | var/obj/flashlight/right_flashlight |
| + | var/obj/flashlight/left_flashlight |
| + | |
| + | New() |
| + | ..() |
| + | right_flashlight = new /obj/flashlight(src) |
| + | left_flashlight = new /obj/flashlight/powerful(src) |
| + | |
| + | verb/Flashlight() |
| + | right_flashlight.Use() |
| + | left_flashlight.Use() |
| + | |
| + | /obj/flashlight |
| + | var/on = 0 |
| + | var/charge = 1000 |
| + | var/obj/bulb/light |
| + | |
| + | New() |
| + | ..() |
| + | light = new /obj/bulb(src) |
| + | |
| + | proc/Switch() |
| + | if(!charge && !on) |
| + | return |
| + | on = !on |
| + | |
| + | proc/Use(var/t = 3) |
| + | charge = max(0, charge - t) |
| + | light.Emit(3) |
| + | if(!charge) |
| + | Switch() |
| + | |
| + | /obj/flashlight/powerful |
| + | var/obj/bulb/anotherLight |
| + | |
| + | New() |
| + | ..() |
| + | anotherLight = new /obj/bulb(src) |
| + | |
| + | Use(var/t = 6) |
| + | charge = max(0, charge - t) |
| + | light.Emit(8) |
| + | if(!charge) |
| + | Switch() |
| + | |
| + | Создадим своего первого моба - монстра-флешлайтера, мухахахаха! У него: переопределена системная переменная name, если быть точнее - изменено её значение по умолчанию, а двум фонарям-рукам присвоены фонари - правый - обычный, левый- сильный. Хотя обе переменных были объявлены как "''/obj/flashlight''", так как метод "''Use()''" переопределён, то у сильного фонаря будет вызван именно "''/obj/flashlight/powerful/Use()''", а не "''/obj/flashlight/Use()''". Если сказать по простому - он будет использовать именно ту версию метода, которая заключена в классе в ссылке, а не в каркасе класса в переменной. |
| + | |
| + | И самое главное - "''verb''"! Это действие, которое может вызвать у объекта, у моба, сам моб, условия изменяемы. Пока что достаточно лишь знать, что это метод, который может быть вызван пользователем вручную, как, к примеру, вращение стула. |
| + | |
| + | К переменным доступ происходит точно так же, как и к методам, лишь не надо ставить скобки. К примеру |
| + | |
| + | verb/Flashlight() |
| + | right_flashlight.Use() |
| + | left_flashlight.Use() |
| + | if(prob(1)) |
| + | right_flashlight.light.glower = 1 |
| + | |
| + | Здесь мы использовали системный метод ''prob(x)'', который выдаёт 1 с вероятностью в то значение, которое вы вобьёте, в процентах. То есть если написать ''prob(100)'', всегда будет 1, если ''prob(0)'', всегда 0, а если ''prob(50''), то 50 на 50. Далее мы обращаемся через ссылку на ссылку и уже к переменной, присваивая ей значение 1. Сломалась лампочка, такие дела. |
| + | |
| + | == Списки == |
| + | |
| + | '''list''' - как ни удивительно, один из краеугольных камней бьёнда и, как я считаю, высокоуровневого программирования в целом. Листом является коллекция, группа элементов, последовательно соединённая друг за другом. Первый элемент имеет ссылку на второй, второй на третий и так далее. Также к листу можно обращаться как к массиву, используя квадратные скобки и адрес. |
| + | |
| + | Теория-теория<nowiki>~~~~~</nowiki> |
| + | |
| + | Лист может хранить в себе числа, строки, ссылки на объекты и много другое. Попробуем сделать свой первый лист. |
| + | |
| + | /obj/tutorial_item |
| + | var/list/test_list = list() |
| + | |
| + | Один важный ньюанс: лист является не переменной, а объектом, следовательно его необходимо создать. Есть много способов сделать это, но ''list()'' я нахожу самым удобным. |
| + | |
| + | Вот мы и создали лист, воспользуемся же им. |
| + | |
| + | /obj/tutorial_item |
| + | var/list/test_list = list() |
| + | |
| + | proc/AddItem() |
| + | test_list += 1 |
| + | test_list.Add(1) |
| + | |
| + | В примере выше мы добавляем обычную цифру к списку. Оба способа работают одинаково, так что можно воспользоваться любым. |
| + | |
| + | После всех этих операций мы получили лист, который содержит две единицы, так как добавляли двумя способами. Они хранятся отдельно друг от друга и не ни коим образом не взаимодействуют (''list(1, 1)''). |
| + | |
| + | proc/RemoveItem() |
| + | test_list -= 1 |
| + | |
| + | Здесь же мы убрали один элемент из списка, получив ''list(1)''. Заметьте, пропали не оба, а лишь один элемент, который стоял в начале списка. |
| + | |
| + | Точно также можно работать и с обычными объектами: |
| + | |
| + | /obj/tutorial_item |
| + | var/list/test_list = list() |
| + | var/obj/item |
| + | |
| + | New() |
| + | ..() |
| + | item = new /obj(usr) |
| + | |
| + | proc/AddItem() |
| + | test_item += item |
| + | |
| + | proc/RemoveItem() |
| + | test_item -= item |
| + | |
| + | Однако есть несколько важных замечаний: ссылка на один и тот же объект спокойно записывается в лист несколько раз. Как этого избежать, будет позже. Также, по причинам, описанным далее, даже если вы сделаете объекты полностью идентичными, удаление из листа не сработает, имейте это ввиду. |
| + | |
| + | Перейдём к чему-то посложнее. |
| + | |
| + | === Бинарные операции с листами === |
| + | |
| + | Выполняются практически так же, как и операции добавления и удаления, но выглядят чуть по-другому. |
| + | |
| + | proc/AddItem() |
| + | test_item |= item |
| + | test_item |= item |
| + | |
| + | proc/RemoveItem() |
| + | test_item &= !item |
| + | |
| + | В чём их соль? Они позволяют гарантировано держать в листе лишь один экземпляр объекта, то есть в листе этот объект будет фигурировать лишь однажды. Логика их проста: |
| + | |
| + | * сложение это создать лист из текущего листа ИЛИ этого объекта. |
| + | * вычитание это создать лист из текущего листа И всех объектов кроме данного. |
| + | |
| + | Я уверен, большинство тут знает конъюнкцию, дизъюнкцию и прочие страшные слова. |
| + | |
| + | var/list/a = list(1, 2, 2) |
| + | var/list/b = list(2, 3) |
| + | |
| + | var/list/sum = a + b // 1, 2, 2, 2, 3 |
| + | var/list/sub = a - b // 1, 2 |
| + | var/list/binary_sum = a | b // 1, 2, 2, 3 |
| + | var/list/binary_sub = a & b // 2 |
| + | |
| + | Как вы могли заметить выше - листы также можно складывать и вычитать друг с другом. Сложение и вычитание, я думаю, не вызовут вопросов, а вот в бинарном пара может. |
| + | |
| + | Почему же в бинарном сложении осталось две двойки? Ответ прост: новый лист записывается бинарным сложением каждого из элементов второго листа к первому листу. То есть вышеописанная операция может выглядеть как: |
| + | |
| + | var/list/binary_sum = a |
| + | a |= 2 |
| + | a |= 3 |
| + | |
| + | С вычитанием всё немного по-другому. Сравниваются все элементы между листами и все повторяющиеся элементы записываются. |
| + | |
| + | Что же, с математикой мы закончили, перейдём к кое-чему другому. |
| + | |
| + | Во-первых, к листу можно обращаться как к массиву: |
| + | |
| + | var/list/a = list() |
| + | a["first"] = 1 |
| + | a["second"] = 2 |
| + | a[/mob] = 3 |
| + | //a[-1] = 4 NOWAY |
| + | |
| + | Это позволяет сделать некий словарь или таблицу хешей, которые возвращают значение по входным данным. Однако будьте осторожны, тут есть свои ньюансы: если записывать через сложение - будет возвращаться значение, но если записывать через индекс, будет возвращаться именно значение индекса, а не значение массива по индексу. |
| + | |
| + | В-третьих, лист обладает длиной, что позволяет обрабатывать его как обычный массив: |
| + | |
| + | var/list/a = list(1, 3, 4) |
| + | var/sum = 0 |
| + | for(var/i = 1; i <= a.len; i++) |
| + | sum += a[i] //8 |