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

в 12:52, , рубрики: calltracking, madness, minecraft, интерфейсы, телефония, Хакатоны, метки:

Пару месяцев назад назад я показал детям Minecraft, а чуть позже — купил им книгу по программированию в MineCraft. Правда, детям купил, чес-слово. Ну сам взял полистить, ну написал пару скриптов.
И на этом история и закочнилась бы, но на днях мне довелось поучаствовать в хакатоне одного calltracking сервиса. Для тех кто не в курсе, calltracking эта такая штука, которая предоставляет статистику звонков. И что важно для нашей истории — эта статистика доступна по API.
В этот момент отдельные части сложились в цельную картину и я подумал — о! статистика звонков в Minecrfat :)
Calltracking в Minecraft или как быстро сделать трехмерный UI - 1
Ну что ж, формат хакатона располагает к безумным идеям, а эта идея показалась мне достаточно безумной, чтобы быть реализованной :)
А если серьезно — то кто сказал что интерфейсы должны быть двумерными?
И кто сказал что трехмерный интерфейс это долго и сложно?
Вся затея у меня заняла 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 вывести на офисный монитор и настроить звук погромче — должно впечатлять :)
Еще идеи?

Автор: n0_quarter

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js