Даже сравнительно простой мир, такой как ArtifactoryMMO, приподносит не мало неожиданностей. Хотя есть много примеров кода для управления этим миром из Javascript и Python, я выбрал более серьезный язык, расчитывая прикрутить туда какие-нибудь интересные алгоритмы машинного обучения. Но все равно слишком часто, по крайней мере при отладке, приходится отдавать отдельные команды и анализировать что получилось вручную. Несмотря на прекрасный REPL в Julia, один из лучших, что мне доводилось использовать, и для отладки своего кода, и просто как калькулятор, здесь это оказалось не очень удобно. Конечно, есть curl и jq, но по эргономичности он тоже не идеален. Не curl-ом единым, удобный HTTP-клиент встроен, например, в PowerShell. Но мне захотелось чего-то нового и прогрессивного, и я решил посмотреть Nu. Эта статья предназначена, чтобы привлечь к этому shell любителей MMO-игр, и заинтересовать MMO-играми пользователей nu-shell, а если повезет, заинтересовать обоими темами тех, кто раньше про них и не знал.
Простой HTTP-запрос в Nu делается практически также, как и с помощью curl. Давайте посмотрим с чем нам предстоит бороться.http gethttps://api.artifactsmmo.com/monsters
В чем-то представления ответа более человекочитаемое, чем тот жеcurl https://api.artifactsmmo.com/monsters | jq
Но и программно обрабатывать результат тоже проще, например так:(http get https://api.artifactsmmo.com/monsters).data | where type == boss | select name level drops
Можно записать результат запроса в переменную:
let items = (http get https://api.artifactsmmo.com/items).data
$items | where subtype == food | select name level
let вводит новую неизменяемую переменную в область видимости. Ее можно скрыть другим let, но старая переменная не исчизает, на нее могут ссылаться ранее определенные функции. Я прекрасно понимаю смысл ограничения доступности мутабельных переменных в языках, типа Rust, но для shell оно несколько странно. Shell - это скорее среда обитания, а не инструмент разработки сложных программ, и в ней полезно иметь доступ ко всем потрoхам, как принято в Lisp и Smalltalk. С другой стороны, возможно, это будет способствовать выработке полезных привычек.
Изменяемые переменные вводятся ключевым словом mut (без let, как можно было бы предположить, зная что nu-shell написан на Rust). Но такие переменные не могут попадать в функции извне, что бывает неудобно.
Размышления об осмысленности этого ограничения.
Новые функции опредеяются остаточно просто:
def "mmo get" [query] {
http get $"https://api.artifactsmmo.com/($query)"
}
Имя функции может состаять из нескольких слов, что добавляет читабельности и удобно при использовании автодополнения (completion).
Более сложная функция, загружающая данные, разбитые на несколько страниц.
def "mmo load" [query] {
let r = mmo get $query
mut data = $r.data
let pages = try { $r.pages } catch { 0 }
if $pages > 1 {
for page in 2..$pages {
let r = mmo get $"($query)?page=($page)"
$data = $data | append $r.data
}
}
$data
}
Для получения доступа к привязанным к аккаунту функциям ArtifactsMMO требуется токен. Мы его прочитаем из файла artifactsmmo.token. Конечно, сначала надо было зарегистрироваться в игре, получить токен и записать его в этот файл.
let token = open artifactsmmo.token | lines | get 0
open, неожидано, читает весь файл, а не возвращает дескриптор или канал для чтения.
Получим список своих персонажей. Его будет удобно сохранить в глобальной переменной и обновлять по мере необходимости. С простой пременной так сделать нельзя, но есть модифицируемый контейнер env, позволяющий обойти это ограничение. В нем хранятся так называемые "переменные среды", которые являются строками, но можно туда поместить и данные произвольной структуры.
$env.CHARACTERS = (http get --headers { Authorization: $"Bearer ($token)" } https://api.artifactsmmo.com/my/characters).data
Теперь можно заставить этих персонажей работать...
def act [name: string, action: string, data] {
http post --headers { Authorization: $"Bearer ($token)", Content-Type: application/json } --content-type application/json $"https://api.artifactsmmo.com/my/($name)/action/($action)" $data
}
Теперь можно отдать команду
act ИмяПерсонажа move {x:0, y:1}
act ИмяПерсонажа fight {}
Сложно, приходится помнить не только имена своих персонажей, но и названия команд. К счастью, мы можем без больших усиний настроить автодополнение. Для этого достаточно к типу параметра дописать через "@" имя функции, которая возвращает допустимые варианты. Возможны более сложные реализации, учитывающие контекст, но мы ограничимся простой функцией без парамтеров.
def characters [] { $env.CHARACTERS | get name }
def actions [] {
[move, fight, rest, transition, equip, unequip, use, gathering,
crafting, bank/deposit/gold, bank/deposit/item, bank/withdraw/item,
bank/withdraw/gold, bank/buy_expansion, npc/buy, npc/sell,
recycling, grandexchange/buy, grandexchange/sell,
grandexchange/cancel, task/complete, task/exchange, task/new,
task/trade, task/cancel, give/gold, give/item, delete]
}
def act [name: string@characters, action: string@actions, data] {
http post --headers { Authorization: $"Bearer ($token)", Content-Type: application/json } --content-type application/json $"https://api.artifactsmmo.com/my/($name)/action/($action)" $data
}
Теперь можно управлять этим игровым миром не выходя из nu!
На самом деле все немного сложнее. Запросы могут выдавать неожиданные ошибки, например если пытаешься переместить персонажа на место, где он уже находится или если персонаж находится в состоянии cooldown после прошлого действия. Играя, нельзя забывать и про реальный мир: сервер иногда пятисотит, сеть иногда отваливается (а в nu таймаут по умолчанию бесконечный и процесс в таком случае зависает). Частично, все одно обрабатывается в более полном скрипте. Все-таки советую сначала попробовать упрощенные примеры из статьи прямо в командной строке, а потом уже разбираться с тонкостями.
Автор: potan
