Programmi per costruire un labirinto di topi


15

Sei stato assunto come assistente di ricerca e ti è stato chiesto di creare un piccolo programma che costruirà labirinti di topi. La scatola dei topi è sempre 62x22 e ha un'entrata (a) e un'uscita (A) per il ratto, in questo modo (input 1):

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

Il tuo programma deve riempire la casella con i blocchi (#) lasciando un percorso per il topo, in questo modo (output 1):

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

Questo è facile, pensi! Inizi a scrivere un piccolo programma, pieno di fiducia. Tuttavia, il Principle Scientist ha avuto una nuova idea: vuole che due topi navighino contemporaneamente nel labirinto. Il dottor Rattanshnorter spiega che hanno porte e uscite diverse (input 2):

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

I ratti sono stati addestrati a muoversi dritto attraverso le intersezioni incrociate, ma le intersezioni a T li lasciano irrimediabilmente confusi e invalideranno l'esperimento. Inizi il tuo nuovo compito più complesso quando il bravo Dottore spiega un requisito finale: i topi sono selvaggi l'uno con l'altro, quindi se si vedono in qualsiasi momento, scoppierà una lotta tra topi e sarete entrambi davanti al comitato etico. Ora ti rendi conto che il tuo programma dovrebbe generare un labirinto simile a questo (output 2):

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

Quando il ratto B raggiunge l'intersezione, il ratto A viaggerà lungo il corridoio per uscire da A e la lotta al topo sarà evitata.

Regole:

  • Il tuo programma dovrebbe leggere in (STDIN o file) un input come quelli sopra, e in output (STDOUT o file) gli stessi dati tranne che molti spazi saranno ora hash (#). È possibile sostituire qualsiasi singolo carattere (come ;) invece che \nnella stringa di input ma la stringa di output richiede ancora \ncaratteri. AGGIORNATO

  • Una via del ratto deve avere una larghezza di un carattere, fatta eccezione per le intersezioni incrociate (ogni spazio deve avere zero o due #caratteri ortogonalmente adiacenti ). Ogni ratto deve avere un unico percorso chiaro, ad eccezione delle intersezioni incrociate. Non sono consentite intersezioni a T.

  • I ratti vengono rilasciati contemporaneamente e si muovono a un ritmo costante. In nessun momento due o più ratti dovrebbero vedersi (essere nella stessa colonna o riga senza uno o più #caratteri in mezzo).

  • Se non è possibile alcuna soluzione (come punti di ingresso adiacenti), stampare Impossible\ned uscire.

  • Gli ingressi e le uscite possono essere su tutti i lati, tuttavia non saranno mai sugli angoli.

  • Se un'entrata e un'uscita abbinate sono adiacenti (ad esempio ##aA##:), il ratto non può andare direttamente da aa A. Ci deve essere una piccola sezione del corridoio di 2 spazi all'interno dell'area del labirinto.

  • Nel turno in cui un topo raggiunge il suo punto di uscita (o in qualsiasi momento successivo), non è più visibile agli altri ratti.

  • Il tuo programma può essere progettato per calcolare labirinti per 1, 2, fino a 26 ratti.

  • Le scappatoie standard non sono ammesse.

Punto:

Con la tua soluzione, nomina quanti ratti per labirinto (N) possono essere risolti dal tuo programma. Il tuo punteggio è la lunghezza del tuo codice in byte diviso per questo numero N.

Includi un output di esempio nella tua risposta in modo che possiamo vedere cosa produce il tuo programma.


L'unica differenza nei possibili input è la posizione di a, A, b, B?
xnor

Per la versione 2 ratti, sì. Se il tuo programma è progettato per un massimo di 3 ratti, dovrai affrontare tutte le possibili posizioni di a, b, c, A, B, C.
Logic Knight,

Sono consentite intersezioni a T se il ratto camminerà solo lungo la parte orizzontale della T?
orlp,

No, questi ratti sono facilmente confusi. Sono ammessi solo percorsi rettilinei, curve a gomito e strade trasversali.
Logic Knight,

@CarpetPython Un ingresso / uscita può trovarsi ovunque lungo il bordo del labirinto? Possono essere adiacenti?
orlp,

Risposte:


2

Haskell, 26 ratti ?, ~ 5000 byte

Teoricamente, questo codice dovrebbe funzionare per qualsiasi numero di ratti, ma non offro alcuna garanzia che terminerà prima della morte per calore dell'universo. Si basa su un algoritmo di backtracking che tenta innanzitutto di seguire il percorso rettilineo, quindi di cambiare percorso se il percorso non funziona. Il numero di alternative è esponenziale rispetto alla lunghezza del percorso e al numero di ratti.

Non mi sono ancora preso la briga di giocare a golf, dato che è così grande e da quando voglio renderlo più veloce prima.

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

Uscita campione, 6 ratti:

##c###B#####b#######C#######F######################f##########
##   #       #       #######                        ##########
####  ######## ###############################################
#####          ###############################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
#############       ##########################################
############# #####  #########################################
D             #    #     #####################################
##############  ## ##### #####################################
#########      #                 #############################
######### ###### # ##### ####### #############################
####      #      #     # #######                        ######
####E######a##########e#d##############################A######

2
Quando barriva all'intersezione di ee b, non viene visto da e? bsembra di arrivarci t = 11, che metterebbe efermo in quel corridoio. Mi sto perdendo qualcosa?
BrainSteel,

@BrainSteel Sì, è corretto. La mia risposta non è valida In precedenza mi sono notato che dovevo verificare anche le collisioni "indietro nel tempo" (dopo aver attraversato altri percorsi di topi), ma per qualche motivo ho deciso che non era necessario. : P
Hjulle,

@BrainSteel Credo di aver corretto quel bug ora.
Hjulle,

1

Haskell, 1 ratto, 681 personaggi

Il problema può essere banalmente risolto per tutti i labirinti con un solo ratto. Questo codice "funziona" anche per qualsiasi numero di ratti, ma non segue nessuno dei vincoli sulle interazioni tra più ratti e percorsi.

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)

Uscita campione:

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

Sto pensando di supportare molti ratti, quindi ho scritto un codice generico, ma non ho ancora trovato un buon algoritmo per quello.

  • parse estrae un elenco di tutti gli ingressi e le uscite, con le loro coordinate
  • rats prende quell'elenco e lo converte in coppie di coordinate per ogni ratto.
  • bnds prende una coordinata su un bordo e la sposta sulla coordinata più vicina all'interno del labirinto.
  • naive prende le posizioni di inizio e fine e restituisce un semplice percorso tra di loro.
  • main quindi sostituisce tutto lo spazio bianco non in un percorso con '#'

@ edc65 "... vincoli tra più ratti". Questa è una risposta per 1 solo ratto, consentita in base alla domanda.
Hjulle,

OK colpa mia. Solo pensando che per 1 topo è una sfida diversa. Ho intenzione di eliminare i miei commenti precedenti
edc65
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.