15
\$\begingroup\$

You have been hired as a research assistant, and asked to create a small program that will build rat mazes. The rat box is always 62x22 and has an entrance (a) and exit (A) for the rat, like this (input 1):

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

Your program must fill the box with blocks (#) leaving a path for the rat, like this (output 1):

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

This is easy you think! You start writing a small program, full of confidence. However, the Principle Scientist has had a new idea -- he wants two rats to navigate the maze at the same time. Dr Rattanshnorter explains that they have different doors and different exits (input 2):

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

The rats have been trained to move straight through cross intersections but T-intersections leave them hopelessly confused and will invalidate the experiment. You start on your new more complex task when the good Doctor explains one final requirement: the rats are savage to each other so if they see each other at any point, a rat fight will break out and you will both be before the ethics board. You now realise your program should output a maze something like this (output 2):

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

By the time rat B reaches the intersection, rat A will be travelling down the corridor to exit A and the rat fight will be avoided.

Rules:

  • Your program should read in (STDIN or file) an input like those above, and output (STDOUT or file) the same data except many spaces will now be hashes (#). You may substitute any single character (such as ;) instead of \n in the input string but the output string still requires \n characters. UPDATED

  • A rat pathway must be one character width wide, except for cross intersections (every space must have zero or two orthogonally adjacent # characters). Each rat must have a clear single path, except for cross intersections. No T-intersections are allowed.

  • Rats are released simultaneously and move at a constant rate. At no time should two or more rats see each other (be in the same column or row without one of more # characters in between).

  • If no solution is possible (such as adjacent entrance points), print Impossible\n and exit.

  • Entrances and exits can be on any sides, however they will never be on the corners.

  • If a matched entrance and exit are adjacent (eg: ##aA##), the rat cannot go directly from a to A. There must be a small 2 space corridor section inside the maze area.

  • On the turn where a rat reaches its exit point (or any time after that), it is no longer visible to other rats.

  • Your program may be designed to compute mazes for 1, 2, up to 26 rats.

  • Standard loopholes are disallowed.

Score:

With your solution, nominate how many rats per maze (N) your program can solve. Your score is your code length in bytes divided by this number N.

Please include a sample output in your answer so we can see what your program produces.

\$\endgroup\$
30
  • \$\begingroup\$ Is the only difference in the possible inputs the locations of a,A,b,B? \$\endgroup\$
    – xnor
    Commented Apr 3, 2015 at 7:09
  • \$\begingroup\$ For the 2 rat version, yes. If your program is designed for up to 3 rats, you would need to cope with all possible locations of a,b,c,A,B,C. \$\endgroup\$ Commented Apr 3, 2015 at 7:21
  • \$\begingroup\$ Are T intersections allowed if the rat will only walk alongside the horizontal part of the T? \$\endgroup\$
    – orlp
    Commented Apr 3, 2015 at 7:38
  • \$\begingroup\$ No, these rats are easily confused. Only straight paths, elbow bends, and cross roads are allowed. \$\endgroup\$ Commented Apr 3, 2015 at 7:49
  • \$\begingroup\$ @CarpetPython Can an entrance/exit be anywhere along the edge of the maze? Can they be adjacent? \$\endgroup\$
    – orlp
    Commented Apr 3, 2015 at 7:50

2 Answers 2

2
\$\begingroup\$

Haskell, 26 rats?, ~5000 bytes

Theoretically, this code should work for any number of rats, but I offer no warranty that it will terminate before the heat death of the universe. It is based on a backtracking algorithm which tries to go the straight path first, and then switch path if the path doesn't work. The number of alternatives is exponential with respect to the length of the path and the number of rats.

I haven't bothered to golf it yet, since it's so large, and since I want to make it faster first.

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

Sample output, 6 rats:

##c###B#####b#######C#######F######################f##########
##   #       #       #######                        ##########
####  ######## ###############################################
#####          ###############################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
#############       ##########################################
############# #####  #########################################
D             #    #     #####################################
##############  ## ##### #####################################
#########      #                 #############################
######### ###### # ##### ####### #############################
####      #      #     # #######                        ######
####E######a##########e#d##############################A######
\$\endgroup\$
3
  • 2
    \$\begingroup\$ When b gets to the intersection of e and b, is he not seen by e? b seems to get there at t = 11, which would put e still in that corridor. Am I missing something? \$\endgroup\$
    – BrainSteel
    Commented Apr 7, 2015 at 5:43
  • \$\begingroup\$ @BrainSteel Yes, that is correct. My answer is invalid. I noted to myself previously that I needed to check for collisions "backwards in time" as well (after crossing other rats paths), but for some reason I decided that it wasn't needed. :P \$\endgroup\$
    – Hjulle
    Commented Apr 7, 2015 at 10:02
  • \$\begingroup\$ @BrainSteel I believe I have fixed that bug now. \$\endgroup\$
    – Hjulle
    Commented Apr 7, 2015 at 16:15
1
\$\begingroup\$

Haskell, 1 rat, 681 Characters

The problem can be trivially solved for all mazes with just one rat. This code also "works" for any number of rats, but doesn't follow any of the constraints on the interactions between multiple rats and paths.

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)

Sample output:

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

I'm planning to support many rats, so I wrote generic code, but I haven't found a good algorithm for that yet.

  • parse extracts a list of all entrances and exits, with their coordinates
  • rats takes that list and converts it to pairs of coordinates for each rat.
  • bnds takes a coordinate on an edge and moves it to the nearest coordinate inside the maze.
  • naive takes a start and end positions and returns a simple path between them.
  • main then replaces all the white space not in a path with '#'
\$\endgroup\$
2
  • \$\begingroup\$ @edc65 "... constraints between multiple rats". This is an answer for just 1 rat, which is allowed according to the question. \$\endgroup\$
    – Hjulle
    Commented Apr 7, 2015 at 12:18
  • \$\begingroup\$ OK my fault. Just thinking that for 1 rat it's a different challenge. I'm going to delete my previous comments \$\endgroup\$
    – edc65
    Commented Apr 7, 2015 at 13:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.