Programas para construir un laberinto de ratas

15

Lo contrataron como asistente de investigación y le pidieron que creara un pequeño programa que construirá laberintos de ratas. La caja de la rata siempre es 62x22 y tiene una entrada (a) y una salida (A) para la rata, como esta (entrada 1):

#######a######################################################
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#################################################A############

Su programa debe llenar el cuadro con bloques (#) dejando una ruta para la rata, así (salida 1):

#######a######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
#######                                           ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
#################################################A############

¡Esto es fácil, piensas! Empiezas a escribir un pequeño programa, lleno de confianza. Sin embargo, el Principio Científico ha tenido una nueva idea: quiere que dos ratas naveguen por el laberinto al mismo tiempo. El Dr. Rattanshnorter explica que tienen diferentes puertas y diferentes salidas (entrada 2):

#b#####a######################################################
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            B
#                                                            #
#################################################A############

Las ratas han sido entrenadas para moverse en línea recta a través de las intersecciones cruzadas, pero las intersecciones en T las dejan confusamente irremediables e invalidarán el experimento. Comienzas en tu nueva tarea más compleja cuando el buen Doctor explica un requisito final: las ratas son salvajes entre sí, por lo que si se ven en algún momento, estallará una pelea de ratas y ambos estarán ante el tablero de ética. Ahora se da cuenta de que su programa debería generar un laberinto similar a este (salida 2):

#b#####a######################################################
# ##### ######################################################
# ##### ######################################################
# ##### #######################################           ####
# ##### ####################################### ######### ####
# #####                                           ####### ####
# ############################################# # ####### ####
# ############################################# # ####### ####
# ############################################# # ####### ####
# ############################################# # ####### ####
#                                               # ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# #######    B
################################################# ############
#################################################A############

Cuando la rata B llegue a la intersección, la rata A viajará por el corredor para salir de A y se evitará la pelea de ratas.

Reglas:

  • Su programa debe leer (STDIN o archivo) una entrada como las anteriores y generar (STDOUT o archivo) los mismos datos, excepto que ahora muchos espacios serán hash (#). Puede sustituir cualquier carácter individual (como ;) en lugar de \nen la cadena de entrada, pero la cadena de salida aún requiere \ncaracteres. ACTUALIZADO

  • Una ruta de rata debe tener un ancho de un carácter de ancho, a excepción de las intersecciones cruzadas (cada espacio debe tener cero o dos adyacentes ortogonalmente # caracteres ). Cada rata debe tener una ruta única clara, excepto las intersecciones cruzadas. No se permiten intersecciones en T.

  • Las ratas se liberan simultáneamente y se mueven a una velocidad constante. En ningún momento deben verse dos o más ratas (estar en la misma columna o fila sin una o más# caracteres intermedios).

  • Si no hay solución posible (como puntos de entrada adyacentes), imprima Impossible\ny salga.

  • Las entradas y salidas pueden estar en cualquier lado, sin embargo, nunca estarán en las esquinas.

  • Si una entrada y una salida coincidentes son adyacentes (por ejemplo:) ##aA##, la rata no puede ir directamente de aa A. Debe haber una pequeña sección de corredor de 2 espacios dentro del área del laberinto.

  • En el turno donde una rata alcanza su punto de salida (o en cualquier momento después de eso), ya no es visible para otras ratas.

  • Su programa puede estar diseñado para calcular laberintos para 1, 2, hasta 26 ratas.

  • Las lagunas estándar no están permitidas.

Puntuación:

Con su solución, nomine cuántas ratas por laberinto (N) puede resolver su programa. Su puntaje es la longitud de su código en bytes dividido por este número N.

Incluya una salida de muestra en su respuesta para que podamos ver qué produce su programa.

Caballero Lógico
fuente
¿Es la única diferencia en las entradas posibles las ubicaciones de a, A, b, B?
xnor
Para la versión de 2 ratas, sí. Si su programa está diseñado para hasta 3 ratas, deberá hacer frente a todas las ubicaciones posibles de a, b, c, A, B, C.
Logic Knight el
¿Se permiten las intersecciones en T si la rata solo camina junto a la parte horizontal de la T?
orlp
No, estas ratas se confunden fácilmente. Solo se permiten caminos rectos, codos y cruces.
Logic Knight el
@CarpetPython ¿Puede una entrada / salida estar en cualquier lugar a lo largo del borde del laberinto? ¿Pueden ser adyacentes?
orlp

Respuestas:

2

Haskell, 26 ratas ?, ~ 5000 bytes

Teóricamente, este código debería funcionar para cualquier cantidad de ratas, pero no ofrezco ninguna garantía de que terminará antes de la muerte por calor del universo. Se basa en un algoritmo de retroceso que intenta ir primero por la ruta recta y luego cambiar de ruta si la ruta no funciona. El número de alternativas es exponencial con respecto a la longitud del camino y el número de ratas.

Todavía no me he molestado en jugar golf, ya que es muy grande y porque primero quiero hacerlo más rápido.

{-# LANGUAGE FlexibleContexts #-}
module Main (main) where

import Control.Lens
import Control.Monad
import Data.Char
import Data.Function
import Data.List
import Data.Maybe

type Pos = (Int,Int)
type Path = [Pos]
type Maze = String
type Input = [(Pos,Char)]
type MazeState = [(Path,Path)]

type ChoiceMonad a = [a]


instance (Num a, Num b) => Num (a,b) where
  (x,y)+(x',y')=(x+x',y+y')
  (x,y)-(x',y')=(x-x',y-y')
  fromInteger n = (fromInteger n,fromInteger n)


parseMaze :: Maze -> Input
parseMaze maze = maze ^@.. inner . filtered (`notElem` "# ")

inner :: IndexedTraversal' Pos Maze Char
inner = lined <.> traversed

main :: IO ()
main = do
    maze <- readFile "Sample2.in"
    putStrLn $ solveAndShow maze

fillMaze :: Maze -> Maze
fillMaze = inner.filtered(==' ').~'#'

updateMaze :: Path -> Maze -> Maze
updateMaze path = inner.indices (`elem` path).filtered(=='#') .~ ' '

isDone :: MazeState -> Bool
isDone = all (null . snd)

showMaze :: Maze -> MazeState -> Maze
showMaze maze path = updateMaze (fst =<< path) $ fillMaze maze

showSolution :: Maze -> ChoiceMonad MazeState -> String
showSolution _    []    = "Impossible"
showSolution maze (x:_) = showMaze maze x


stopCondition :: ChoiceMonad MazeState ->  Bool
stopCondition x = not $ null x || isDone (head x)

solveAndShow :: Maze -> String
solveAndShow maze = showSolution maze . solve $ mazeToState maze

solve :: ChoiceMonad MazeState -> ChoiceMonad MazeState
solve = fromJust . find (not.stopCondition) . iterate fullStep

mazeToState :: Maze -> ChoiceMonad MazeState
mazeToState maze = do
    let startsEnds = paths $ parseMaze maze
        return $ startsEnds & traverse.both %~ (:[])


fullStep :: ChoiceMonad MazeState -> ChoiceMonad MazeState
fullStep = (>>= stepAll)

stepAll :: MazeState -> ChoiceMonad MazeState
stepAll input = do
    pths <- mapM goStep input
    guard $ iall (checkVisible pths) $ map fst pths
    return $ pths
  where
    goStep :: (Path,Path) -> ChoiceMonad (Path,Path)
    goStep (curr:rest,[]) = return (curr:curr:rest,[])
    goStep (curr:these,end:ends)
       | distance curr end == 1 = return (end:curr:these,ends)

       | curr == end = goStep (curr:these,ends)
    goStep (path,end) = do
      next <- twoSteps (head end) path
      prev <- twoSteps next end
      return $ (next:path,prev:end)
    inMaze = inMazeWith input

    twoSteps :: Pos -> Path -> ChoiceMonad Pos
    twoSteps goal path = do
      next <- oneStep goal path inMaze
      guard $ not.null $ oneStep goal (next:path) (\x -> x==next || inMaze x)
      return next

checkVisible :: MazeState -> Int -> Path -> Bool
checkVisible _    _ [] = True
checkVisible pths i xs@(x:_) = checkBack && checkNow
  where
    nBack = 1 + visibleBackwards xs
    --checkBack = none (any (==x).take nBack .fst) pths
    checkBack = hasn't (folded.indices (/=i)._1.taking nBack folded.filtered (==x)) pths
    checkNow  = inone (\i' (x':_,_) -> (i/=i') && (==x') `any` take nBack xs ) pths

-- How long have you stayed on a line
visibleBackwards :: Path -> Int
visibleBackwards as = length . takeWhile ((==headDiff as) .headDiff). filter ((>=2).length) $ tails as
      where headDiff (a:a1:_) = a-a1
            headDiff x        = error $ "Bug: Too short list " ++ show x


inMazeWith :: [(Path, Path)] -> Pos -> Bool
inMazeWith = flip elem . concatMap (\x->snd x ++ fst x)

oneStep :: MonadPlus m => Pos -> Path -> (Pos -> Bool)  -> m Pos
oneStep end (curr:prev:_) inMaze =
  if distance curr end <= 1
     then return end
     else do
    let distance' :: Pos -> Double
        distance' x = fromIntegral (distance x end) + if curr - prev == x - curr then 0 else 0.4
    next <- msum . map return $ sortBy (compare`on`distance') $ neighbors curr

    -- Don't go back
    guard $ next /= prev

    -- Stay in bounds
    guard $ isInBounds next

    let dir = (next - curr)
    let lr = neighbors next \\ [curr,next+dir,end]

    -- If next is blocked, check that the one after that is free
    if inMaze next
      then do
        guard $ not . (\x->(x/=end)&&inMaze x) $ next + dir
        -- Both sides should be blocked as well
        guard $ (==2). length . filter inMaze $ lr
      else do
        -- No neighbors if empty
        guard $ null . filter inMaze $ lr

    -- All neighbors of 'curr', including 'next'
    let neigh' = filter (\i -> inMaze i || i == next) $ neighbors curr
        -- should be an even number
        guard $ even $ length neigh'

    return next
oneStep _ [start] _ = return $ inBounds start
oneStep _ _ _ = error "Too short path given"


toBounds :: (Num a, Eq a) => (a,a) -> a -> a
toBounds (low, high) x
    | x == low  = x + 1
    | x == high = x - 1
    | otherwise = x

distance :: Pos -> Pos -> Int
distance (x1,y1) (x2,y2) = abs(x1-x2)+abs(y1-y2)

-- Moves a pos to the closest one inside the bounds
inBounds :: Pos -> Pos
inBounds = bimap (toBounds (0,21)) (toBounds (0,61))

isInBounds :: Pos -> Bool
isInBounds x = x == inBounds x

neighbors :: Pos -> [Pos]
neighbors pos = [ pos & l %~ p| l <- [_1,_2], p <- [succ,pred]]

paths :: Input -> [(Pos,Pos)]
paths pos = flip unfoldr 'a' $ \x ->
  do (y,_) <- find ((==x).snd) pos
     (z,_) <- find ((==toUpper x).snd) pos
     return ((y,z),succ x)

Salida de muestra, 6 ratas:

##c###B#####b#######C#######F######################f##########
##   #       #       #######                        ##########
####  ######## ###############################################
#####          ###############################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
#############       ##########################################
############# #####  #########################################
D             #    #     #####################################
##############  ## ##### #####################################
#########      #                 #############################
######### ###### # ##### ####### #############################
####      #      #     # #######                        ######
####E######a##########e#d##############################A######
Hjulle
fuente
2
¿Cuándo bllega a la intersección de ey b, no es visto por él e? bparece llegar a eso t = 11, lo que pondría etodavía en ese corredor. ¿Me estoy perdiendo de algo?
BrainSteel
@BrainSteel Sí, eso es correcto. Mi respuesta no es valida Me di cuenta anteriormente que también necesitaba verificar las colisiones "hacia atrás en el tiempo" (después de cruzar otros caminos de ratas), pero por alguna razón decidí que no era necesario. : P
Hjulle
@BrainSteel Creo que he solucionado ese error ahora.
Hjulle
1

Haskell, 1 rata, 681 personajes

El problema se puede resolver trivialmente para todos los laberintos con una sola rata. Este código también "funciona" para cualquier número de ratas, pero no sigue ninguna de las restricciones en las interacciones entre múltiples ratas y caminos.

module Main where
import Control.Lens
import Data.List(unfoldr,find)
import Data.Char(toUpper)
parse=(^@..lined<.>folded.filtered(`notElem`"# "))
main=interact$do i<-(naive=<<).rats.parse;(lined<.>traversed).filtered(==' ').indices (`notElem`i).~'#'
    naive(start,(ex,ey))=start':unfoldr go start' where
     start'=bnds start
     (ex',ey')=bnds(ex,ey)
     go(x,y)
      |(x,y)==(ex',ey')=Nothing
      |x== ex'=ok(x,y`t`ey')
      |otherwise=ok(x`t`ex',y)
     ok z=Just(z,z)
     t x y=if x>y then x-1 else x+1
    bnd(l,h)x |x==l=x+1 |x==h=x-1 |True=x
    bnds=bimap(bnd(0,21))(bnd(0,61))
    rats pos=(`unfoldr`'a')$ \x->
  do (y,_)<-find((==x).snd)pos
     (z,_)<-find((==toUpper x).snd)pos
     return((y,z),succ x)

Salida de muestra:

#######a######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
#######                                           ############
#################################################A############

Estoy planeando admitir muchas ratas, así que escribí un código genérico, pero aún no he encontrado un buen algoritmo para eso.

  • parse extrae una lista de todas las entradas y salidas, con sus coordenadas
  • rats toma esa lista y la convierte en pares de coordenadas para cada rata.
  • bnds toma una coordenada en un borde y la mueve a la coordenada más cercana dentro del laberinto.
  • naive toma una posición inicial y final y devuelve un camino simple entre ellas.
  • main luego reemplaza todo el espacio en blanco que no está en una ruta con '#'
Hjulle
fuente
@ edc65 "... restricciones entre múltiples ratas". Esta es una respuesta para solo 1 rata, que se permite según la pregunta.
Hjulle
OK mi culpa. Solo pensar que para 1 rata es un desafío diferente. Voy a eliminar mis comentarios anteriores
edc65