Générer une carte pour un roguelike

10

Aujourd'hui, nous allons générer une carte pour un RPG roguelike!

Exemple de carte:

##########
####    F#
####    ##
##    C#C#
#     ## #
# C   #E #
####  #  #
#        #
#P       #
##########

#sont des murs, Pest l'emplacement de départ du joueur, Fest la finition qui doit être atteinte, Csont des pièces de monnaie qui peuvent être collectées et Esont des ennemis qui peuvent être combattus.

Spécifications de la carte:

  • La hauteur et la largeur doivent être comprises entre 10 et 39 inclus. La hauteur n'a pas à être égale à la largeur.
  • Les bordures des cartes doivent être remplies de murs.
  • P doit être placé dans le coin inférieur gauche.
  • F doit être placé dans le coin supérieur droit.
  • Il devrait y avoir entre 1 et 3 ennemis.
  • Il devrait y avoir entre 2 et 4 pièces.
  • Il devrait y avoir une certaine quantité de murs au milieu. Il doit y avoir un chemin d'accès Pà Tous C, Eet Fen gardant à l'esprit que le joueur ne peut pas se déplacer en diagonale.
  • Chaque combinaison possible devrait avoir une chance de se produire.

Règles

  • Le moins de programmes d'octets gagne.
  • Votre programme ne doit prendre aucune entrée.
  • Votre programme peut ne pas se terminer avec une erreur (une sortie non fatale STDERRest ok, mais nous ne pouvons pas avoir notre crash de type voyou après la génération de la carte!)
  • Une seule nouvelle ligne de fin est autorisée et l'espace de fin est autorisé.
  • Aucune autre sortie n'est autorisée.
Pavel
la source
3
C'est roguelike, juste fyi.
Rɪᴋᴇʀ
2
Pouvez-vous préciser "toutes les combinaisons possibles devraient avoir une chance égale de se produire"? Voulez-vous dire littéralement que toutes les cartes valides (en particulier, toutes les cartes où P peut atteindre tous les C / E / F) doivent se produire avec une probabilité égale? Si c'est le cas, il semble que le seul algorithme possible soit de générer uniformément des cartes au hasard, puis de vérifier que P peut tout atteindre, en rejetant les cartes invalides jusqu'à ce que cela se produise.
Greg Martin
Pouvez-vous également préciser - "Il devrait y avoir une certaine quantité de murs au milieu", que se passe-t-il si je ne place que 2 murs tout le temps?
Gurupad Mamadapur
1
@GregMartin Je vais le changer aussi "Chaque mise en page possible devrait avoir une chance de se produire", pas nécessairement une chance égale.
Pavel
2
Qu'en est-il des places vides inaccessibles entourées de murs? Est-ce une mise en page valide ou faut-il les éviter complètement? (En d'autres termes: chaque case vide doit-elle être accessible?)
Arnauld

Réponses:

5

Perl, 293 octets

-9 octets grâce à @Dom Hastings

{$==7+rand 30;@r=$"=();@a=((C)x4,(E)x3,("#")x1369,(" ")x1369);for$i(0..7+rand 30){$r[$i][$_]=splice@a,rand@a,1for 0..$=}$r[0][$=]=F;$r[-1][0]=P;$_=$r=join$/,$v="#"x($=+=3),(map"#@$_#",@r),$v;1while$r=~s/F(.{$=})?[^#F]/F$1F/s||$r=~s/[^#F](.{$=})?F/F$1F/s;$r!~/[CEP]/&&/C.*C/s&&/E/?last:redo}say

Ajoutez un -Eindicateur pour l'exécuter:

perl -E '{$==7+rand 30;@r=$"=();@a=((C)x4,(E)x3,("#")x1369,(" ")x1369);for$i(0..7+rand 30){$r[$i][$_]=splice@a,rand@a,1for 0..$=}$r[0][$=]=F;$r[-1][0]=P;$_=$r=join$/,$v="#"x($=+=3),(map"#@$_#",@r),$v;1while$r=~s/F(.{$=})?[^#F]/F$1F/s||$r=~s/[^#F](.{$=})?F/F$1F/s;$r!~/[CEP]/&&/C.*C/s&&/E/?last:redo}say'

Cependant, son exécution prend beaucoup de temps, je recommande donc d'utiliser cette version à la place:

perl -E '{${$_}=8+rand 30for"=","%";@r=$"=();@a=((C)x4,(E)x3,("#")x($v=rand $=*$%),(" ")x($=*$%-$v));for$i(0..$%-1){$r[$i][$_]=splice@a,rand@a,1for 0..$=-1}$r[0][$=-1]=F;$r[$%-1][0]=P;$_=$r=join$/,$v="#"x($=+=2),(map"#@$_#",@r),$v;1while$r=~s/F(.{$=})?[^#F]/F$1F/s||$r=~s/[^#F](.{$=})?F/F$1F/s;$r!~/[CEP]/&&/C.*C/s&&/E/?last:redo}say'

Essayez-le en ligne!

Explication

{ # entrez un bloc (qui est utilisé comme boucle) { $ == 7 + rand 30 ; # sélectionnez aléatoirement la largeur de la carte -2 # (-2 car nous n'incluons pas encore les bordures) @r = $ "= (); # reset @r, et définissez $" sur undef @a = ( # create une liste des personnages qui peuvent être sur le plateau ( C ) x4 , # 4 pièces 'C'                     
                       
                                     
    
                                 
                               
      ( E ) x3 , # 3 ennemis 'E' ( "#" ) x1369 , # 37 * 37 '#'                          
                          
      ("" ) x1369 ); # 37 * 37 espaces pour $ i ( 0..7 + rand 30 ) # créer la carte 2D (7 + rand 30 est la hauteur, qui est générée tout à l'heure) pour $ _ ( 0 .. $ = - 1 ) { 
        $ r [ $ i ] [ $ _ ] = # index [$ i] [$ _] reçoit ... 
           splice @ a , rand @ a , 1 # .. un caractère aléatoire de la liste précédemment générée # (le caractère est puis supprimé de la liste grâce à 'splice') } } 
    $ r [                    
                     
                                     
                                       
      
     0 ] [ $ =] = F ; # ajouter la cellule d'arrivée 
    $ r [- 1 ] [ 0 ] = P ; # ajoutez la cellule de départ 
    $ _ = $ r = # ici nous générons une représentation sous forme de chaîne de la carte 
          join $ /, # joignez les éléments suivants avec des nouvelles lignes 
            $ v = "#" x                                                                                                 ( $ = + = 3 ), # a en premier ligne de # uniquement ( carte "# @ $ _ #" , @r ), # ajoutez un # au début et à la fin de chaque ligne            
                       
            $ v ; # la dernière ligne de #                        

    1 tandis que # l' expression régulière suivante remplacera chaque cellule accessible par une cellule F 
       $ r = ~ s / F (. { $ =})? [^ # F ] / F $ 1F / s   # une cellule à droite ou en bas d'une La cellule F est remplacée   || # ou 
       $ r = ~ s / [^ # F ] (. { $ =})? F                
                                   / F $ 1F / s ; # une cellule à gauche ou en haut d'une cellule F est remplacée 
    $ r ! ~ / [CEP] / # s'il n'y a pas de C, E ou P sur la carte (ce qui signifie qu'ils étaient tous accessibles) &&          
      /C.*C/ s          # et il y a au moins 2 pièces && / E / ? # et 1 ennemi en dernier : # la carte est valide, on quitte la boucle refaire # else, recommencer } 
dire                      # et imprimer la carte
                  
                   
                    

Cela prend beaucoup de temps, car la liste dans laquelle nous choisissons au hasard les personnages à mettre sur le plateau ( @a) contient 1369 espaces blancs et #, et seulement 4 pièces et 3 ennemis. Donc, si la taille de la largeur et de la hauteur sont petites, il y a beaucoup d'espaces et #par rapport à la pièce et aux ennemis, il est donc très probable qu'une carte aléatoire ne soit pas valide. C'est pourquoi la version "optimisée" est plus rapide: la liste dans laquelle nous choisissons les personnages est juste un peu plus grande que la carte (la liste est @a=((C)x4,(E)x3,("#")x($v=rand $=*$%),($")x($=*$%-$v)): un nombre aléatoire $vde #(inférieur à la taille de la carte), et des size of the map - $vespaces blancs).

Dada
la source
Je ne connais pas vraiment perl, mais en regardant la coloration syntaxique, vous semblez avoir une citation inégalée dans ($ ") x $ = ** 2);. Peut-être que la surbrillance ne fonctionne pas correctement et c'est une fonctionnalité. , les espaces peuvent être inaccessibles.
Pavel
1
@Pavel $"est une variable Perl légitime, mais la coloration syntaxique ne le sait pas, c'est pourquoi elle ressemble à ça. Ok, je vais supprimer le commentaire sur les espaces inaccessibles.
Dada
5

PHP, 422 417 415 309 373 369 364 361 octets

function w($p){global$r,$h,$w;for($q=$p;$r[$q]<A;)for($r[$p=$q]=" ";($q=$p+(1-(2&$m=rand()))*($m&1?:$w))%$w%($w-1)<1|$q/$w%$h<1;);}$r=str_pad("",($w=rand(10,39))*$h=rand(10,39),"#");$r[$w*2-2]=F;w($p=$q=$w*(--$h-1)+1);$r[$p]=P;for($c=rand(2,4);$i<$c+rand(1,3);$p=rand($w,$h*$w))if($r[$p]<A&&$p%$w%($w-1)){w($p);$r[$p]=EC[$i++<$c];w($p);}echo chunk_split($r,$w);

fonctionne sur une chaîne sans saut de ligne; creuse des chemins aléatoires entre les extras. Courez avec -r.

Remarque: Les chemins sont créés en marchant dans des directions aléatoires. Le choix de la direction pour chaque étape générera principalement des cartes grandes ouvertes; et l'exemple de carte est très peu susceptible d'apparaître; mais il est possible.

panne

// aux function: randomly walk away from $p placing spaces, stop when a special is reached
function w($p)
{global$r,$h,$w;
    for($q=$p;
        $r[$q]<A;                               // while $q is not special
    )
        for($r[$p=$q]=" ";                          // 3. replace with space
            ($q=$p+(1-(2&$m=rand()))*($m&1?:$w))    // 1. pick random $q next to $p
            %$w%($w-1)<1|$q/$w%$h<1;                // 2. that is not on the borders
        );
}

// initialize map
$r=str_pad("",
    ($w=rand(10,39))*$h=rand(10,39) // random width and height
    ,"#");                          // fill with "#"
$r[$w*2-2]=F;                       // place Finish
w($p=$q=$w*(--$h-1)+1);             // build path from Player position to F
// $h is now height -1 !
$r[$p]=P;                           // place Player

// place Coins ans Enemies
for($c=rand(2,4);$i<$c+rand(1,3);   // while $i has not reached no. of coins+no. of enemies
    $p=rand($w,$h*$w))              // pick a random position
    if($r[$p]<A&&$p%$w%($w-1))      // that is neither special nor out of bounds
    {
        w($p);                      // build path from there to another special
        $r[$p]=EC[$i++<$c];         // place this special
        w($p);      // additional path to allow special in the middle of a dead end tunnel
    }

// insert linebreaks and print
echo chunk_split($r,$w);
Titus
la source
Dans votre explication, vous générez une hauteur et une largeur à 37, pas à 39.
Pavel
@Pavel fixed; merci d'avoir remarqué
Titus
Génère son propre code source lorsque j'ai essayé Try it online
Pavel
@Pavel vous devez entourer le code avec<?php .... ?>
Dada
1
Ok, je l'ai fait, et j'ai remarqué que les murs sont générés en morceaux rectangulaires réguliers. Il devrait être capable de générer quelque chose comme l'exemple de carte. Il ne génère pas toujours non plus de Es.
Pavel
3

C # (Visual C # Interactive Compiler) , 730 octets

var R=new Random();for(;;){char P='P',C='C',E='E',Q='#';int w=R.Next(8,37),h=R.Next(8,37),H=h,t,g=99,W,i,j,r;string l,s,p=new string(Q,w+2);var m=new List<string>();for(;H>0;H--){l="";for(W=w;W>0;W--){r=R.Next(999);l+=r<3?C:r<6?E:r<g?Q:' ';}m.Add(l);}m[0]=m[0].Substring(0,w-1)+'F';m[h-1]=P+m[h-1].Substring(1);s=String.Join("#\n#",m);t=s.Split(E).Length-1;if(t<1||t>3)continue;t=s.Split(C).Length-1;if(t<2||t>4)continue;while(g>0){g--;for(i=0;i<h;i++)for(j=0;j<w;j++)if(m[i][j]!=Q&&m[i][j]!=P&&(i>0&&m[i-1][j]==P)||(i<h-1&&m[i+1][j]==P)||(j>0&&m[i][j-1]==P)||(j<w-1&&m[i][j+1]==P))m[i]=m[i].Substring(0,j)+P+m[i].Substring(j+1,w-j-1);}if(String.Join("",m).Split(E,C,'F').Length>1)continue;Console.Write(p+"\n#"+s+"#\n"+p);break;}

Essayez-le en ligne!

Non golfé:

var R = new Random();
for (;;)
{
    char P = 'P', C = 'C', E = 'E', poundSymbol = '#';
    int width = R.Next(8, 37), height = R.Next(8, 37), HeightTemp = height, testVariable, goThroughLoop = 99, WidthTemp, i, j, rand;
    string line, strMap, poundSymbolPadding = new string(poundSymbol, width + 2);

    var map = new List<string>(); //initialize map
    for (; HeightTemp > 0; HeightTemp--)
    {
        line = "";
        for (WidthTemp = width; WidthTemp > 0; WidthTemp--)
        {
            rand = R.Next(999);
            //add a character randomly.  Re-use the goThroughLoop variable here, which gives approx. 1 wall per 10 spaces.
            line += rand < 3 ? C : rand < 6 ? E : rand < goThroughLoop ? poundSymbol : ' ';
        }
        map.Add(line);
    }
    //add finish and player
    map[0] = map[0].Substring(0, width - 1) + 'F';
    map[height - 1] = P + map[height - 1].Substring(1);

    strMap = String.Join("#\n#", map);
    //check proper # of enemies, regenerate if invalid
    testVariable = strMap.Split(E).Length - 1;
    if (testVariable < 1 || testVariable > 3)
        continue;
    //check proper # of coins, regenerate if invalid
    testVariable = strMap.Split(C).Length - 1;
    if (testVariable < 2 || testVariable > 4)
        continue;
    //map out areas Player can access.  Iterates until all accessible places have been marked as such.
    while (goThroughLoop > 0)
    {
        goThroughLoop--;
        for (i = 0; i < height; i++)
            for (j = 0; j < width; j++)
                if (map[i][j] != poundSymbol && map[i][j] != P && ((i > 0 && map[i - 1][j] == P) || (i < height - 1 && map[i + 1][j] == P) || (j > 0 && map[i][j - 1] == P) || (j < width - 1 && map[i][j + 1] == P)))
                    //mark this space as accessible
                    map[i] = map[i].Substring(0, j) + P + map[i].Substring(j + 1, width - j - 1);
    }
    //if player cannot access all features (defeated enmies, collected coins, arrived at finish), regenerate map.
    if (String.Join("", map).Split(E, C, 'F').Length > 1)
        continue;

    //output our final map
    Console.Write(poundSymbolPadding + "\n#" + strMap + "#\n" + poundSymbolPadding);

    break;
}

Édition: économisé 8 octets, le rendait légèrement moins efficace en verrouillant la boucle de test accessible au lecteur à 99 itérations. Je sais que ça ne rivalisera jamais vraiment avec les autres réponses ici, mais je m'amuse!

Bence Joful
la source
@GregMartin Maintenant, c'est à votre tour de l'implémenter en F # ;-)
Bence Joful
2
Une modulation simple au sous-dominant, pas de problème :)
Greg Martin