Изменения

Материал из Chaotic Onyx
Перейти к навигацииПерейти к поиску
Склеил три статьи в одну. К самому тексту даже не притрагивался, поправил только начала и концы частей.
Строка 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
231

правка

Навигация