Симулятор микромыши mms: настройка, протокол и где брать лабиринты

Я собираю микромышь — маленького автономного робота, который сам находит центр лабиринта 16×16. Детали заказаны и едут недели три, а руки чешутся уже сейчас. К счастью, самая интересная часть проекта — алгоритм поиска — вообще не требует железа. Нужен симулятор.
На картинке выше — как раз он в работе: мышь (зелёная клетка со стрелкой) ползёт по лабиринту, синим закрашено то, что она уже разведала, а числа в клетках — расстояния до центра, которые она насчитала сама. Справа виден её отладочный вывод. Как всё это завести — дальше.
Что такое mms#
mms — симулятор соревнований Micromouse. Он рисует лабиринт, катает по нему мышь и разговаривает с вашим алгоритмом через обычные stdin и stdout. Из этого следует приятное: алгоритм можно писать на чём угодно — на Python, C++, Rust, хоть на shell-скрипте. Симулятору всё равно, он просто запускает вашу программу как подпроцесс и обменивается с ней строками.
Ещё он умеет то, чего от железа не добьёшься: мгновенно перезапускать прогон, крутить скорость, подсвечивать клетки цветом и писать в них текст. Последнее бесценно для отладки — можно прямо в клетках рисовать значения, которые насчитал ваш алгоритм, и глазами видеть, где он ошибается.
Установка#
Никакой. Под Linux в релизах лежит готовый AppImage — самодостаточный образ, внутри которого уже упакованы Qt, плагины и сам движок:
$ ls -la ~/Downloads/mms-x86_64.AppImage
-rwxr-xr-x 1 user user 54793408 mms-x86_64.AppImage
Права на исполнение обычно уже стоят, но если нет:
chmod +x mms-x86_64.AppImage
./mms-x86_64.AppImage
Единственное требование — в системе должен быть FUSE 2, иначе AppImage не сможет себя смонтировать. Проверить:
$ ldconfig -p | grep libfuse.so.2
libfuse.so.2 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libfuse.so.2
Если пусто — sudo apt install libfuse2 на Debian и производных.
При запуске в консоль сыпется пара строк вида
libpng warning: iCCP: known incorrect sRGB profile — это про цветовой профиль
в иконках, на работу не влияет, игнорируйте.
Файл стоит переложить из «Загрузок» куда-нибудь в постоянное место — скажем,
в ~/bin, — чтобы не снести случайно при уборке.
Подключаем свой алгоритм#
В правом верхнем углу есть панель Config, а в ней строка Mouse. Жмём в ней
кнопку + и заполняем поля:
| Поле | Значение |
|---|---|
| Name | micromouse |
| Directory | путь к каталогу с вашим кодом |
| Build command | оставляем пустым |
| Run command | python3 -u main.py |
Обратите внимание на флаг -u. Он обязателен, и это первая ловушка, на
которой легко потерять вечер. Без него Python буферизует stdout: ваша программа
честно отправляет команду, но та оседает в буфере и до симулятора не доходит.
mms послушно ждёт ответа, программа ждёт ответа от mms — и всё замирает намертво,
без единого сообщения об ошибке. Выглядит так, будто симулятор сломан.
Альтернатива, если не хочется зависеть от настроек запуска, — выставить
PYTHONUNBUFFERED=1 или вызывать print(..., flush=True). Я делаю и то и другое:
флаг в команде запуска и flush=True в самой функции отправки.

Лабиринт загружен, но алгоритм ещё не запускали. Числа в клетках рисует сам симулятор — это истинное расстояние до центра, и в четырёх центральных клетках стоят нули. Удобная шпаргалка: видно, что ваш алгоритм должен насчитать в идеале. Справа вверху — выбор лабиринта и алгоритма.
Кнопка Build нужна только компилируемым языкам — для Python жмём сразу Run.
Настройки лежат в ~/.config/mackorone/mms.conf — обычный ini. Если хочется
завести алгоритм не мышкой, а из скрипта, секция выглядит так:
[mouseAlgos]
1\name=micromouse
1\directory=/path/to/your/sim
1\buildCommand=
1\runCommand=python3 -u main.py
size=1
Только правьте файл при закрытом симуляторе: Qt перезаписывает настройки при выходе и затрёт ваши изменения.
Как устроен протокол#
Протокол текстовый и предельно простой: пишете строку в stdout — получаете строку в stdin. Обёртка на Python умещается в несколько функций:
import sys
def _send(command: str) -> None:
print(command, flush=True)
def _ask(command: str) -> str:
print(command, flush=True)
return sys.stdin.readline().strip()
def wall_front() -> bool:
return _ask("wallFront") == "true"
def move_forward() -> None:
if _ask("moveForward") == "crash":
raise RuntimeError("crash")
Команды делятся на три группы:
- запросы о лабиринте —
mazeWidth,mazeHeight; - датчики —
wallFront,wallLeft,wallRight, отвечаютtrueилиfalse; - движение —
moveForward,turnLeft,turnRight, отвечаютackилиcrash; - отрисовка —
setColor,setText,clearAllColor,clearAllText.
Две ловушки, которые стоит знать заранее#
Первая: stdout занят протоколом. Обычный print() для отладки немедленно
ломает связь с симулятором — ваша отладочная строка приходит туда, где mms ждёт
команду. Отладочный вывод надо писать в stderr:
def log(*parts: object) -> None:
print(*parts, file=sys.stderr, flush=True)
Всё, что программа пишет в stderr, mms показывает на вкладке Run Output —
это видно на картинке в начале поста, там строка про размер лабиринта и целевые
клетки. Рядом есть Build Output для вывода сборки и Simulator Logs, куда
симулятор пишет уже своё.
Вторая, куда неприятнее: ответы обязательно надо вычитывать. Команды движения
отвечают ack, и если этот ответ не прочитать, он останется в буфере. Следующий
запрос датчика получит чужой ответ — протокол разъедется на одну строку и дальше
уже не сойдётся. Команды отрисовки, наоборот, не отвечают ничего, и читать ответ
после них нельзя — программа повиснет.
Коварство в том, что проявляется рассинхрон далеко от места, где возник, и выглядит как «мышь внезапно сошла с ума»: едет в стену, поворачивает не туда. Искать причину в алгоритме можно долго, потому что алгоритм-то исправен.
Лечится это тестом, который запускает ваш main.py подпроцессом и сам играет
роль симулятора, отвечая на команды и проверяя, что программа спрашивает ровно
то, что должна. У меня это единственный тест, который вообще ловит такой класс
ошибок, — обычные юнит-тесты на алгоритм проходят при полностью разъехавшемся
протоколе.
А лабиринтов-то нет#
Тут я и наткнулся на главный сюрприз. Скачиваете mms, запускаете, жмёте выбор лабиринта — и выбирать нечего. В релизной сборке лабиринтов нет вообще.
Я распаковал AppImage целиком, чтобы убедиться:
$ ./mms-x86_64.AppImage --appimage-extract
$ ls squashfs-root
32x32.png AppRun doc lib mms mms.desktop plugins qt.conf translations
Ни каталога с лабиринтами, ни единого файла .num или .maz. Только движок.
В репозитории проекта лежат шесть примеров (src/resources/mazes/), но в сборку
они не попадают, да и это именно примеры — example1…example5 и пустой
blank, а не соревновательные трассы.
Где брать настоящие#
README самого mms отсылает к коллекции micromouseonline/mazefiles, и это правильный адрес. Там больше пятисот лабиринтов с реальных соревнований, собранных за много лет:
mazefiles/
classic/ 522 соревновательные 16×16
halfsize/ 42 half-size 32×32
training/ 16 маленькие 8×8 и 10×5
Вся коллекция весит около полутора мегабайт — это простые текстовые файлы.
Начинать удобнее не с classic/, а с training/. Лабиринт 8×8 мышь проходит
за секунды, ошибки видно сразу, и цикл «поправил — посмотрел» получается
коротким. На полноразмерных 16×16 один прогон уже заметно дольше, а разглядеть
в нём момент, где всё пошло не так, труднее.
Форматы#
mms понимает два формата, и оба — текстовые.
Map — картинка лабиринта символами. Столбы, горизонтальные стены ---,
вертикальные |:
o---o---o---o
| |
o o---o o
| | | |
o---o---o---o
Клетка занимает 4 символа по горизонтали и 2 по вертикали, поэтому классический 16×16 — это ровно 65 символов в ширину и 33 строки. Полезная проверка: если файл не такого размера, он либо не 16×16, либо битый.
Важная деталь: для mms стеной считается любой символ кроме пробела, и
проверяются только позиции стен, а не центры клеток. Благодаря этому файлы из
mazefiles читаются напрямую, хотя используют o вместо + для столбов.
А ещё в них в центрах клеток стоят метки S (старт) и G (цель) — симулятор их
просто не видит, они нужны человеку и сторонним инструментам.
Num — по строке на клетку, шесть чисел:
X Y N E S W
Координаты клетки и по единице на каждую сторону: 1 если стена есть, 0 если
нет. Формат менее наглядный, зато его тривиально генерировать из кода.
Оговорка, которую стоит прочитать#
Автор коллекции честно предупреждает в README: среди файлов есть ошибки и дубликаты, это не эталонный список. Часть лабиринтов переиспользовалась разными соревнованиями, поэтому одна и та же планировка встречается под разными именами.
Практический вывод простой. Если мышь сходит с ума ровно на одном файле, а на остальных ведёт себя прилично — подозревайте сначала файл, а уже потом свой алгоритм. Я записал это себе в заметки к проекту крупными буквами, потому что иначе на такой ерунде теряется вечер.
Что в итоге#
Порог входа оказался почти нулевым: скачали один файл, прописали три поля, принесли лабиринты. Дальше можно неделями отлаживать поиск пути, пока посылка с моторами едет через полмира.
И это, по-моему, главная ценность симулятора. Когда железо наконец приедет, отлаживать придётся моторы, энкодеры и датчики — то есть вещи, которые ломаются физически и чинятся паяльником. Тащить туда ещё и непроверенный алгоритм — верный способ не разобраться ни в чём.
Ссылки#
- mackorone/mms — сам симулятор, там же описание протокола и форматов
- micromouseonline/mazefiles — коллекция лабиринтов
- Micromouse Book Питера Харрисона — по сути учебник по теме; раздел про решение лабиринта стоит прочитать до того, как писать свой алгоритм