HappyCube Puzzle Solver

9

Esse desafio é inspirado em um quebra-cabeça que eu joguei, consistindo de peças de espuma como estas:

peças de quebra-cabeças

que precisam ser montados em cubos 3D, como estes:

cubos resolvidos

As peças do quebra-cabeça podem ser vistas como grades de 5 * 5 quadrados, cujos quadrados 3 * 3 do meio são sempre sólidos, enquanto os 16 quadrados nas bordas podem ser sólidos ou vazios.

Uma peça irá ser descrita utilizando uma cadeia de 16 caracteres ( 0S e 1s), que representa a configuração de um dos seus bordos ( 0= esvaziar, 1= sólido), a fim dos ponteiros do relógio, a partir do canto superior esquerdo,.

Por exemplo, a sequência:

0101001000101101

representa esta peça:

 # #
####
 ####
####
# #

Para encaixar as peças para formar o cubo, cada peça pode ser girada em qualquer direção. Por exemplo, estas são as rotações válidas da peça mostrada acima:

 # #    # #     #    ## # 
 ####  ####    ####   ####
####    ####  ####   #### 
 ####  ####    ####   ####
  # #  # #    ## #     #  

# #      # #   # ##    #  
####    ####  ####   #### 
 ####  ####    ####   ####
####    ####  ####   #### 
 # #    # #     #     # ##

Desafio

Escreva um programa ou função que tenha como entrada 6 peças do quebra-cabeça e imprima ou retorne uma representação 2D do cubo resolvido.

Entrada

A entrada será uma sequência de 6 linhas, em que cada linha consiste em 16 0ou 1caracteres, representando as arestas de uma peça (no formato descrito acima).

Pode-se assumir que existe uma solução para a entrada.

A nova linha à direita é opcional.

Resultado

O resultado será uma representação ASCII do cubo resolvido, desdobrado em 2D, como este (o diagrama usa a notação Cubo de Rubik para nomes laterais):

    +---+
    |BA |
    |CK |
    |   |
+---+---+---+---+
|LE |DO |RI |UP |
|FT |WN |GHT|   |
|   |   |   |   |
+---+---+---+---+
    |FR |
    |ONT|
    |   |
    +---+

Para evitar a possibilidade de apresentar a solução de várias maneiras, a peça colocada PARA BAIXO será sempre a primeira peça presente na entrada, na mesma rotação especificada lá.

Cada peça será representada graficamente como uma matriz 5 * 5, usando espaços para indicar quadrados vazios. Para quadrados sólidos, você pode usar qualquer caractere não espacial que desejar, desde que:

  • qualquer peça do quebra-cabeça terá seus quadrados sólidos representados usando o mesmo personagem
  • quaisquer duas peças adjacentes usam caracteres diferentes

O preenchimento de espaço à direita e a nova linha à direita são opcionais.

Casos de teste

1

Entrada:

0010010101010101
0010001011011010
0101001001010010
0010110100101101
0010110110101101
0010001011010101

Resultado:

     @ @         
     @@@         
    @@@@@        
     @@@         
** **@#@** *# #  
 ***#####***#####
*****###*****### 
 ***#####***#####
  * @#@#** ** # #
    @@@@         
     @@@@        
    @@@@         
     @ @         

2)

Entrada:

0001110110101101
1010010111011101
0101010101010010
1010001000100011
1010001001010001
0110010100100010

Resultado:

      @          
     @@@@        
    @@@@         
     @@@@        
** **@@## * *# # 
****#####****### 
 ****###*****####
****#####***#### 
** *#@#@# * # #  
     @@@@        
    @@@@         
     @@@@        
     @ @         

3)

Entrada:

0101001011011010
0010001000100010
0101001011010010
0101010101011010
0101101001011101
1010001001011101

Resultado:

     @ @@        
    @@@@@        
     @@@         
    @@@@@        
* * @#@#* *   #  
*****###*****### 
 ***#####***#####
*****###*****### 
  * ##@##* *  #  
    @@@@         
     @@@@        
    @@@@         
    @@ @@        

Este é um codegolf, então o programa mais curto em bytes vence.

Cristian Lupascu
fonte
Sim, pelo menos na minha tela, "costas", "baixo" e "frente" têm a mesma cor / caractere.
Reto Koradi
"Para evitar a possibilidade de apresentar a solução de várias maneiras, a peça colocada PARA BAIXO será sempre a primeira peça presente na entrada, na mesma rotação especificada lá." Mesmo que você mantenha constante a primeira peça do quebra-cabeça, não acho que as cinco peças restantes tenham locais únicos.
Rainbolt
@Rainbolt Para as entradas que uso, isso vale - existe apenas uma maneira de organizar a saída. De um modo geral, porém, você está certo; há, obviamente, entradas para o qual são possíveis vários arranjos válidos,
Cristian Lupascu

Respostas:

6

Haskell, 1007 Mil e Um Bytes 923 900 830 bytes

Aconteceu que eu já fiz um solucionador de happycube, agora só preciso jogar golfe. Tomando uma penalidade de dez bytes pelo uso de elementos de bloco sofisticados:

import Data.List
r=reverse;z=zipWith;f=foldl1;t=take;g=t 4;d=drop;m=map
n x=t 5x:n(d 4x)
u v a b=init a++v max(last a)(b!!0):d 1b
a!b|k<- \x y->last$' ':[a|b!!x!!y>0]=(k 0<$>[0..4]):((\i->k 3(4-i):[a,a,a]++[k 1i])<$>[1..3])++[k 2<$>[4,3..0]]
p y|(e:v)<-m(g.n.cycle.m(read.pure))$"0":(lines$y),[j,k,l,x,y,r]<-x v=mapM putStrLn$f(u z)$f(z(u id))<$>z(z(!))[a,"▒█▒█",a][[e,k,e,e],[l,j,x,r],[e,y,e,e]];a=" ░  "
x(p:q)=[p:u|x<-permutations q,u@[e,g,y,k,l]<-sequence$(\c->nub$[c,r.m r$c]>>=g.m g.tails.cycle)<$>x,and$zipWith4(\a n b m->all(==1).init.d 1$z(+)(a!!n)$r$b!!m)([l,e,p,p,p,p,e]++u)[3,3,0,3,1,2,0,1,2,2,2,1](y:g:u++[y,k,k,l,g])[1,0,2,1,3,0,0,0,3,1,2,3]++z((((==1).sum.m(!!0)).).z(!!))[[p,e,g],[y,p,e],[k,p,g],[k,p,y],[l,y,e],[l,y,k],[l,g,e],[l,g,k]][[0,3,1],[0..2],[0,3,2],[1..3],[0,1,1],[3,2,2],[1,0,0],[2,3,3]]]!!0

Isso é um bocado. Uso:

*Main> mapM_ (\s->p s>>putStrLn"")["0010010101010101\n0010001011011010\n0101001001010010\n0010110100101101\n0010110110101101\n0010001011010101","0001110110101101\n1010010111011101\n0101010101010010\n1010001000100011\n1010001001010001\n0110010100100010","0101001011011010\n0010001000100010\n0101001011010010\n0101010101011010\n0101101001011101\n1010001001011101"]
            
     ░░░      
    ░░░░░     
     ░░░      
▒▒ ▒▒░█░▒▒ ▒█ 
 ▒▒▒█████▒▒▒█████
▒▒▒▒▒███▒▒▒▒▒███
 ▒▒▒█████▒▒▒█████
   ░█░█▒▒ ▒▒  
    ░░░░      
     ░░░░     
    ░░░░      
            

             
     ░░░░     
    ░░░░      
     ░░░░     
▒▒ ▒▒░░██  ▒█ 
▒▒▒▒█████▒▒▒▒███
 ▒▒▒▒███▒▒▒▒▒████
▒▒▒▒█████▒▒▒████
▒▒ ▒█░█░█   
     ░░░░     
    ░░░░      
     ░░░░     
            

    ░░       
    ░░░░░     
     ░░░      
    ░░░░░     
   ▒█░█░   
▒▒▒▒▒███▒▒▒▒▒███
 ▒▒▒█████▒▒▒█████
▒▒▒▒▒███▒▒▒▒▒███
  ▒██░██    
     ░░░░     
    ░░░░      
     ░░░░     
    ░░ ░░     

Alguns dos exemplos têm mais de uma solução, é por isso que algumas das saídas parecem diferentes. Ungolfed:

import Data.List (nub, transpose, (\\))
import Control.Monad (guard)

newtype CubePiece = CubePiece [[Int]] deriving Eq
newtype Solution = Solution [CubePiece]

side :: Int -> CubePiece -> [Int]
side n (CubePiece c) = c!!n

corner :: Int -> CubePiece -> Int
corner n (CubePiece c) = head $ c!!n

strToCube str = CubePiece $ hs' . (\x@(a:_)->x++[a]) $ l
  where
    l = map (read.pure) str
    hs' [a] = []
    hs' x = take 5 x : hs' (drop 4 x)

orientations :: CubePiece -> [CubePiece]
orientations (CubePiece cube) = map CubePiece $ nub $ (take 4 . iterate rotate $ cube) ++
  (take 4 . iterate rotate . reverse . map reverse $ cube)
  where
  rotate (a:as) = as++[a]

sideFits ::  (CubePiece, Int) -> (CubePiece, Int) -> Bool
sideFits (c1,n1) (c2,n2) = case (zipWith (+) a b) of
  [_,1,1,1,_] -> True
  _ -> False
  where
  a = side n1 c1
  b = reverse $ side n2 c2

cornerFits :: (CubePiece, Int) -> (CubePiece, Int) -> (CubePiece, Int) -> Bool
cornerFits (c1,n1) (c2,n2) (c3,n3) = a + b + c == 1
  where
  a = corner n1 c1
  b = corner n2 c2
  c = corner n3 c3

printSolution str = putStrLn . specialUnlines . map rivi $
  [[empty,gshow '░' c2,empty,empty],[gshow '▒' c3,gshow '█'c1,gshow '▒'c4,gshow '█'c6],[empty,gshow '░'c5,empty,empty]]
  where
  Solution [c1,c2,c3,c4,c5,c6] = solve . map strToCube . lines $ str
  empty = replicate 5 "     "
  rivi = map (foldl1 specialConcat) . transpose
  specialUnlines = unlines . foldl1(\a b->init a++[zipWith max(last a)(head b)]++tail b)
  specialConcat a b
    | last a==' '=init a++b
    | otherwise = a++tail b

gshow char (CubePiece c) =
  [ map (k 0) [0..4]
  , (k 3 3) : m ++ [(k 1 1)]
  , (k 3 2) : m ++ [(k 1 2)]
  , (k 3 1) : m ++ [(k 1 3)]
  , map (k 2)[4,3..0]
  ]
  where
  k n1 n2 = if (c!!n1)!!n2 == 1 then char else ' '
  m=replicate 3 char

solve :: [CubePiece] -> Solution
solve pieces = Solution $ head $ do
  let c1' = pieces!!0
  let c1 = c1'

  c2'  <- pieces \\ [c1']
  c2   <- orientations c2'
  guard $ sideFits (c1,0) (c2,2)

  c3'  <- pieces \\ [c1',c2']
  c3   <- orientations c3'
  guard $ sideFits (c1,3) (c3,1)
  guard $ sideFits (c2,3) (c3,0)
  guard $ cornerFits (c1,0) (c2,3) (c3,1)

  c4'  <- pieces \\ [c1',c2',c3']
  c4   <- orientations c4'
  guard $ sideFits (c1,1) (c4,3)
  guard $ sideFits (c2,1) (c4,0)
  guard $ cornerFits (c1,1) (c2,2) (c4,0)
  c5' <- pieces \\ [c1',c2',c3',c4']
  c5 <- orientations c5'
  guard $ sideFits (c1,2) (c5,0)
  guard $ sideFits (c4,2) (c5,1)
  guard $ sideFits (c3,2) (c5,3)
  guard $ cornerFits (c5,0) (c1,3) (c3,2)
  guard $ cornerFits (c5,1) (c1,2) (c4,3)

  c6' <- pieces \\ [c1',c2',c3',c4',c5']
  c6 <- orientations c6'
  guard $ sideFits (c6,0) (c2,0)
  guard $ sideFits (c6,1) (c3,3)
  guard $ sideFits (c6,2) (c5,2)
  guard $ sideFits (c6,3) (c4,1)
  guard $ cornerFits (c6,0) (c4,1) (c2,1)
  guard $ cornerFits (c6,3) (c4,2) (c5,2)
  guard $ cornerFits (c6,1) (c3,0) (c2,0)
  guard $ cornerFits (c6,2) (c3,3) (c5,3)
  return $ [c1,c2,c3,c4,c5,c6]

main = mapM_ printSolution ["0010010101010101\n0010001011011010\n0101001001010010\n0010110100101101\n0010110110101101\n0010001011010101","0001110110101101\n1010010111011101\n0101010101010010\n1010001000100011\n1010001001010001\n0110010100100010","0101001011011010\n0010001000100010\n0101001011010010\n0101010101011010\n0101101001011101\n1010001001011101"]
Angs
fonte