Програми побудови щурячого лабіринту


15

Вас прийняли на роботу в якості наукового співробітника і попросили створити невелику програму, яка буде створювати лабіринти щурів. Коробка щурів завжди 62х22 і має вхід (а) та вихід (А) для щура, як це (вхід 1):

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

Ваша програма повинна заповнити поле блоками (#), залишивши шлях для щура, як це (вихід 1):

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

Це ви легко думаєте! Ви починаєте писати невелику програму, сповнену впевненості. Однак у "Принципного вченого" виникла нова ідея - він хоче, щоб двоє щурів одночасно орієнтувались у лабіринті. Д-р Раттаншнортер пояснює, що у них різні двері та різні виходи (вхід 2):

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

Щурів було навчено рухатись прямо через поперечні перехрестя, але Т-перехрестя залишають їх безнадійно заплутаними і призведе до недійсності експерименту. Ви починаєте своє нове більш складне завдання, коли добрий лікар пояснює одну останню вимогу: щури дикують одне одному, тому якщо вони побачать один одного в будь-який момент, вибухне бійка з щурами, і ви обоє будете перед комісією з етики. Тепер ви розумієте, що ваша програма повинна вивести лабіринт на зразок цього (вихід 2):

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

До того моменту, коли щур B дістанеться до перехрестя, щур А буде подорожувати коридором до виходу A і уникнути боротьби з щурами.

Правила:

  • Ваша програма повинна прочитати в (STDIN або файл) вхід, як описано вище, і вивести (STDOUT або файл) ті самі дані, за винятком багатьох пробілів, буде хешами (#). Ви можете замінити будь-який окремий символ (наприклад ;) замість \nвведення рядка, але для виведення рядка все ще потрібні \nсимволи.ОНОВЛЕНО

  • Шлях щура повинен бути шириною одного символу, за винятком поперечних перехресть (кожен простір повинен мати нуль або два ортогонально сусідніх # символи). Кожна щур повинна мати чіткий єдиний шлях, за винятком поперечних перехресть. Не допускаються Т-перехрестя.

  • Щури звільняються одночасно і рухаються зі сталою швидкістю. Ніколи дві або більше щурів не повинні бачити один одного (бути в одному стовпчику або ряду без одного з більше# символів між ними).

  • Якщо рішення неможливо (наприклад, сусідні точки входу), надрукуйте Impossible\n та вийдіть.

  • Входи та виходи можуть бути з будь-якої сторони, проте вони ніколи не будуть на кутах.

  • Якщо узгоджувані вхід та вихід є сусідніми (наприклад:) ##aA##, щур не може прямувати звідти aнаA . Всередині лабіринту має бути невеликий простір коридору на 2 простори.

  • На повороті, де щур досягає своєї точки виходу (або в будь-який час після цього), він більше не видно для інших щурів.

  • Ваша програма може бути розроблена для обчислення лабіринтів для 1, 2, до 26 щурів.

  • Стандартні лазівки заборонені.

Оцінка:

За допомогою свого рішення призначте, скільки щурів на лабіринт (N) може вирішити ваша програма. Ваш рахунок - це ваша довжина коду в байтах, поділена на це число N.

Будь ласка, включіть у відповідь зразок результатів, щоб ми могли побачити, що виробляє ваша програма.


Чи є єдиною різницею можливих входів розташування a, A, b, B?
xnor

Для 2 щурячої версії так. Якщо ваша програма розрахована на до 3 щурів, вам потрібно буде впоратися з усіма можливими місцями a, b, c, A, B, C.
Логічний лицар

Чи дозволено Т перехрестя, якщо щур буде ходити лише уздовж горизонтальної частини Т?
orlp

Ні, цих щурів легко сплутати. Допускаються лише прямі доріжки, ліктьові згини та перехресні дороги.
Логічний лицар

@CarpetPython Чи може вхід / вихід бути десь уздовж краю лабіринту? Чи можуть вони бути суміжними?
orlp

Відповіді:


2

Haskell, 26 щурів ?, ~ 5000 байт

Теоретично цей код повинен працювати на будь-яку кількість щурів, але я не пропоную жодної гарантії, що він припиниться до теплової загибелі Всесвіту. Він заснований на алгоритмі зворотного відстеження, який намагається спершу пройти прямий шлях, а потім переключити шлях, якщо шлях не працює. Кількість альтернатив є експоненціальним щодо довжини шляху та кількості щурів.

Я ще не займався гольфом, оскільки він настільки великий, і тому що я хочу зробити це швидше.

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

Вихід зразка, 6 щурів:

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

2
Коли bпотрапляє до перехрестя eі b, його не бачать e? bначебто потрапляє туди t = 11, що все eще покладеться в тому коридорі. Я щось пропускаю?
BrainSteel

@BrainSteel Так, це правильно. Моя відповідь недійсна. Раніше я зазначав собі, що мені потрібно перевірити на зіткнення також "назад у часі" (після перетину інших щурів доріжок), але чомусь я вирішив, що це не потрібно. : P
Hjulle

@BrainSteel Я вважаю, що виправили цю помилку зараз.
Hjulle

1

Хаскелл, 1 щур, 681 персонажів

Проблему можна тривіально вирішити для всіх лабіринтів лише одним щуром. Цей код також "працює" для будь-якої кількості щурів, але не дотримується жодних обмежень щодо взаємодії між декількома щурами та шляхами.

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)

Вибірка зразка:

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

Я планую підтримувати багатьох щурів, тому я написав загальний код, але ще не знайшов хорошого алгоритму для цього.

  • parse витягує список усіх входів і виходів з їх координатами
  • rats бере цей список і перетворює його в пари координат для кожного щура.
  • bnds бере координату на край і переміщує її до найближчої координати всередині лабіринту.
  • naive займає стартову і кінцеву позиції і повертає простий шлях між ними.
  • main потім замінює весь пробіл не в шляху на "#"

@ edc65 "... обмеження між кількома щурами". Це відповідь лише на 1 щура, що дозволено відповідно до питання.
Hjulle

ОК моя вина. Думаючи, що для 1 щура це вже інший виклик. Я збираюся видалити свої попередні коментарі
edc65
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.