Si j'essaie de simuler un Rubik's Cube , comment créer une structure de données pour stocker l'état du cube en mémoire, avec un nombre X de tuiles par côté?
Choses à considérer:
Quel est le problème avec un ancien tableau simple de taille [6X][X]
? Vous n'avez pas besoin de connaître intérieur les mini-cubes, car vous ne les voyez pas; ils ne font pas partie de l'état du cube. Cachez deux méthodes moches derrière une interface agréable et simple à utiliser, testez-la à mort, et le tour est joué!
Il convient de noter que je suis un passionné de vitesse, mais je n'ai jamais essayé de représenter par programmation un cube Rubik dans un algorithme ou une structure de données.
Je créerais probablement des structures de données distinctes pour capturer les aspects uniques de chaque bloc dans un cube.
Il existe 3 types distincts de blocs sur un cube:
Corner Block - Il a trois faces colorées et trois pièces adjacentes avec lesquelles il partagera un côté à tout moment.
Bloc de bord - Il a deux faces de couleur et a 4 pièces adjacentes avec lesquelles il partagera un côté à tout moment. Dans les blocs 3x3, il a toujours 2 pièces centrales et 2 pièces d'angle.
Bloc central - Dans un cube 3x3, cette pièce n'est pas mobile, mais elle peut être tournée. Il aura toujours 4 blocs Edge adjacents. Dans les cubes plus grands, il existe plusieurs blocs centraux qui pourraient être partagés avec un autre bloc central ou une pièce Edge. Les blocs centraux ne sont jamais adjacents à un bloc d'angle.
Sachant cela, un bloc peut avoir une liste de références à d'autres blocs qu'il touche. Je conserverais une autre liste de listes, qui serait une liste de blocs qui représentent une seule face de cube et une liste qui conserve des références à chaque face de cube.
Chaque face de cube serait représentée comme une face unique.
Avec ces structures de données, il serait assez facile d'écrire un algorithme qui effectue une transformation de rotation sur chaque face, en déplaçant les blocs appropriés dans et hors des listes appropriées.
EDIT: Remarque importante, ces listes doivent être commandées bien sûr mais j'ai oublié de le mentionner. Par exemple, si je retourne le côté droit, le bloc du coin droit du côté gauche se déplace vers le coin droit du côté droit et tourne dans le sens horaire.
Quand je pense à ce problème, je pense à un cube statique avec des couleurs qui le traversent dans des motifs connus. Donc....
Un objet Cube contient 6 objets Side qui restent fixes indexés 0-5. Chaque côté contient 9 objets de position qui restent fixes indexés 0-8. Chaque position contient une couleur.
Pour plus de simplicité, gérez chaque action par incréments d'un quart de tour. Il y a 3 axes de rotation, chacun dans 2 directions possibles pour un total de 6 actions possibles sur le cube. Avec ces informations, il devient une tâche assez simple de cartographier les 6 actions possibles sur le cube.
Ainsi, la couleur verte du côté 6, position 3, peut se déplacer entre le côté 1, la position 3 ou le côté 2, la position 7, entre autres, en fonction de l'action entreprise. Je n'ai pas suffisamment exploré cela pour trouver des traductions mathématiques, mais des modèles vont probablement émerger dont vous pourrez tirer parti dans le code.
En utilisant la structure de données, comment puis-je savoir si un certain cube dans un certain état est résoluble? J'ai moi-même eu du mal avec cette question et je n'ai pas encore trouvé la réponse.
Pour ce faire, ne commencez jamais par un état de cube aléatoire. Au lieu de cela, commencez par un état résolu et effectuez par programme des actions n pour mettre le cube dans un état de départ aléatoire. Étant donné que vous avez uniquement intenté des actions en justice pour atteindre l'état actuel, le cube doit être résoluble.
J'ai trouvé qu'un système de coordonnées x-y-z était un moyen simple d'adresser un cube de Rubik, et les matrices de rotation un moyen simple et générique de mettre en œuvre les rotations.
J'ai créé une classe Piece contenant un vecteur de position (x, y, z)
. Une pièce peut être tournée en appliquant une matrice de rotation à sa position (une multiplication matrice-vecteur). La pièce conserve également ses couleurs dans un Tuple (cx, cy, cz)
, donnant les couleurs orientées le long de chaque axe. Une petite quantité de logique garantit que ces couleurs sont mises à jour de manière appropriée pendant une rotation: une rotation de 90 degrés dans le plan X-Y signifie que nous échangerions les valeurs de cx
et cy
.
Étant donné que toute la logique de rotation est encapsulée dans la classe Pièce, le cube peut stocker une liste non ordonnée de pièces et les rotations peuvent être effectuées de manière générique. Pour effectuer une rotation de la face gauche, sélectionnez toutes les pièces avec une coordonnée x de -1 et appliquez la matrice de rotation appropriée à chaque pièce. Pour effectuer une rotation de l'ensemble du cube, appliquez la même matrice de rotation à chaque pièce.
Cette implémentation est simple et présente quelques subtilités:
(-1, 1, 1)
), un Edge a exactement un zéro ((1, 0, -1)
), et une pièce centrale a deux zéros ((-1, 0, 0)
).Inconvénients:
vous pouvez utiliser un tableau simple (chaque élément ayant une correspondance de 1 à 1 avec un carré sur une face) et simuler chaque rotation avec une certaine permutation
vous pouvez vous en sortir avec seulement 3 permutations essentielles: faire pivoter une tranche avec l'axe à travers la face avant, faire pivoter le cube autour de l'axe vertical et faire pivoter le cube sur l'axe horizontal à travers les faces gauche et droite. tous les autres mouvements peuvent être exprimés par une concaténation de ces trois.
la façon la plus simple de savoir si un cube est résoluble est de le résoudre (trouver une série de permutations qui résoudra le cube), si vous vous retrouvez avec 2 arêtes qui ont changé de place, une arête inversée unique, un coin inversé unique ou 2 coins échangés vous avez un cube insoluble
La première condition pour qu'elle soit résoluble serait que chaque pièce soit présente et que les couleurs sur chaque pièce puissent être utilisées pour assembler un cube "sovled". Il s'agit d'une condition relativement banale dont la vérité peut être déterminée à l'aide d'une simple liste de contrôle. Le jeu de couleurs sur un cube "standard" est défini , mais même si vous n'avez pas affaire à un cube standard, il n'y en a que 6! combinaisons possibles de faces résolues.
Une fois que vous avez toutes les pièces et les couleurs correctes, il s'agit de déterminer si une configuration physique donnée est résoluble. Pas tous. La façon la plus naïve de vérifier cela est d'exécuter un algorithme de résolution de cube et de voir s'il se termine par un cube résolu. Je ne sais pas s'il existe des techniques combinatoires sophistiquées pour déterminer la solvabilité sans vraiment essayer de résoudre le cube.
Quant à la structure des données ... cela n'a presque pas d'importance. La partie délicate consiste à obtenir les bonnes transformations et à pouvoir représenter l'état du cube de manière à vous permettre de travailler avec les algorithmes disponibles dans la littérature. Comme indiqué par Maple-shaft, il existe trois types de pièces. La littérature sur la résolution de rubik's cube se réfère toujours aux pièces par leur type. Les transformations sont également représentées de manière courante (recherche notation Singmaster ). De plus, toutes les solutions que j'ai vues font toujours référence à une seule pièce comme point de référence (généralement en plaçant la pièce centrale blanche en bas).
Puisque vous avez déjà reçu d'excellentes réponses, permettez-moi d'ajouter juste un détail.
Indépendamment de votre représentation concrète, notez que les lentilles sont un outil très fin pour "zoomer" sur les différentes parties d'un cube. Par exemple, regardez la fonction cycleLeft
dans ce code Haskell . C'est une fonction générique qui permute cycliquement toute liste de longueur 4. Le code pour effectuer le déplacement L ressemble à ceci:
moveL :: Aut (RubiksCube a)
moveL =
cong cube $ cong leftCols cycleLeft
. cong leftSide rotateSideCW
Ainsi cycleLeft
opère sur la vue donnée par leftCols
. De même, rotateSideCW
, qui est une fonction générique prenant parti pour une version pivotée de celle-ci, opère sur la vue donnée par leftSide
. Les autres mouvements peuvent être mis en œuvre de manière similaire.
Le but de cette bibliothèque Haskell est de créer de jolies images. Je pense que cela a réussi:
Vous semblez poser deux questions distinctes.
- Comment représenter un cube avec X nombre de côtés?
Si vous allez simuler un cube Rubic du monde réel, alors tous les cubes Rubik ont 6 côtés. Je pense que vous voulez dire "X nombre de tuiles par dimension par côté". Chaque côté du cube Rubic d'origine fait 3x3. D'autres tailles incluent 4x4 (cube du professeur), 5x5 et 6x6.
Je représenterais les données avec 6 côtés, en utilisant la notation de résolution de cube "standard":
Chaque côté est un tableau 2D de X par X.
Qu'en est-il des nœuds et des pointeurs?
En supposant qu'il y a toujours 6 faces, et que 1 nœud représente 1 carré sur 1 face:
r , g , b
r , g , b
r , g , b
| | |
r , g , b - r , g , b
r , g , b - r , g , b
r , g , b - r , g , b
Un nœud a un pointeur sur chaque nœud à côté de lui. Une rotation de cercle migre simplement le pointeur (nombre de nœuds/nombre de faces) sur -1 nœuds, dans ce cas 2. Puisque toutes les rotations sont des rotations de cercle, vous créez simplement une fonction rotate
. Il est récursif, déplaçant chaque nœud d'un espace et vérifiant s'il les a suffisamment déplacés, car il aura collecté le nombre de nœuds et il y a toujours quatre faces. Sinon, incrémentez le nombre de fois où la valeur a été déplacée et appelez à nouveau la rotation.
N'oubliez pas qu'il est doublement lié, mettez également à jour les nouveaux nœuds pointés. Il y aura toujours Hauteur * Largeur nombre de nœuds déplacés, avec un pointeur mis à jour par nœud, donc il devrait y avoir Hauteur * Largeur * 2 nombre de pointeurs mis à jour.
Étant donné que tous les nœuds pointent les uns vers les autres, parcourez simplement un cercle en mettant à jour chaque nœud lorsque vous y arrivez.
Cela devrait fonctionner pour n'importe quel cube de taille, sans cas Edge ou logique complexe. C'est juste une marche/mise à jour du pointeur.
J'aime l'idée de @maple_shaft pour représenter différentes pièces (mini-cubes) différemment: les pièces centrales, Edge et d'angle portent respectivement 1, 2 ou 3 couleurs.
Je représenterais les relations entre eux comme un graphique (bidirectionnel), avec des bords reliant les pièces adjacentes. Chaque pièce aurait un tableau de fentes pour les bords (connexions): 4 fentes dans les pièces centrales, 4 fentes dans les pièces de bord, 3 fentes dans les pièces d'angle. Alternativement, les pièces centrales peuvent avoir 4 connexions aux pièces de bord et 4 pour les pièces d'angle séparément, et/ou les pièces de bord peuvent avoir 2 connexions aux pièces centrales et 2 aux pièces d'angle séparément.
Ces tableaux sont ordonnés de sorte que l'itération sur les bords du graphique représente toujours la même rotation, modulo la rotation du cube. C'est-à-dire, par exemple pour une pièce centrale, si vous faites pivoter le cube de sorte que sa face soit au-dessus, l'ordre des connexions est toujours dans le sens des aiguilles d'une montre. De même pour les pièces Edge et Corner. Cette propriété tient après les rotations de visage (ou il me semble maintenant).
La détection de conditions clairement insolubles (bords échangés/retournés, coin échangé) est, espérons-le, facile aussi, car la recherche de pièces d'un type particulier et leur orientation est simple.