Создать карту для рогалика

10

Сегодня мы создадим карту для ролевых RPG!

Пример карты:

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

#Стены, Pначальное местоположение игрока, Fфиниш, который должен быть достигнут, Cмонеты, которые можно собрать, и Eвраги, с которыми можно бороться.

Характеристики карты:

  • Высота и ширина должны быть между 10 и 39 включительно. Высота не должна равняться ширине.
  • Границы карты должны быть заполнены стенами.
  • P должны быть размещены в левом нижнем углу.
  • F должны быть размещены в верхнем правом углу.
  • Там должно быть от 1 до 3 врагов.
  • Там должно быть от 2 до 4 монет.
  • Там должно быть некоторое количество стен в середине. Должен быть путь, чтобы добраться Pдо каждого C, Eи F, имея в виду, что игрок не может двигаться по диагонали.
  • У каждой возможной комбинации должна быть некоторая вероятность возникновения.

правила

  • Побеждает самая маленькая байтовая программа.
  • Ваша программа не должна принимать никаких данных.
  • Ваша программа может не завершиться с ошибкой (нефатальный вывод в STDERRпорядке, но у нас не может быть нашего сбоя, подобного мошенническому, после генерации карты!)
  • Разрешен только один завершающий символ новой строки и завершающий пробел.
  • Другие выходы не допускаются.
Павел
источник
3
Это roguelike, просто к вашему сведению.
Rɪᴋᴇʀ
2
Можете ли вы уточнить, что «каждая возможная комбинация должна иметь одинаковую вероятность появления»? Вы в буквальном смысле имеете в виду, что все действительные карты (в частности, все карты, где P может достичь всех C / E / F) должны встречаться с равной вероятностью? Если это так, то кажется, что единственный возможный алгоритм - генерировать карты равномерно случайным образом, а затем проверять, что P может достичь всего, отбрасывая недействительные карты, пока это не произойдет.
Грег Мартин
Вы также можете уточнить: «В середине должно быть какое-то количество стен», что если я все время размещаю только 2 стены?
Гурупад Мамадапур
1
@GregMartin Я тоже его поменяю «У каждого возможного макета должна быть возможность», не обязательно равный шанс.
Павел
2
Как насчет недоступных пустых квадратов, окруженных стенами? Это действительный макет или их следует вообще избегать? (Другими словами: должен ли быть доступен каждый пустой квадрат?)
Арно

Ответы:

5

Perl, 293 байта

-9 байт благодаря @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

Добавьте -Eфлаг для запуска:

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'

Однако запуск занимает много времени, поэтому я рекомендую использовать эту версию:

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'

Попробуйте онлайн!

объяснение

{ # введите блок (который используется как цикл) { $ == 7 + rand 30 ; # Случайным образом выбрать ширину карты -2 # (-2 , потому что мы не включаем границы пока) @r = $ «= (); # сброс @R, и множество $» на неопределенное значение @a = ( # создать список персонажей, которые могут быть на доске ( C ) x4 , # 4 монеты 'C' ( E ) x3 , # 3 враги 'E' ( "#" ) x1369 , # 37 * 37 '#' (                     
                       
                                     
    
                                 
                               
                               
                          
     "" ) x1369 ); # 37 * 37 пробелов для $ i ( 0..7 + rand 30 ) # создать 2D-карту (7 + rand 30 - высота, которая генерируется только сейчас) для 
        $ r [ $ i ] [ $ _ ] = # index [$ i] [$ _] получает ... 
           splice @ a , rand @ a , 1 # .. случайный символ из ранее созданного списка # (символ затем удаляется из списка благодаря функции "splice") } } 
    $ r [                    
                     
      $ _ ( 0 .. $ = - 1 ) { 0 ] [ $ =] = F ; # добавить финишную ячейку 
    $ r [- 1 ] [ 0 ] = P ; # добавить начальную ячейку 
    $ _ = $ r = # здесь мы генерируем строковое представление карты 
          join $ /,                               
                                       
      
                                                                                                      # объединяем следующие элементы с символами новой строки 
            $ v = "#" x ( $ = + = 3 ), # a first # только строка ( map "# @ $ _ #" , @r ), # добавьте # в начало и конец каждой строки            
                       
            $ v ; # последняя строка #                        

    1 while # следующее регулярное выражение заменит каждую доступную ячейку на F 
       $ r = ~ s / F (. { $ =})? [^ # F ] / F $ 1F / s   # ячейку справа или снизу F ячейка заменяется   || # или 
       $ r = ~ s / [^ # F ] (. { $ =})? F / F $ 1F / s ; # ячейка слева или верхняя часть ячейки F заменяется 
    $ r ! ~ / [CEP] / #, если на карте нет C, E или P (то есть все они были доступны) &&                
                                            
      /C.*C/ s          # а там как минимум 2 монеты && / E / ? # и 1 враг последним : # карта действительна, мы выходим из цикла redo # else, начинаем заново } 
say                      # и печатаем доску
                  
                   
                    

Запуск занимает много времени, потому что список, из которого мы случайным образом выбираем символы для размещения на доске ( @a), содержит 1369 пробелов и #, и только 4 монеты и 3 врага. Так что, если размеры ширины и высоты невелики, есть много мест #по сравнению с монетой и врагами, так что вполне вероятно, что случайная карта не будет действительной. Вот почему «оптимизированы» версия быстрее: список , из которого мы выбираем символы просто немного больше , чем карта (список @a=((C)x4,(E)x3,("#")x($v=rand $=*$%),($")x($=*$%-$v)): случайное число $vиз #(ниже размера карты), и size of the map - $vпробелы).

папа
источник
Я на самом деле не знаю Perl, но, глядя на подсветку синтаксиса, вы, кажется, получаете непревзойденную кавычку в ($ ") x $ = ** 2) ;. Возможно, подсветка не работает правильно, и это особенность. Также , пробелы могут быть недоступны
Павел
1
@Pavel $"является допустимой переменной Perl, но подсветка синтаксиса не знает об этом, поэтому выглядит так. Хорошо, я удалю комментарий о недоступных местах.
Дада
5

PHP, 422 417 415 309 373 369 364 361 байт

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

работает со строкой без разрывов строк; копает случайные пути между статистами. Беги с -r.

Примечание . Пути создаются путем случайного направления. Выбор направления для каждого шага будет в основном генерировать карты, которые широко открыты; и пример карты вряд ли появится; но это возможно.

сломать

// 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
источник
В своем объяснении вы генерируете высоту и ширину до 37, а не до 39.
Павел
@Pavel исправлен; спасибо, что обратили внимание
Titus
Выводит свой собственный исходный код, когда я попробовал его. Попробуй онлайн
Павел
@Pavel вам нужно окружить код<?php .... ?>
Dada
1
Хорошо, я сделал это, и я заметил, что стены сформированы в правильные прямоугольные куски. Должно быть в состоянии генерировать что-то вроде примера карты. Это также не всегда генерирует Es.
Павел
3

C # (интерактивный компилятор Visual C #) , 730 байт

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

Попробуйте онлайн!

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

Изменить: сохранил 8 байт, сделал его немного менее эффективным, заблокировав доступный игроку цикл тестирования до 99 итераций. Я знаю, что это никогда не будет конкурировать с другими ответами здесь, но я получаю удовольствие!

Бенс Джофул
источник
@GregMartin Теперь ваша очередь реализовать ее в F # ;-)
Бенс Джофул
2
Простая модуляция на субдоминанту, нет проблем :)
Грег Мартин