Creación de laberinto 4D!

Tengo un problema para cualquiera que quiera intentarlo.

Su trabajo es tomar un tesseract de tamaño 10x10x10x10 y diseñar un laberinto que encaja. El laberinto debe ser un laberinto perfecto (sin bucles, no se puede seguir un camino y encontrar el inicio de su camino sin que el solucionador se dé la vuelta), y debe tener al menos 25 callejones sin salida y al menos 8 bifurcaciones de otros callejones sin salida.

La respuesta que proporciones debe ser una imagen, y como máximo se debe usar el tesseract (si no todos).

nota; La diversión está en la creación del laberinto 4D. 2D e incluso 3D es demasiado fácil para ustedes, ¡los voy a desafiar!

Para Limite las posibilidades de respuesta, para que su laberinto sea aceptado, tiene que ser el laberinto más corto posible que cumpla con todos los requisitos ya establecidos. Por más corto me refiero al laberinto con la menor área del tesseract llena.

Comentarios

  • ‘ no es una muy buena pregunta. 1-No ‘ t explica qué es un laberinto perfecto. Claro, puedo buscarlo en Google, pero no debería ‘ tener que hacerlo. 2-¿Cómo se supone que respondamos a la pregunta? Dibujar un laberinto 2D es bastante fácil. 3D es más complicado, ¿pero 4D? No suena ‘ divertido. Y simplemente describir el laberinto significaría que el laberinto tendría que ser trivial. » Hay un camino recto desde el principio hasta el final y 10 callejones sin salida cortos se ramifican y la mayor parte del tesseract no se utiliza. » ¿Es esa una respuesta válida?
  • @Golden I ‘ trabajaré en eso, tal vez entonces ‘ ll obtenga una mejor respuesta.
  • +1 de mi parte porque creo que es una buena idea sobre la construcción de acertijos. Sin embargo, algunas ilustraciones que tenga en mente, o algunas ideas sobre cómo podría ser un formato de respuesta potencial, serían buenas. De lo contrario, al menos esperaré una auto-respuesta más tarde …
  • @BmyGuest I ‘ dejaré en manos de la comunidad cómo se debe hacer esto . Si no ‘ no obtengo ninguna respuesta, ‘ eventualmente agregaré una recompensa.
  • Una idea que tengo sobre esta pregunta: tal vez en lugar de pedir laberintos individuales como respuestas, puede solicitar técnicas para diseñar laberintos 4D y representarlos de una manera que permita presentar e incluso resolver laberintos no triviales.

Respuesta

Aquí está. Acceda al enlace directo para verlo en su tamaño completo (o ampliar la imagen).

Laberinto

Este es un plano de tableros (horizontal es $ w $ y vertical es $ z $) donde cada tablero es un plano 2D (horizontal es $ x $ y vertical es $ y $). Para cambiar sus posiciones $ x $ y $ y $, simplemente camine en el tablero actual.

Las flechas le permiten cambiar sus posiciones $ w $ y $ z $. Te hacen saltar un tablero hacia arriba (azul), hacia abajo (amarillo), hacia la izquierda (rojo) o hacia la derecha (verde), de acuerdo a su dirección.

Entonces, si estás en un $ (a, b) $ posición de un tablero:

  • Al usar la flecha hacia arriba (azul), aterrizará en la posición $ (a, b) $ del tablero inmediatamente arriba del actual.
  • Al usar la flecha hacia abajo (amarilla), aterrizará en la posición $ (a, b) $ del tablero inmediatamente debajo del actual.
  • Al usar el botón izquierdo (rojo ), aterrizará en la posición $ (a, b) $ del tablero inmediatamente a la izquierda del actual.
  • Al usar la flecha derecha (verde), aterrizará en el $ (a, b) $ posición del tablero inmediatamente a la derecha del actual.

Para generar esto, creé este programa Java:

import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.imageio.ImageIO; /** * @author Victor */ public class Tesseracter { public static void main(String[] args) throws IOException { TesseractMaze maze = new TesseractMaze(10, 10, 10, 10); BufferedImage im = maze.draw(); ImageIO.write(im, "png", new File("maze.png")); } public static final class Coordinate4D { private final TesseractMaze maze; private final int w, x, y, z; public Coordinate4D(TesseractMaze maze, int w, int x, int y, int z) { Objects.requireNonNull(maze); if (w < 0 || w >= maze.wSize || x < 0 || x >= maze.xSize || y < 0 || y >= maze.ySize || z < 0 || z >= maze.zSize) throw new IndexOutOfBoundsException(); this.maze = maze; this.w = w; this.x = x; this.y = y; this.z = z; } @Override public int hashCode() { return Objects.hash(maze, w, x, y, z); } @Override public boolean equals(Object another) { if (!(another instanceof Coordinate4D)) return false; Coordinate4D c4d = (Coordinate4D) another; return maze == c4d.maze && w == c4d.w && x == c4d.x && y == c4d.y && z == c4d.z; } public int squareDistance(Coordinate4D another) { Objects.requireNonNull(another); if (maze != another.maze) throw new IllegalArgumentException(); int dw = Math.abs(w - another.w); int dx = Math.abs(x - another.x); int dy = Math.abs(y - another.y); int dz = Math.abs(z - another.z); return dw + dx + dy + dz; } public Coordinate4D minusW() { return w == 0 ? null : new Coordinate4D(maze, w - 1, x, y, z); }; public Coordinate4D plusW() { return w == maze.wSize - 1 ? null : new Coordinate4D(maze, w + 1, x, y, z); }; public Coordinate4D minusX() { return x == 0 ? null : new Coordinate4D(maze, w, x - 1, y, z); }; public Coordinate4D plusX() { return x == maze.xSize - 1 ? null : new Coordinate4D(maze, w, x + 1, y, z); }; public Coordinate4D minusY() { return y == 0 ? null : new Coordinate4D(maze, w, x, y - 1, z); }; public Coordinate4D plusY() { return y == maze.ySize - 1 ? null : new Coordinate4D(maze, w, x, y + 1, z); }; public Coordinate4D minusZ() { return z == 0 ? null : new Coordinate4D(maze, w, x, y, z - 1); }; public Coordinate4D plusZ() { return z == maze.zSize - 1 ? null : new Coordinate4D(maze, w, x, y, z + 1); }; public TesseractMaze getMaze() { return maze; } } public static final class TesseractMaze { private final int wSize, xSize, ySize, zSize; private final Map<Coordinate4D, Node> nodes; private final Node start; public TesseractMaze(int w, int x, int y, int z) { this.wSize = w; this.xSize = x; this.ySize = y; this.zSize = z; nodes = new HashMap<>(w * x * y * z); fill(); this.start = chooseRandomNode(); growMaze(); } private void fill() { for (int a = 0; a < wSize; a++) { for (int b = 0; b < xSize; b++) { for (int c = 0; c < ySize; c++) { for (int d = 0; d < zSize; d++) { Coordinate4D coord = new Coordinate4D(this, a, b, c, d); nodes.put(coord, new Node(coord)); } } } } } public Node nodeAt(Coordinate4D coord) { if (coord == null) return null; return nodes.get(coord); } private Node chooseRandomNode() { int n = (int) (Math.random() * wSize * xSize * ySize * zSize); return new ArrayList<>(nodes.values()).get(n); } private void growMaze() { List<Node> frontier = new ArrayList<>(wSize * xSize * ySize * zSize); frontier.add(start); start.linked = true; while (!frontier.isEmpty()) { Collections.shuffle(frontier); Node n = frontier.get(0); Node next = n.linkRandomUnlinkedNeighbour(); if (next != null) { frontier.add(next); } else { frontier.remove(0); } } } public BufferedImage draw() { int cellWidth = 16; int cellHeight = 16; int boardWidth = cellWidth * (xSize + 1); int boardHeight = cellHeight * (ySize + 1); int arrowSize = 3; int margin = 2; Color red = Color.RED; Color blue = Color.BLUE; Color yellow = new Color(128, 128, 0); Color green = new Color(0, 128, 0); BufferedImage im = new BufferedImage(wSize * boardWidth + cellWidth - 1, zSize * boardHeight + cellHeight - 1, BufferedImage.TYPE_INT_ARGB); Graphics2D g = im.createGraphics(); for (int w = 0; w < wSize; w++) { for (int z = 0; z < zSize; z++) { for (int x = 0; x < xSize; x++) { for (int y = 0; y < ySize; y++) { Coordinate4D c = new Coordinate4D(this, w, x, y, z); Node n = nodeAt(c); int x1 = cellWidth * x + boardWidth * w + cellWidth - 1; int y1 = cellHeight * y + boardHeight * z + cellHeight - 1; int x2 = x1 + cellWidth; int y2 = y1 + cellHeight; int x3 = (x1 + x2) / 2; int y3 = (y1 + y2) / 2; g.setColor(Color.BLACK); if (!n.isLinkedTo(n.minusY())) g.drawLine(x1, y1, x2, y1); if (!n.isLinkedTo(n.plusY())) g.drawLine(x1, y2, x2, y2); if (!n.isLinkedTo(n.minusX())) g.drawLine(x1, y1, x1, y2); if (!n.isLinkedTo(n.plusX())) g.drawLine(x2, y1, x2, y2); if (n.isLinkedTo(n.minusW())) { // Board left, left arrow. g.setColor(red); for (int i = 0; i < arrowSize; i++) { g.drawLine(x1 + margin + i, y3 - i, x1 + margin + i, y3 + i); } } if (n.isLinkedTo(n.plusW())) { // Board right, right arrow. g.setColor(green); for (int i = 0; i < arrowSize; i++) { g.drawLine(x2 - margin - i, y3 - i, x2 - margin - i, y3 + i); } } if (n.isLinkedTo(n.minusZ())) { // Board up, up arrow. g.setColor(blue); for (int i = 0; i < arrowSize; i++) { g.drawLine(x3 - i, y1 + margin + i, x3 + i, y1 + margin + i); } } if (n.isLinkedTo(n.plusZ())) { // Board down, down arrow. g.setColor(yellow); for (int i = 0; i < arrowSize; i++) { g.drawLine(x3 - i, y2 - margin - i, x3 + i, y2 - margin - i); } } } } } } return im; } } public static final class Node { private final Coordinate4D coord; private final List<Node> linkedNeighbours; private List<Node> neighbours; private boolean linked; public Node(Coordinate4D coord) { Objects.requireNonNull(coord); this.coord = coord; linkedNeighbours = new ArrayList<>(8); } public Node linkRandomUnlinkedNeighbour() { List<Node> list = new ArrayList<>(getNeighbours()); list.removeIf(n -> n.linked); if (list.isEmpty()) return null; Collections.shuffle(list); Node next = list.get(0); next.getNeighbours(); linkedNeighbours.add(next); next.linkedNeighbours.add(this); next.linked = true; return next; } @SuppressWarnings("ReturnOfCollectionOrArrayField") public List<Node> getNeighbours() { if (neighbours == null) { List<Node> nodes = new ArrayList<>(Arrays.asList(minusW(), plusW(), minusX(), plusX(), minusY(), plusY(), minusZ(), plusZ())); nodes.removeIf(x -> x == null); neighbours = Collections.unmodifiableList(nodes); } return neighbours; } public boolean isDeadEnd() { return linkedNeighbours.size() == 1; } public boolean isBranch() { return linkedNeighbours.size() > 2; } public boolean isLinkedTo(Node node) { return linkedNeighbours.contains(node); } public TesseractMaze getMaze() { return coord.getMaze(); } public Coordinate4D getCoord() { return coord; } public Node minusW() { return getMaze().nodeAt(coord.minusW()); }; public Node plusW() { return getMaze().nodeAt(coord.plusW()); }; public Node minusX() { return getMaze().nodeAt(coord.minusX()); }; public Node plusX() { return getMaze().nodeAt(coord.plusX()); }; public Node minusY() { return getMaze().nodeAt(coord.minusY()); }; public Node plusY() { return getMaze().nodeAt(coord.plusY()); }; public Node minusZ() { return getMaze().nodeAt(coord.minusZ()); }; public Node plusZ() { return getMaze().nodeAt(coord.plusZ()); }; } } 

Lamento que este sitio no tenga colores de sintaxis.

Hay cientos o miles de puntos sin salida y ramificaciones. Mucho más que solo 25 y 8 requeridos por el OP.

Para cada dos ubicaciones hay exactamente una ruta para cada una de las otras ubicaciones. No hay ciclos en el gráfico y está conectado. El programa asegura que (growMaze método).

No hay un punto inicial o final definido. Simplemente obtenga dos puntos al azar e intente encontrar un camino. Como puede ver, encontrar manualmente un camino aquí debería ser difícil, ya que hay diez mil posiciones en este laberinto y mirar alrededor de las dimensiones $ w $ y $ z $ para encontrar un camino útil es más difícil para los ojos humanos que en el $ x $ y $ y $ dimensiones.

Puede usar el programa para generar aleatoriamente otros laberintos. Cambiar su tamaño también es fácil: son esas cuatro decenas al comienzo del método main. En el método main puede cambiar en qué archivo se guarda el laberinto generado.Para mostrar eso, aquí hay un laberinto $ 4 \ times 5 \ times 3 \ times 2 $ mucho más pequeño y simple generado por el programa:

Small maze

Por cierto, al establecer $ w $ en 1 y $ z $ en 1, puede usarlo para generar laberintos 2D. Si establece solo uno de ellos en 1, será un laberinto 3D.

Comentarios

  • Bonito, Victor. Tú ‘ me has adelantado durante la noche. Iba a escribir una respuesta esta mañana, aunque no ahora. (@warspyking: Puede ser condenado para siempre por mantenerme despierto a la 1 de la madrugada. Estuvo muy cerca permanecer agradable, acogedor y cálido en la cama, o salir de nuevo para escribir algunas letras frías en una habitación fría en una PC indiferente … )
  • @BmyGuest Fue un buen ejercicio de programación. Y es difícil encontrar uno aquí. 🙂
  • @BmyGuest Editado. ¿Está mejor ahora?
  • Bien: D ¡Me encanta, buen trabajo Víctor!
  • ¿Por dónde empezar? : /

Responder

Ahora que se ha vuelto a abrir, quiero publicar el » solución » Tenía para esto la noche anterior a la respuesta de Victor. Victor me ha adelantado y Warspyking ha modificado las condiciones de victoria desde , por lo que ya no es una respuesta válida. Todavía quería publicar esto para comentar (y verificar / refutar).

La respuesta funciona para laberintos n-dimensionales y también es un algoritmo no es un laberinto específico. No publico código aquí, sino el concepto de algoritmo.


Mi solución se basa en la idea de que cada píxel / voxel / n-dim-element ( llamado voxel de ahora en adelante ) puede ser una ruta (0) o una pared (1). Esto es diferente de lo que hizo Víctor, pero puede transformarse entre sí con bastante facilidad. ( Simplemente convierta las aberturas de las paredes & en vóxeles adicionales o al revés. )

  • Los tamaños de las dimensiones de datos se denominan n,m,k,l…
  • La indexación comienza con 0 para el primer vóxel

Inicializar datos:

  • Cree los datos de laberinto como una matriz booleana de dimensionalidad necesaria

  • Inicializar todos los vóxeles como 0 (vacío)

  • Crear una lista para índices de vóxeles de paredes (vacío )

  • Establezca cada vóxel con al menos un índice par en 1 (muro)

  • Almacene el índice wall-voxel en la lista.

  • De ahora en adelante, siempre que se elimine un muro:

    • eliminar el índice correspondiente de la lista
    • establecer el booleano correspondiente en 0 (vacío)

Definir inicio & posición final:

Esencialmente, establezca las dos » esquinas » opuestas para que sean inicio y final.

  • Establecer el vóxel de índice (0,0, …) como la ubicación de inicio
  • Establecer el vóxel de índice (n, m, …) para ser el destino de la meta
  • Si el vóxel de destino es un muro, retírelo. (Todas las dimensiones son de tamaño uniforme)

Crea el laberinto:

El laberinto actual es una cuadrícula con espacios vacíos aislados.

  • Etiquete todos los espacios vacíos con una etiqueta única.

Ahora vaya iterativamente ( WHILE-loop ):

  • Elija aleatoriamente un muro de la lista de índice.
  • Elimine el índice de la lista ( nunca pruebe un muro dos veces )

Prueba : eliminar este muro fusionará dos espacios vacíos de etiqueta diferente? NO: próxima iteración. MÁS:

  • Eliminar muro

  • De todas las etiquetas involucradas, elija la que tenga el valor más bajo

  • Establezca todos los vóxeles que tengan etiquetas involucradas en este valor elegido (= fusionar espacios vacíos)

  • próxima iteración

Detener la iteración cuando no haya índices en la lista.

Sin verificarlo, creo que este algoritmo le dará:

- a non-looping maze from start to end - maximize the size of the maze within the volume - The identical algorithm could be used for arbitrary amount of dimensions 

Originalmente tenía la intención de codificar este algoritmo y luego pensar en una forma agradable de mostrar el caso 4D, pero Victor ya ha hecho un gran trabajo, así que lo dejo con esto .

Comentarios

  • Gracias por esta respuesta verbal de alcance! Seguro que es útil para mí.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *