Criação do labirinto 4D!

Eu tenho um problema para quem quiser tentar.

Seu trabalho é pegar um tesseract de tamanho 10x10x10x10 e projetar um labirinto que se encaixa. O labirinto deve ser um labirinto perfeito (sem loops, um caminho não pode ser seguido e encontrar o início de seu caminho sem que o solucionador dê uma volta) e deve ter pelo menos 25 becos sem saída e pelo menos 8 ramificações de outros becos sem saída.

A resposta que você fornecer deve ser uma imagem e, no máximo, o tesseract (se não todos) deve ser usado.

observação; A diversão está na criação do labirinto 4D! 2D e até 3D é muito fácil para você quebra-cabeças, vou desafiá-lo!

Para limitar as possibilidades de respostas, para que o seu labirinto seja aceite, tem que ser o labirinto mais curto possível que cumpra todos os requisitos já enunciados. Por mais curto, quero dizer o labirinto com a menor área do tesserato preenchida.

Comentários

  • Não ‘ é uma pergunta muito boa. 1-Você não ‘ não explica o que é um labirinto perfeito. Claro, posso pesquisar no Google, mas não ‘ não preciso. 2-Como devemos responder à pergunta? Desenhar um labirinto 2D é bastante fácil. 3D é mais complicado, mas 4D? Não ‘ não parece divertido. E apenas descrever o labirinto significaria que ele teria que ser trivial. ” Há um caminho direto do início ao fim e 10 becos sem saída curtos se ramificam e a maior parte do tesserato não é utilizada. ” Essa é uma resposta válida?
  • @Golden eu ‘ irei trabalhar nisso, talvez então ‘ ll obter uma resposta melhor.
  • +1 de mim porque acho que é uma boa ideia sobre a construção de quebra-cabeças. No entanto, algumas ilustrações que você tem em mente, ou algumas idéias sobre como um possível formato de resposta poderia ser, seriam boas. Caso contrário, pelo menos vou esperar uma auto-resposta mais tarde …
  • @BmyGuest I ‘ vou deixar para a comunidade como isso deve ser feito . Se eu não ‘ receber nenhuma resposta, ‘ eventualmente adicionarei uma recompensa.
  • Uma ideia que tenho sobre esta pergunta: talvez em vez de pedir labirintos individuais como respostas, você pode pedir técnicas para projetar labirintos 4D e representá-los de uma forma que permita que labirintos não triviais sejam apresentados e até resolvidos.

Resposta

Aqui está. Acesse o link direto para vê-lo em tamanho real (ou amplie a imagem).

Labirinto

Este é um plano de placas (horizontal é $ w $ e vertical é $ z $) onde cada placa é um plano 2D (horizontal é $ x $ e vertical é $ y $). Para mudar suas posições $ x $ e $ y $, apenas ande pelo tabuleiro atual.

As setas permitem que você mude suas posições $ w $ e $ z $. Eles fazem você pular um quadro para cima (azul), para baixo (amarelo), para a esquerda (vermelho) ou para a direita (verde), de acordo com sua direção.

Então, se você estiver em um $ (a, b) $ posição de um tabuleiro:

  • Usando a seta para cima (azul), você pousará na $ (a, b) $ posição do tabuleiro imediatamente acima do atual.
  • Usando a seta para baixo (amarela), você pousará na posição $ (a, b) $ do tabuleiro imediatamente abaixo do atual.
  • Usando a esquerda (vermelho ), você pousará na posição $ (a, b) $ do tabuleiro imediatamente à esquerda do atual.
  • Ao usar a seta para a direita (verde), você pousará na $ (a, b) $ posição do tabuleiro imediatamente à direita do atual.

Para gerar isso, criei 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 site não tenha sintaxe-coloração.

Existem centenas ou milhares de becos sem saída e pontos de ramificação. Muito mais do que apenas 25 e 8 exigidos pelo OP.

Para cada dois locais, há exatamente um caminho para cada local. Não há ciclos no gráfico e ele está conectado. O programa garante que (método growMaze).

Não há ponto inicial ou final definido. Consiga dois pontos aleatoriamente e tente encontrar um caminho. Como você pode ver, encontrar manualmente um caminho aqui deve ser difícil, uma vez que há dez mil posições neste labirinto e olhar em volta das dimensões $ w $ e $ z $ para encontrar um caminho útil é mais difícil para os olhos humanos do que em $ x $ e $ y $ dimensões.

Você pode usar o programa para gerar aleatoriamente outros labirintos. Alterar seu tamanho também é fácil: eles são as quatro dezenas no início do método main. No método main, você pode alterar para qual arquivo o labirinto gerado é salvo.Para mostrar isso, aqui é um labirinto $ 4 \ vezes 5 \ vezes 3 \ vezes 2 $ muito menor e mais simples gerado pelo programa:

Pequeno labirinto

A propósito, definindo $ w $ como 1 e $ z $ como 1, você pode usá-lo para gerar labirintos 2D. Se você definir apenas um deles como 1, será um labirinto 3D.

Comentários

  • Muito bom, Victor. Você ‘ me venceu durante a noite. Eu ia escrever uma resposta esta manhã – não agora. (@warspyking: Você pode ser condenado para sempre por me manter acordado à 1h da manhã. Era muito difícil ficar bem, aconchegante e aquecido na cama, ou sair de novo para digitar algumas letras frias em um quarto frio em um PC indiferente … )
  • @BmyGuest foi um bom exercício de programação. E é difícil encontrar um aqui. 🙂
  • @BmyGuest editado. Está melhor agora?
  • Legal: D Eu amo isso, bom trabalho Victor!
  • Por onde você começa? : /

Resposta

Agora que foi reaberto, quero postar o ” solution ” Eu esperava por isso na noite antes da resposta de Victor. Victor me venceu e Warspyking modificou as condições de vitória desde então , então não é mais uma resposta válida. Eu ainda queria postar isso para comentários (e verificação / refutação).

A resposta funciona para labirintos n-dimensionais e também é algoritmo não um labirinto específico. Não posto código aqui, mas o conceito de algoritmo.


Minha solução é baseada na ideia de que cada pixel / voxel / n-dim-element ( chamado de voxel de agora em diante ) pode ser um caminho (0) ou uma parede (1). Isso é diferente do que Victor fez, mas pode podem ser transformadas umas nas outras com bastante facilidade. ( Basta converter paredes & aberturas em voxels adicionais ou vice-versa. )

  • Os tamanhos das dimensões dos dados são chamados de n,m,k,l…
  • A indexação começa com 0 para o primeiro voxel

Inicializar dados:

  • Crie os dados do labirinto como uma matriz booleana de dimensionalidade necessária

  • Inicializar todos os voxels como 0 (vazio)

  • Crie uma lista para índices de voxels de paredes (vazio )

  • Defina cada voxel com pelo menos um índice par como 1 (parede)

  • Armazene o índice wall-voxel na lista.

  • A partir de agora, sempre que uma parede for removida:

    • remova o índice correspondente da lista
    • defina o booleano correspondente para 0 (vazio)

Definir início & posição final:

Basicamente, defina os dois ” cantos ” opostos como início e fim.

  • Defina o voxel do índice (0,0, …) para ser o local de início
  • Defina o voxel do índice (n, m, …) para ser o destino do objetivo
  • Se o voxel de destino for uma parede, remova a parede. (Todas as dimensões são iguais)

Crie o labirinto:

O labirinto atual é uma grade com espaços vazios isolados.

  • Rotule todos os espaços vazios com um rótulo exclusivo.

Agora vá iterativamente ( WHILE-loop ):

  • Escolha aleatoriamente uma parede da lista de índice.
  • Remova o índice da lista ( nunca teste um parede duas vezes )

Teste : A remoção desta parede mesclará dois espaços vazios de rótulo diferente? NÃO: próxima iteração. ELSE:

  • Remover parede

  • De todos os rótulos envolvidos, escolha aquele com o menor valor

  • Defina todos os voxels com rótulos envolvidos para o valor escolhido (= mesclar espaços vazios)

  • próxima iteração

Pare a iteração quando não houver índices na lista.

Sem revisá-lo, acho que este algoritmo fornecerá a você:

- 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, eu pretendia codificar este algoritmo e então pensar em uma boa maneira de exibir o caso 4D, mas Victor já fez um ótimo trabalho, então vou deixar isso .

Comentários

  • Obrigado por esta resposta verbal de alcance! Com certeza é útil para mim.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *