Calltracking в Minecraft или как быстро сделать трехмерный UI

от автора

Пару месяцев назад назад я показал детям Minecraft, а чуть позже — купил им книгу по программированию в MineCraft. Правда, детям купил, чес-слово. Ну сам взял полистить, ну написал пару скриптов.
И на этом история и закочнилась бы, но на днях мне довелось поучаствовать в хакатоне одного calltracking сервиса. Для тех кто не в курсе, calltracking эта такая штука, которая предоставляет статистику звонков. И что важно для нашей истории — эта статистика доступна по API.
В этот момент отдельные части сложились в цельную картину и я подумал — о! статистика звонков в Minecrfat 🙂

Ну что ж, формат хакатона располагает к безумным идеям, а эта идея показалась мне достаточно безумной, чтобы быть реализованной 🙂
А если серьезно — то кто сказал что интерфейсы должны быть двумерными?
И кто сказал что трехмерный интерфейс это долго и сложно?
Вся затея у меня заняла 3 часа (57 строк на питоне), учитывая, что первые полчаса я разбирался как на python парсить джейсон %)

Под катом — вся история целиком, видео с результатом и бонус для дочитавших до конца — все 3 часа разработки в 3 минутном time-lapse видео.

Чтобы это все заработало мне понадобилось 3 простых шага:

1. Поднимаем сервер Minecraft который позволяет взаимодействовать с миром Minecraft по API на Python
2. Берем статистику звонков по calltracking API
3. Создаем кубики в Minecraft.

Ок, поехали!

1. Сервер Minecraft

Сервер майнкрафта с красивым названием bukkit у меня уже был готов (скачать его можно с сайта книги вместе с очень подробными видеоинструкциями)
Версия игры — 1.6.4, скрипты будем писать на Python, т.к. этот сервер их понимает.

2. Берем статистику звонков по API

Тут тоже все просто, пару строк на питоне и все готово

import json, requests data = requests.get("https://istat24.com.ua/api/{your_api_key}/calls.json?counter_start_date=2016-02-01&counter_end_date=2016-02-05") json = json.loads(data.content) 

Пару слов про сам API.
Я использую API Calltracking сервиса iStat24 (в отделе разработки которого и происходил этот самый хакатон), он возвращает журнал звонков в JSON, а чтобы получить данные для определенного аккаунта нужно в этом самом аккаунте сгенерировать API_key, который у меня к хакатону был заготовлен заранее.
Пример звонка из JSON:

{ call_id: 5555555, numberA: "380555555555", numberB: "380555555555", start: "2016-02-05 15:11:13", duration: "00:03:57", wait_duration: "00:00:07", speak_duration: "00:03:50", record: "http://url_to_the_audio_record.mp3", accepted: 1, direction: "incoming", reklama_name: "Google organic", reklama_id: 729 } 

Из этого всего нас интересует
wait_duration — время ожидания звонка (гудки, короче говоря)
speak_duration — время разговора
accepted — значение 1/0 определяет был звонок принят или пропущен.

Итого, имеем массив звонков. Теперь осталось их только представить визуально.

3. Создаем кубики в Minecraft.

До этого все было просто, да? даже не просто — тривиально.
Вы наверное думаете что с этого места начнется какая-то магия? А вот и нет 🙂
Дело в том, что все написано до нас.
Есть расчудесная библиотека на питоне — minecraftstuff, которая умеет делать все основные вещи в Minecraft.
Нам осталось только описать где и какого типа кубик мы хотим создать.

Подключаем и инициализируем библиотеку:

import mcpi.minecraft as minecraft import mcpi.block as block import mcpi.minecraftstuff as minecraftstuff  mc = minecraft.Minecraft.create() mcdrawing = minecraftstuff.MinecraftDrawing(mc) 

Теперь координаты. Minecraft — мир трехмерный, поэтому очевидно что нам нужны x,y и z.
Можно брать текущие координаты персонажа командой mc.player.getTilePos(), но я решил захардкодить определенное место в мире Minecrft (просто потому, что после каждой итерации мне нужно было очищать все пространство с предыдущей попыткой «строительства».) Для дебага удобнее, в общем.

Кубик рисуется командой mc.setBlock(x, y, z, blockType)
А как удалить кубик?
Как оказалось — воздух в Minecraft — это тоже кубик 🙂 Поэтому вместо удаления кубиков — нужно просто нарисовать кубики с воздухом.
Можно делать это поштучно при помощи это же команды mc.setBlock(x, y, z, block.AIR.id) — указывая тип блока «block.AIR.id»
А можно использовать команду mc.setBlocks() которая забивает прямоугольную область кубиками нужного типа. В книжке написано что это быстрее, чем рисовать кубики поштучно.

В итоге у меня получился вот такой код для очистки пространства:

mc.postToChat("START")  startX=146; startY=0; startZ=-30 // хардкод начальных координат  # Clean up mc.setBlocks(startX-2, startY-20, startZ+5, startX+2, startY+200, startZ-250, 8) time.sleep(2) mc.setBlocks(startX-3, startY-1, startZ+6, startX+3, startY+210, startZ-551, block.AIR.id) time.sleep(2)  # cleanUp(pos.x, pos.y, pos.z, 40) # Clean up self mc.postToChat("Clean up is done") 

Лайвхак. Здесь я сначала забиваю пространство кубиками с ID=8 а потом забиваю это же пространство воздухом.
Это делается исключительно для дебага, чтобы было видно какой же именно участок через 2 секунды будет очищен. Иначе это совершенно не очевидно и занимает кучу времени подгадать нужные координаты.
Вообще, весь этот участок кода можно заменить на всего одну команду: mc.setBlocks(startX-3, startY-1, startZ+6, startX+3, startY+210, startZ-551, block.AIR.id), все остальное исключительно для дебага.

Чтобы было красивее, я решил что каждый звонок будет представлен в виде башни шириной (и толщиной) в 2 кубика — а длительность звонка будет представлена высотой башни (1 секунда = 1 кубик)
Поэтому простенькая процедурка которая рисует башню заданной высоты

def drawCall(x, y, z, length, blockType): 	length = (length, 200)[length>200] 	for i in range(y, y+length): 		mc.setBlock(x, i, z, blockType) 		mc.setBlock(x+1, i, z, blockType) 		mc.setBlock(x+1, i, z+1, blockType) 		mc.setBlock(x, i, z+1, blockType) 

Обратите внимание, что в нее встроен дисторшн — потому что высота мира в Minecraft, как оказалось, 255 кубиков, поэтому если звонок был длиннее 255 секунд (а таких конечно же много) — они уходят выше «крыши мира» и продолжаются с «дна мира», что конечно же не эстетично.

Теперь у нас готово все, чтобы нарисовать звонки.
Просто пробегаемся по массиву звонков полученному из API и рисуем башни (используя кубики разных типов, чтобы визуально представить время ожидания звонка, время разговора и пропущенные звонки — красным цветом).

for call in json: 	offset+=3 	duration = get_sec(call['duration']) 	wait_duration = get_sec(call['wait_duration']) 	speak_duration = get_sec(call['speak_duration'])	 	 	if call['accepted'] == 1: 		drawCall(startX, startY, startZ-offset, wait_duration, 41) # wait_duration 		drawCall(startX, startY+wait_duration, startZ-offset, speak_duration, 133) #speak_duration 	else:		  		drawCall(startX, startY, startZ-offset, duration, 152) # duration 

На этом все, заходим в майнкрафт и любуемся трехмерной статистикой звонков.
Кстати, одно видео я записал сам, а второе — записал ребенок. Угадаете какое где? 🙂

Желтые кубики — время ожидания(гудки), зеленые — время разговора, красные — пропущенный звонок.

Исходник скрипта на Python

И обещанное в начале поста видео 3 часов разработки сжатое до 3 минут:

И напоследок вопрос, какие процессы/данные, по вашему, смотрелись бы лучше в трехмерном виде?
Навскидку:
— дашборд для отображения продакшн-серверов, в случае падения какого-либо из них — скрипт автоматически добавляет кубик-динамит и взрывает его 🙂 если Minecraft вывести на офисный монитор и настроить звук погромче — должно впечатлять 🙂
Еще идеи?

ссылка на оригинал статьи https://habrahabr.ru/post/278995/