Jag har problem för alla som bryr sig om att försöka.
Du jobbar med att ta en 10x10x10x10 storlek tesserakt och utforma en labyrint som passar. Labyrinten måste vara en perfekt labyrint (inga slingor, en väg kan inte följas och hitta början på sin väg utan att lösaren vänder sig) och måste ha minst 25 återvändsgränder och minst 8 förgreningar av andra återvändsgrändar.
Svaret du ger ska vara en bild, och som mest av smaken (om inte alla) ska användas.
not; det roliga är att skapa 4D-labyrinten! 2D och till och med 3D är för lätt för dina pusslare, jag kommer att utmana dig!
Till begränsa svarens möjligheter, för att din labyrint ska accepteras måste det vara kortast möjliga labyrint som uppfyller alla krav som redan anges. Med det kortaste menar jag labyrinten med minst fylld tesserakt.
Kommentarer
- Det ’ är inte en mycket bra fråga. 1-Du förklarar inte ’ vad en perfekt labyrint är. Visst, jag kan google det, men jag borde inte ’. 2-Hur ska vi svara på frågan? Att rita en 2D-labyrint är lätt nog. 3D är knepigare, men 4D? Låter inte ’ roligt. Och att bara beskriva labyrinten skulle betyda att labyrinten måste vara trivial. ” Det finns en rak väg från början till slutet och 10 korta återvändsgrändar förgrenas och det mesta av tesserakten är oanvänd. ” Är det ett giltigt svar?
- @Golden I ’ Jag kommer att arbeta med det, kanske då ’ ll få ett bättre svar.
- +1 från mig eftersom jag tycker att det är en bra idé om pusselbyggande. Vissa illustrationer du tänker på, eller några idéer om hur ett potentiellt svarsformat kan vara, skulle dock vara bra. Annars förväntar jag mig åtminstone ett självsvar senare …
- @BmyGuest Jag ’ Jag ska bara lämna upp till samhället hur detta ska göras . Om jag inte får ’ får jag inga svar jag ’ så småningom lägger jag till en bounty.
- En idé jag har om den här frågan: kanske istället för att be om enskilda labyrinter som svar kan du be om tekniker för att designa 4D-labyrinter och representera dem på ett sätt som gör att icke-triviala labyrinter kan presenteras och till och med lösas.
Svar
Här är det. Gå till direktlänken för att se den i full storlek (eller zooma in bilden).
Detta är ett plan av kort (horisontellt är $ w $ och vertikalt är $ z $) där varje kort är ett 2D-plan (horisontellt är $ x $ och vertikalt är $ y $). För att ändra dina $ x $ och $ y $ positioner, gå bara runt i nuvarande tavla.
Pilarna låter dig ändra dina $ w $ och $ z $ positioner. De får dig att hoppa ett bräde uppåt (blått), nedåt (gult), vänster (rött) eller höger (grönt), i enlighet med dess riktning.
Så om du befinner dig i en viss $ (a, b) $ brädans position:
- Genom att använda uppåt (blå) pilen kommer du att landa i brädans position $ (a, b) $ omedelbart ovanför den nuvarande.
- Genom att använda nedåtpilen (gul) landar du i $ (a, b) $ -positionen på brädet omedelbart nedanför den aktuella.
- Genom att använda vänster (röd ) pil kommer du att landa i $ (a, b) $ -positionen på brädet omedelbart till vänster om den aktuella.
- Genom att använda höger (grön) pil kommer du att landa i $ (a, b) $ position för styrelsen direkt till höger om den aktuella.
För att skapa detta skapade jag det här Java-programmet:
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()); }; } }
Jag är ledsen att den här webbplatsen inte har någon syntaxfärgning.
Det finns hundratals eller tusentals återvändsgrändar och grenar. Mycket mer än bara 25 och 8 krävs av OP.
För varje två platser finns det exakt en väg till varandra. Det finns inga cykler i diagrammet och den är ansluten. Programmet säkerställer att (growMaze
-metoden).
Det finns ingen definierad start- eller slutpunkt. Få bara slumpmässigt två poäng och försök hitta en väg. Som du ser borde det vara svårt att hitta en väg här manuellt, eftersom det finns tiotusen positioner i denna labyrint och att titta runt dimensionerna $ w $ och $ z $ för att hitta en användbar väg är svårare för mänskliga ögon än det är i $ x $ och $ y $ dimensioner.
Du kan använda programmet för att slumpmässigt generera andra labyrinter. Det är också enkelt att ändra storlek: De är de fyra tiotalet i början av metoden main
. I main
-metoden kan du ändra till vilken fil den genererade labyrinten sparas.För att visa det här är det en mycket mindre och enklare $ 4 \ gånger 5 \ gånger 3 \ gånger 2 $ labyrint genererad av programmet:
Förresten, genom att ställa $ w $ till 1 och $ z $ till 1, kan du använda den för att generera 2D-labyrinter. Om du bara ställer in en av dem till 1 blir det en 3D-labyrint.
Kommentarer
- Bra, Victor. Du ’ har slagit mig till det över natten. Jag tänkte skriva ett svar i morse – dock inte nu. (@warspyking: Du kan bli fördömd för alltid för att hålla mig vaken klockan 1. Det var nära samtal att hålla mig trevlig och mysig och varm i sängen, eller gå ut igen för att skriva några kalla bokstäver i ett kallt rum till en oskälig dator … )
- @BmyGuest Var en bra programmeringsövning. Och det är svårt att hitta en här. 🙂
- @BmyGuest Redigerad. Är det bättre nu?
- Trevligt: D Jag älskar det, bra jobb Victor!
- Var börjar du? : /
Svar
Nu när detta har öppnats igen vill jag lägga upp ” lösning ” Jag hade för detta natten innan Victor svar. Victor har slagit mig till det och Warspyking har ändrat segervillkoren sedan , så det är inte längre ett giltigt svar. Jag ville fortfarande lägga upp detta för att kommentera (och verifiera / motbevisa.)
Svaret fungerar för n-dimensionella labyrinter och det är också ett algoritm inte en specifik labyrint. Jag lägger inte kod här utan algoritmkonceptet.
Min lösning bygger på tanken att varje pixel / voxel / n-dim-element ( kallas voxel från och med nu ) kan antingen vara en väg (0) eller en vägg (1). Detta skiljer sig från vad Victor gjorde, men det kan förvandlas till varandra ganska enkelt. ( Konvertera bara väggar & öppningar till ytterligare voxels eller tvärtom. )
- Datamåttstorlekarna heter n,m,k,l…
- Indexering börjar med 0 för den första voxel
Initiera data:
Skapa labyrintdata som Boolean array av nödvändig dimensionalitet
Initiera alla voxels som 0 (tom)
Skapa en lista för voxel-index på väggar (tom )
Ställ in varje voxel med minst ett jämnt index till 1 (vägg)
Spara indexet wall-voxel i listan.
Från och med nu, när en vägg tas bort:
- ta bort det enligt indexet från listan
- ställ in enligt Boolean till 0 (tom)
Definiera start & slutposition:
Ställ i huvudsak de två motsatta ” hörnen ” start och slut.
- Ställ in voxel för index (0,0, …) som startplats
- Ställ in voxel för index (n, m, …) för att vara måldestinationen
- Om destinations voxel är en vägg, ta bort väggen. (Alla mått är lika stora)
Skapa labyrinten:
Den aktuella labyrinten är ett rutnät med isolerade tomma utrymmen.
- Märk alla tomma utrymmen med en unik etikett.
Gå nu iterativt ( WHILE-loop ):
- Slumpmässigt välja en vägg från indexlistan.
- Ta bort indexet från listan ( testa aldrig en vägg två gånger )
Test : Kommer att ta bort den här väggen slås samman två tomma utrymmen med olika etiketter? NEJ: nästa iteration. ELSE:
Ta bort vägg
Av alla inblandade etiketter väljer du den med lägsta värde
Ställ in alla voxels med inblandade etiketter till det valda värdet (= slå samman tomma utrymmen)
nästa iteration
Stoppa iteration när inga index finns i listan.
Utan att bevisa det tror jag att denna algoritm ger dig:
- 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
Ursprungligen tänkte jag koda denna algoritm och tänkte sedan på ett trevligt sätt att visa 4D-fallet, men Victor har redan gjort ett bra jobb med det, så jag lämnar det med det här .
Kommentarer
- Tack för det här muntliga svaret! Det är säkert användbart för mig.