HappyCube Puzzle Solver

9

Diese Herausforderung ist inspiriert von einem Puzzle, das ich gespielt habe und das aus Schaumstoffstücken wie diesen besteht:

Puzzle Stücke

die wie folgt zu 3D-Würfeln zusammengesetzt werden müssen:

gelöste Würfel

Die Puzzleteile können als Gitter aus 5 * 5 Quadraten betrachtet werden, deren mittlere 3 * 3 Quadrate immer durchgehend sind, während die 16 Quadrate an den Kanten durchgehend oder leer sein können.

Ein Stück wird mit einer Zeichenfolge aus 16 Zeichen ( 0s und 1s) beschrieben, die die Konfiguration seiner Kanten ( 0= leer, 1= durchgehend) im Uhrzeigersinn ab der oberen linken Ecke darstellt.

Zum Beispiel die Zeichenfolge:

0101001000101101

repräsentiert dieses Stück:

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

Um die Teile zu einem Würfel zusammenzufügen, kann jedes Teil in jede Richtung gedreht werden. Dies sind beispielsweise die gültigen Rotationen des oben gezeigten Stücks:

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

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

Herausforderung

Schreiben Sie ein Programm oder eine Funktion, die 6 Puzzleteile als Eingabe verwendet und eine 2D-Darstellung des gelösten Würfels druckt oder zurückgibt.

Eingang

Die Eingabe ist eine Zeichenfolge aus 6 Zeilen, wobei jede Zeile aus 16 0oder 1Zeichen besteht, die die Kanten eines Stücks darstellen (in dem oben beschriebenen Format).

Es kann davon ausgegangen werden, dass es eine Lösung für die Eingabe gibt.

Die nachfolgende Newline ist optional.

Ausgabe

Das Ergebnis ist eine ASCII-Darstellung des gelösten Würfels, die wie folgt in 2D entfaltet wird (das Diagramm verwendet die Rubik-Würfel-Notation für Seitennamen):

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

Um die Möglichkeit zu vermeiden, die Lösung auf mehrere Arten zu präsentieren, ist das nach unten platzierte Stück immer das erste in der Eingabe vorhandene Stück in derselben Drehung, wie es dort angegeben ist.

Jedes Stück wird grafisch als 5 * 5-Matrix dargestellt, wobei Leerzeichen zur Bezeichnung leerer Quadrate verwendet werden. Für durchgezogene Quadrate können Sie ein beliebiges Nicht-Leerzeichen verwenden, solange:

  • Bei jedem Puzzleteil werden die ausgefüllten Quadrate mit demselben Zeichen dargestellt
  • Zwei beliebige benachbarte Teile verwenden unterschiedliche Zeichen

Das Leerzeichen rechts und die nachfolgende neue Zeile sind optional.

Testfälle

1.

Eingang:

0010010101010101
0010001011011010
0101001001010010
0010110100101101
0010110110101101
0010001011010101

Ausgabe:

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

2.

Eingang:

0001110110101101
1010010111011101
0101010101010010
1010001000100011
1010001001010001
0110010100100010

Ausgabe:

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

3.

Eingang:

0101001011011010
0010001000100010
0101001011010010
0101010101011010
0101101001011101
1010001001011101

Ausgabe:

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

Dies ist Codegolf, also gewinnt das kürzeste Programm in Bytes.

Cristian Lupascu
quelle
Ja, zumindest auf meinem Display sehen "hinten", "unten" und "vorne" wie die gleiche Farbe / das gleiche Zeichen aus.
Reto Koradi
"Um die Möglichkeit zu vermeiden, die Lösung auf mehrere Arten zu präsentieren, ist das nach unten platzierte Stück immer das erste in der Eingabe vorhandene Stück in derselben Drehung, wie es dort angegeben ist." Selbst wenn Sie das erste Puzzleteil konstant halten, glaube ich nicht, dass die verbleibenden fünf Teile garantiert eindeutige Positionen haben.
Rainbolt
@Rainbolt Für die von mir verwendeten Eingaben gilt das - es gibt nur eine Möglichkeit, die Ausgabe anzuordnen. Im Allgemeinen haben Sie jedoch Recht; Es gibt offensichtlich Eingaben, für die mehrere gültige Vereinbarungen möglich sind
Cristian Lupascu

Antworten:

6

Haskell, 1007 Tausendundein Bytes 923 900 830 Bytes

Ich habe zufällig schon einen Happycube-Löser gemacht, jetzt muss ich nur noch Golf spielen. Eine 10-Byte-Strafe für die Verwendung ausgefallener Blockelemente:

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

Das ist ein Schluck. Verwendungszweck:

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

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

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

Einige der Beispiele haben mehr als eine Lösung, deshalb sehen einige der Ausgaben anders aus. 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
quelle