Programas para construir um labirinto de ratos

15

Você foi contratado como assistente de pesquisa e solicitado a criar um pequeno programa que criará labirintos de ratos. A caixa do rato é sempre 62x22 e possui uma entrada (a) e uma saída (A) para o rato, desta forma (entrada 1):

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

Seu programa deve preencher a caixa com blocos (#) deixando um caminho para o rato, como este (saída 1):

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

Isso é fácil, você pensa! Você começa a escrever um pequeno programa, cheio de confiança. No entanto, o cientista principal teve uma nova idéia - ele quer dois ratos para navegar no labirinto ao mesmo tempo. O Dr. Rattanshnorter explica que eles têm portas e saídas diferentes (entrada 2):

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

Os ratos foram treinados para atravessar cruzamentos cruzados, mas os cruzamentos em T os deixam irremediavelmente confusos e invalidarão o experimento. Você começa sua nova tarefa mais complexa quando o bom doutor explica um requisito final: os ratos são selvagens um com o outro; se eles se virem a qualquer momento, uma briga de ratos será iniciada e vocês dois estarão diante do conselho de ética. Agora você percebe que seu programa deve gerar um labirinto semelhante a este (saída 2):

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

Quando o rato B chegar ao cruzamento, o rato A estará viajando pelo corredor para sair de A e a luta por ratos será evitada.

Regras:

  • Seu programa deve ler (STDIN ou arquivo) uma entrada como as acima e gerar (STDOUT ou arquivo) os mesmos dados, exceto que muitos espaços serão agora hashes (#). Você pode substituir qualquer caractere único (como ;) em vez de \nna sequência de entrada, mas a sequência de saída ainda requer \ncaracteres. ATUALIZADA

  • Um caminho de rato deve ter a largura de um caractere, exceto as interseções cruzadas (todo espaço deve ter zero ou dois ortogonalmente adjacentes # caracteres ). Cada rato deve ter um único caminho claro, exceto cruzamentos cruzados. Não são permitidas interseções em T.

  • Os ratos são liberados simultaneamente e se movem a uma taxa constante. Em nenhum momento dois ou mais ratos devem se ver (estar na mesma coluna ou linha sem um dos mais# caracteres no meio).

  • Se nenhuma solução for possível (como pontos de entrada adjacentes), imprima Impossible\n e saia.

  • As entradas e saídas podem estar de qualquer lado, porém nunca estarão nas esquinas.

  • Se uma entrada e uma saída correspondentes forem adjacentes (por exemplo ##aA##:), o rato não poderá ir diretamente de aparaA . Deve haver uma pequena seção de corredor de 2 espaços dentro da área do labirinto.

  • No turno em que um rato atinge seu ponto de saída (ou a qualquer momento depois disso), ele não é mais visível para outros ratos.

  • Seu programa pode ser projetado para calcular labirintos para 1, 2, até 26 ratos.

  • As brechas padrão não são permitidas.

Ponto:

Com sua solução, indique quantos ratos por labirinto (N) seu programa pode resolver. Sua pontuação é o tamanho do código em bytes dividido por este número N.

Inclua uma amostra de saída em sua resposta para que possamos ver o que seu programa produz.

Cavaleiro Lógico
fonte
A única diferença nas entradas possíveis é a localização de a, A, b, B?
Xnor
Para a versão de 2 ratos, sim. Se o seu programa foi projetado para até três ratos, você precisaria lidar com todos os locais possíveis de a, b, c, A, B, C.
Cavaleiro Lógico
São permitidas interseções em T se o rato caminhar apenas ao lado da parte horizontal do T?
Orlp 03/04/19
Não, esses ratos são facilmente confundidos. Somente caminhos retos, curvas de cotovelo e estradas transversais são permitidos.
Cavaleiro Lógico
@CarpetPython Uma entrada / saída pode estar em qualquer lugar ao longo da borda do labirinto? Eles podem ser adjacentes?
Orlp 03/04/19

Respostas:

2

Haskell, 26 ratos ?, ~ 5000 bytes

Teoricamente, esse código deve funcionar para qualquer número de ratos, mas não ofereço garantia de que ele terminará antes da morte por calor do universo. Ele é baseado em um algoritmo de retorno que tenta seguir o caminho direto primeiro e depois alternar o caminho se o caminho não funcionar. O número de alternativas é exponencial em relação ao comprimento do caminho e ao número de ratos.

Ainda não me incomodei em jogar golfe, já que é muito grande e quero torná-lo mais rápido primeiro.

{-# 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)

Saída de amostra, 6 ratos:

##c###B#####b#######C#######F######################f##########
##   #       #       #######                        ##########
####  ######## ###############################################
#####          ###############################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
#############       ##########################################
############# #####  #########################################
D             #    #     #####################################
##############  ## ##### #####################################
#########      #                 #############################
######### ###### # ##### ####### #############################
####      #      #     # #######                        ######
####E######a##########e#d##############################A######
Hjulle
fonte
2
Quando bchega ao cruzamento de ee b, ele não é visto por e? bparece chegar lá t = 11, o que colocaria eainda naquele corredor. Estou esquecendo de algo?
precisa saber é o seguinte
@BrainSteel Sim, está correto. Minha resposta é inválida. Eu observei anteriormente que também precisava verificar colisões "no tempo" (depois de cruzar os caminhos de outros ratos), mas, por alguma razão, decidi que não era necessário. : P
Hjulle
@BrainSteel Acredito que corrigi esse bug agora.
Hjulle
1

Haskell, 1 rato, 681 caracteres

O problema pode ser resolvido trivialmente para todos os labirintos com apenas um rato. Esse código também "funciona" para qualquer número de ratos, mas não segue nenhuma das restrições nas interações entre vários ratos e caminhos.

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)

Saída de amostra:

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

Estou planejando suportar muitos ratos, então escrevi código genérico, mas ainda não encontrei um bom algoritmo para isso.

  • parse extrai uma lista de todas as entradas e saídas, com suas coordenadas
  • rats pega essa lista e a converte em pares de coordenadas para cada rato.
  • bnds pega uma coordenada em uma aresta e a move para a coordenada mais próxima dentro do labirinto.
  • naive toma as posições inicial e final e retorna um caminho simples entre elas.
  • main em seguida, substitui todo o espaço em branco que não estiver no caminho por '#'
Hjulle
fonte
@ edc65 "... restrições entre múltiplos ratos". Esta é uma resposta para apenas 1 rato, o que é permitido de acordo com a pergunta.
Hjulle
OK minha culpa. Só de pensar que para 1 rato é um desafio diferente. Eu vou apagar minhas observações anteriores
edc65