Даже сравнительно простой мир, такой как 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, но старая переменная не исчизает, на нее могут ссылаться ранее определенные функции.
Изменяемые переменные вводятся ключевым словом mut (без let, как можно было бы предположить, зная что nu-shell написан на Rust). Но такие переменные не могут попадать в функции извне, что бывает неудобно.
Размышления об осмысленности этого ограничения.
Я прекрасно понимаю смысл ограничения доступности мутабельных переменных в языках, типа Rust, но для shell оно несколько странно. Shell - это скорее среда обитания, а не инструмент разработки сложных программ, и в ней полезно иметь доступ ко всем потрoхам, как принято в Lisp и Smalltalk. С другой стороны, возможно, это будет способствовать выработке полезных привычек.
Новые функции опредеяются остаточно просто:
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 таймаут по умолчанию бесконечный и процесс в таком случае зависает). Частично, все одно обрабатывается в более полном скрипте. Все-таки советую сначала попробовать упрощенные примеры из статьи прямо в командной строке, а потом уже разбираться с тонкостями.
azTotMD
вот тоже думаю из свой игры сделать окружение для reinforcement learning
potan Автор
Я думаю о своей игре, где игроки могли бы заключать смартконтракты. Одна из целей - проверить, сможет ли ИИ научиться в нее играть и программировать такие контракты, договариваясь с другими игроками, в том числе и живыми.
azTotMD
как это, заключать смартконтракты?
potan Автор
Несколько игроков догавариваются о некотором коде, который будет ограничивать их возможности, например запрещает одному игроку ловить рыбу в данном пруду, а второго обязывающего отдавать первому половину улова.
azTotMD
Вас видимо интересует именно эта тема со смартконтрактами.
Я понимаю, что пример утрированный, но здесь если первый игрок будет получать кучу рыбы, то ему особо смысла ловить и нет. Т.е. это не сказать, что сильный запрет. Другое дело, зачем это второму игроку, он ведь тоже какую-то выгоду должен получить. То что кто-то другой не будет там ловить тоже не сказать, что высокая награда.
С моей точки зрения, интереснее симулировать ситуации, когда взаимовыгода получается естественным образом, а не в результате каких-то процедурных ограничений.
potan Автор
В задуманной мной игре производительность пруда уменьшается, если там ловят рыбу несколько игроков. Баланс построен так, что каждому выгоднее ловить рыбу в лучшем пруде, но если часть игроков выберет менее хороший пруд, общий улов возрастет. Примерно как в "трагедии общин".