Générer un chemin sans intersection ascii-art

18

Étant donné 2 entrées entières représentant la taille du champ, xet y, sortie un chemin à travers le champ.

Exemple de sortie pour 5, 4:

#    
#    
# ###
### #

Le champ entier mesure 5 par 4, et il y a un chemin fait de hachages traversant le champ.

Le chemin doit toujours commencer dans le coin supérieur gauche et aller en bas à droite. Le chemin entier doit être randomisé à chaque exécution du programme. Chaque chemin valide doit être une sortie possible.

Les règles pour les chemins sont les suivantes:

  • Fait de hachures

  • Chaque hachage n'est connecté qu'à 2 autres hachages (c'est-à-dire que le chemin ne se croise pas ou ne court pas le long de lui-même)

Les espaces non hachés peuvent être remplis avec n'importe quel autre caractère, mais il doit être cohérent (c'est-à-dire tous les espaces, toutes les périodes, etc.)

Exemples:

2, 2

##
 #

3, 4

##
 ##
  #
  #

5, 5

#####
    #
    #
    #
    #

6, 5

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

7, 9

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

Ce type de chemin est similaire à une marche aléatoire à évitement automatique, mais il ne peut pas être adjacent à lui-même contrairement à une vraie SCIE.

La continuité du chemin et le toucher du chemin sont tous deux définis sans diagonales.

Rɪᴋᴇʀ
la source
Format de sortie RBX.Lua valide? ;)
devRicher
Est-il exact que tant que chaque chemin valide a une probabilité positive d'être choisi, la distribution de probabilité est arbitraire?
flawr
@devRicher yeah
Rɪᴋᴇʀ
@flawr ouais, c'est exact
Rɪᴋᴇʀ

Réponses:

11

MATLAB, 316 305 300 293 octets

function P=f(a,b);z(a,b)=0;P=z;c=@(X)conv2(+X,[0,1,0;1,1,1;0,1,0],'s');B=1;while B;P=z;P(1)=1;for K=1:a*b;S=z;S(a,b)=1;for L=2:a*b;S(~S&c(S)&~P)=L;end;[i,j]=find(S&c(P==K));if i;r=randi(nnz(i));else;break;end;P(i(r),j(r))=K+1;if P(a,b);break;end;end;B=any(any(c(P>0)>3));end;P(P>0)=35;P=[P,'']

Merci @LuisMendo pour diverses suggestions et un tas d'octets =)

Essayez-le en ligne! (Sans garantie: Notez que quelques ajustements ont été nécessaires pour le faire fonctionner sur Octave: Tout d'abord, j'ai dû retirer lefunction mot clé et coder en dur les valeurs, deuxièmement: Les espaces ne sont pas imprimés correctement comme dans Matlab. De plus, je n'ai pas vérifiez les commandes de convolution d'Octave, qui pourraient agir différemment.)

Exemple de sortie pour l'entrée (7,10)(peut déjà prendre un certain temps):

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

Explication

Cela génère des chemins séquentiellement du haut à gauche vers le bas à droite avec la connectivité 4 souhaitée, puis utilise l'échantillonnage de rejet pour rejeter les chemins qui violent le critère selon lequel vous ne pouvez pas avoir de parties adjacentes.

function P=f(a,b);
z(a,b)=0;                                 % a matrix of zeros of the size of th efield
P=z;                                    
c=@(X)conv2(+X,[0,1,0;1,1,1;0,1,0],'s');  % our convolution function, we always convolute with the same 4-neighbourhood kernel
B=1;
while B;                                  % while we reject, we generate paths:
    P=z;
    P(1)=1;                               % P is our path, we place the first seed
    for K=1:a*b;                          % in this loop we generate the all shortest paths (flood fill) from the bottom right, withot crossing the path to see what fiels are reachable from the bottom left
        S=z;
        S(a,b)=1;                         % seed on the bottom left
        for L=2:a*b;
            S(~S&c(S)&~P)=L;              % update flood front
        end;
        [i,j]=find(S&c(P==K));            % find a neighbour of the current end of the path, that is also reachable from the bottom left
        if i;                             % if we found some choose a random one
            r=randi(nnz(i));
        else;
            break;                        % otherwise restart (might not be necessary, but I'm too tired to think about it properly=)
        end;
        P(i(r),j(r))=K+1;                 % update the end of the current path
        if P(a,b);                        % if we finished, stop continuing this path
            break;
        end;
    end;
    B=any(any(c(P>0)>3));                 % check if we actually have a valid path
end;
P(P>0)=35;                                % format the path nicely
P=[P,''];

Oh et comme toujours:

La convolution est la clé du succès.

flawr
la source
19

Befunge, 344 octets

&v>>>#p_:63p:43g`\!+v>/*53g+\01g:2%2*1-\2/!*63g+\0\:v
 40$ v++!\`g14:p35:\<^2\-1*2%2p10::%4+g00:\g36\g35-1_v
#11^$_83p73v >1+:41g`!#v_$,1+:43g`!#v_@>->2>+00p+141^_
<p1^     vp< ^,g+7g36:<<<<1+55p36:<<<< ^1?0^#7g36g35*
8&p|!++!%9#2g+7g10\*!-g38g10!-g37:g00!!*<>3^
443>:!#v_>>1-::3%1-:53g+00p\3/1-:63g+01p^
^>^>>$#<"#"53g63g7+p41g53g-43g63g-+!#^_

Essayez-le en ligne!

Comme @flawr l'a mentionné dans sa réponse MATLAB, cela peut prendre un certain temps si la taille du champ est d'une taille non triviale. En fait, il est assez facile de se retrouver dans une situation où cela ne vaut vraiment pas la peine d'attendre qu'il se termine, car il est fort probable que vous attendiez la fin des temps.

Pour comprendre pourquoi cela se produit, il est utile de visualiser le programme en cours d'exécution dans l'un des nombreux "débogueurs visuels" de Befunge. Étant donné que les données et le code sont la même chose dans Befunge, vous pourrez voir le chemin au fil du temps. Par exemple, voici une courte animation montrant à quoi pourrait ressembler une partie d'une course sur un chemin lent.

Animation montrant la construction du chemin coincé dans un coin

Une fois que l'algorithme décide de faire ce virage fatidique vers la gauche au bas de la limite du champ, il s'est essentiellement condamné à une vie d'errance sans but. À partir de ce moment, il doit suivre tous les chemins possibles dans cette zone clôturée avant de pouvoir reculer et essayer de tourner à droite. Et le nombre de chemins potentiels dans ces cas peut facilement devenir astronomique.

Conclusion: si cela semble prendre beaucoup de temps, c'est probablement une bonne idée d'interrompre l'exécution et de recommencer.

Explication

Il s'agit essentiellement d'un algorithme récursif, essayant tous les chemins possibles à travers le champ, puis déroulant les étapes qui ont déjà été suivies chaque fois qu'il est bloqué. Puisque Befunge n'a pas le concept de fonctions, une fonction récursive est hors de question, mais nous pouvons émuler le processus en suivant l'état sur la pile.

Cela fonctionne en remplissant la pile de coordonnées potentielles que nous pourrions vouloir suivre. Ensuite, nous tirons un ensemble de la pile et vérifions s'il est approprié (c'est-à-dire à portée et ne chevauchant pas un chemin existant). Une fois que nous avons un bon emplacement, nous écrivons un #dans le champ de jeu à cet endroit et ajoutons ces détails à la pile au cas où nous aurions besoin de revenir en arrière plus tard.

Nous poussons ensuite quatre autres ensembles de coordonnées sur la pile (dans un ordre aléatoire) indiquant les chemins potentiels que nous pouvons emprunter à partir de ce nouvel emplacement, et sautons au début de la boucle. Si aucun des chemins potentiels n'est réalisable, nous arriverons au point de la pile où nous avons enregistré l'emplacement de celui que #nous avons écrit, donc nous annulerons cette étape et continuerons d'essayer les coordonnées potentielles d'une étape précédente.

Voici à quoi ressemble le code avec les différents composants mis en évidence:

Code source avec les chemins d'exécution mis en évidence

*Lisez la largeur et la hauteur du champ et appuyez sur les coordonnées de départ avec un 0marqueur de type pour indiquer un chemin potentiel plutôt qu'un emplacement de retour en arrière.
*Vérifiez les emplacements de retour en arrière (indiqués par un 1marqueur de type) qui sont inversés avec une simple pcommande, car ils sont stockés au format exact nécessaire pour réécrire un espace dans le champ de jeu.
*Vérifiez si les coordonnées sont toujours à l'intérieur du champ de jeu. S'ils sont hors de portée, supprimez-les de la pile et faites une boucle pour essayer les prochaines coordonnées potentielles.
*S'ils sont dans la plage, obtenez les deux valeurs suivantes de la pile, qui est l'emplacement de l'étape précédente (requise dans le test qui suit).
*Vérifiez si les coordonnées vont entrer en contact avec un segment existant du chemin. L'emplacement de l'étape précédente est évidemment ignoré de cette vérification.
*Si tous les tests réussissent, écrivez un #dans le champ de jeu et vérifiez si nous avons atteint l'emplacement de destination.
*Si c'est le cas, écrivez le chemin final *et quittez.
*Sinon, enregistrez les coordonnées dans la pile avec un 1marqueur de type pour un retour en arrière ultérieur.
*Ceci est interrompu par un calcul de nombres aléatoires dont nous aurons bientôt besoin.
*Poussez quatre destinations potentielles qui peuvent être atteintes à partir de l'emplacement actuel. Le nombre aléatoire détermine l'ordre dans lequel ils sont poussés et donc l'ordre dans lequel ils seront suivis.
* Revenez au début de la boucle principale et traitez les valeurs suivantes sur la pile.

James Holderness
la source
2
Sainte vache. Explication?
Rɪᴋᴇʀ
@EasterlyIrk Merci pour la générosité. C'est très apprécié.
James Holderness
Heureux que ce soit utile!
Rɪᴋᴇʀ
2

QBasic, 259 octets

J'adore vraiment l' GOTOart.

RANDOMIZE TIMER
INPUT w,h
DO
CLS
x=1
y=1
REDIM a(w+3,h+3)
2a(x+1,y+1)=1
LOCATE y,x
?"#"
d=INT(RND*4)
m=1AND d
x=x+m*(d-2)
y=y-d*m+m+d-1
c=a(x,y+1)+a(x+2,y+1)+a(x+1,y)+a(x+1,y+2)=1
IF(x=w)*c*(y=h)GOTO 9
IF(x*y-1)*x*y*c*(x<=w)*(y<=h)GOTO 2
LOOP
9LOCATE y,x
?"#"

Stratégie de base: à chaque étape, imprimez un #à l'emplacement actuel et déplacez-vous dans une direction aléatoire. Un tableaua de 0 et de 1 permet de savoir où nous en sommes. Si le mouvement est légal et nous amène au point final, GOTO 9pour sortir de la boucle et imprimer la finale# . Sinon, si le déménagement est légal, faites un autre pas. Sinon, effacez l'écran et recommencez (beaucoup plus golfique que de coder un algorithme de retour en arrière!).

Testé sur mon ordinateur portable en QB64, cela produit généralement un résultat 9, 9en cinq secondes ou moins. Les séries de 10, 10ont pris entre trois et 45 secondes. Théoriquement, tous les chemins légaux ont une probabilité non nulle, mais la probabilité d'un chemin avec de grandes courbes est extrêmement faible. J'ai parfois vu des chemins avec une ou deux petites courbes, cependant:

Exemple de chemin

Version non golfée et / ou explication détaillée disponible sur demande.

DLosc
la source
2

R, 225 octets

function(x,y){library(igraph);a=matrix(rep(" ",x*y),c(y,x));g=make_lattice(c(y,x));w=runif(ecount(g));for (i in 1:2){v=shortest_paths(g,1,x*y,weights=w)$vpath[[1]];w[]=1;w[E(g)[v%--%v]]=0;};a[v]="#";cat(rbind(a,"\n"),sep="")}

Explication:

Nous générons un graphe régulier (réseau) [x * y] non orienté avec des poids aléatoires sur les bords puis nous trouvons le chemin le plus court du début à la fin. Cependant, dans le chemin généré, il peut y avoir des cellules qui ont plus de deux neigbors par exemple:

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

Nous devons donc appliquer l'algorithme du chemin le plus court deux fois. Dans la deuxième fois, nous avons défini tous les poids sur 1, à l'exception de ceux qui se trouvent dans le chemin trouvé actuel qui sont définis sur 0;

résultat

#
#
### 
  # 
  #
  ###

Non golfé:

function(x,y){
    library(igraph);#igraph library should be installed
    a=matrix(rep(" ",x*y),c(y,x));#ASCII representation of the graph
    g=make_lattice(c(y,x));# regular graph
    w=runif(ecount(g));#weights
    for (i in 1:2){
        #find vertices that are in the path
        v=shortest_paths(g,1,x*y,weights=w)$vpath[[1]];
        #set all weights to 1 except those that are in the current found path that set to 0
        w[]=1;
        w[E(g)[v%--%v]]=0;
    }
    a[v]="#";
    cat(rbind(a,"\n"),sep="")
}
rahnema1
la source
1

JavaScript (ES7), 333 331 330 329 324 318 312 octets

f=
(h,w,c=[...Array(h)].map(_=>Array(w).fill` `),g=a=>{for(z of b=[[[h-1,w-1]]])a.map(([x,y])=>b.every(([[i,j]])=>i-x|j-y)&(z[0][0]-x)**2+(z[0][1]-y)**2<2&&b.push([[x,y],...z]));return b.find(([[x,y]])=>!x&!y)||g([...a,[h,w].map(n=>Math.random()*n|0)])})=>g([]).map(([x,y])=>c[x][y]=`#`)&&c.map(a=>a.join``).join`
`
Height: <input type=number min=1 id=h>Width: <input type=number min=1 id=w><input type=button value="Generate!" onclick=o.textContent=f(+h.value,+w.value)><pre id=o>

Expansion: les #s sont placés aléatoirement dans le tableau jusqu'à ce qu'un chemin soit trouvé à travers le champ à l'aide d'une recherche en largeur; le premier, et donc le plus court, un tel chemin est ensuite sorti; cela garantit que le chemin ne se croise pas. Notez que, en particulier pour les champs plus grands, il est possible de dépasser la pile du moteur JS avant de trouver un chemin. Non golfé:

function r(n) {
    return Math.floor(Math.random() * n);
}
function f(h, w) {
    var a = []; // array of placed #s
    var b; // breadth-first search results
    var c;
    do {
        a.push([r(h), r(w)]); // place a # randomly
        b = [[[h - 1, w - 1]]]; // start from bottom right corner
        for (z of b) // breadth-first search
            for ([x, y] of a) // find possible next steps
                if (!b.some(([[i, j]]) => i == x && j == y))
                    if ((z[0][0] - x) ** 2 + (z[0][1] - y) ** 2 < 2)
                        if (x || y)
                            b.push([[x, y], ...z]); // add to search
                        else if (!c)
                            c = [[x, y], ...z]; // found path
    } while (!c);
    a = [...Array(h)].map(() => Array(w).fill(' '));
    for ([x, y] of c) // convert path to output
        a[x][y] = '#';
    return a.map(b => b.join('')).join('\n');
}
Neil
la source