- PVSM.RU - https://www.pvsm.ru -

MarkovJunior — это вероятностный язык программирования, в котором программы являются сочетаниями правил перезаписи, а инференс выполняется при помощи распространения ограничений. MarkovJunior назван в честь математика Андрея Андреевича Маркова [1], придумавшего и исследовавшего то, что сейчас называется алгоритмами Маркова [2].


В своей базовой форме программа на MarkovJunior — это упорядоченный список правил перезаписи. Например, показанная ниже анимация MazeBacktracker [3] — это список из двух правил перезаписи:
RBB=GGR, или «заменить красный-чёрный-чёрный на зелёный-зелёный-красный».RGG=WWR, или «заменить красный-зелёный-зелёный на белый-белый красный».
На каждом этапе исполнения интерпретатор MJ находит в списке первое правило, которое соответствует элементам в сетке, находит все совпадения для этого правила и применяет его к случайному совпадению. В примере maze backtracker [4] (алгоритма поиска с возвратом) интерпретатор сначала применяет множество правил RBB=GGR. Но в конце концов зелёное избегающее себя блуждание заходит в тупик. Так как теперь у первого правила нет совпадений, интерпретатор применяет второе правило RGG=WWR, пока блуждание не выйдет из тупика. Затем он снова может применить первое правило, и так далее. Интерпретатор останавливается, когда не остаётся совпадений ни для одного из правил.


Вероятностный инференс в MarkovJunior позволяет накладывать ограничения на будущее состояние и генерировать только те прогоны, которые приводят к будущему с ограничениями. Например, инференс в правилах Sokoban {RWB=BRW RB=BR} заставляет группу агентов (красных) выстраивать ящики (белые) в указанные фигуры.
На основе этих идей мы создали множество вероятностных генераторов [5] подземелий, архитектуры, головоломок и интересных симуляций.

Дополнительные материалы:
Алгоритм Маркова для алфавита A — это упорядоченный список правил. Каждое правило — это строка вида x=y, где x и y — слова алфавита A, а некоторые правила могут быть помечены как правила останова. Применение алгоритма Маркова к слову w выполняется следующим образом:
x=y, где x является подстрокой w. Если таких правил нет, выполняем останов.x в w на y.
Например, рассмотрим следующий алгоритм Маркова для алфавита {0, 1, x} (ε — это пустое слово):
1=0x
x0=0xx
0=ε
Если мы применим его к строке 110, то получим следующую последовательность строк:
110 -> 0x10 -> 0x0x0 -> 00xxx0 -> 00xx0xx -> 00x0xxxx -> 000xxxxxx -> 00xxxxxx -> 0xxxxxx -> xxxxxx
В целом, этот алгоритм преобразует двоичное описание числа в его унарное описание.
Студент Маркова Вилнис Детловс [17] доказал [18], что для любой машины Тьюринга существует алгоритм Маркова, вычисляющий ту же функцию. Для сравнения: грамматики — это неупорядоченные множества правил перезаписи, а L-системы — это правила перезаписи, применяемые параллельно. Более интересные примеры алгоритмов Маркова можно найти в книге Маркова [19]; пример наибольшего общего делителя приведён в комментариях к оригиналу поста [20], а пример умножения [21] — в Википедии.
Как обобщить алгоритмы Маркова до многомерных операций? Во-первых, при нескольких измерениях не существует естественных способов вставки строки в другую строку, поэтому левые и правые части правил перезаписи должны иметь одинаковый размер. Во-вторых, отсутствуют естественные способы выбрать самое левое совпадение. Решить эту проблему можно следующими способами:
(exists) языка MarkovJunior.{forall} языка MarkovJunior.Мы теряем полноту по Тьюрингу, поскольку наша новая процедура не детерминирована, но практика показывает, что такая формулировка всё равно описывает огромный диапазон интересных случайных процессов.
Наверно, самая простая программа на MarkovJunior — это (B=W). Она содержит всего одно правило B=W. На каждом шаге эта программа преобразует случайный чёрный квадрат в белый.

(B=W)

(WB=WW)

(WBB=WAW)

(WBB=WAW)
Более интересна модель Growth [22] (WB=WW). На каждом шаге она заменяет чёрно-белую пару соседних клеток BW бело-белой парой WW. Иными словами, на каждом шаге она выбирает случайную чёрную ячейку, соседнюю с какой-то белой ячейкой, и раскрашивает её в белый цвет. Эта модель практически идентична модели роста Идена [23]: на каждом шаге обе модели выбирают в одном множестве чёрных ячеек. Они различаются только распределением вероятностей: равномерное распределение по чёрным ячейкам, соседним с белыми, отличается от равномерного распределения по парам соседних чёрных и белых ячеек.
Модель (WBB=WAW) одной строкой кода генерирует лабиринт! Сравните её с реализацией [24] на традиционном языке программирования. Любую модель MarkovJunior можно без изменений выполнять в любом количестве измерений. На последнем из показанных выше изображений показан конечный результат MazeGrowth [25] в 3D, отрендеренный в MagicaVoxel [26]. По умолчанию мы используем палитру PICO-8 [27]:

Модель (RBB=WWR) — это избегающее себя случайное блуждание [28]. Стоит отметить, что избегающие себя блуждания в 3D в среднем занимают больше шагов, чем в 2D. В целом, сравнение поведений схожих случайных процессов в разных измерениях — потрясающая тема. В классическом результате [29] Джорджа Полиа говорится, что случайное блуждание в 2D возвращается к своей исходной позиции с вероятностью единица, но в 3D это уже не так.

(RBB=WWR)

LoopErasedWalk

(RB=WR RW=WR)
В один rulenode (узел правил) можно поместить множество разных правил. Например, (RBB=WWR RBW=GWP PWG=PBU UWW=BBU UWP=BBR) — это случайное блуждание с удалёнными петлями [30]. Модель Trail (RB=WR RW=WR) генерирует красивые соединённые пещеры [31].
Модель (RBB=WWR R*W=W*R) известна как алгоритм генерации лабиринтов Олдоса-Бродера [32]. Символ подстановки * во вводе означает, что в квадрате может быть любой цвет. Символ подстановки в выводе означает, что после применения правила цвет не меняется. В среднем алгоритм Олдоса-Бродера требует гораздо больше шагов для генерации лабиринта, чем, например, MazeGrowth, но обладает удобным свойством, которого нет у MazeGrowth: вероятность генерации каждого из лабиринтов одинакова. Иными словами, MazeTrail — это алгоритм генерации лабиринтов без перекоса, то есть он сэмплирует лабиринты (или связные деревья) с равномерным распределением. Ещё более эффективный алгоритм генерации лабиринтов без перекосов — алгоритм Уилсона [33]. Сравните его реализацию [34] на MarkovJunior [35] с реализацией [36] на традиционном языке!
Можно поместить множество узлов правил в узел последовательности для выполнения один за другим. В модели River [37] мы сначала создаём стохастическую диаграмму Вороного [38] с двумя источниками и используем границу между образованными областями как основание для реки. Затем мы создаём ещё пару сидов Вороного, чтобы вырастить леса и параллельно растить траву от реки. В результате этого мы получаем случайные долины рек!


В Apartemazements [39] мы начинаем с узла WFC, а затем выполняем конструктивную постобработку при помощи узлов правил:
Более интересный способ объединения узлов заключается в объединении их в узел Маркова. Узлы Маркова существенно расширяют наши возможности, потому что они позволяют возвращаться к предыдущим узлам. Когда узел Маркова активен, интерпретатор находит его первый дочерний узел, имеющий совпадение, и применяет его. На следующем шаге он снова находит следующий совпадающий узел в списке, и так далее. Простейший пример использования узлов Маркова — это MazeBacktracker [3], объяснённый в одном из предыдущих разделов.


Один из моих примеров, мотивировавших меня на разработку MarkovJunior — это алгоритм генерации подземелий Боба Нистрома [41]. Он имеет следующий вид:
{PBB=**P}.(room.png).({GWW=**G}(GBW=*WG)).(GBG=*W* #5), чтобы получившееся подземелье имело петли. Подземелья без петель довольно скучны, потому что игроку приходится возвращаться через уже исследованные зоны.{BBB/BWB=BBB/BBB}.

Как в Рефале, узлы Маркова могут быть вложенными: зайдя в дочерний узел, мы игнорируем внешние узлы, пока не завершится дочерняя ветвь.
Вероятностный инференс в MarkovJunior позволяет накладывать ограничения на будущее состояние и генерировать только те прогоны, которые ведут к будущему с ограничениями. Иными словами, инференс соединяет два заданных состояния (или частично наблюдаемых состояния) цепочкой правил перезаписи.
Простейший пример использования инференса — это соединение двух точек путём. В модели блуждания с избеганием себя (RBB=WWR) мы можем наблюдать, как квадрат на сетке становится красным (R). Тогда интерпретатор генерирует только те блуждания, которые ведут к наблюдаемому квадрату. Мы можем настроить интерпретатор так, чтобы он стремился к цели более или менее строго, меняя параметр температуры. По умолчанию температура установлена на ноль.

Самая низкая температура

Холодная

Горячая

Максимально высокая
Также мы можем наблюдать за тем, как все нечётные квадраты сетки становятся белыми или красными. Тогда интерпретатор генерирует блуждания с избеганием себя, покрывающие всю сетку.


Мы можем задействовать инференс для любых правил перезаписи. Например, инференс для правил рисования лестниц [42] соединяет две точки путём-лестницей. Инференс для правила R**/**B=B**/**R генерирует пути, которыми может двигаться шахматный конь. Инференс модели CrossCountry [43] соединяет две точки путём, учитывающим стоимость перемещения по разным типам рельефа. Инференс для набора правил Sokoban {RB=BR RWB=BRW} решает головоломки Sokoban или даже головоломки Sokoban с несколькими агентами [44]!

Инференс в MarkovJunior выполняется однонаправленным (быстрое) или двунаправленным (медленное, но более мощное) распространением ограничений. Однонаправленное распространение ограничений для правил перезаписи можно эквивалентно описать на основе полей распространения правил, которые обобщают поля Дейкстры для произвольных правил перезаписи. Правила Дейкстры — популярная техника процедурной генерации на сетках (1 [45], 2 [46], 3 [47]). Они, в свою очередь, обобщают поля расстояний [48], используемые в компьютерной графике.
Если распространение ограничений завершается успешно, это необязательно значит, что целевое состояние достижимо. Но если распространение завершается неудачно, то мы точно знаем, что цель недостижима. Это позволяет отлавливать в Sokoban состояния, когда ящик толкают не к той стене, или когда блуждание с целью заполнения сетки разделяет сетку на две разделённые части. В дополнение к булевой эвристике стоит отслеживать минимальное количество шагов, необходимое для успешного завершения распространения ограничений. Эвристика с целочисленными значениями приемлема [49], и мы используем её в поиске A* для сэмплирования путей, созданных из правил перезаписи между двумя заданными состояниями.

На мой взгляд, левый уровень выглядит так, как будто генерировался процедурно! Он очень похож по ощущениям на мои процедурные воксельные головоломки [51]. Можем ли мы писать генераторы, создающие уровни, которые больше похожи на правый? Может показаться, что ИИ готов справиться с такой задачей. Но я возражу, что она очень похожа на классические задачи генетического программирования наподобие задачи газонокосилки Козы [52]. Например, возьмём простую задачу процедурной генерации по созданию гамильтоновых путей на сетке [53]. Даже для небольших размеров сетки наподобие 29x29 эта задача уже вычислительно затратна. Но действительно ли нам нужно на практике сэмплировать из всех возможных путей? Если мы дадим эту задачу человеку, то он, скорее всего, нарисует спираль или зигзаг, а это гораздо более запоминающиеся и логичные структуры, чем случайный гамильтонов путь, плюс их можно обобщить до любых размеров сетки. Подведём итог: мы можем попросить систему или найти случайный гамильтонов путь, или короткую программу, генерирующую гамильтоновы пути. В первом случае результат будет похож на левый уровень слайда, во втором случае — на правый уровень. Решение задачи синтеза программ создаст более запоминающиеся и логичные генераторы.
По сравнению с машинами Тьюринга и лямбда-исчислением, алгоритмы Маркова, вероятно, простейший и кратчайший способ формулирования строгого определения того, что же такое алгоритм.
Упражнение: доказать, что следующий алгоритм Маркова находит наибольший общий делитель двух чисел, записанных в унарном представлении. Например, если мы применим его к 111111*1111111111, то получим 11.
1a=a1
1*1=a*
1*=*b
b=1
a=c
c=1
*=ε (останов)
Быстрое сопоставление паттернов. Сэмплы интерпретатора MarkovJunior совпадают равномерно, но на каждом шаге он не сканирует всю сетку. Чтобы сохранить высокую скорость сопоставления паттернов, интерпретатор запоминает ранее найденные совпадения и ищет только вокруг изменившихся участков. Когда узел правил встречается в первый раз, интерпретатор MarkovJunior использует многомерную версию алгоритма Бойера-Мура [57].
Стохастическая релаксация. Узлы Маркова имеют очень удобные описания как границы дифференцируемых узлов. Рассмотрим неупорядоченное множество правил перезаписи, где каждому правилу r присвоен вес w(r). На каждом шаге интерпретатор находит все совпадения для всех правил и выбирает случайное совпадение согласно распределению Больцмана p(r) ~ exp(-w(r)/t). Находясь на границе замерзания t->0, мы получаем узел Маркова, упорядоченный по весам. В этой конструкции удобно то, что для любой t>0 и для типичной функции отсчёта среднее значение отсчёта в многократных прогонах будет непрерывной (и гладкой для практических применений) функцией весов. Это значит, что можно найти оптимальные веса при помощи градиентного спуска, а затем заморозить систему, чтобы получить готовую дискретную программу.
Прочитайте это эссе [58] Бориса Кушнера [59] об А.А. Маркове и его работах по конструктивной математике.
Главный использованный труд:
Связанные работы:
Источники примеров:
Воксельные сцены отрендерены в MagicaVoxel [26], разработанной ephtracy [110]. Особая благодарность Брайану Баклью [111] за демонстрацию мощи полей Дейкстры на примере генерации уровней roguelike и Кевину Шапелье [112] за множество хороших подсказок. В GUI использован шрифт Tamzen [113].
Интерпретатор MarkovJunior — это консольное приложение, зависящее только от стандартной библиотеки. Установите .NET Core [114] для Windows, Linux или macOS и выполните следующую команду:
dotnet run --configuration Release MarkovJunior.csproj
Также можно скачать и запустить последний релиз [115] для Windows.
Сгенерированные результаты помещаются в папку output. Для изменения параметров моделей отредактируйте models.xml. Файлы .vox открываются при помощи MagicaVoxel [26].
Автор:
PatientZero
Источник [121]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/383482
Ссылки в тексте:
[1] Андрея Андреевича Маркова: https://en.wikipedia.org/wiki/Andrey_Markov,_Jr.
[2] алгоритмами Маркова: https://en.wikipedia.org/wiki/Markov_algorithm
[3] MazeBacktracker: https://github.com/mxgmn/MarkovJunior/blob/main/models/MazeBacktracker.xml
[4] maze backtracker: https://en.wikipedia.org/wiki/Maze_generation_algorithm#Depth-first_search
[5] множество вероятностных генераторов: https://github.com/mxgmn/MarkovJunior/blob/main/models
[6] обзор Xml-синтаксиса: https://github.com/mxgmn/MarkovJunior/blob/main/syntax.md
[7] ModernHouse: https://github.com/mxgmn/Blog/blob/master/ModernHouse.md
[8] SeaVilla: https://github.com/mxgmn/Blog/blob/master/SeaVilla.md
[9] Apartemazements: https://github.com/mxgmn/Blog/blob/master/Apartemazements.md
[10] CarmaTower: https://github.com/mxgmn/Blog/blob/master/CarmaTower.md
[11] Escheresque: https://github.com/mxgmn/Blog/blob/master/Escheresque.md
[12] PillarsOfEternity: https://github.com/mxgmn/Blog/blob/master/PillarsOfEternity.md
[13] Surface: https://github.com/mxgmn/Blog/blob/master/RandomSurface.md
[14] Knots: https://twitter.com/ExUtumno/status/895688856304992256
[15] технические заметки: https://gist.github.com/dogles/a926ab890552cc7e45400a930398449d
[16] документация кода: https://github.com/kaya3/MarkovJunior-docs
[17] Вилнис Детловс: https://lv.wikipedia.org/wiki/Vilnis_Detlovs
[18] доказал: http://www.mathnet.ru/php/archive.phtml?wshow=paper&jrnid=tm&paperid=1293
[19] книге Маркова: http://www.mathnet.ru/links/1543dd6e347b444e6f3e108fafaf9f2a/tm1178.pdf
[20] комментариях к оригиналу поста: https://github.com/mxgmn/test#comments
[21] пример умножения: https://en.wikipedia.org/wiki/Markov_algorithm#Description
[22] Growth: https://github.com/mxgmn/MarkovJunior/blob/main/models/Growth.xml
[23] модели роста Идена: http://digitalassets.lib.berkeley.edu/math/ucb/text/math_s4_v4_article-15.pdf
[24] реализацией: https://bl.ocks.org/mbostock/70a28267db0354261476
[25] MazeGrowth: https://github.com/mxgmn/MarkovJunior/blob/main/models/MazeGrowth.xml
[26] MagicaVoxel: https://ephtracy.github.io/
[27] палитру PICO-8: https://github.com/mxgmn/MarkovJunior/blob/main/resources/palette.xml
[28] избегающее себя случайное блуждание: https://en.wikipedia.org/wiki/Self-avoiding_walk
[29] классическом результате: https://sites.math.washington.edu/~morrow/336_19/papers19/Legrand.pdf
[30] случайное блуждание с удалёнными петлями: https://en.wikipedia.org/wiki/Loop-erased_random_walk
[31] красивые соединённые пещеры: https://blog.jrheard.com/procedural-dungeon-generation-drunkards-walk-in-clojurescript
[32] алгоритм генерации лабиринтов Олдоса-Бродера: http://weblog.jamisbuck.org/2011/1/17/maze-generation-aldous-broder-algorithm
[33] алгоритм Уилсона: http://web.stanford.edu/~yuvalwig/math/teaching/UniformSpanningTrees.pdf
[34] реализацию: https://github.com/mxgmn/MarkovJunior/blob/main/models/Wilson.xml
[35] MarkovJunior: https://github.com/mxgmn/MarkovJunior/blob/main/images/Wilson.gif
[36] реализацией: https://bl.ocks.org/mbostock/11357811
[37] River: https://github.com/mxgmn/MarkovJunior/blob/main/models/River.xml
[38] диаграмму Вороного: https://github.com/mxgmn/MarkovJunior/blob/main/models/Voronoi.xml
[39] Apartemazements: https://github.com/mxgmn/MarkovJunior/blob/main/models/Apartemazements.xml
[40] Paths: https://www.pvsm.ru/mxgmn/MarkovJunior/blob/main/resources/tilesets/Paths.xml
[41] алгоритм генерации подземелий Боба Нистрома: https://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/
[42] правил рисования лестниц: https://github.com//mxgmn/MarkovJunior/blob/main/models/StairsPath.xml
[43] CrossCountry: https://github.com//mxgmn/MarkovJunior/blob/main/models/CrossCountry.xml
[44] головоломки Sokoban с несколькими агентами: https://github.com//mxgmn/MarkovJunior/blob/main/images/multisokoban.gif
[45] 1: https://groups.google.com/forum/#!topic/rec.games.roguelike.development/6yNIuhSerpM
[46] 2: http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps
[47] 3: http://www.roguebasin.com/index.php?title=Dijkstra_Maps_Visualized
[48] поля расстояний: https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
[49] приемлема: https://en.wikipedia.org/wiki/Admissible_heuristic
[50] «Level Design in Impossible Geometry»: https://youtu.be/ed2zmmcEryw?t=1298
[51] процедурные воксельные головоломки: https://twitter.com/ExUtumno/status/971031987304763393
[52] задачи газонокосилки Козы: https://pdfs.semanticscholar.org/555e/13cc2dd246e3d63ceb00590605f3ff59593d.pdf
[53] созданию гамильтоновых путей на сетке: https://github.com/mxgmn/MarkovJunior/blob/main/models/CompleteSAW.xml
[54] Abstraction and Reasoning Challenge: https://www.kaggle.com/c/abstraction-and-reasoning-challenge
[55] ModernHouse: https://twitter.com/ExUtumno/status/1141354217774428160
[56] домов в Sims 2: https://www.thesimsresource.com/downloads/browse/category/sims2-lots/featured/1/search/modern%20house/
[57] алгоритма Бойера-Мура: https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm
[58] эссе: https://www.jstor.org/stable/27641983
[59] Бориса Кушнера: https://en.wikipedia.org/wiki/Boris_Kushner_(mathematician)
[60] The Theory of Algorithms: http://www.mathnet.ru/php/archive.phtml?wshow=paper&jrnid=tm&paperid=1117&option_lang=eng
[61] книгу: http://www.mathnet.ru/php/archive.phtml?wshow=paper&jrnid=tm&paperid=1178&option_lang=eng
[62] Imagegram: https://zaratustra.itch.io/imagegram
[63] REFAL language: http://fprog.ru/2011/issue7/practice-fp-7-screen.pdf
[64] Weighted Random Sampling: https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf
[65] Model Synthesis: http://graphics.stanford.edu/~pmerrell/thesis.pdf
[66] Wave Function Collapse Algorithm: https://github.com/mxgmn/WaveFunctionCollapse
[67] ConvChain Algorithm: https://github.com/mxgmn/ConvChain
[68] распространение ограничений: https://en.wikipedia.org/wiki/Local_consistency
[69] алгоритмы решения ограничений: https://www.cs.ubc.ca/~mack/Publications/AI77.pdf
[70] обход графов: https://en.wikipedia.org/wiki/Graph_traversal
[71] поиск A*: https://www.cs.auckland.ac.nz/courses/compsci709s2c/resources/Mike.d/astarNilsson.pdf
[72] Probabilistic Programming for Procedural Modeling and Design: https://dritchie.github.io/pdf/thesis.pdf
[73] From Execution Traces to Specialized Inference: https://stacks.stanford.edu/file/druid:kq822ym0815/et2si-reduced-opt-augmented.pdf
[74] BasicKeys: https://github.com/mxgmn/MarkovJunior/blob/main/models/BasicKeys.xml
[75] Keys: https://github.com/mxgmn/MarkovJunior/blob/main/models/Keys.xml
[76] Engineering Emergence: Applied Theory for Game Design: https://www.illc.uva.nl/Research/Publications/Dissertations/DS-2012-12.text.pdf
[77] Automatic Generation of Dungeons for Computer Games: https://pdfs.semanticscholar.org/2502/0f8d955aee07b7dd49a3ec23b1f2a8cf1d06.pdf
[78] SeaVilla: https://github.com/mxgmn/MarkovJunior/blob/main/models/SeaVilla.xml
[79] CarmaTower: https://github.com/mxgmn/MarkovJunior/blob/main/models/CarmaTower.xml
[80] воксельной сцены: https://twitter.com/Sir_carma/status/851883489628704768
[81] NystromDungeon: https://github.com/mxgmn/MarkovJunior/blob/main/models/NystromDungeon.xml
[82] HamiltonianPath: https://github.com/mxgmn/MarkovJunior/blob/main/models/HamiltonianPath.xml
[83] этой: http://aip.scitation.org/doi/pdf/10.1063/1.443937
[84] реализацией: http://clisby.net/projects/hamiltonian_path/hamiltonian_path_v1.html
[85] DungeonGrowth: https://github.com/mxgmn/MarkovJunior/blob/main/models/DungeonGrowth.xml
[86] поста на r/proceduralgeneration: https://old.reddit.com/r/proceduralgeneration/comments/3pa8a1/my_take_at_a_roguelike_level_generator_ft/
[87] алгоритма Уилсона: https://en.wikipedia.org/wiki/Loop-erased_random_walk#Uniform_spanning_tree
[88] BernoulliPercolation: https://github.com/mxgmn/MarkovJunior/blob/main/models/BernoulliPercolation.xml
[89] теории перколяции: https://en.wikipedia.org/wiki/Percolation_theory
[90] NestedGrowth: https://github.com/mxgmn/MarkovJunior/blob/main/models/NestedGrowth.xml
[91] SmoothTrail: https://github.com/mxgmn/MarkovJunior/blob/main/models/SmoothTrail.xml
[92] твита 128_mhz: https://twitter.com/128_mhz/status/953847394403205120
[93] SokobanLevel1: https://github.com/mxgmn/MarkovJunior/blob/main/models/SokobanLevel1.xml
[94] SokobanLevel2: https://www.pvsm.ru/mxgmn/MarkovJunior/blob/main/models/SokobanLevel2.xml
[95] уровень 452: https://www.sokobanonline.com/play/web-archive/razorflame/ionic-catalysts-xi/58022_ionic-catalysts-xi-452
[96] RainbowGrowth: https://github.com/mxgmn/MarkovJunior/blob/main/models/RainbowGrowth.xml
[97] предложена: https://github.com/mxgmn/MarkovJunior/discussions/25
[98] mure: https://github.com/mure
[99] MultiHeadedWalk: https://github.com/mxgmn/MarkovJunior/blob/main/models/MultiHeadedWalk.xml
[100] MultiHeadedDungeon: https://github.com/mxgmn/MarkovJunior/blob/main/models/MultiHeadedDungeon.xml
[101] MultiHeadedWalkDungeon: https://github.com/mxgmn/MarkovJunior/blob/main/models/MultiHeadedWalkDungeon.xml
[102] основаны: https://github.com/mxgmn/MarkovJunior/discussions/38
[103] Ильи Кудрицкого: https://github.com/Inferdy
[104] Island: https://github.com/mxgmn/MarkovJunior/blob/main/models/Island.xml
[105] Гийомом Фьетом: https://github.com/woldendans/MJ-simple-island
[106] LostCity: https://github.com/mxgmn/MarkovJunior/blob/main/models/LostCity.xml
[107] Forest: https://github.com/mxgmn/MarkovJunior/blob/main/models/Forest.xml
[108] Texture: https://github.com/mxgmn/MarkovJunior/blob/main/models/Texture.xml
[109] Эндрю Кэя: https://github.com/kaya3/pattern-match-2d
[110] ephtracy: https://github.com/ephtracy
[111] Брайану Баклью: https://github.com/unormal
[112] Кевину Шапелье: https://github.com/kchapelier
[113] Tamzen: https://github.com/sunaku/tamzen-font
[114] .NET Core: https://dotnet.microsoft.com/download
[115] релиз: https://github.com/mxgmn/MarkovJunior/releases
[116] версию MarkovJunior на TypeScript: https://github.com/Yuu6883/MarkovJuniorWeb
[117] выполняется в вебе: https://yuu6883.github.io/MarkovJuniorWeb/
[118] портирует: https://github.com/aseaday/MarkovJunior.js
[119] Дэн Оглс: https://github.com/dogles
[120] MJr: https://github.com/kaya3/MJr
[121] Источник: https://habr.com/ru/post/721314/?utm_source=habrahabr&utm_medium=rss&utm_campaign=721314
Нажмите здесь для печати.