Программы по созданию крысиного лабиринта


15

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

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

Ваша программа должна заполнить поле блоками (#), оставляя путь для крысы, вот так (вывод 1):

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

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

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

Крыс обучали двигаться прямо через перекрестки, но Т-образные перекрестки оставляют их безнадежно запутанными и лишают законной силы эксперимент. Вы начинаете свою новую более сложную задачу, когда хороший Доктор объясняет одно последнее требование: крысы дикие друг с другом, поэтому, если они увидят друг друга в какой-то момент, начнется драка крыс, и вы оба окажетесь перед доской этики. Теперь вы понимаете, что ваша программа должна выводить лабиринт примерно так (вывод 2):

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

К тому времени, когда крыса B достигнет перекрестка, крыса A будет двигаться по коридору, чтобы выйти из 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.
— Логика Найт

Допускаются ли пересечения Т, если крыса будет ходить только вдоль горизонтальной части Т?
— Орл

Нет, эти крысы легко путаются. Допускаются только прямые пути, изгибы локтей и перекрестки.
— Логика Найт

@CarpetPython Может ли вход / выход находиться где-нибудь на краю лабиринта? Могут ли они быть смежными?
— Орл

Ответы:


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
— Хюлле

@BrainSteel Полагаю, теперь я исправил эту ошибку.
— Хюлле

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 крысы, что допускается в зависимости от вопроса.
— Хюлле

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