Перейти к публикации

Doob

Гуру
  • Публикации

    766
  • Зарегистрирован

  • Посещение

  • Дней в лидерах

    74

Все публикации пользователя Doob

  1. На старой версии светлая тема была норм. Кнопки в шапке норм, но немного сливаются с фоном, можно было бы их оживить цветом. Или вообще стиль переделать. Блок чата вообще расползается, я долго не мог понять, как в него писать. Обе темы вырвиглазные, но светлая еще и кислотная. Все блоки слишком массивно смотрятся, на oc.cil.li визуально тоже самое, но выглядит аккуратней. Надо глядеть на другие игровые проекты, сделать такой дизайн, какой ожидают увидеть потенциальные игроки. А то сейчас человек попадает сюда, а тут какой-то потусторонний даркнет. Могу нарисовать картинку на шапку, вектор или пихель-стайл, но надо определится с палитрой, чтобы потом не переделывать. Или текстуры для контента, чтобы не платить $30 за новую тему, а проапгрейдить текущую.
  2. Чтобы отобразить иконки файлов и папок, а затем использовать их как кнопки, нужно разработать удобную в управлении структуру данных. При помощи filesystem API можно получить контент текущей директории, что с этим делать? Для начала разметим экран. В верхней части, на всю ширину экрана будет что-то вроде статус-бара высотой в 4 строки, там будет состояние памяти, батареи, может быть адресная и поисковая строка. Иконки 10x5 символов, с именем снизу, будут располагаться по сетке, через 1 символ. Загруженные иконки уже хранятся в таблице, осталось назначить их файлам и нарисовать. При загрузке программы надобно рассчитать, сколько иконок войдет по горизонтали и вертикали, создать таблицу для хранения сетки. Иконка начинает рисоваться от левого верхнего угла, поэтому в таблицу будем заносить именно эти начальные координаты. Обзовем таблицу, например, grid. В этой же таблице сделаем буфер для хранения имен иконок, чтобы при переходе из папки в папку не рисовать иконки, которые уже есть. Кстати, все содержимое может не влезть на экран, поэтому будем его разбивать на страницы. Для этого создадим таблицу pages и при сканировании директории будем добавлять в нее таблицы с содержимым страницы, если количество файлов больше размерности #grid. Сами страницы будут с такими же индексами, что и grid, по индексам будут хранится: имя файла или папки, назначенная иконка и флаг, директория это или нет. Приступим к описанию функции обновления информации о содержимом. Для начала обнулим страницы. Получим текущую директорию при помощи filesystem.realPath(os.getenv('PWD')) или shell.getWorkingDirectory(). Для того, чтобы в результате получить привычный вид, надо будет отсортировать файлы отдельно от папок по алфавиту. Для этого создадим две временные таблицы, просканируем директорию через filesystem.list(), если имя оканчивается символом '/', то кидаем его к папкам, иначе к файлам, затем сортируем обе таблицы обычным table.sort(). Добавляем имена папок к именам файлов в том же порядке, но в начало таблицы и начинаем обработку результата. Обходим таблицу с именами файлов, если это папка, то назначаем иконку 'folder', если это ссылка, то 'link', во всех остальных случаях получаем расширение файла паттерном ([^%.]+)$ и пробуем назначить иконку с таким же названием. Как-то лень было изучить работу lua-patterns, по идее он должен захватывать одно и больше вхождений, но захватывает от нуля, поэтому файлы без расширения, получают иконки. Если расширения нет, назначается иконка 'unknown'. Далее, в таблицу pages записываем имя файлв, имя иконки и флаг. Потом обновляем индекс, по условию индекс == размерность сетки сбрасываем индекс и обновляем счетчик страниц. local W, H = gpu.getResolution() -- получить разрешение экрана local grid, pages = {buffer = {}}, {{}} -- создать таблицу для сетки и страниц local wm = math.floor(W/11) -- вычислить, сколько иконок войдет по горизонтали local index = 1 -- создать счетчик for Y = 1, math.floor((H*2-5)/14) do -- пройти цикл по вертикали for X = 1, wm do -- пройти цикл по горизонтали grid[index] = {x = X*11-9+(W-wm*11-1)/2, y = Y*7-2, z = Y*7+3} -- рассчитать и задать координаты для текущего индекса index = index + 1 end end local function update() pages = {{}} -- обнулить страницы local index, page, pwd = 1, 1, os.getenv('PWD') -- создать счетчики и получить текущую директорию local names, folders = {}, {} -- создать таблицы для имен if fs.realPath(pwd) ~= '' then -- если текущая директория не корневая folders[1] = '..' -- добавить папку для перехода на верхний уровень end for name in fs.list(fs.realPath(pwd)) do -- получить имена в текущей папке if name:sub(-1) == '/' then -- если в конце слэш table.insert(folders, name) -- добавить к папкам else -- иначе table.insert(names, name) -- к файлам end end table.sort(folders) -- отсортировать имена папок table.sort(names) -- отсортировать имена файлов for i = #folders, 1, -1 do -- в цикле объеденить имена в одну таблицу table.insert(names, 1, folders[i]) end folders = nil -- удалить таблицу для папок for n, name in pairs(names) do -- пройти по всем именам local icon, isDir -- создать переменные для имени иконки и флага if fs.isDirectory(pwd..'/'..name) then -- назначить иконку для папки icon, isDir = 'folder', true elseif fs.isLink(pwd..'/'..name) then -- назначить для ссылки icon = 'link' elseif icons[name:match('([^%.]+)$')] then -- если есть иконка для этого расширения icon = name:match('([^%.]+)$') -- назначить по имени else icon = 'unknown' -- для всех остальных назначить стандартную иконку end pages[page][index] = {name = name:gsub('/', ''), icon = icon, dir = isDir} -- записать имя, имя иконки и флаг в текущую страницу if index == #grid then -- если текущая страница заполнена index, page = 1, page + 1 -- обновить индекс и номер страницы pages[page] = {} -- создать страницу else index = index + 1 -- обновить индекс end end end Теперь надо отрисовать иконки по сетке. В цикле пройдем по индексам сетки, из координат получим индекс для буфера, для быстрого обращения. Если на текущей странице и с текущим индексом что-то есть, а в буфере по этим координатам другая иконка. Берем имя иконки и координаты сетки, вызываем функцию draw_icon(), записываем в буфер имя новой иконки. Сбрасываем цвета, стираем зону, где будет имя файла. Пишем имя файла, со смещением, чтобы оно было примерно по центру иконки. Не забывая обрезать имя до 10 символов. Если по текущему индексу на странице ничего нет, но в буфере осталось имя иконки. Стираем его из буфера. Устанавливаем фоновый цвет и заливаем иконку вместе с именем по текущему индексу пустотой. local function draw(page) page = page or 1 -- если страница не указана, назначить первую for index = 1, #grid do -- пройти по индексам сетки local hash = grid[index].x*W+grid[index].y -- получить хеш if pages[page][index] then -- если на странице по этому индексу есть запись if pages[page][index].icon ~= grid.buffer[hash] then -- если новая иконка отличается draw_icon(pages[page][index].icon, grid[index].x, grid[index].y) -- нарисовать иконку grid.buffer[hash] = pages[page][index].icon -- обновить буфер end local name = pages[page][index].name gpu.setBackground(0) -- задать фоновый цвет local color = 0xffffff -- задать цвет текста if pages[page][index].dir then -- если это папка color = 0xffff00 -- задать другой end gpu.setForeground(color) -- установить цвет gpu.fill(grid[index].x, grid[index].z, 10, 1, ' ') -- очистить место gpu.set(grid[index].x+5-#name:sub(1, 10)/2, grid[index].z, name:sub(1, 10)) -- написать имя else -- если страница кончилась if grid.buffer[hash] then -- если в буфере что-то есть grid.buffer[hash] = nil -- обновить буфер gpu.setBackground(0) -- задать фоновый цвет gpu.fill(grid[index].x, grid[index].y, 10, 6, ' ') -- очистить место end end end end Теперь можно добавить слушателей из части #0, очистить экран, вызвать update() и draw() По событию 'click' запускать следующую конструкцию: for index = 1, #grid do if grid[index].x <= e[3] and grid[index].x+10 >= e[3] and grid[index].y <= e[4] and grid[index].y+5 >= e[4] then if pages[1][index] then if pages[1][index].dir then shell.setWorkingDirectory(shell.getWorkingDirectory()..'/'..pages[1][index].name) update() draw() break end end end end Теперь можно ползать по диску.
  3. Чтобы было куда кликать, надо на экране разметить места для иконок, еще и иконки нарисовать. Для иконок возьмем формат PPM, а конкретно, цветную бинарную версию P6. Формат ультра-примитивный, иконки можно будет без лишних заморочек рисовать в любом нормальном растровом редакторе, в опенкомпах, а при наличии нужного скрипта - прямо в консоли. Но в этом формате будем хранить иконки на диске. Внутри программы они будут преобразовываться в таблицу, хранящую цвет каждого пикселя. Например, создадим иконку, которая будет рисоваться для всех типов файлов по умолчанию. (Будет отображаться, если подходящая иконка не загружена) local icons = {} -- создать массив с иконками local icons.unknown = {x = 10} -- создать таблицу для иконки, указать ширину в пикселях for i = 1, 100 do -- цикл заполнения таблицы 10x10 if i%3 == 0 then -- если номер пикселя делится на 3 icons.unknown[i] = 3394611 -- сделать пиксель зеленым else -- иначе icons.unknown[i] = 3355443 -- сделать серым end end С внутренним представлением определились, теперь напишем функцию отрисовки. Создадим счетчики для индексов, горизонтальной и вертикальной координаты. Запустим цикл, с условием: пока индекс меньше или равен (количество пикселей - ширина изображения). Установим для символа цвет текущего пикселя, а для фона получим пиксель через текущий индекс + ширина изображения. И выведем полученные пиксели одним символом u+2580. Если счетчик по горизонтали досчитал до ширины изображения - сбросить в начало, к счетчику по вертикали добавить 1, а к индексу прибавить ширину. Получим пропуск строки, т. к. она уже была отрисована в текущей итерации. local function draw_icon(name, X, Y) -- получить название и координаты if not icons[name] then return false end -- прервать, если нет такой иконки local x, y, index = 1, 1, 1 -- создать счетчики while index <= #icons[name]-icons[name].x do -- пройти по индексам gpu.setForeground(icons[name][index]) -- установить цвет верхнего пикселя gpu.setBackground(icons[name][index+icons[name].x]) -- цвет нижнего gpu.set(x+X-1, y+Y-1, quad) -- вывести на экран if x == icons[name].x then -- если достигнута ширина изображения x, y, index = 1, y + 1, index + icons[name].x+1 -- обновить все счетчики else -- простая итерация x, index = x + 1, index + 1 -- обновить счетчик горизонтали и индекса end end end Одна иконка уже сгенерирована, чтобы вывести ее в углу экрана, вызовем ее по имени - draw_icon('unknown', 1, 1) В итоге, на экране получим такое изображение: Чтобы загрузить иконки и конвертировать в удобный вид, создадим такую функцию: Перебрать все файлы в папке, получив их список через filesystem API. Прочитать файл построчно в таблицу, попутно удалив строки с комментариями. Если заголовок файла равен , получить ширину картинки, информацию о пикселях объединить в одну строку. В цикле пройти по пикселям, конвертируя бинарное значение пикселя в число. Тут надо помнить, что значение одного пикселя хранится в трех символах (по одному на канал), поэтому цикл будет скакать через 3. Первый символ конвертируем в число, умножаем на 65536, второй на 256 и складываем. Полученное число добавляем в массив пикселей текущей иконки. Получаем примерно такую реализацию: local function load_icons(path) -- получить путь к папке с иконками local multiplier, path = {65536, 256, 1}, path or '' -- создать таблицу множителей for name in fs.list(path) do -- получить имя файла в папке local file = io.open(path..name, 'r') -- открыть файл if not file then break end -- если файла нет, прервать цикл name = name:gsub('%..+', '') -- обрезать название файла до первой точки local raw_img = {} -- создать массив для сырых данных for line in file:lines() do -- в цикле пройти по строкам if line and line:sub(1,1) ~= '#' then -- если строка не закоментированна table.insert(raw_img, line) -- добавить в таблицу end end file:close() -- закрыть файл if raw_img[1] == 'P6' then -- если заголовок совпадает local _ = raw_img[2]:find(' ') -- проверить наличие пробела на второй строке if _ then -- если размеры на одной строке _, raw_img[2] = raw_img[2]:sub(_+1), raw_img[2]:sub(1,_-1) -- разделить table.insert(raw_img, 3, _) -- перенести высоту на другую строку end raw_img[2] = tonumber(raw_img[2]) -- преобразовать ширину изображения в число icons[name] = {x = raw_img[2]} -- создать пустую таблицу для пикселей local current = '' -- создать переменную с сырой информацией о пикселях for i = 5, #raw_img do -- пройти до конца файла current = current..raw_img[i]..'\n' -- объединить данные в одну строку end local color, n for i = 1, #current-1, 3 do -- пройти по каждому третьему символу, исключая последний перевод строки n, color = 1, 0 -- сбросить счетчик для таблицы множителей и цвет for j = i, i+2 do -- перебрать три символа color = color+current:sub(j,j):byte()*multiplier[n] -- преобразовать символ в число и добавить к значению цвета n = n + 1 -- обновить счетчик end table.insert(icons[name], color) -- добавить цвет пикселя к остальным end end end end Реализация очень примитивная, но главное, что иконки будут загружаться. Можно было бы сотворить свой формат, который быстрей распаковывается и занимает меньше места, но плодить сущностей очень вредно. Для проверки, осталось нарисовать иконки, сложить их в папку /home/icons/, например. И запустить весь код: local fs = require('filesystem') local gpu = require('component').gpu local quad = require('unicode').char(0x2580) local icons = {unknown = {x = 10}} for i = 1, 100 do if i%3 == 0 then icons.unknown[i] = 3394611 else icons.unknown[i] = 3355443 end end local function load_icons(path) local multiplier, path = {65536, 256, 1}, path or '' for name in fs.list(path) do local file = io.open(path..name, 'r') if not file then break end name = name:gsub('%..+', '') local raw_img = {} for line in file:lines() do if line and line:sub(1,1) ~= '#' then table.insert(raw_img, line) end end file:close() if raw_img[1] == 'P6' then local _ = raw_img[2]:find(' ') if _ then _, raw_img[2] = raw_img[2]:sub(_+1), raw_img[2]:sub(1,_-1) table.insert(raw_img, 3, _) end raw_img[2] = tonumber(raw_img[2]) icons[name] = {x = raw_img[2]} local current = '' for i = 5, #raw_img do current = current..raw_img[i]..'\n' end local n, color for i = 1, #current-1, 3 do n, color = 1, 0 for j = i, i+2 do color = color+current:sub(j,j):byte()*multiplier[n] n = n+1 end table.insert(icons[name], color) end end end end local function draw_icon(name, X, Y) if not icons[name] then return false end local x, y, index = 1, 1, 1 while index <= #icons[name]-icons[name].x do gpu.setForeground(icons[name][index]) gpu.setBackground(icons[name][index+icons[name].x]) gpu.set(x+X-1, y+Y-1, quad) if x == icons[name].x then x, y, index = 1, y + 1, index + icons[name].x+1 else x, index = x + 1, index + 1 end end end load_icons('/home/icons/') local n = 1 for name in pairs(icons) do draw_icon(name, (n*11)-10, 1) n = n + 1 end Получаем:
  4. Опечатка тут: gpu.setForeground(cf) аргумент приходит cF Вообще, при ошибке выводится название проблемной функции, а на скрине только аргумент. Но это тоже может помочь - рассмотреть или переписать строку, указанную в ошибке.
  5. Давным-давно делал модный файловый менеджер с графическим интерфейсом для опенкомпов. Переходы по папкам, запуск файлов, распаковака tarball'ов и просмотр картинок в одной программе, к тому же фичи в виде листания свайпами, экранной клавиатуры и горстки настроек. И все это добро занимало меньше килобайта. Но развивать идею не стал, код удалил и осталась только одна картинка тестовой версии. Недавно решил это дело возродить, без зависимостей и лишних свистоплясок. Для начала напишем функции, которые добавят дополнительные возможности для пользователя. Когда игрок тыкает в экран, создаются два события - touch и drop. Когда зажимает и тащит - touch, потом куча drag и в конце drop. Из имеющихся событий, можно развить дополнительные события - клик, двойной клик и свайп. Можно даже добавить сложные жесты, но пока не понятно, как они могут пригодиться. На все нужные события повесим слушателей и будем сохранять результат в переменную. Слушатель для события touch будет проверять, было ли предыдущее событие drop. Затем сравнит с временем от последнего клика, вычислит расстояние между точками, в которых произошло событие. При совпадении координат и заданным временем между кликами пошлет событие double_click. Для события drop надо проверить, было ли предыдущим touch и по тому же параметру скорости проверять время между событиями, чтобы не захватывать долгие нажатия. Если предыдущим событием было drag, то надо определить расстояние между началом и концом действия, вычислить угол и послать это все в виде события swipe. В итоге получится примерно такой код: local computer = require('computer') -- подгрузить обертку для uptime & pushSignal local event = require('event') -- подгрузить библиотеку событий local lastEvent = nil -- последнее действие local lastTouch = nil -- последнее касание local eventTime = nil -- время от последнего события local clickSpeed = 0.5 -- время, за которое совершается клик и дабл-клик event.listen('drag', function(...) lastEvent = {...} -- просто сохранить событие end) event.listen('touch', function(...) local e = {...} -- сохранить событие в таблицу if e[5] == 0 and lastEvent and lastEvent[1] == 'drop' then -- если нажата ЛКМ и предыдущее было drop if eventTime and computer.uptime()-eventTime < clickSpeed then -- если прошло меньше времени, чем задано if lastTouch and lastTouch[3]-e[3]+lastTouch[4]-e[4] == 0 then -- если координаты событий не отличаются computer.pushSignal('double_click', e[2], e[3], e[4], e[6]) -- послать дабл-клик с координатами end end lastTouch = e -- сохранить последнее касание end eventTime = computer.uptime() -- обновить таймштамп события lastEvent = e -- сохранить событие end) event.listen('drop', function(...) local e = {...} -- сохранить событие в таблицу if e[5] == 0 and lastEvent then -- если нажата ЛКМ if lastEvent[1] == 'touch' then -- если предыдущее событие было касанием if eventTime and computer.uptime()-eventTime < clickSpeed then -- если прошло меньше времени, чем задано computer.pushSignal('click', e[2], e[3], e[4], e[6]) -- послать клик с координатами end elseif lastEvent[1] == 'drag' then -- если предыдущее было тасканием local dx, dy = lastTouch[3]-e[3], lastTouch[4]-e[4] -- найти дельту до координат касания computer.pushSignal('swipe', e[2], dx, dy, math.floor(math.deg(math.atan(dx/dy))), e[6]) -- послать свайп с дельтой и углом end end eventTime = computer.uptime() -- обновить таймштамп события lastEvent = e -- сохранить событие end) Пока он ничего не делает, только создает события, когда будет готов функционал отрисовки и взаимодействия с файловой системой, добавим к этим слушателям управляющие функции.
  6. Угу, оберточная функция только занимает память, а к ней еще приколхожен объект, который моментально создается и исчезает. Нужны либо мухи, либо котлеты. А это ниндзя-индусский код.
  7. Вот подборка глючных шейдеров, некоторые смотрятся не так колхозно. https://www.shadertoy.com/view/MlVSD3 https://www.shadertoy.com/view/MllBzs https://www.shadertoy.com/view/4tyfDR https://www.shadertoy.com/view/4syfRt https://www.shadertoy.com/view/MltBzf
  8. Doob

    Дубокоп

    В коде есть переменные, которые можно настроить. chunks - сколько чанков нужно обработать min и max - минимальная и максимальная твердость добываемых блоков port - порт для взаимодействия с роботом (если установлена беспроводная карта)
  9. Doob

    Поиск пути

    Когда я узнал о JPS, у меня возникла идея упростить A*. Можно выкинуть волновую рекурсию, которая используется во всех алгоритмах поиска пути. Для этого, берем все узлы, которые являются препятствиями, помечаем соседние свободные узлы и строим путь. Из самого описания выходит, что данный алгоритм подойдет не для всех типов графов, но ботам на регулярной сетке в самый раз. Проблема данного алгоритма в том, что препятствий может быть очень много, а на пути между стартом и финишем очень мало. Фактически это алгоритм Дейкстры без рекурсии, прямой переход в самое последнее возможное состояние. Потребление памяти меньше чем у Дейкстры и А*, т. к. отмечается только финальный фронт. Чтобы оптимизировать, будем отмечать только те граничные узлы, которые находятся ближе к стартовой и финишной точке. Получаем JPS, только избыточней, но лучше чем A* по потреблению памяти. Остается построить путь поиском в ширину из доступных оптимальных путей. Вот сравнение по потреблению памяти при обходе препятствий: A* хранит данные о свободных клетках, полученных на данной эвристике. В эти данные входят координаты точки, расстояние до цели, количество предыдущих шагов. Jump Point Search хранит точки перехода на пути от старта к финишу, хранятся только координаты точек и по выбору: координаты соседних переходов или расстояние до финиша. При удалении рекурсии надо хранить координаты граничных точек, а все вычисления производить только при построении пути. Если найти способ не хранить все доступные граничные точки, то алгоритм без рекурсии вполне годится для практического применения. JPS в большинстве применений превосходит A*, он быстрее, потребляет меньше памяти. Но не годится для открытых пространств, если регион поиска искусственно не ограничить, он будет вечно искать оптимальный путь, т. к. его преимущество оборачивается недостатком. Возможно эту проблему уже решили, но никакой информации об этом я не нашел.
  10. Windows 10, Debian 9 полет нормальный. Может каких-то библиотек нет?
  11. Скачал демку, все работает идеально. Как я и предполагал, есть артефакт склейки, по осям X и Z координаты 111, -112 (т. е. мир квадратный) но артефакт незначительный - просто не рендерится рука и предмет в ней. Не понял, как нормально залезть на вышку, на той высоте гравитация настолько низкая, что при схождении с лестницы я улетел в межпланетное пространство. Падал в туннель. На высоте -30 снизу ничего не рендерится, т. е. выглядит как пустота под бедроком в майне (но тут вместо пустоты отрицательные координаты по Y залиты лавой) при падении, игрок просто телепортируется на другую сторону туннеля. P.S. Однако, находясь на шве мира, нельзя взаимодействовать с блоками, которые находятся на другой стороне. Если с одной стороны блока нет, а с другой есть, то на грани не рендерятся текстуры и вся планета видна насквозь.
  12. Какого типа хранилище? Можно сделать так, чтобы робот сам доставал блоки и ломал их, пока не кончится кирка.
  13. Doob

    Дубокоп

    Понял, исправлю. Отвечаю по порядку. После обработки чанка, робот скидывает мусор и упаковывает ресурсы, при переполнении разгружается. По дефолту обсидиан вне области видимости, доски не проблема. Площадь сканирования 16x16 блоков, бедрок хорошо обнаруживается, а если не обнаруживается, то робот не застрянет. В программе можно настроить, сколько чанков добывать. Такую задачу не решить имеющимися ресурсами, можно запустить тысячи роботов, полученные данные загрузить в одну кучу и вычислять задачку до конца Вселенной. Но я сделал проще и быстрей, подход не идеальный, но очень хорошо оптимизирован при помощи практических тестов.
  14. Doob

    Дубокоп

    Представляю вам опять программу для робота, которая позволяет добывать руду, не лазая по пещерам. Робот, используя геолизер, может самостоятельно находить и добывать руду. Реализованы еще не все возможности, поэтому прошу тестировать и сообщать мне о багах. Требования: Корпус компьютера (уровень II или III) Апгрейд инвентарь (больше - лучше) Апгрейд контроллер инвентаря Жесткий диск EEPROM с прошитым Lua BIOS Геосканер Память (уровень I или выше) Процессор (любой) Апгрейд полета (I уровень) Алмазная кирка или аналогичный инструмент. Опционально: Апгрейд верстак Беспроводная сетевая карта Апгрейд батарея Апгрейд опыта Апгрейд чанклоадер Апгрейд генератор Эндерсундук из мода EnderStorage Установка: Скачать и сохранить файл как init.lua wget https://raw.githubusercontent.com/DOOBW/geominer/master/miner.lua init.lua Закинуть этот файл в корень диска. Добавить диск при сборке робота. Установить робота на платформу из твердых блоков. Дать роботу кирку. Поставить возле робота контейнер и зарядник. Нажать кнопку питания и наслаждаться процессом.
  15. Тут весь фокус в том, что нет реальных торов и сфер. Есть плоскость, которая модификацией рендера выглядит как сфера. Я даже могу реализовать похожий эффект при помощи OpenComputers, если можно получить эффект этого рыбоглазного искажения. Достаточно сдублировать плоскость во все стороны - получится фокус с торусом. Игрок будет бежать в одну сторону, видеть горизонт, получившийся визуальным изгибом плоскости, плоскость будет постоянно копироваться спереди и исчезать сзади. Но тут проблема в том, что получится только грубая иллюзия. Надо чтобы рендер корректно дорисовывал плоскость на месте стыков и без рывков переносил игрока с одного края на другой. Я давно хотел сделать кое-что подобное, там игрок мог бы перемещаться по гиперграням тессерактов, сложенных в какую-нибудь фигуру. Получался бы трехмерный лабиринт, заключенный в четырехмерный. Для игрока это выглядело бы как приключения из фильма Куб. Но все уперлось в малое быстродействие телепортера. Телепортация дебагой занимает минимум два тика, это очень режет глаз, поэтому без специально созданного мода, красивой иллюзии не добиться. И я отказался от этой затеи.
  16. Doob

    TabletOS

    Я тоже такую оболочку делал, но идея себя быстро исчерпала.
  17. Нужен мод на рендер и генерацию. Разделить платформу на две части, свернуть перпендикулярно самой себе. Надо только понять, как рендерить место склейки платформ и скрыть переходы. Локальная система координат никак не изменится.
  18. Doob

    Python 3 и OpenComputers

    Есть такое, OpenPython называется. Но клепает его один человек, поэтому похвастать нечем. Работает кое-как, даже стабильной версии нет, годится только для микроконтроллеров. Где-то еще встречал упоминания о питоно-подобном процессоре для опенкомпов, но сейчас найти не могу.
  19. Doob

    Робот с геосканером. Часть #7 [способы добычи]

    Никто и не собирался нейросетью решать задачу коммивояжера, это абсолютно бессмысленное занятие. А вот научить робота самому добывать пропитание и ресурсы для людей - это задача как раз по силам полносвязной сетки.
  20. В процессе подгонки всех функций программа очень быстро разрослась. Дам краткий обзор всех новинок. Минимальная и максимальная плотность были вынесены в начало программы к остальным переменным. + переменная port, для модема. + переменные steps и turns, вначале для отладки. steps пригодилась для подсчета шагов, чтобы каждые 32 шага проверять состояние инструмента и батареи. + функция arr2a_arr() - преобразование списков в ассоциативный массив для быстрого доступа к элементам. Массивы tails, fragments преобразуются этой функцией, функции работающие с ними, адаптированны соответствующим образом. + функция report(), позволяющая передавать пользователю статусные сообщения посредством модема или связанной карты. В данный момент еще пикает и выводит сообщения на экран. Также завершает работу программы, при получении соответствующего параметра. remove_point() отделилась от функций, удаляющих метки из таблицы. Функция check() отделилась от функции step(). Каждые 32 шага или по принуждению вычисляет расстояние до стартовой точки, сохраняет текущие координаты. Если уровень энергии или инструмента не хватает на дорогу до дома + 64 шага, совершается переход домой, потом возврат к работе. Если есть генератор, происходит попытка заправки. Каждый шаг проверяются точки вокруг робота и удаляются, если робот может их достать. Функция calibration() объединилась с калибровкой компаса. Происходит проверка компонентов: геосканер, контроллер инвентаря, инструмент и наличие блока под роботом, в случае не обнаружения - программа завершается. Настраиваются модем или связанная карта. sorter() упростилась и ускорилась. + функция home() перемещает робота на точку старта, запускает сортировку и упаковку предметов, ищет сундук (требует, в случае не обнаружения). Складывает добычу в сундук, в случае переполнения ждет, когда освободится место. Достает из сундука неупакованные предметы и переупаковывает. Забирает из сундука стак с углем, при наличии генератора. Кроме перехода к точке старта, ищет в инвентаре эндерсундук из EnderStorage и сгружает ресурсы в него. Пытается найти более новый инструмент в сундуке, либо ищет зарядник и пробует засунуть в него и зарядить. На точке старта ждет, пока уровень энергии не достигнет 99% потом возвращается к работе. + функция main() - основной цикл сканирования и добычи. Робот сканирует весь чанк, в цикле перебирает все метки, выбирает ближайшую и перемещается к ней, пока не пройдет по всем. Ну и цикл перехода по спирали от чанка к чанку. Запускает функцию main(), по завершении вычисляет координаты следующего чанка, перемещает к нему, запускает main() и т. д., пока не дойдет до последнего, потом вызывает функуию home() и завершает работу. Осталось добавить возвращение по хлебным крошкам при аварии. И правильную работу с инструментами, имеющими нестандартную механику износа - энергетические и магические кирки/буры из модов. Еще можно заставить робота таскать с собой генератор из какого-нибудь мода и зарядник, тогда ему не нужно будет возвращаться на точку старта для подзарядки (а с эндерсундуком вообще на надо будет возвращаться) Бета-версию можно посмотреть и скачать тут - https://pastebin.com/hXWLDKre
  21. Doob

    Робот с геосканером. Часть #7 [способы добычи]

    @Totoro и ко делают крутой эмулятор, в котором можно будет работать с майновскими сохранениями, в комплекте с ускоренным выполнением кода, можно будет обучать сеть без всяких костылей.
  22. Doob

    Робот с геосканером. Часть #7 [способы добычи]

    Меня прельщает то, что входов и выходов мало, их легко нормализовать, есть кое-какие возможности (функция получения времени или уровня энергии), позволяющие спроектировать очень простую нейросеть. Обучать, да, сложно. Это собрать данные, собрать статистику, без прямого обучения через тушку робота это очень сложно. Но можно будет дообучать сеть в процессе работы. Например, будет найдено равновесное состояние, при котором роботы будут добывать себе топливо и алмазные кирки. Но в таком случае, эти автономные агенты будут совершенствоваться не зависимо друг от друга. Либо, можно подключить все тушки к одному серверу, который на основе данных от роботов, будет дообучать сеть и выдавать обновления.
  23. Doob

    Робот с геосканером. Часть #7 [способы добычи]

    Большую часть я уже решил штрафами и логической связкой, на каждый хак там всего пара строчек. Конечно, это довольно механическое решение, оно не всегда работает оптимально. Но и нет такого огорода с вычислениями. Года два или три назад, я гонял генетические алгоритмы для того, чтобы заставить роботов искать ресурсы, обеспечивать себя энергией, взаимодействовать с игроком, без контроля со стороны игрока. Дело шло хорошо, но очень медленно, даже распараллелив вычисления и ускорив время, ждать результата обучения, пришлось бы очень долго (это при том, что все сложные функции были написаны руками) Меня давно интересует вариант засунуть в робота простую нейросетку, самый сложный этап обучения будет как раз у сканирования и добычи. Но практической пользы все-таки маловато, т. к. в текущем варианте робот добывает все и сразу. А с нейросеткой, будет блуждать по миру, выдавая ресурсы только по приказу игрока (можно сделать тягу к заряднику, но тогда робот много не добудет) Но потребление памяти и процессора во время работы, будет практически нулевым, т. к. обученная нейронка будет просто выплевывать готовые последовательности действий на внешние данные.
  24. Эвристические функции: Давление - штраф на ход по вертикали. Нужен из-за того, что начало сканирования либо сверху, либо снизу, следовательно, заканчивать добычу надо ближе туда, где начнется следующее сканирование. Недоход - робот не заходит на позицию метки, чтобы сэкономить лишний шаг (не всегда оптимально, но суммарный эффект - положительный) Штраф на повороты - добавление шага, при оценке расстояния, если целевой блок находится дальше, чем в одном блоке от текущей оси. Изначально идея была в том, чтобы собрать такое сочетание эвристик, при котором робот будет быстрее заканчивать добычу и выходить к точке старта. Важно, чтобы робот финишировал наверху, для быстрого перехода к следующему чанку. Сначала добавил ограничение хода по вертикали. Если робот может достать блок, не доходя до него, то добывает и ищет следующий. Потом добавил такую же функцию для всех осей. Прибавил еще мягкое давление по вертикали, чтобы нижние блоки имели приоритет над верхними. Но когда запустил пачку роботов, перепутал знак и получил давление вверх. Пришлось потом запускать еше, с правильной формулой. Из всех решений выбрал лучший результат - штраф на повороты, недоход по всем осям и давление вниз. В тесте робот довольно много скакал по вертикали, я решил это исправить. Увеличил давление в два раза, но не заметил, что половина формулы поменяла знак. Робот стал настолько мало скакать по вертикали, что процесс превратился в послойную добычу, со слоем 2 блока. Перевернул формулу, чтобы добыча опять шла снизу вверх. Поменял обратно, получил не очень хороший результат - робот получает меньше свободы, делает больше лишних движений. По факту, сейчас штрафы на повороты конкурируют с недоходом, т. к. при добыче больших залежей, у робота вокруг много блоков и ему без разницы, сделать шаг к следующему или повернуться к соседнему. Самый оптимальный вариант для таких случаев - змейкой, но у этих залежей бывают всякие аппендиксы. Это вызывает некоторую нагрузку на дальнейшие ходы - роботу приходится больше вертеться и ходить. К тому же, недоход надо расширить: из-за того, что счет идет шагами, без точного учета поворотов - робот может не повернуться к соседнему блоку. Или зайти в блок, если он находится в соседнем блоке к текущей оси, хотя мог бы достать без последнего шага. Осталось придумать, как это починить без лапши (хотя там лапша из одного бряка по флагу, но все-равно не приятно) и подобрать такие уровни штрафов/давления, чтобы робот красиво закольцовывал добычу с большим выигрышем по времени, чем сейчас. Параметры: чанк со стандартной генерацией (IC2+AE2), без каверн, алмазная кирка без всяких заклятий и примочек, робот без бустеров. Время Шаги Повороты Сумма 1361 1094 430 1524 послойный 1189 982 358 1340 свободный без улучшений 1185 972 358 1330 недоход по y 1156 932 368 1300 недоход по y, давление вниз 1248 1076 360 1436 недоход по y, давление вверх 1254 1036 380 1416 недоход по xyz 1186 948 368 1316 недоход по xyz, давление вниз 1267 1064 374 1438 недоход по xyz, давление вверх 1210 996 302 1298 штраф на повороты, недоход по y 1141 928 326 1254 штраф на повороты, недоход по xyz, давление вниз 1291 1122 358 1480 штраф на повороты, недоход по xyz, давление вверх x2 1244 1032 368 1400 штраф на повороты, недоход по xyz, давление вниз x2 Итого, самый оптимальный получился - 1254 действия за 19 минут 1 секунду. А вот прогон на тестовом стенде ничего не показал - слишком маленькое расстояние. Но все-таки, на 10 секунд раньше взял последний блок, чем в прошлом тесте.
×