HappyCube Puzzle Solver

9

Este desafío está inspirado en un rompecabezas que jugué, que consiste en piezas de espuma como estas:

piezas de rompecabezas

que deben ensamblarse en cubos 3D, como estos:

cubos resueltos

Las piezas del rompecabezas se pueden ver como cuadrículas de 5 * 5 cuadrados, cuyos cuadrados 3 * 3 centrales siempre son sólidos, mientras que los 16 cuadrados en los bordes pueden ser sólidos o vacíos.

Se describirá una pieza utilizando una cadena de 16 caracteres ( 0sys 1), que representa la configuración de sus bordes ( 0= vacío, 1= sólido), en el sentido de las agujas del reloj, comenzando desde la esquina superior izquierda.

Por ejemplo, la cadena:

0101001000101101

representa esta pieza:

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

Para unir las piezas para formar el cubo, cada pieza se puede girar en cualquier dirección. Por ejemplo, estas son las rotaciones válidas de la pieza que se muestra arriba:

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

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

Desafío

Escriba un programa o función que tome como entrada 6 piezas del rompecabezas e imprima o devuelva una representación 2D del cubo resuelto.

Entrada

La entrada será una cadena de 6 filas, donde cada fila consta de 16 0o 1caracteres, que representan los bordes de una pieza (en el formato descrito anteriormente).

Se puede suponer que hay una solución para la entrada.

La nueva línea final es opcional.

Salida

El resultado será una representación ASCII del cubo resuelto, desplegado en 2D, de esta manera (el diagrama usa la notación Cubo de Rubik para los nombres laterales):

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

Para evitar la posibilidad de presentar la solución de múltiples maneras, la pieza colocada ABAJO siempre será la primera pieza presente en la entrada, en la misma rotación que se especifica allí.

Cada pieza se representará gráficamente como una matriz de 5 * 5, utilizando espacios para denotar cuadrados vacíos. Para cuadrados sólidos puede usar cualquier carácter que no sea espacio que desee, siempre que:

  • cualquier pieza del rompecabezas tendrá sus cuadrados sólidos representados usando el mismo personaje
  • dos piezas adyacentes usan caracteres diferentes

El relleno de espacio a la derecha y la nueva línea final son opcionales.

Casos de prueba

1)

Entrada:

0010010101010101
0010001011011010
0101001001010010
0010110100101101
0010110110101101
0010001011010101

Salida:

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

2)

Entrada:

0001110110101101
1010010111011101
0101010101010010
1010001000100011
1010001001010001
0110010100100010

Salida:

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

3)

Entrada:

0101001011011010
0010001000100010
0101001011010010
0101010101011010
0101101001011101
1010001001011101

Salida:

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

Este es codegolf, por lo que gana el programa más corto en bytes.

Cristian Lupascu
fuente
Sí, al menos en mi pantalla, "atrás", "abajo" y "frente" tienen el mismo color / carácter.
Reto Koradi
"Para evitar la posibilidad de presentar la solución de múltiples maneras, la pieza colocada ABAJO siempre será la primera pieza presente en la entrada, en la misma rotación que se especifica allí". Incluso si mantiene constante la primera pieza del rompecabezas, no creo que se garantice que las cinco piezas restantes tengan ubicaciones únicas.
Rainbolt
@Rainbolt Para las entradas que uso, eso es cierto: solo hay una forma de organizar la salida. En general, sin embargo, tienes razón; obviamente hay entradas para las cuales son posibles múltiples arreglos válidos,
Cristian Lupascu

Respuestas:

6

Haskell, 1007 mil y un bytes 923 900 830 bytes

Por casualidad ya había hecho un solucionador de happycube, ahora solo necesito jugar golf. Tomar una penalización de diez bytes por usar elementos de bloque elegantes:

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

Eso es un 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"]
            
     ░░░      
    ░░░░░     
     ░░░      
▒▒ ▒▒░█░▒▒ ▒█ 
 ▒▒▒█████▒▒▒█████
▒▒▒▒▒███▒▒▒▒▒███
 ▒▒▒█████▒▒▒█████
   ░█░█▒▒ ▒▒  
    ░░░░      
     ░░░░     
    ░░░░      
            

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

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

Algunos de los ejemplos tienen más de una solución, por eso algunos de los resultados se ven diferentes. Sin golf:

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
fuente