Мышь с динамитом

23

Ты мышь. Все ваши друзья-мыши были захвачены, находятся в бессознательном состоянии и попали в лабиринт, в котором есть только один вход / выход. У вас получилась отличная карта лабиринта, так что вы можете найти решение, чтобы спешить и отнести их всех в безопасное место. Тем не менее, лабиринт охраняется системой безопасности, которая будет вызывать предупреждение, если будет достигнут порог 1000, в результате чего вы будете захвачены и провалите свою спасательную миссию.

Из ваших предыдущих исследований лабиринта каждый квадрат, который вы делаете (т.е. каждое горизонтальное или вертикальное движение - мыши не могут двигаться по диагонали ) добавляет 1счетчика системы безопасности. Однако, если вы несете вес (блок динамита или мышь без сознания), он вместо этого добавляет, 2поскольку обнаруживает дополнительное давление. Квадрат входа / выхода не имеет этой системы безопасности, и поэтому не добавляет к прилавку.

У вас есть неограниченный запас динамита, который вы принесли на вход, так что вы можете просто взорвать все стены, чтобы освободить своих друзей. Но вы должны быть осторожны с этим, так как каждый взрыв добавляет 50к счетчику от сотрясения давления. Кроме того, вы можете носить только одну вещь за один раз, одну мышь или один блок динамита. Поскольку каждый блок динамита может взорвать только одну стену, это означает, что если в стенке несколько стен, вам нужно с пустыми руками вернуться ко входу, чтобы захватить больше.

Проработанный пример

Предположим, наш лабиринт выглядит следующим образом:

######
#M# E#
######

Я буду использовать cдля счетчика. Мы начинаем с Entrance, передвигаясь на одну клетку влево, неся динамит c=2. Мы взрываем динамит, чтобы взорвать стену c=52. Мы перемещаем два квадрата влево, с пустыми руками, чтобы получить c=54, и теперь мы стоим на квадрате мыши. Мы подбираем нашего друга и перемещаем 3 квадрата обратно к Eкситу, но последний квадрат не считается, так как у него нет датчиков, так что это всего 2 квадрата с чем-то на нашей спине. Это означает, что когда мы дойдем до выхода с последней мышью c=58, которой меньше 1000, и, следовательно, миссия выполнена успешно.

Вызов

Учитывая входной лабиринт, укажите, можете ли вы, герой мыши, успешно спасти всех пойманных в ловушку мышей в рамках ограничений, описанных выше, или же миссия провалена.

вход

  • Двухмерный лабиринт в любом приемлемом формате (многострочная строка, массив строк и т. Д.).
  • Для этой задачи я буду использовать #как внутренние, так и внешние стены, Mдля друзей-мышки и Eдля входа.
  • Вход никогда не будет непосредственно примыкать к внутренней стене (всегда будет хотя бы одно пространство, в котором можно свободно перемещаться).
  • Вы можете заменить любые печатные символы ASCII, какие пожелаете, при условии их согласованности. Это действительно позволяет использовать два различные символ для внутренних стен против наружных стен, так долго , как вы поддерживать согласованность (например, если вы решили использовать @для внутренних стен вместо и оставить #на внешний вид, каждая внутренняя стенка должна быть @и каждая внешняя стена #).
  • Лабиринт всегда будет полностью ограничен стенами, но не обязательно будет прямоугольным. При желании вы можете предположить, что лабиринт дополнен пробелами для прямоугольного ввода (необязательно).
  • Лабиринт может иметь разделы, которые недоступны без динамита.
  • Вы не можете динамить внешние стены лабиринта.

Выход

Truthy / falsey значение. Правда для «Да, мышь может спасти любую другую мышь» или Falsey для «Нет, сигнализация будет отключена».

Правила

  • Либо полная программа или функция приемлемы.
  • Стандартные лазейки запрещены.
  • Это поэтому применяются все обычные правила игры в гольф, и выигрывает самый короткий код (в байтах).

Примеры

Правдивые примеры, разделенные пустыми строками.

#####
#M E#
#####

######
#M# E#
######

########
#E  # M#
#   #  #
#   #  #
#      #
########

#############################
#    ##      #       #      #
#  M ## M    #       #      #
#    ##      #   M   #    E #
#M   ##      #       #      #
#############################

###############
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMM MM#
#MMMMMMMMMMMME#
###############

Ложные примеры, разделенные пустыми строками

#############################
#M   ##      ##      ##     #
#  M ## M    ##      ##     #
#    ##      ##  M   ##   E #
#M   ##      ##      ##     #
#############################
#############################
                     ########
                     ########
                     #   #  #
                     # M # M#
                     ########

              #####
              # M #
              #####
              #####
              #####
              #####
###################
# # # ##   ## # # #
#M#M#M## E ##M#M#M#
# # # ##   ## # # #
###################
#######
######
#####
####
# M#
####

###############
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMMMME#
###############
AdmBorkBork
источник
3
Мыши были в ярости (мягкий спойлер)
Луис Мендо
3
@AlexA. Извините, что вам пришлось узнать это от незнакомца в Интернете. ;-)
AdmBorkBork
2
Я подозреваю, что большинству людей будет трудно решить эту проблему с помощью обычного кода, не говоря уже об игре в гольф. Это большой вызов, над которым у меня, к сожалению, нет времени.
Моше Кац
2
Допустимо ли иметь другой символ для внешних стен (так как они не разрушаемы)?
Тенсибай
2
@ Моше Кац , конечно, у тебя нет времени. Ты просто не хочешь спасти Mäuse.
msh210

Ответы:

19

Perl, 216 215 байт

Включает +2 для -0p

Дайте вход на STDIN. Используйте %для наружных стен, #для внутренних стен, 0для пустых пространств, 8для мышей и rдля начальной позиции. Все доски должны быть дополнены так, чтобы они образовывали прямоугольник. Вы можете преобразовать и запустить примеры как:

cat dynamite.txt | perl -p0e 's/.+/$a^=$&/egr;s//sprintf"%-*s",length$a,$&/eg;1while/\n/,s/^ *\K#|#(?= *$)|^ *.{@{-}}\K#|\A[^\n]*\K#|#(?=[^\n]*\n\z)|#(?=.{@{-}} *$)/%/sm;y/ EM/0x2/' | dynamite.pl

dynamite.pl:

#!/usr/bin/perl -0p
sub f{@a{@_}||=push@{$%+($&?$1?50:$&=~8?0:$&&"5"?2:1:0)},@_}f$_;for(@{$%}){f y/xr|/ytx/r;{f s/\pL\d/$&^(E&$&)x2/er}{f s/(q|s|y)#/$&^"\x01\x13"/er}my$o;{$\|=/x/>/2/;$o.="
"while s/.$/$o.=$&,""/meg}f$o}$%++>999|$\||redo}{

Замените \xhhпобеги на заявленный счет.

Программа не может реально обрабатывать сложные случаи. В частности, он не может справиться ни с одним из случаев отказа. Это связано с тем, что существует слишком много разных способов взорвать внутренние стены или подобрать мышей, поэтому поиск становится слишком широким и использует слишком много памяти, хотя он по крайней мере достаточно умен, чтобы никогда не обрабатывать одно и то же состояние несколько раз. Предел давления должен быть снижен до 100или около того для несколько терпимого времени выполнения и использования памяти.

объяснение

Я использую битовую комбинацию символа для представления состояния поля:

contains victim: 0000 0010
has hero:        0100 0000
carry dynamite   0000 0001
carry mouse      0000 0100
home             0000 1000
walkable         0001 0000 (not really needed but results in shorter regexes)
make printable   0010 0000
wall             0010 xxxx (bit patterns not very important,
permawall        0010 xxxx  just avoid letters and digits)

Например, hero ( 01000000), несущий Dynamite ( 00000001), должен находиться в месте, где он может ходить ( 00010000), и мы хотим, чтобы все значения были пригодны для печати ASCII ( 00100000). Взятие побитовых orвсех этих битовых масок дает 01110001код ASCII для q. Всего это становится ::

p: hero                     r hero on victim
q: hero carrying dynamite   s hero carrying dynamite on victim
t: hero carrying mouse      v hero carrying mouse on victim

x : hero at home
y : hero at home carrying dynamite
| : hero at home carrying mouse

0: empty  without hero
8: home   without hero
2: victim without hero

%: permanent wall
#: normal wall

В программе будет учитываться только движение героя вправо (вращение, описанное ниже, позаботится о других направлениях) Битовые маски были тщательно подобраны таким образом, чтобы герой всегда был представлен буквой, а место, в которое он может переместиться, - цифрой (за исключением того, что герой дома несет жертву, но, опять же, это преднамеренно, так что единственным ходом героя будет падение жертва). Таким образом, герой, который может двигаться вперед, соответствует /\pL\d/. Соответствующая подстрока должна быть модифицирована таким образом, чтобы герой и то, что он несет, были удалены из первого символа и добавлены ко второму, что можно сделать с помощью побитового значения xorс одинаковым значением для обоих символов. Значение xor состоит из бита героя ( 01000000), бита динамита ( 00000001) и бита мыши для переноса ( 00000100). Вместе они orв01000101который является ASCII E. Итак, перемещение героя становится:

s/\pL\d/$&^(E&$&)x2/e

Герой может взорвать стену , если он стоит прямо перед ним и несет динамит ( q, sили y). Герой потеряет свой динамит ( xorс 00000001), и стена #изменится на проход 0(xor с 00010011), поэтому

s/(q|s|y)#/$&^"\x01\x13"/e

Чтобы справиться с другими направлениями, вся доска вращается (повернутая доска заканчивается $o):

my$o;$o.="\n"while s/.$/$o.=$&,""/meg

Помимо перемещения у героя также есть ряд других вариантов, которые он может сделать:

When at home, pick up dynamite:                   x -> y
When on victim not carrying anything pick him up: r -> t
When at home carrying a victim, drop him off:     | -> x

Это сделано

y/xr|/ytx/

Доска заканчивается, если герой дома не несет ничего ( x) и больше нет жертв для спасения (нет 2). Это может быть удобно проверено с помощью

/x/>/2/

Как только доска решена, я хочу запомнить это состояние и в конце напечатать его. Для этого я несу флаг «решено» $\и печатаю его в конце программы без печати $_, поэтому

$\|=/x/>/2/  ...   }{

Состояния, которые должны обрабатываться при давлении 0, сохраняются @0, при давлении 1 @1и т. Д. Текущее давление сохраняется $%. Использование $nили что-то вроде этого было бы короче, но код не работает, если переменная не инициализируется чем-то, потому что в противном случае автовивификация изменится $nна ссылку ARRAY. Цикл по состояниям при определенном давлении выполняется с использованием а, forа не mapпотому, что с помощью a forвы можете расширить массив, пока он все еще зацикливается, и выберете новые элементы. Это необходимо, потому что повороты и выбор героя в одном поле происходят за 0 раз и заканчиваются в одном массиве давления. Таким образом, цикл для данного давления осуществляется

for(@{$%}){...}

Это повторяется до тех пор, пока давление не достигнет 1000 или не будет найдено решение:

$%++>999|$\||redo

Осталось только добавить вновь обнаруженные состояния в соответствующие им массивы давления. Это будет сделано с помощью подпрограммы f. Мы хотим добавить состояние, только если оно еще не было просмотрено. Состояния, которые были замечены ранее, сохраняются в %aтак:

sub f{@a{@_}||=push@$n, @_}

$nпредставляет новое давление для государства. Я выведу это из состояния, которое все еще имеют переменные регулярного выражения в результате действия героя, ведущего к этому вызову:

if $1 is set it was from s/(q|s|y)#/$&^"\x01\x13"/e which blows a wall -> 50
else if $& is set it was from s/\pL\d/$&^(E&$&)x2/e, so the hero moves.
    if $& contains 8 the hero went home -> 0
    else if the hero has carry bits (5) -> 2
    else                                   1
else ($& was not set) it was from y/xr|/ytx/r -> 0

Это приводит к следующей формуле $n:

$%+($&?$1?50:$&=~8?0:$&&"5"?2:1:0)

Все замены получают rмодификатор, поэтому они возвращают измененное состояние и оставляют текущее состояние в $_покое. fзатем вызывается в этом измененном состоянии, так что вы получите код как

f y/xr|/ytx/r;

Поскольку при вычислении $nпотребностей в переменных регулярного выражения они должны по умолчанию не задавать значения, если подстановки ничего не меняют (поскольку условие для их запуска не выполняется). Я также не должен брать какие-либо переменные регулярного выражения из предыдущего цикла. Поэтому подстановки заключены в {}блоки для сохранения и восстановления состояния регулярного выражения. Вот как вы получаете такие заявления, как

{f s/\pL\d/$&^(E&$&)x2/er}

В частности, вращение так упаковано, что вызывает fбез регулярных выражений и получает вклад давления 0.

Единственное, что еще нужно сделать, это @0начать с начального состояния в начале

f$_

Это находится в основном цикле, поэтому он также пытается добавить $_к более поздним массивам давления, но, поскольку начальное состояние уже будет в, %aничего не произойдет.

Вместе все это в основном находит самое короткое решение, используя алгоритм Дейкстры . Хотя есть потенциальная проблема. Текущий код не будет добавлять состояние, если он будет заново открыт с более низким давлением, чем первое обнаружение. Я не смог построить доску, которая могла бы вызвать это, но я также не смог доказать, что это невозможно.

Тон Хоспел
источник
3
Ооо, интригующе. Это значительно короче, чем я ожидал от любого ответа. Можете ли вы добавить немного объяснения? Я не очень люблю Perl.
AdmBorkBork
3
@TimmyD Хорошо, объяснение добавлено с достаточным количеством деталей, так что даже не программист на Perl должен получить хотя бы представление о том, как это работает
Тон Хоспел
1
Очень впечатляюще!
Emigna
Удивительный гольф, это действительно впечатляет ... Когда я думаю, что я неплохо играю в гольф с Perl, я смотрю на ваши игры в гольф, и это чувство исчезает довольно быстро!
Дада
13

JavaScript, 863 834 785 781 байт

Сохранено 29 байтов благодаря ETHproductions
Сохранено 53 байта благодаря Иордании

L=[]
f=(S,r="",R="",p=0,s=S.replace(RegExp(r),R),l=`((.|\n){${s.split`
`[0].length}})`,q=p+1,o=p+2,n=p+50)=>s in L|p>999?1e3:!/M/.test(s,L[s]=0)&/E/.test(s)?p:Math.min(...[[/ E/,"me",q],[/ E/,"de",o],[/ME/,"ce",q],[/E /,"em",q],[/E /,"ed",o],[/EM/,"ec",q],[`E${l} `,"e$1m",q],[`E${l} `,"e$1d",o],[`E${l}M`,"e$1c",q],[` ${l}E`,"m$1e",q],[` ${l}E`,"d$1e",o],[`M${l}E`,"c$1e",q],[/ m/,"m ",q],[/m /," m",q],[`m${l} `," $1m",q],[` ${l}m`,"m$1 ",q],[/ (d|c)/,"$1 ",o],[/(d|c) /," $1",o],[`(d|c)${l} `," $2$1",o],[` ${l}(d|c)`,"$3$1 ",o],[/d#/,"m ",n],[/#d/," m",n],[`#${l}d`," $1m",n],[`d${l}#`,"m$1 ",n],[/mM/," c",q],[/Mm/,"c ",q],[`M${l}m`,"c$1 ",q],[`m${l}M`," $1c",q],[/[mc]e/," E",p],[/e[mc]/,"E ",p],[`e${l}[mc]`,"E$1 ",p],[`[mc]${l}e`," $1E",p]].map(a=>f(s,...a)))
F=s=>f(s)<1e3

Принимает ввод в виде многострочной строки.

Это определяет анонимную функцию, которая использует рекурсивную функцию, fчтобы определить, отключили ли вы тревогу, прежде чем получить все мыши. fвозвращается, 1000если давление выше 1000 (чтобы избежать бесконечной рекурсии), возвращает давление, если больше нет мышей для спасения и мыши на выходе, и возвращает минимальное давление всех возможных ходов из текущего состояния в противном случае. Он использует массив Lдля отслеживания уже посещенных позиций, где, L[pos]==0если он посещен, и неопределенный, если это не так. Это может не быть необходимым, но это препятствует тому, чтобы мышь делала бесполезные движения и выбрасывала ошибки рекурсии по крайней мере. (Это означает, что вы должны переопределить, Lесли вы тестируете это несколько раз)

Это использует формат в вопросе, кроме того, что он требует, чтобы вы использовали другой символ для внешних стен. (Что-нибудь кроме # MEmecd)

Более читаемая версия:

stateList = []
f=(s,regex="",replacement="",pressure=0,state=s.replace(regexp(regex),replacement),line=`((.|\n){${state.split("\n")[0].length}})`)=>{
    if (state in stateList || pressure > 999) return 1e3
    if (!/M/.test(state) && /E/.test(state)) return pressure

    stateList[state] = 0

    return [
        [/ E/,"me",pressure+1],
        [/ E/,"de",pressure+2],
        [/ME/,"ce",pressure+1],
        [/E /,"em",pressure+1],
        [/E /,"ed",pressure+2],
        [/EM/,"ec",pressure+1],
        [`E${line} `,"e$1m",pressure+1],
        [`E${line} `,"e$1d",pressure+2],
        [`E${line}M`,"e$1c",pressure+1],
        [` ${line}E`,"m$1e",pressure+1],
        [` ${line}E`,"d$1e",pressure+2],
        [`M${line}E`,"c$1e",pressure+1],
        [/ m/,"m ",pressure+1],
        [/m /," m",pressure+1],
        [`m${line} `," $1m",pressure+1],
        [` ${line}m`,"m$1 ",pressure+1],
        [/ ([dc])/,"$1 ",pressure+2],
        [/([dc]) /," $1",pressure+2],
        [`([dc])${line} `," $2$1",pressure+2],
        [` ${line}([dc])`,"$3$1 ",pressure+2],
        [/d#/,"m ",pressure+50],
        [/#d/," m",pressure+50],
        [`#${line}d`," $1m",pressure+50],
        [`d${line}#`,"m$1 ",pressure+50],
        [/mM/," c",pressure+1],
        [/Mm/,"c ",pressure+1],
        [`M${line}m`,"c$1 ",pressure+1],
        [`m${line}M`," $1c",pressure+1],
        [/[mc]e/," E",pressure],
        [/e[mc]/,"E ",pressure],
        [`e${line}[mc]`,"E$1 ",pressure],
        [`[mc]${line}e`," $1E",pressure]
    ].map(a=>f(state,...a)).reduce((a,b)=>a-b<0?a:b) //reduce used for support in more browsers.
}
s=>f(s)>1e3
DanTheMan
источник
Бесполезные пробелы в s in L|p > 999.
Yytsi
@TuukkaX Спасибо, что напомнили мне об этом, bytecount уже был для версии без пробелов.
DanTheMan
Смотрите , если вы можете сохранить байт обертыванием код evalи заменить @с $1(не уверен , если это будет работать, но вы пишете $1много)
Cyoce
Я думаю, что вы могли бы сэкономить кучу, сделав fоднострочник:f=(...)=>s in L|p>999?1e3:!/M/.test(s,L[s]=0)&/E/.test(s)?p:Math.min(...
ETHproductions
@Cyoce я использую $118 раз и .replace("@","$1")составляет 18 байт. Я не вижу способа осуществить это.
DanTheMan