Un ratón con dinamita

23

Eres un raton Todos tus amigos del ratón han sido capturados, y están inconscientes y atrapados en un laberinto que solo tiene una entrada / salida. Tienes un mapa perfecto del laberinto, por lo que puedes trazar una solución para apresurarte y llevarlos a todos a un lugar seguro. Sin embargo, el laberinto está protegido con un sistema de seguridad que activará una alerta si 1000se alcanza un umbral de , lo que hace que seas capturado y falles en tu misión de rescate.

De sus investigaciones anteriores sobre el laberinto, cada cuadrado que pisa (es decir, cada movimiento horizontal o vertical, los ratones no pueden moverse en diagonal ) se suma 1al contador del sistema de seguridad. Sin embargo, si lleva un peso (ya sea un bloque de dinamita o un ratón amigo inconsciente), en su lugar se agrega, 2ya que detecta la presión adicional. La plaza de entrada / salida no tiene este sistema de seguridad, por lo que no se agrega al mostrador.

Tienes un suministro ilimitado de dinamita que has traído a la entrada, por lo que puedes volar todos los muros para liberar a tus amigos. Pero debe tener cuidado al hacerlo, ya que cada explosión se suma 50al contador por la presión de conmoción. Además, solo puede llevar una cosa a la vez, ya sea un mouse o un bloque de dinamita. Dado que cada bloque de dinamita solo puede detonar un espacio de pared, esto significa que si hay varias paredes en una fila, debe hacer un viaje con las manos vacías de regreso a la entrada para agarrar más.

Ejemplo resuelto

Supongamos que nuestro laberinto se ve así:

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

Lo usaré cpara el mostrador. Empezamos por el Entrance, movemos quede un solo cuadrado en el ejercicio de la dinamita, c=2. Nos detonar la dinamita para explotar la pared, c=52. Nos movemos dos casillas a la izquierda, con las manos vacías, para llegar c=54, y ahora estamos parados en la casilla del ratón. ERecogemos a nuestro amigo y volvemos 3 casillas al xit, pero la última casilla no cuenta, ya que no tiene sensores, por lo que solo hay 2 casillas con algo en la espalda. Eso significa que cuando llegamos a la salida con el mouse final c=58, que es menor que 1000, y por lo tanto, la misión tiene éxito.

Reto

Dado un laberinto de entrada, salida si usted, el héroe del ratón, puede rescatar con éxito a todos los ratones atrapados dentro de las limitaciones descritas anteriormente, o si la misión es un fracaso.

Entrada

  • Un laberinto 2D en cualquier formato aceptable (cadena multilínea, matriz de cadenas, etc.).
  • Para este desafío, lo usaré #tanto para las paredes interiores como para las exteriores, Mpara los amigos del ratón y Epara la entrada.
  • La entrada nunca estará inmediatamente adyacente a una pared interior (siempre habrá al menos un espacio para moverse libremente).
  • Puede sustituir cualquier carácter ASCII imprimible que desee siempre que sea coherente. Esto le permite usar dos símbolos diferentes para las paredes interiores frente a las paredes exteriores, siempre y cuando mantenga la consistencia (por ejemplo, si elige usar @para las paredes interiores en su lugar y se va #hacia el exterior, cada pared interior debe ser @y cada pared exterior #)
  • El laberinto siempre estará completamente delimitado por paredes, pero no es necesariamente rectangular. Si lo desea, puede suponer que el laberinto se rellena con espacios para hacer una entrada rectangular (opcional).
  • El laberinto puede tener secciones inalcanzables sin dinamita.
  • No puedes dinamitar las paredes exteriores del laberinto.

Salida

Un valor verdadero / falso . Verdad para "Sí, el mouse puede rescatar a cualquier otro mouse" o Falsey para "No, el sistema de alarma se disparará".

Las normas

  • Un programa completo o una función son aceptables.
  • Las lagunas estándar están prohibidas.
  • Este es el por lo que se aplican todas las reglas habituales de golf, y gana el código más corto (en bytes).

Ejemplos

Ejemplos verdaderos, separados por líneas en blanco.

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

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

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

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

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

Ejemplos de Falsey, separados por líneas en blanco

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

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

###############
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMMMMM#
#MMMMMMMMMMMME#
###############
AdmBorkBork
fuente
3
Los ratones estaban furiosos (spoiler leve)
Luis Mendo
3
@AlexA. Lamento que haya tenido que aprenderlo de un extraño en Internet. ;-)
AdmBorkBork
2
Sospecho que a la mayoría de la gente le resultaría difícil resolver esto con un código regular, y mucho menos jugarlo. Es un gran desafío que desafortunadamente no tengo tiempo para trabajar en este momento.
Moshe Katz
2
¿Es aceptable tener un carácter diferente para las paredes externas (ya que no son desmontables)?
Tensibai
2
@ Moshe Katz , seguro que no tienes tiempo. Simplemente no quieres salvar a la Mäuse.
msh210

Respuestas:

19

Perl, 216 215 bytes

Incluye +2 para -0p

Dar entrada en STDIN. Úselo %para paredes externas, #para paredes internas, 0para espacios vacíos, 8para ratones y rpara la posición inicial. Todos los tableros deben estar acolchados para que formen un rectángulo. Puede transformar y ejecutar los ejemplos como:

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}{

Reemplace los \xhhescapes por el puntaje reclamado.

El programa no puede manejar de manera realista casos complejos. En particular, no puede manejar ninguno de los casos de falla. Esto se debe a que hay demasiadas formas diferentes de volar las paredes internas o levantar ratones, por lo que la búsqueda se vuelve demasiado amplia y usa demasiada memoria, aunque es al menos lo suficientemente inteligente como para nunca procesar el mismo estado varias veces. El límite de presión debe reducirse a 100unos tiempos de ejecución y uso de memoria algo soportables.

Explicación

Utilizo el patrón de bits de un personaje para representar el estado de un campo:

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)

Por ejemplo, el héroe ( 01000000) que lleva dinamita ( 00000001) debe estar en un lugar donde pueda caminar ( 00010000) y queremos que todos los valores sean ASCII ( 00100000) imprimibles . Tomar el bit a bit orde todas estas máscaras de bits da 01110001cuál es el código ASCII para q. En total esto se convierte en ::

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

El programa solo considerará al héroe moviéndose hacia la derecha (la rotación explicada más adelante se encargará de las otras direcciones). Las máscaras de bits se eligieron cuidadosamente de modo que el héroe siempre esté representado por una letra y un lugar al que pueda moverse por un dígito (excepto el héroe en casa que lleva una víctima, pero nuevamente esto es intencional para que el único movimiento del héroe sea soltar la víctima). Entonces, un héroe que puede avanzar es igualado /\pL\d/. La subcadena coincidente debe modificarse para que el héroe y lo que lleva se eliminen del primer personaje y se agreguen al segundo, lo que se puede hacer de manera bit xora bit con el mismo valor para ambos personajes. El valor xor consiste en el bit de héroe ( 01000000), el bit de dinamita ( 00000001) y el bit de mouse de acarreo ( 00000100). Juntos ellos orpara01000101que es ASCII E. Entonces, mover al héroe se convierte en:

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

El héroe puede volar una pared si está parado justo frente a ella y lleva dinamita ( q, so y). El héroe perderá su dinamita ( xorcon 00000001) y el muro #cambiará a un pasaje 0(xor con 00010011), así que

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

Para manejar las otras direcciones, se gira todo el tablero (el tablero girado termina en $o):

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

Además de moverse, el héroe también tiene otras elecciones que puede hacer:

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

Esto se hace por

y/xr|/ytx/

El tablero está terminado si el héroe está en casa sin llevar nada ( x) y no hay más víctimas para rescatar (no 2). Esto se puede probar convenientemente usando

/x/>/2/

Una vez que se resuelve el tablero quiero recordar este estado y al final imprimirlo. Para eso llevo la bandera "está resuelto" $\e imprimo eso al final del programa sin imprimir $_, así que

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

Los estados a procesar a presión 0 se mantienen @0, a presión 1, @1etc. La presión actual se mantiene $%. Utilizando $no algo así sería más corto, pero el código no funciona si la variable no se inicializa a algo, porque de lo contrario será autovivification cambiar $na un reference.Looping ARRAY lo largo de los estados a una cierta presión se realiza mediante una fory no una mapcausa con un forpuede extender la matriz mientras aún se está repitiendo y recogerá los nuevos elementos. Esto es necesario porque las rotaciones y las elecciones de campo único del héroe suceden en tiempo 0 y terminan en la misma matriz de presión. Entonces el ciclo para una presión dada es hecho por

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

Esto se repite hasta que la presión alcanza 1000 o se encuentra una solución:

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

Todo lo que queda es agregar estados recién descubiertos a sus respectivas matrices de presión. Eso se hará por subrutina f. Solo queremos agregar un estado si aún no se ha visto. Los estados que se han visto antes se mantienen %aasí:

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

$nrepresenta la nueva presión para un estado. Lo deduciré del estado que las variables regex todavía tienen como resultado de la acción del héroe que condujo a esta llamada:

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

Esto lleva a la siguiente fórmula para $n:

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

Todas las sustituciones obtienen un rmodificador, por lo que devuelven el estado modificado y dejan solo el estado actual $_. fluego se llama en este estado modificado, por lo que obtiene un código como

f y/xr|/ytx/r;

Debido a que el cálculo de $nnecesita las variables de expresión regular, deben deshabilitarse por defecto en caso de que las sustituciones no cambien nada (porque no se cumple la condición para activarlas). Tampoco debo recoger ninguna variable regex de un bucle anterior. Por lo tanto, las sustituciones se envuelven en {}bloques para guardar y restaurar el estado regex. Así es como obtienes declaraciones como

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

En particular, la rotación está tan ajustada que llama fsin variables regex y obtiene una contribución de presión de 0.

Lo único que queda por hacer es cebar @0con el estado inicial al inicio

f$_

Esto está en el bucle principal, por lo que también intenta agregarse $_a matrices de presión posteriores, pero dado que el estado inicial ya %ano estará en nada sucederá.

Juntos, todo esto básicamente encuentra la solución más corta usando el algoritmo de Dijkstra . Sin embargo, hay un problema potencial. El código actual no agregará un estado si se redescubre con una presión menor que el primer descubrimiento. No he podido construir un tablero que desencadene esto, pero tampoco he podido demostrar que es imposible.

Ton Hospel
fuente
3
Ooo, intrigante. Esto es significativamente más corto de lo que esperaba que fuera cualquier respuesta. ¿Puedes agregar un poco de explicación? Realmente no entiendo a Perl.
AdmBorkBork
3
@TimmyD Ok, explicación agregada con suficientes detalles para que incluso un programador que no sea perl debería tener al menos una impresión de cómo funciona
Ton Hospel
1
¡Muy impresionante!
Emigna
Golf impresionante, eso es realmente impresionante ... Cuando creo que no soy tan malo jugando al golf con Perl, miro tus juegos de golf, ¡y esa sensación desaparece bastante rápido!
Dada
13

JavaScript, 863 834 785 781 bytes

Guardado 29 bytes gracias a ETHproductions
Guardado 53 bytes gracias a Jordania

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

Toma la entrada como una cadena multilínea.

Esto define una función anónima que utiliza una función recursiva fpara determinar si activa la alarma antes de recuperar todos los ratones. fregresa 1000si la presión es superior a 1000 (para evitar una recursión sin fin), regresa la presión si no hay más ratones para rescatar y el mouse en la salida, y devuelve la presión mínima de todos los movimientos posibles desde el estado actual de lo contrario. Utiliza una matriz Lpara realizar un seguimiento de las posiciones ya visitadas donde L[pos]==0se visita, y no está definida si no lo es. Esto puede no ser necesario, pero evita que el mouse haga movimientos inútiles y arroje errores de recursión como mínimo. (Esto significa que debe redefinir Lsi está probando esto varias veces)

Esto usa el formato en la pregunta además de que requiere que uses un carácter diferente para las paredes externas. (Cualquier otra cosa que no sea # MEmecd)

Versión más legible:

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
fuente
Espacios en blanco inútiles en s in L|p > 999.
Yytsi
@TuukkaX Gracias por recordarme eso, el bytecount era para la versión sin espacios ya.
DanTheMan
Vea si puede guardar bytes envolviendo el código evaly reemplazándolo @con $1(no estoy seguro de si esto funcionará, pero escribe $1mucho)
Cyoce
Creo que podría ahorrar un montón haciendo funa f=(...)=>s in L|p>999?1e3:!/M/.test(s,L[s]=0)&/E/.test(s)?p:Math.min(...
frase
@Cyoce Yo uso $118 veces, y .replace("@","$1")es de 18 bytes. No veo una manera de lograrlo.
DanTheMan