Generieren Sie eine Karte für ein Roguelike

10

Heute erstellen wir eine Karte für ein schurkenhaftes Rollenspiel!

Beispielkarte:

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

#sind Mauern, Pist der Startort des Spielers, Fist das Ziel, das erreicht Cwerden muss, sind Münzen, die gesammelt werden können, und Esind Feinde, die bekämpft werden können.

Kartenspezifikationen:

  • Höhe und Breite sollten beide zwischen 10 und einschließlich 39 liegen. Die Höhe muss nicht gleich breit sein.
  • Die Kartenränder sollten mit Wänden ausgefüllt werden.
  • P sollte in der unteren linken Ecke platziert werden.
  • F sollte in der oberen rechten Ecke platziert werden.
  • Es sollte zwischen 1 und 3 Feinde geben.
  • Es sollten zwischen 2 und 4 Münzen sein.
  • In der Mitte sollten sich einige Wände befinden. Es sollte einen Weg geben, von Pdem aus man zu Jedem gelangen Ckann E, und Fdabei zu berücksichtigen, dass sich der Spieler nicht diagonal bewegen kann.
  • Jede mögliche Kombination sollte eine gewisse Chance haben.

Regeln

  • Wenig Byte-Programm gewinnt.
  • Ihr Programm sollte keine Eingaben annehmen.
  • Ihr Programm wird möglicherweise nicht mit einem Fehler beendet (eine nicht schwerwiegende Ausgabe ist STDERRin Ordnung, aber wir können nach der Kartengenerierung keinen schurkenhaften Absturz haben!)
  • Eine einzelne nachfolgende Newline ist zulässig, und nachfolgender Leerzeichen ist zulässig.
  • Es ist keine andere Ausgabe zulässig.
Pavel
quelle
3
Es ist schurkenhaft, nur zu Ihrer Information.
Rɪᴋᴇʀ
2
Können Sie klarstellen, dass "jede mögliche Kombination die gleiche Chance haben sollte"? Meinen Sie wörtlich, dass alle gültigen Karten (insbesondere alle Karten, auf denen P alle C / E / Fs erreichen kann) mit gleicher Wahrscheinlichkeit auftreten müssen? Wenn ja, scheint es der einzig mögliche Algorithmus zu sein, Karten gleichmäßig zufällig zu generieren und dann zu überprüfen, ob P alles erreichen kann, wobei ungültige Karten verworfen werden, bis dies geschieht.
Greg Martin
Können Sie auch klarstellen: "Es sollten einige Wände in der Mitte sein". Was ist, wenn ich immer nur 2 Wände platziere?
Gurupad Mamadapur
1
@ GregMartin Ich werde es auch ändern "Jedes mögliche Layout sollte eine Chance haben", nicht unbedingt eine gleiche Chance.
Pavel
2
Was ist mit unerreichbaren leeren Plätzen, die von Mauern umgeben sind? Ist es ein gültiges Layout oder sollten sie ganz vermieden werden? (Mit anderen Worten: Sollte jedes leere Quadrat erreichbar sein?)
Arnauld

Antworten:

5

Perl, 293 Bytes

-9 Bytes dank @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

Fügen Sie ein -EFlag hinzu, um es auszuführen:

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'

Die Ausführung dauert jedoch sehr lange. Ich empfehle daher, stattdessen diese Version zu verwenden:

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'

Probieren Sie es online aus!

Erläuterung

{ # einen Block eingeben (der als Schleife verwendet wird) { $ == 7 + rand 30 ; # wähle zufällig die Breite der Karte -2 # (-2, da wir die Ränder noch nicht einschließen) @r = $ "= (); # setze @r zurück und setze $" auf undef @a = ( # create eine Liste der Charaktere, die auf dem Brett sein können ( C ) x4 , # 4 Münzen 'C' ( E ) x3 , # 3 Feinde 'E' ( "#" ) x1369 , # 37 * 37 '#' (                     
                       
                                     
    
                                 
                               
                               
                          
     "" ) X1369 ); # 37 * 37 Räume für $ i ( 0..7 + rand 30 ) # Erstellen Sie die 2D - Karte (7 + rand 30 ist die Höhe, die gerade erzeugt wird) für $ _ ( 0 . $ = - 1 ) { 
        $ r [ $ i ] [ $ _ ] = # index [$ i] [$ _] empfängt ... 
           splice @ a , rand @ a , 1 # .. ein zufälliges Zeichen aus der zuvor generierten Liste # (das Zeichen ist dann dank 'splice' von der Liste entfernt) } } 
    $ r [                    
                     
                                     
                                       
      
    0 ] [ $ =] = F ; # füge die Zielzelle hinzu 
    $ r [- 1 ] [ 0 ] = P ; # füge die Startzelle hinzu 
    $ _ = $ r = # hier generieren wir eine String-Darstellung des Map- 
          Joins $ /, # verbinde die folgenden Elemente mit Zeilenumbrüchen 
            $ v = "#" x ( $ = + = 3 ), # a first Nur Zeile # ( Map "# @ $ _ #" , @r ), # Fügen Sie am Anfang und am Ende jeder Zeile ein # hinzu                                                                                                             
                       
            $ v ; # die letzte Zeile von #                        

    1 während # der folgende reguläre Ausdruck jede zugängliche Zelle durch ein F 
       $ r = ~ s / F (. { $ =}) Ersetzt ? [^ # F ] / F $ 1F / s   # eine Zelle rechts oder unten in a F-Zelle wird ersetzt   || # oder 
       $ r = ~ s / [^ # F ] (. { $ =})? F / F $ 1F / s ; # Eine Zelle links oder oben in einer F-Zelle wird durch 
    $ r ! ~ / [CEP] / # ersetzt, wenn auf der Karte kein C, E oder P vorhanden ist (was bedeutet, dass alle zugänglich waren) &&                
                                            
      /C.*C/ s          # und es gibt mindestens 2 Münzen && / E / ? # und 1 Feind zuletzt : # Wenn die Karte gültig ist, verlassen wir die Schleife, wiederholen # sonst, beginnen von vorne } 
sagen                      # und drucken das Brett
                  
                   
                    

Das Ausführen dauert lange, da die Liste, aus der wir zufällig die Zeichen auswählen, die auf das Brett gelegt werden sollen ( @a), 1369 Leerzeichen und #und nur 4 Münzen und 3 Feinde enthält. Wenn also die Größe der Breite und Höhe klein ist, gibt es viele Leerzeichen und im #Vergleich zur Münze und den Feinden ist es sehr wahrscheinlich, dass eine zufällige Karte nicht gültig ist. Deshalb ist die "optimierte" Version schneller: Die Liste, aus der wir die Zeichen auswählen, ist nur ein wenig größer als die Karte (die Liste ist @a=((C)x4,(E)x3,("#")x($v=rand $=*$%),($")x($=*$%-$v)): eine Zufallszahl $vvon #(schlechter als die Größe der Karte) und size of the map - $vLeerzeichen).

Dada
quelle
Ich kenne Perl nicht wirklich, aber wenn Sie sich die Syntaxhervorhebung ansehen, scheinen Sie ein unübertroffenes Anführungszeichen in ($ ") x $ = ** 2) zu haben. Vielleicht funktioniert die Hervorhebung nicht richtig und das ist auch eine Funktion , Leerzeichen können nicht erreichbar sein.
Pavel
1
@Pavel $"ist eine legitime Perl-Variable, aber die Syntaxhervorhebung weiß nichts darüber, deshalb sieht es so aus. Ok, ich werde den Kommentar zu nicht erreichbaren Leerzeichen löschen.
Dada
5

PHP, 422 417 415 309 373 369 364 361 Bytes

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);

arbeitet mit einer Zeichenfolge ohne Zeilenumbrüche; gräbt zufällige Pfade zwischen den Extras. Laufen Sie mit -r.

Hinweis: Die Pfade werden durch Gehen in zufällige Richtungen erstellt. Die Wahl der Richtung für jeden Schritt erzeugt meistens Karten, die weit offen sind. und es ist sehr unwahrscheinlich, dass die Beispielkarte erscheint; aber es ist möglich.

Nervenzusammenbruch

// 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
quelle
In Ihrer Erklärung generieren Sie Höhe und Breite auf 37, nicht auf 39.
Pavel
@Pavel behoben; danke für das Bemerken
Titus
Gibt seinen eigenen Quellcode aus, als ich es online
Pavel
@ Pavel müssen Sie den Code mit umgeben<?php .... ?>
Dada
1
Ok, ich habe das gemacht und festgestellt, dass die Wände in regelmäßigen rechteckigen Stücken erzeugt werden. Es sollte in der Lage sein, so etwas wie die Beispielkarte zu generieren. Es werden auch nicht immer Es generiert .
Pavel
3

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

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;}

Probieren Sie es online aus!

Ungolfed:

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;
}

Bearbeiten: 8 Bytes gespeichert, etwas weniger effizient, da die vom Player zugängliche Testschleife auf 99 Iterationen gesperrt wurde. Ich weiß, dass es hier nie wirklich mit den anderen Antworten konkurrieren wird, aber ich habe Spaß!

Bence Joful
quelle
@ GregMartin Jetzt sind Sie an der Reihe, es in F # zu implementieren
;-)
2
Eine einfache Modulation der Subdominante, kein Problem :)
Greg Martin