Перейти к публикации
Форум - ComputerCraft

eutomatic blog

  • записей
    10
  • комментарий
    51
  • просмотров
    22 911

Записи в этом блоге

 

7 способов улучшить код

Здравствуй, брат автоматизатор!   Желаешь ли ты услышать от меня несколько мыслей о красивом, понятном, и эффективном коде?
На истину в последней инстанции я не претендую, и ты, возможно, знаешь решения получше моих. В этом случае оставь свои соображения в комментариях, и код на проекте станет немного чище.   Небольшое отступление:
Полгода назад я задумал написать серию статей о программировании для новичков, где я писал код и объяснял, почему он должен писаться так, а не иначе. Но такое объяснение оказалось для меня непосильным занятием, т. к. мне приходилось спорить с самим собой. Легко было только один раз, когда я обнаружил серьезный дефект, наверное, во всех копалках на проекте. Но потом я получил стрелу в колено...
Вернувшись на проект, я неожиданно для себя понял, что демонстрировать эффективность удачного кода значительно проще, противопоставляя его коду неудачному.   В прошлый раз я рассказал о том, как математика помогает писать более простые, понятные и при этом более эффективные программы.
qwertyMAN заметил, что использование взятия остатка от деления – это не математическая проблема, и я с ним отчасти согласился. Решение о том, использовать или нет операцию %, мало влияет на общий алгоритм, но сильно влияет на то, как будет выглядеть готовый код. Но всё-таки об этой операции следует помнить уже на этапе проектирования алгоритма, а не только на этапе кодинга.
А вот, о тонкостях написания кода я хочу рассказать сегодня, не особо вдаваясь в общие алгоритмы. Моим подопытным будет уже знакомый код из охранной системы турелей.
Вот сам код:     Первое, на что я обратил внимание – это использование pcall. Именно оно и побудило меня к разбору кода, но прежде я предлагаю обсудить другой нюанс.   1. Избегай повторения уже выполненных вычислений
Имеются два фрагмента кода:
if target and radar.getPlayerByName(target) then target=radar.getPlayerByName(target).all()...local mobif radar.getMobData(scan[i]).basic() then mob = radar.getMobData(scan[i]).basic() target = mob И в обоих фрагментах происходит дублирование вызовов. Первый раз вроде как для проверки. Но такой код всё равно не снимает проблему, т. к. при первом вызове функция может вернуть одно значение, а при втором вернет другое. Правильным решением будет сохранить результат в переменную. И если ее значение удовлетворяет условиям, использовать ее в дальнейшем. Во втором фрагменте следует заодно избавиться от переменной mob, которая больше нигде не используется.
if target then target = radar.getPlayerByName(target) if target then target=target.all()...target = radar.getMobData(scan[i])if target then target = target.basic() Почему я проверяю результаты getPlayerByName и getMobData, а не других функций вроде all() или basic()? Потому что именно они могут вызвать ошибку. Ошибка возникает от того, что игрок или моб может покинуть зону действия сенсора, пока выполняется обработка внутри программы. К сожалению, указанной проверки недостаточно, т. к. мод OpenPeripheral вместо того чтобы вернуть nil или иным образом указать на проблему, тупо генерирует ошибку, не оставляя иного варианта кроме использования pcall.   2. Используй защищенный режим только там, где это необходимо
Код программы выглядит примерно так:
local function func() -- почти весь код программы помещен в эту функцию -- включая инициализацию переменных и определение функций ... -- этот бесконечный цикл прерывается из-за отсутствующей обработки ошибок while true do ... local target = false local scan=radar.getPlayers() ... target = ... if target and radar.getPlayerByName(target) then target=radar.getPlayerByName(target).all() ... end ... endend-- мегакостыль (комментарий самого автора)while true do local oop = pcall(func) print(oop)end Автор понимает, что использует костыль, но правильное решение использовать не хочет. Подобные городушки провоцируют появление новых: уже сейчас на ровном месте появилась дополнительная функция и вложение двух бесконечных циклов. Но главная проблема этого кода в том, что pcall скрывает любые ошибки, и дальнейшая отладка программы становится затруднительной. Даже печать результата, возвращаемого pcall, реализована неверно – ничего кроме false выведено не будет.
Для решения проблемы следует использовать pcall точечно, только там, где это необходимо, а именно в вызове getPlayerByName и getMobData. А если есть возможность вообще обойтись без pcall, то в готовой программе без него следует обойтись. В нашем случае обойтись без pcall, похоже, нельзя. Поэтому убираем наш костыль и функцию func, оставив лишь ее содержимое, и дорабатываем проблемные участки таким образом:
if target then local flag,res = pcall(radar.getPlayerByName,target) if flag then target=res.all()...local flag,res = pcall(radar.getMobData,scan[i])if flag then target = res.basic() Пришлось ввести дополнительные переменные, зато код избавился от костыля со всей его обвязкой, а ошибка, генерируемая модом OpenPeripheral, локализована, и новых проблем не создаёт. Программа работает стабильно и ее отладка не нарушена. На этом можно было бы и закончить, но я уже вошёл во вкус. Поэтому продолжу давать советы, иллюстрируя их фрагментами кода:   3. Выноси за цикл всё, что возможно
Это правило подобно первому, но пренебрежение им не так бросается в глаза начинающего программиста, т.к. сам код не дублируется, зато дублируется его исполнение.
Восстановим общий алгоритм работы программы, чуть подробнее разобрав этот кусок:
while true do local target = false ... local scan=radar.getPlayers() if firePleayers and #scan>0 then if #White_Player_List>0 then добавление списка авторов в в белый список target = первый игрок на радаре вне белого списка elseif #Black_Player_List>0 then удаление авторов из черного списка target = первый игрок на радаре в черном списке else if #autors>0 then перенос авторов в белый список else target = первый игрок на радаре вычисление координат цели и выполнение выстрела сканирование мобов, вычисление их координат с последующим расстрелом. Как ты уже догадался по заголовку, в бесконечном цикле выполняется что-то явно лишнее. А именно, работа со списком авторов. Всё это могло бы прекрасно работать и до основного цикла, создавая при этом нужный эффект. Более того, вынос этого кода за цикл позволит исправить серьезнейшую ошибку: на каждой итерации бесконечного цикла происходит добавление авторов в белый список, при этом их наличие в белом списке никак не проверяется, но каждый раз происходит добавление новых. Даже с двумя планками памяти 3.5 уровня через сотню-другую тысяч итераций программа завершится с ошибкой «not enough memory».
Вынося лишние действия за цикл, ты избавишь программу как от неконтролируемого расхода памяти, так и от лишних действий, замедляющих ее работу. Думаю, демонстрировать корректный код здесь излишне. Достаточно лишь вынести эти участки кода из цикла.   В программе присутствуют и менее очевидные вычисления, которые можно вынести за цикл.
-- этот код находится за цикломlocal correct = { x = 0, y = 4, z = 0 }...-- а этот внутриlocal x,y,z = target.position.x-0.5-correct.x, target.position.y+0.3-correct.y, target.position.z-0.5-correct.z...local x,y,z = target.position.x-0.5-correct.x, target.position.y-0.5-correct.y, target.position.z-0.5-correct.z Логичным будет переписать код таким образом:
-- этот код за цикломlocal correct = { x = 0+0.5, y = 4+1, z = 0+0.5 }...-- а этот внутриlocal x,y,z = target.position.x-correct.x, target.position.y+1.3-correct.y, target.position.z-correct.z...local x,y,z = target.position.x-correct.x, target.position.y+0.5-correct.y, target.position.z-correct.z Так мы избавимся от лишних сложений и вычитаний в цикле. Лишние сложения при инициализации таблицы correct не сильно помещают, т. к. они выполняются всего один раз, а нужны они для наглядности кода. Поясню, что здесь происходит.
Во-первых, турель и сенсор находятся в разных блоках, поэтому координаты игрока следует скорректировать на эту разницу. Координаты турели относительно сенсора хранятся в таблице correct.
Во-вторых, сенсор определяет координаты игроков и мобов относительно своего северо-западного угла, а турель вращается вокруг центра блока. Поэтому приходится корректировать координаты (x,z) на половину блока по горизонтали.
В-третьих, сама турель стреляет на один блок выше уровня ног игрока или моба. Автор корректирует высоту цели в зависимости от цели: для игрока прицел приподнимается на 0.3 блока, чтобы игрок не мог уходить от выстрела прыжком или прятаться за блоки, а для мобов прицел опускается на полблока, чтобы попадать, например, в кур или свиней. Но тут тоже не всё просто. Чтобы попадать в цыплят, прицел следует опустить еще ниже, но тогда турель промахивается мимо взрослых кур, стреляя им куда-то под ноги. Для эффективной стрельбы по любым мобам нужен алгоритм, определяющий вид моба, его возраст, и находящий по таблице его рост. Причем, нужен не только рост. Например, нет смысла целиться в нижнюю часть слизня, т. к. тот постоянно прыгает. В общем, это отдельная тема для поиска оптимального алгоритма, сейчас же я хочу продолжить рассказ о кодинге.
В итоговом коде не только вынесены лишние вычисления из цикла. Кроме этого он стал более логичным: числа 1.3 и 0.5 по сути означают высоту цели относительно ног игрока или моба.   4. Прерывай циклы, когда они уже выполнили свою задачу
Вот два подобных друг другу фрагмента кода:
local swich = truefor j=1, #White_Player_List do if scan[i].name==White_Player_List[j] or not scan[i].name then swich = false endendif swich then...local swich = falsefor j=1, #Black_Player_List do if scan[i].name==Black_Player_List[j] then swich = true endendif swich then Задача циклов в том, чтобы при подходящем случае изменить значение переменной switch. Но как только оно изменилось, зачем продолжать работу? Далай break. Иначе выполнение твоей программы замедляется.
Пока я писал этот текст, то напрягался при каждом наборе названия переменной swich. Нет такого слова, зато есть слово switch, и не глядя я набирал именно его. Поэтому буду писать switch. Кроме того, название переменной все равно не отражает ее сути. С тем же успехом можно было использовать однобуквенное название переменной. А лучше бы и вовсе избавиться от нее.   5. Избавляйся от лишних переменных
Вот те же фрагменты в немного дополненном составе, внутри других циклов и с оператором break, как же теперь без него:
local target = false...for i=1, #scan do local swich = true for j=1, #White_Player_List do if scan[i].name==White_Player_List[j] or not scan[i].name then swich = false break end end if swich then target = scan[i].name break endend...for i=1, #scan do local swich = false for j=1, #Black_Player_List do if scan[i].name==Black_Player_List[j] then swich = true break end end if swich then target = scan[i].name break endend Что выполняют оба фрагмента? Ищут подходящую цель по белому и черному спискам игроков.
Где хранится цель? В переменной target.
Что хранится в переменной switch? Флаг того, что переменная target должна быть изменена.
А зачем нам этот флаг? Что мешает сразу изменить переменную?
local target = false...for i=1, #scan do target = scan[i].name for j=1, #White_Player_List do if scan[i].name==White_Player_List[j] or not scan[i].name then target = false break end end if target then break endend...for i=1, #scan do for j=1, #Black_Player_List do if scan[i].name==Black_Player_List[j] then target = scan[i].name break end end if target then break endend Код стал немного короче и быстрее, но и это еще не предел.   Еще непонятно, что делает, or not scan.name в условии. Повлиять это выражение может в том случае, если scan.name будет равно false, или nil. А разве такое возможно? Даже не знаю, как классифицировать этот недочет. Видимо, от старых экспериментов осталось. Посоветовать можно только одно: вычищать код перед публикацией.   6. Используй ассоциативные возможности таблиц Lua
Таблицы в Lua – это больше чем массивы. Это и объекты, и списки, и словари. А может, и что-то еще, о чем я забыл или даже не знал.
Таблицы в Lua – это наборы пар ключ-значение. В качестве ключей и значений таблицы может быть что угодно кроме nil.
Пара ключ-значение в таблицах Lua присутствует даже если мы не используем ключи явным образом.
Попробуй запустить такой код:
PlayerList={"Ded", "Baba", "KurochkaRyaba"}for k,v in pairs(PlayerList)do print(k,v)endfor i=1,#PlayerList do print(PlayerList[i])endPlayerList={[1]="Ded", [2]="Baba", [3]="KurochkaRyaba"}for k,v in pairs(PlayerList)do print(k,v)endfor i=1,#PlayerList do print(PlayerList[i])endPlayerList={["Ded"]=1, ["Baba"]=2, ["KurochkaRyaba"]=3}for k,v in pairs(PlayerList)do print(k,v)endfor i=1,#PlayerList do print(PlayerList[i])endprint(#PlayerList)print(PlayerList["Ded"])print(PlayerList["Baba"])print(PlayerList["RedHatBaby"])
В первом случае мы не указываем ключи явно, но они создаются. Мы свободно перебираем как пары ключ-значение, так и значения по их индексу, который совпадает с ключом.
Во втором случае мы явно указали ключи. Перебор пар ключ-значение показывает, что элементы таблицы хранятся в неведомом нам порядке, скорее всего, в соответствии с неким хешем, но на перебор по индексу это никак не влияет, порядок не нарушен, а это самое главное.
В третьем случае мы поменяли ключи и значения местами. Естественно, ни о каком переборе по индексу теперь не идет и речи. Более того, определить длину таблицы теперь тоже не так просто. Зато появилась возможность, не выполняя перебор всех значений в цикле, одной командой определить, присутствует ли игрок в списке. Для игроков Дед и Баба есть значение, а игрок КраснаяШапочка в список не внесен, и имеет значение nil.   Как это соотносится с нашим кодом? Попробуем заполнять списки игроков таким образом:
local Black_Player_List = { ["qwertyMAN"]=1 }
local White_Player_List = { ["Ded"]=1, ["Baba"]=1, ["KurochkaRyaba"]=1 }
В качестве значения я использовал 1, и оно может быть любым, но 1 - записывается кратко. Главное, чтобы не nil. И не false, чтобы проще было выполнять проверку элемента.
То, что qwertyMAN оказался в черном списке, ему никак не повредит, он же автор.   Теперь все фрагменты кода изменятся таким образом:   Добавление списка авторов в в белый список:
-- былоfor i=1, #autors do White_Player_List[#White_Player_List+1] = autors[i]end-- сталоfor i=1, #autors do White_Player_List[autors[i]] = 1end Код немного укоротился, а главное – теперь переполнение памяти не грозит даже при работе в бесконечном цикле, т. к. элемент создается один раз, а в последующие – лишь изменяется его значение.   Удаление авторов из черного списка:
-- былоfor i=#Black_Player_List, 1, -1 do for j=1, #autors do if Black_Player_List[i] == autors[j] then table.remove(Black_Player_List,i) end endend-- сталоfor j=1, #autors do Black_Player_List[autors[i]] = nilend Код заметно укоротился.
Сомневаешься, действительно ли элемент таблицы удаляется? Запусти этот код, и все станет понятным:
PlayerList={["Ded"]=1, ["Baba"]=2, ["KurochkaRyaba"]=3}for k,v in pairs(PlayerList)do print(k,v)endPlayerList["Ded"]=nilfor k,v in pairs(PlayerList)do print(k,v)end И напоследок два уже разобранных перед этим фрагмента, которые еще более упростились:
-- былоfor i=1, #scan do target = scan[i].name for j=1, #White_Player_List do if scan[i].name==White_Player_List[j] then target = false break end end if target then break endend...for i=1, #scan do for j=1, #Black_Player_List do if scan[i].name==Black_Player_List[j] then target = scan[i].name break end end if target then break endend-- сталоfor i=1, #scan do if not White_Player_List[scan[i].name] then target = scan[i].name break endend...for i=1, #scan do if Black_Player_List[scan[i].name] then target = scan[i].name break endend 7. Минимизируй идентичные участки кода
При добавлении сходного функционала в программу можно скопировать рабочий участок кода и немного изменить его. Иногда до неузнаваемости. Но если фрагмент достаточно велик, а изменения малы, то правильнее будет вынести этот фрагмент в отдельную функцию. Во-первых, итоговой код будет более компактным. Во-вторых, при необходимости будет проще вносить изменения, не правя все участки кода.
В программе имеется два таких похожих участка:
local x,y,z = target.position.x-correct.x, target.position.y+1.3-correct.y, target.position.z-correct.zlocal vx,vy = pointer(x,y,z)turret.moveTo(vx,vy)if turret.isOnTarget() then turret.fire()end...local x,y,z = target.position.x-correct.x, target.position.y+0.5-correct.y, target.position.z-correct.zlocal vx,vy = pointer(x,y,z)turret.moveTo(vx,vy)if turret.isOnTarget() then turret.fire()end Ранее мы уже выяснили, что единственная изменяемая величина здесь – это коррекция высоты цели. Сейчас основной вопрос: что из этого следует вынести в отдельную функцию. С точки зрения минимизации кода следует выносить почти всё. Но чтобы сделать код более логичным, не следует всё мешать в одну кучу. Лучшим решением мне кажется вынос всего, что связано с вычислениями, в уже имеющуюся функцию pointer. Вот она:
local function pointer(x,y,z) local distXY = math.sqrt(x^2+z^2) local distDY = math.sqrt(y^2+distXY^2) local outX = math.deg(math.acos(x/distXY))+90 local outY = 90-math.deg(math.acos(y/distDY)) if z<0 then outX = (180-outX)%360 end return outX,outYend Учитывая то, как я переписал эту функцию в прошлый раз, а также избавляясь от лишних переменных и перенося часть вычислений внутрь функции, перепишу код таким образом:
local function pointer(pos,h) local x,y,z = pos.x-correct.x, pos.y-correct.y+h, pos.z-correct.z local azimuth=math.deg(math.atan2(x,-z))%360 local elevation=math.deg(math.atan2(y,math.sqrt(x*x+z*z))) return azimuth, elevationend...turret.moveTo(pointer(target.position,1.3))if turret.isOnTarget() then turret.fire()end...turret.moveTo(pointer(target.position,0.5))if turret.isOnTarget() then turret.fire()end Конечно, этот код можно ужать еще плотнее, но я увижу в этом смысл, если дальнейшая доработка программы вынудит меня продублировать эти блоки кода.   На этом закончу. Возможно, я дал бы тебе еще пару советов, но за исключением описанных выше моментов код qwertyMAN'а вполне адекватен.   Программируй красиво, брат!

eu_tomat

eu_tomat

 

Математика в Майнкрафте

Здравствуй, брат автоматизатор.

Надеюсь, ты помнишь, что всеми нами любимый брат qwertyMAN порадовал нас своей охранной системой турелей. И казалось бы, всё замечательно: турельки бьют прицельно, гриферы боятся, а мобы рассыпаются в лут. Но как всегда я нашел, к чему придраться. Не смотря на прицельный огонь, направление турели на цель вычисляется по очень неэффективному алгоритму.

Весь код охранной системы я разбирать не буду, сосредоточусь только на математике.

Остаток от деления

Начнем с такого фрагмента:
local function kostil(x) while true do if x>=360 then x = x - 360 elseif x<0 then x = x + 360 else break end end return xend По названию функции видно, что автор осознавал ущербность этого кода, но лучшего решения не знал или забыл. Разберемся, что делает этот код. В цикле вычитает 360 из аргумента, если он больше или равен 360, или добавляет 360, если меньше нуля. А по сути, приводит значение угла к диапазону [0,360). Зачем это делается? Ведь, например, углы и -90˚ и 270˚ указывают одинаковое направление. Сколько бы полных оборотов (360˚) в какую бы сторону мы не совершили, направление от этого не изменится.


Тем не менее турель из OpenSecurity не принимает угол, выходящий за границы диапазона [0..360]. Приведение угла к нужному диапазону называют нормализацией. Угол можно нормализовать и в других диапазонах с полным углом, например [-180,+180]. Но наша турель требует диапазон [0,360]. Как же эффективно произвести нормализацию?

Лучшим решением является операция взятия остатка от деления. Заглянем в справочник:
a % b == a — math.floor(a/b)*b Не сложно увидеть, что a % b дает нам ровно тот же результат, что и костыль qwertyMAN'а, но код сильно сократился и стал выполняться быстрее. qwertyMAN с этого костыля быстро спрыгнул, и в следующей версии упростил свой код, но поверь мне, брат, на форуме есть много таких костылей, и калеки не спешат с них слезать.
Вот, например, фрагмент кода, до которого я уже давно хотел докопаться. Код слишком длинный не только для своего функционала, но и для этой статьи, поэтому я спрячу его в спойлер:

Можно ли этот код упростить? Легко. Для начала заменим строки числами, например, по такой схеме: N=0; W=1; S=2; E=3
После чего становится очевидным такое решение:
function smartTurnLeft() robot.turnLeft(); N=(N+1)%4; endfunction smartTurnRight() robot.turnRight(); N=(N-1)%4; endfunction smartTurnAround() robot.turnAround(); N=(N+2)%4; end С какой стороны света начинать отсчет, и в каком направлении, это уже другой вопрос, но принцип остается неизменным.
Главное, что код упростился, он не занимает огромное место в памяти и быстро выполняется.
Конечно, при использовании некоторых приближенных к машинным языков и при определенных условиях работы алгоритма использование деления может оказаться менее эффективным, чем проверки условий с последующим сложением/вычитанием. Но интерпретируемые языки, к которым относится Lua, это преимущество нивелируют. Поэтому по возможности используй операцию взятия остатка от деления при обсчете всяких циклических значений. Это удобно и эффективно.


Тригонометрия

Теперь взгляни на эту функцию
local function pointer(x,y,z) local distXY = math.sqrt(x^2+z^2) local distDY = math.sqrt(y^2+distXY^2) local outX = math.deg(math.acos(x/distXY))+90 local outY = 90-math.deg(math.acos(y/distDY)) if z<0 then outX = kostil(180-outX) end return outX,outYend Что она делает? Понять не сложно: вычисляет угловые координаты цели: азимут и угол места в градусах.
Что тут не так?
1) присутствует acos с последующим добавлением/вычитанием 90˚, хотя известно, что и синус и косинус при добавлении/вычитании 90˚ преобразуются друг в друга. На этом я задерживаться не буду, формулы и их вывод есть в любом учебнике.
2) использование только asin или только acos тоже является не лучшей идеей: asin имеет низкое разрешение по углу в окрестности +/-90˚, а acos – в области 0 ˚и 180˚, снижая точность наведения, и для ее повышения потребуется использовать asin в окрестностях 0 ˚и 180˚ и acos в окрестности +/-90˚.
3) вычисление синусов и косинусов требует вычисления расстояния до цели r=sqrt(x*x+z*z), а, например, вычисление тангенса не требует.
4) область значений asin, acos и atan не покрывает полный оборот в 360˚, поэтому приходится прибегать к дополнительным вычислениям, как, например, это сделал автор: if z<0 then outX = (180-outX)%360 end.
5) не сошелся свет клином на всем известных со школы синусах, косинусах и тангенсах. Уделив несколько минут чтению списка математических функций, можно обнаружить такую красоту:
В геометрии об этом не рассказывают, но такая функция имеется в стандартной библиотеке, пожалуй, любого высокоуровневого языка программирования. С ее помощью мы находим азимут цели единственной строкой azimuth=math.atan2(x, -z). Осталось только выполнить преобразование в градусы, да скорректировать возвращаемый диапазон углов c [-180,+180] на принимаемый турелью [0..360], с чем мы уже разобрались.


Теперь надо правильно заполнить аргументы функции atan2. Для этого приведем всё, что у нас имеется, к картинке из школьного учебника.

Во-первых, на картинках в школьном учебнике угол растет при повороте от оси X, к оси Y, и тангенс угла вычисляется как tg(α) = y/x.
В справочнике же к atan2 указаны аргументы atan2(x, y) для вычисления арктангенса x/y. То есть, нам следует поменять аргументы местами. Имеем: math.atan2(y, x)

Теперь разберемся с координатами мира Майнкрафта, положением турели, и тоже приведем их к картинке из школьного учебника. Сначала нарисуем координаты Майнкрафта при обращении лицом на север, вид сверху. Ось X направлена на восток, ось Z – на юг. Азимут поворота турели отсчитывается от северного направления по часовой стрелке.

Прошу простить мне изображение угла прямой линией, а не дугой. Надеюсь, это не сильно помешает пониманию выполненных преобразований.

X, Z – координаты мира Майнкрафта;
R – вектор на цель;
α – небольшой положительный угол между нулевым положением турели и вектором на цель.

На первом рисунке изображены исходные координаты майна и поворота турели.
На втором – поворот рисунка на 90˚ по часовой стрелке.
На третьем – отражение рисунка по вертикали (как бы взгляд на плоскость карты не сверху, а снизу, из-под земли). В принципе, можно было бы обойтись без второго рисунка, отразив первый по диагонали, идущей из правого верхнего угла, но для улучшения восприятия я привел оба рисунка.
На четвертом рисунке выполняется инверсия оси Z.

Важно понять, что от всех этих преобразований координаты совершенно не меняются, меняется лишь их представление. И вообще следует помнить, что координаты не существуют в реальности и являются лишь математической абстракцией, которую легко можно вывернуть наизнанку в отличие от реальных объектов. Как ни вращай мир Майнкрафта на рисунках, он от этого не сдвинется и не изменится. Меняется лишь твоя точка зрения, но она-то и позволяет тебе упростить программу, избавившись от бесполезных вычислений.

Все совершённые преобразования были нужны лишь для наглядности, и из них можно вывести более простое правило. Тригонометрия из школьного учебника основана на росте угла от оси X к оси Y. Но аналогичные формулы применимы при росте угла от оси -Z к оси X при соответствующих заменах в формулах. То есть, надо всего лишь знать от какой оси начинается рост угла и к какой оси происходит движение.

В результате выполненных преобразований ты можешь легко заметить, что координаты из учебника (x,y) соответствуют координатам Майнкрафта (-z,x).
В нашем случае:
sin(α) = y/r → sin(α) = x/rcos(α) = x/r → cos(α) = -z/rtg(α) = y/x → tg(α) = x/-z Таким образом наш код приобретает вид: math.atan2(x, -z)
Осталось лишь преобразовать радианы в градусы и нормализовать их в диапазоне [0..360]: azimuth=math.deg(math.atan2(x,-z))%360


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

Теперь тело функции, вычисляющей углы направления турели, умещается в три строки.
local azimuth=math.deg(math.atan2(x,-z))%360local elevation=math.deg(math.atan2(y,math.sqrt(x*x+z*z)))return azimuth, elevation Или даже в одну, если не создавать промежуточные переменные и сразу возвращать результат.


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

Не спеши кодить, подумай о математике, брат

eu_tomat

eu_tomat

 

Рыбалка #1 Оптимизация постройки

Здравствуй, брат автоматизатор!

Сегодня я расскажу тебе, как можно оптимизировать постройку рыбной фермы Asior'а. Если ты слишком слаб для чтения длинных текстов, ни в коем случае не открывай спойлер – для тебя есть несколько скриншотов с их кратким описанием. Но если ты хочешь узнать, как можно достичь такого результата, тебя ждет много текста с картинками, а в качестве бонуса — схема постройки сверхкомпактной масштабируемой фермы.

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

Сразу предупреждаю, что я не буду сильно вдаваться в объяснение механики игры, мода OpenComputers, или языка Lua. Всё это ты можешь узнать в блоке «Полезные ссылки» на главной странице проекта. Я скажу больше – без пользования этими ссылками, я бы даже этот текст не смог написать. Скажу еще больше – я дилетант и в MineCraft, и в Lua, и в OpenComputers. Но это не помешает мне оптимизировать схемы, постройки, алгоритмы и коды некоторых игроков.


Итог
В результате оптимизации получились две очень компактные и при этом красивые схемы, умещающиеся в объеме 3x5x3. Обе они радуют глаз, сохраняют место для прохода и строятся гораздо быстрее схем Asior'а. По моим тестам работают они не хуже исходных, а схема с нижним размещением робота теоретически может работать даже чуть быстрее, но для этого надо немного подправить задержку. Программу придется править в любом случае, т. к. изменилась сторона приема роботом сигнала и сброса дропа. Получение удочек из сундука следует выполнять контроллером инвентаря.
[Схема №1]
[Схема №2]
Отличия построек незначительны.
В первом не требуется тянуть редстоун, а робот может использовать для зарядки солнечную панель.
Второй вариант может оказаться более быстрым, но для проверки этого придется переписать код, отвечающий за работу с датчиком. В остальном – одни недостатки: робот от солнечной панели не работает, редстоун дотягивается, но некрасиво, или же требуются провода RedLogic. И для компактного масштабирования потребуются цветные провода того же RedLogic. Кусок красного провода на правом столбе использован для симметрии блока воды, его можно заменить табличкой или стеклом.
Место для зарядника имеется в обеих схемах, даже эстетика не сильно пострадает от его установки.

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

А теперь тесты!
Устанавливай программу Asior'а, затем в зависимости от выбранной схемы настраивай в программе сторону, с которой расположен сигнал редстоуна, а также сундук для дропа. И получение удочек из того же сундука тоже придется написать. Главное, что интересовало в тестах меня: как часто происходят сбои заброса удочки, как часто вытягивается пустой крючок, и каков диапазон времени ожидания. Для этого придется добавить в программу соответствующие счетчики. Они помогут выявить неправильную работу либо программы, либо общей схемы. Может, крючок удочки за что-то цепляется. Может, дроп не доходит до инвентаря робота. Может, времени ожидания недостаточно.
Что именно нужно изменить в программе, пока не скажу, и так уже много текста, но ты ж программист, брат! Может, и Asior что напишет, он шустрее меня.

Не прощаюсь. Напоследок перефразирую Asior'а:
Грызи знания, брат. В них сила.

eu_tomat

eu_tomat

 

Рыбалка #0

Здравствуй, брат автоматизатор!   Присаживайся на деревянный сундочок, да угощайся рыбкой жареной. И не забывай на поплавок поглядывать, он тебе многое расскажет о механике майнарафта и о том, как из этой механики извлечь выгоду для себя.
Ты, конечно, знаешь уже, что Asior принес новую технологию в наш пустынный мир оверворлда. Да-да, пустынный – теперь ты в нем даже курицу не найдешь, намедни OKA отрапортовал об уничтожении последней фермы. Тяжело стало жить на сервере без белковой-то пищи. Мясо, конечно, можно и в магазине купить, но не каждый может себе позволить такую роскошь. А ходить в майно-миры на охоту тоже не всяк решится – и опасно и далеко. Можно, конечно, ловить рыбу прямо дома, в тазике, но ведь и удочку правильно держать – тоже не каждый умеет. Вот, и страдали мы, пока Asior не научил нас роботом рыбу ловить.   Что тут скажешь: игрок спит, рыба ловится, а жизнь налаживается. Только вот, порыбачил я роботом Asior'а, восполнил недостаток фосфора в организме, и мне даже показалось, будто бы поумнел: стал я называть робота Asior'а глупым, а самому Asior'у начал советы давать. Даже чего-то лишнего сбрехнул, видимо, еще не наелся фосфору-то. Asior часть советов принял, но всё же где-то не удержался и сказал, что недостатки схемы – это проблемы игрока. Ну, и Fingercomp тоже поддержал его в этом. И другие тоже, насколько могли. А некоторые хуже того – текст не осилили, плакали горько.   И решил я тогда на время отложить критику, да сам под критику встать и тексты писать покороче, если получится. А так как писать программы я не сильно хочу (разве только самую малость), то буду говорить о схемах и алгоритмах. Программы-то тут почти все пишут, но зачастую логика этих программ и общая схема работы меня весьма удивляют. Тексты буду складывать в этот блог, а вдохновляться – текстами форума, да простят меня их авторы.   Вот, скажи мне, брат, в чем сила?
В упоминании имени автора программы или его замалчивании?

eu_tomat

eu_tomat

 

Включился

Приветствую, вас, браться по автоматизации!   Не прошло и полугода, как я вновь вернулся к вам. Не буду загадывать, долго ли и активно ли я смогу веселиться с вами, т. к. многое изменилось с тех пор.   Последнее, что я помню, это то, как Quant нехотя писал свою аналоговую связь, как artem221 только еще начинал крошить майно-миры своим геороботом, и как лагофилы боролись за свои права на автоматизацию модом AE2. Помню внезапно оживившие форум веселье одних и печаль других от робогрифа, насаждаемого тогда Алексом. Остальное помню смутно, не все темы мне интересны. А потом я неудачно сходил погулять. Поскользнулся, упал... сломал лодыжку.   Сидя с загипсованной ногой, я пытался играть на сервере и немного обсуждал с Артемом его копалку. Помню, народ пытался наградить Алекса, и был даже примерен орден на его аватарку. И еще помню очередной надвигающийся вайп, да не простой, а с переселением VIP'ов в новый, чистый и никогда не лагающий и неумирающий мир.   И тогда я начал напрягаться отсутствием у меня статуса программиста, и возможности переселиться в этот славный программистский безвайпный мир, но выяснилось, что просто достаточно быть в вайт-листе. И, успокоившись, я взял перерыв на пару недель. А потом, не зайдя в игру, еще на пару. А потом стало совсем не до игры. Единственное, что я не бросил – голосование за проект в ТОПах.   Тем временем лодыжка срослась, я вновь освоил искусство ходьбы ногами, потом взялся за накопившиеся дела, а про майнкрафт с его компами вспомнил лишь в долгие темные новогодние вечера.   Артем, как выяснилось, дописал свою копалку, и она оказалась весьма хороша. Код, конечно, дикий. Особенно я бомбанул, когда разбирал реализацию Артемом моей идеи замены апгрейда-карты. Уже тогда у меня начало просыпаться желание переписать этот кусок кода.   Но новогодняя атмосфера это желание во мне подавила. Потом меня увлекли размышления о системе хранения и автокрафта на роботах, затем почему-то увлекли ядерные реакторы, хотя на IT-сервере я ни разу их не использовал, а потом вдруг заинтересовала свежая тема автоматической рыбалки, и тогда я вновь потянулся за паролем от аккаунта.   Сегодняшний просмотр свежих тем уже не позволил мне воздержаться. Тут полный комплект: и наказание за робогриф, и обсуждение возврата AE2 , и даже Quant доработал-таки свою систему связи.   И я включился.

eu_tomat

eu_tomat

 

Церковь Доброй Автоматизации (Eutomation Church) открыта для вас

Ликуйте, братья! По результатам обсуждения предыдущего поста мною было принято решение учредить Церковь Доброй Автоматизации (Eutomation Church). Я временно займу место ее пророка, а все, кто уже успел отметиться в предыдущем посте, объявляются ее апостолами.   Случайно отметившиеся могут снять с себя полномочия без каких либо последствий, Добрая Автоматизация не станет преследовать вас.   На оставшихся налагается обязанность писать код, делать это вдумчиво и максимально осмысленно, а также распространять по планете радость Доброй Автоматизации.   В будущем, если потребуется, изберем пасторов и епископов путем открытого голосования.   В комментариях к предыдущему посту наш Крутой брат предложил не забывать и сестер тоже. Но потенциальные сестры пока не спешат поддерживать нашу веру, хотя кое-кто уже сейчас исполняет некоторые из наших заповедей. Наша Церковь готова принять и их тоже. Добрая Автоматизация любит всех вас.   Спасибо всем за поддержку.   Радуйтесь братья, ибо Автоматизация добра к нам!

eu_tomat

eu_tomat

 

Сизифов код

Сизифов код   Расскажу тебе поучительную историю, брат. Присаживайся поудобнее, да тушеными мухоморами угощайся. История, значит, такая:   Был у нас на проекте игрок. Назовем его Сизифом. И лежало на нем проклятие: что ни напишет — говнокод получается. Бывало, всего две строчки напишет — и даже там говнокод выходит. И вот, старается Сизиф, шлифует свою программу. И казалось бы, совсем чуть-чуть остается. Но добавляет строчку — и опять говнокод получается. Что он только ни делал: брал примеры из статей, воровал дискеты из компьютеров других игроков, и даже как-то скачал себе рекурсивную копалку Totoro. Но стоило ему внести в этот код небольшую правку, всё в скатывалось в говнокод. И прекрасный робот-шахтер превращался в тыкву не дожидаясь полуночи.   И вот однажды робот Сизифа не выдержал издевательств и сделал жалобный «бип» почти человеческим голосом. Глянул Сизиф на экран, а робот пишет ему: — Обожди писать код. Включи голову, будь программистом. Призадумался тогда Сизиф над правками кода, стал размышлять над тем, что делают разные команды и как они сочетаются между собой, и вот, не прошло и недели, как написал он свой вариант копалки. И как знать, мог бы он написать копалку даже круче Totoro и выкопать все алмазы на сервере, но неожиданно вайп случился.   Удалил тогда Сизиф свой старый аккаунт, зарегистрировался под новым ником и, поговаривают, даже в вайт-лист попал, и скоро мы сможем увидеть его прекрасные программы. Хотя казалось бы, говнокодером раньше был.   Историю эту я узнал из перехваченного секретного сообщения в закрытой части OpenNet, расшифровал худо-бедно, да тебе поведал. Настоящий ник игрока сообщать не буду, и так уже много лишнего рассказал. Ты же знаешь — администрация наша кровава. Но этой историей я не мог не поделиться с тобой.   Радуйся, брат! Ибо в твоей голове сокрыта великая сила.

eu_tomat

eu_tomat

 

Благословение или проклятие? Свинец против булыжника.

Благословение или проклятие? Свинец против булыжника.   Вот тебе еще одно решение еще одной проблемы, брат. Известно, что геосканер не различает по плотности свинец и булыжник. artem221 говорит, что и доски имеют ту же плотность. Поверю ему на слово, он на этом зомбяку съел. Но часто ли встречаются доски под землей? Заброшенные шахты не так часты. Но даже они не будут большой проблемой, поверь мне, брат. Булыжник же чаще всего встречается в шахтах игроков.   artem221 говорит, что свинец весьма ценен. Я не знаю, насколько ценен свинец для тебя, брат, и сколько стоит время твоего робота, это ты сам знаешь. Но если тебе реально нужен свинец, и при этом ты не хочешь тратить время своего робота на доски и коблу, предлагаю решение: В чистом мире сплошной добычей проверь частоту появления свинца, коблы и досок. Копай целыми чанками и строй статистику. Да не забудь дать роботу инструмент с шелковым касанием, чтоб коблосчетчик его не порвался. Так ты узнаешь, в каких пределах лежит частота появления свинца, коблы и досок в чанках. Также ты узнаешь эти пределы для аномальных по статистике чанков. Наверняка в них находятся заброшенные шахты.   Видя статистику, ты, возможно, решишь, что доски столь редки, а свинец столь ценен, что стоит пройтись даже по доскам. А ежели время твоего робота стоит дороже, а твой ум проницательнее, ты можешь попробовать проанализировать найденные структуры. Ведь доски в заброшенных шахтах не в кучу свалены, у них есть известный порядок.   Если ты смог вычленить доски из списка руд или же решил пренебречь ими, подумай, что делать с коблой. Много ли булыжника ты видел в первозданном мире? Наверняка этот чистый и данный нам Богом мир был подпорчен простыми игроками. И наверняка они проредили те руды, на которые претендовал твой робот. Сравни статистику по рудам со статистикой этого чанка. Возможно, более разумным будет переход в другой чанк, где и руд побольше и булыжника поменьше. А ежели руд много при большом количестве коблы, то это аномальная зона, советую присмотреться к ней. Так ты обменяешь ценное время своего робота на собственное бесценное понимание мира майнкрафта.   Будь счастлив и проницателен, брат!

eu_tomat

eu_tomat

 

Автоматическое определение направления робота с геосканером

Автоматическое определение направления робота с геосканером   Прознал я, что мои браться по IT-серверу страдают от того, что вынуждены каждый раз устанавливать робота с геосканером исключительно мордой на восток. Иначе, дескать, он, неразумный, не понимает, в каком направлении находятся прозондированные руды. Использовать же карту для навигации они не хотят, не смотря на уговоры Алекса. И я с ними согласен: ну ее к чОрту! Только слот занимает, а диапазон действия ограниченный. Но направление взгляда она должна указывать даже вне зоны действия карты. Но слот занимает... Что же делать...   Нахрена попу баян, если есть колокола?   Используй геосканер, брат. Зондируй 4 прилегающих к роботу столба: северный, западный, восточный и южный. Теперь проверяй блок перед мордой неразумного робота, не мне тебя учить, как это делается. И если впереди стоит блок, руби его и еще раз зондируй столбы. А ежели впереди блок отсутствует, то выруби его снизу и поставь вперед. Ведь ты же не на воздух поставил своего робота. И опять сканируй столбы.   Что делать дальше, ты уже догадался: находишь различия в сканах блоков на уровне робота, и определяешь его ориентацию. И ежели его ориентация не вписывается в православный канон, ты данной тебе властью программиста и известной всем нам молитвой меняешь ориентацию робота на православную, лицом к восходящему светилу.   Радуйся, брат! Ибо есть счастье на IT-сервере.

eu_tomat

eu_tomat

 

Как обрести счастье на IT-сервере

Как обрести счастье на IT-сервере   “Не собирайте себе сокровищ на земле, где моль и ржа истребляют и где воры подкапывают и крадут, но собирайте себе сокровища на небе, где ни моль, ни ржа не истребляют и где воры не подкапываются и не крадут” (Матф.6:19-20). Часть первая. То измена, то засада. В последние два месяца я редко захожу в игру, в основном для проверки и поддержания привата. Даже форум редко просматриваю. Зайдя же в очередной раз на сервер после двухнедельного отсутствия в игре, я обнаружил себя на новом спавне, почти голым, в майке и трениках, без бура, джетпака и наношапочки. И самое печальное, без дома: /home не работал, и мой дом на карте тоже не просматривался. Да что об этом говорить – сама карта была совершенно новой. И я приуныл.   И вот, стою я один, как Робинзон на острове, с комплектом железной брони и инструментов в инвентаре, но прямо сейчас я боюсь потерять даже их. Вокруг непроглядная ночь, наполненная голосами местной нечисти. А если очень сильно не повезет, можно даже погибнуть от лазера злодея на спавне. Стоящий на спавне сундочок Края кажется единственной надеждой спасти подаренное богами имущество. Но нет же, сундук не открывается, лишь выдается сообщение "У вас нет разрешение игрока прав администратора на использование предметов".   Благо, форум подсказал мне про /enderchest. Я быстренько сложил свой примитивный скарб в виртуальный сундук, и стал неспешно оглядываться на спавне и ждать окончания ночи. И вот тут меня ждала вторая неожиданность – ночь не кончалась, время тянулось медленно, и это не фигура речи, это новая фишка сервера, как выяснилось. И я опять приуныл.   Часть вторая. Спасение. Реально помогают лишь две вещи: голоса в ТОПах и программирование, как и заповедовал нам AlexCC, в этом он преуспел. Теоретически могли бы помочь друзья, но я не очень общительный человек, и в тот момент на сервере никого из игроков не было, да и друзья до сих пор сами буквально умирают от голода. На сервере находился только AlexCC, но я не стал тревожить его своим плачем. Даже Яхве не помогал евреям, пока те не вставали на путь исправления.   За голоса я приобрел себе ПНВ, чтоб лучше видеть, наноботы, чтобы мягче падать, и несколько джетпаков, чтобы летать и не сильно заботиться об их зарядке, после чего, отбиваясь от зомбей и скелетов, стремительно закопался в гору недалеко от спавна и заприватил участок. Это сберегло мне время и душевное равновесие на старте игры.   Потом я все же отклонился от праведной жизни, несколько раз сходив с киркой, а потом и буром в майн за рудами, построил батбокс для зарядки джетпака, а также всяческие дробилки-плавилки-сжималки-выжималки, скрафтил бур, пилу, и даже смог собрать свой первый компьютер, закупив для него лишь глину и кактусы.   Сейчас же думаю, что на этом этапе я ошибался. Я не пробовал посчитать, но думаю, что стартовых денег достаточно для покупки ресурсов, необходимых для постройки первого хотя бы простого (или даже сложного) робота, способного к добыче ресурсов. Запрограммировать и отладить его можно в креативе, а можно просто взять одну из замечательных программ с форума computercraft.ru. А дальше лишь остается стоять рядом и, свернув окно майнкрафта, читать умные книжки, статьи и думать над следующими программами. И дом теперь тоже не хочется строить руками. Строительство должно быть автоматизировано. Всё. Всё в мире майнкрафта должно быть полностью и хорошенько томатизировано. Только в этом можно обрести счастье на IT-сервере. Смерти нет. Вайп не конец всему, если только всем для тебя не являлись сундуки, набитые рудой и выстроенный руками дворец. Твои программы и твои знания останутся с тобой и после вайпа, а робота даст администрация, насколько бы кровавой она ни была. Всё остальное должно прийти автоматически.   В той горе, где я поселился, мне довелось узнать много всего интересного. Однажды, когда я жарил булыжник в электрической печке, не смотря на тщательно замурованные проходы, зашел ко мне человек в сияющем плаще. Ник его я не запомнил, хотя говорили мы с ним всю долгую ночь. Нет смысла пересказывать весь разговор, но запомнились некоторые его изречения: Блаженны нищие печками, ибо их есть царство IT.
Блаженны ошибающиеся, ибо научатся.
Блаженны спрашивающие, ибо они наследуют опыт IT.
Блаженны алчущие и жаждущие знаний, ибо получат их.
Блаженны помогающие, ибо они помощь получат.
Блаженны думающие, ибо они программирование осилят.
Блаженны программирующие, ибо они будут наречены сынами IT.
Блаженны бегущие с серверов без OpenComputers, ибо их есть сервер IT.
Блаженны вы, когда будут поносить вас и гнать и всячески неправедно злословить фанаты других серверов. Радуйтесь и веселитесь, ибо велика ваша награда на IT-сервере: так гнали и программистов, бывших прежде вас.

eu_tomat

eu_tomat

×