PDA

View Full Version : Random level generation.



Robert Kosek
08-06-2007, 03:54 PM
Hey guys, and gal(s), I've been working on some "blind" level generation. Right now the stuff is still just a prototype that uses a string grid to make a level. The explanation is better in code as to how I'm doing it, so I won't bother to state anything special here, but here's a dirty preview. Walls are pound signs (#) and floors are periods (.), while the entry and exit are less-than (<) and greater-than (>) symbols, just in case you couldn't decipher them all.


##################################################
##########...........#.........................###
##########.##...#.#....#......####.#..<..........#
##########.##........#.#.#.######..#.........#..##
##########.##..##................................#
#.########.##..##..............................#.#
#.########.##..####.........#####.#.#..##.######.#
#.########.##..####....#.###...........##.######.#
#.########.##....##..###.########.......#.######.#
#.########....##.##....#.#########.....##.######.#
#.##########..##.##......#########.######.####...#
#.##########..##......############.######.######.#
#######>......##########..########.######.########
#######.###...##########.#########.######.########
#######.###...#####......#########.######.########
#######.###...#####................######.########
#######.###...####...................####.....####
#######.###...........#######################.####
#######.###..########.#######################.####
#######.###..########.#######################....#
#######.####.########.##########################.#
#######...........###.########################...#
#######.####.####.###.#############..............#
#######.####......#...############################
##################################################

##################################################
#............................................#####
######.#######.##..........#######..########.#####
######.#######.##.#.....##.#######..########.#####
######.#######.##.#......#.#........########.#####
##############.##...#..#.#...<.....#########.#####
########.#.............#...#.#####.###############
#.................#.#....#.#.#####################
#.########.########.#.##.#.#.#####################
#.########.########.#....#.#.#####################
#.#################.#......#......################
#.#################.#############.################
#.#################.........#####.#######.####.###
#.##...############.####.##.#####.#######.####.###
#.##.#.#######..###.####.##.##.##.........####.###
#.##.#.#######......####.#..##..##############.###
#.##.#.########.##..####.#.##.#.##############.###
#...............##..............##############...#
####.#.########.............#.#...##############.#
#.##.#.######...................#.##############.#
#>.....###########.#######...####.##########.....#
#.##.#.########..........#...####.##########.###.#
#.##.........##.########.#...#....##########.##..#
#......#####....########.....#.................###
##################################################


Currently the application fills in a third of the level with a 75% chance to avoid a floor tile that is 2 units away. It only moves in the primary n/s/e/w directions, though I need to fix their directional modifiers because they're inverted. East is West and so on. While generating the level a log is shown to track down some very bizarre errors.

Basically, any advice on improvement? It's really dirty work that I've only been doing for about two days tops now. At first it was entirely random in the for loop, then later a little logic got added, and then it was revamped into the following. I also coded a copy button so you could see the results and learned at the same time that I hate the windows clipboard. ;) Download the binary and mess with it for a few minutes and see what the levels look like more than just the one preview.

Full download is here (http://thewickedflea.com/download.php?f=rand_lvl_gen.zip), source to the main unit follows. (Updated: 3:25PM EST [-700 GMT])

unit uMain;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Grids, Buttons, Clipbrd, uMessages, Math;

type
tTile = record
pic: char;
odds: byte;
end;
TForm1 = class(TForm)
StringGrid1: TStringGrid;
Panel1: TPanel;
Button1: TButton;
Label1: TLabel;
Cols: TEdit;
Rows: TEdit;
Label2: TLabel;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure DimensionKeyPress(Sender: TObject; var Key: Char);
procedure FormResize(Sender: TObject);
procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
private
{ Private declarations }
public
{ Public declarations }
end;

TDirection = (dNorth,dNorthWest,dWest,dSouthWest,dSouth,dSouthE ast,dEast,dNorthEast);
TDirections = set of TDirection;

const
DirTransforms: array[TDirection,0..1] of smallint = ((0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1),(-1,0),(-1,1));
DirNames: array[TDirection] of shortstring = ('North', 'Northwest', 'West', 'SouthWest',
'South', 'Southeast', 'East', 'Northeast');

var
Form1: TForm1;

procedure turn(var dir: TDirection; deg: smallint);

implementation

{$R *.dfm}

// Turn(dir = dNorth, 2) = dWest, Turn(dir = dNorth, -2) = dEast
procedure turn(var dir: TDirection; deg: smallint);
begin
if deg <0>= 0.50 then
result := 1
else
result := -1;
end;

procedure TForm1.Button1Click(Sender: TObject);
var dx, dy, x, y, tx, ty, sx, sy, fx, fy: integer;
floorspace: integer;
validdirs: TDirections;
dir: TDirection;
begin
StringGrid1.DefaultDrawing := False;

with frmStatus do begin
if not Visible then
Show
else
BringToFront;

Entries.Clear;
end;

if TryStrToInt(Cols.Text,dx) and TryStrToInt(Rows.Text,dy) then begin
Randomize;
Assert((dx > 4) and (dy > 4));

StringGrid1.ColCount := dx;
StringGrid1.RowCount := dy;
validdirs := [dNorth,dWest,dEast,dSouth];
dir := dNorthWest;
floorspace := (dx*dy) div 3;

frmStatus.Log('Initializing...');
frmStatus.Log(format('RowCount: %d',[dy]));
frmStatus.Log(format('ColCount: %d',[dx]));
frmStatus.Log(format('Floorarea: %d',[floorspace]));

for y := 0 to dy - 1 do
for x := 0 to dx - 1 do
StringGrid1.Cells[x,y] := '#';

while not (dir in validdirs) do
dir := TDirection(Random(Byte(High(TDirection))+1));

x := 1 + Random(dx-2);
y := 1 + Random(dy-2);

frmStatus.Log(Format('Starting point (%d,%d)',[x,y]));

while floorspace > 0 do begin
validdirs := [dNorth,dWest,dEast,dSouth];

if (floorspace >= 5) and (random(100) < 2) then begin
repeat
x := 1 + Random(dx-2);
y := 1 + Random(dy-2);
until (StringGrid1.Cells[x,y] = '#');
while not (dir in validdirs) do
dir := TDirection(Random(Byte(High(TDirection))+1));
end;

if x <2> dx-3 then
Exclude(validdirs,dWest);

if y <2> dy-3 then
Exclude(validdirs,dNorth);

if (random(100) < 15) then
repeat
turn(dir,2*randomSign);
until (dir in validdirs);

tx := (DirTransforms[dir][0]*2);
ty := (DirTransforms[dir][1]*2);

// here we check to see the the approaching tile two ahead is floor, and
// then probably turn away from it. It's better than having a ton of huge
// rooms or identical passages.
if (x+tx in [1..dx-2]) and (y+ty in [1..dy-2]) then
if (StringGrid1.Cells[x+tx,y+ty] = '.') and (Random(100) <70> (Sqrt(Sqr(dx) + Sqr(dy)) * 0.55) then
Break;
until (false);

frmStatus.Log(Format('Entry point ("<") at (%d,%d)',[sx,sy]));
frmStatus.Log(Format('Exit point (">") at (%d,%d)',[fx,fy]));
StringGrid1.Cells[sx,sy] := '<';
StringGrid1.Cells[fx,fy] := '>';
end else
ShowMessage('Error, one of the dimensions is not an integer.');

BringToFront;
end;

procedure TForm1.DimensionKeyPress(Sender: TObject; var Key: Char);
begin
if not (Key in ['0'..'9',Char(VK_BACK)]) then
Key := #0;
end;

procedure TForm1.FormResize(Sender: TObject);
begin
Button2.Left := Panel1.Width - 78;
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
var sl: TStringList;
x,y: integer;
s: string;
begin
sl := TStringList.Create;

for y := 0 to StringGrid1.RowCount - 1 do begin
s := '';
for x := 0 to StringGrid1.ColCount - 1 do
s := s + StringGrid1.Cells[x,y][1];
sl.Add(s);
end;

with Clipboard do begin
Open;
SetTextBuf(PChar('
'+Sl.Text+''));
Close;
end;

sl.Free;
end;

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var x,y: integer;
begin
if TStringGrid(Sender).DefaultDrawing then
Exit;

with sender as TStringGrid do begin
// if gdSelected in State then
// Brush.Color := clHighlight
// else
// Brush.Color := clWindow;
// Canvas.FillRect(Rect);

if trim(Cells[ACol,ARow]) = '' then exit;

x := ((Rect.Right - Rect.Left) - Canvas.TextWidth(Cells[ACol,ARow])) div 2;
y := ((Rect.Bottom - Rect.Top) - Canvas.TextHeight(Cells[ACol,ARow])) div 2;

if Cells[ACol,ARow][1] in ['<','>'] then begin
Canvas.Font.Color := clRed;
Canvas.Font.Style := [fsBold];
end else begin
if gdSelected in State then
Canvas.Font.Color := clHighlightText
else
Canvas.Font.Color := clWindowText;
Canvas.Font.Style := [];
end;

Canvas.TextRect(Rect,Rect.Left+x,Rect.Top+y,Cells[ACol,ARow]);
end;
end;

end.

Robert Kosek
08-06-2007, 07:23 PM
New version supports entry(<)/exit(>) points, renders those points as bold red characters so they're easier to see, and is just generally more tricky in how the level is generated.

The download has been updated in the first post, as well as the source code.

Any ideas for better performance, aside from the performance costs of logging the actions for debugging?


################################################## #########################
#..##......................####................... ........########.########
#..##.#.................................#.#....... .................########
#..##.#.#############.#############.....#.##.##### ####...#########.########
#..##....#.....................#######............ ........................#
#..####..#.##########.########.#######.##.##.#.... .......#########.######.#
#..####..#.#.########............................. ..###..#########.######.#
#..####..#.#.#################.#######.##..#.##... .................####...#
#..####..#.#...............###.####..........##.## ##.##..########..####.#.#
#..####..#.#.##.#..###########.####.....#..####.## ##.##............####.#.#
#........#.#.>.....###########.######.###..####.####.....####... ........#.#
#..........#....#..###########.######......####.## ##############.##########
##...........####..###########..##########.####.## #####............########
##............###............######........####.## ########....##...########
########......####.##########...................## ########......<.#########
########.#########.#############.#.....##.....#### ###########.....#########
########.#########.#############.#..........#.#### #########################
########.......###.#############.....#.#...##.#### #########################
##################.#############.......###....#### #########################
##################.#############.....#####........ ..#######################
##################.###############.##.......###### #########################
##################.#######################.....### #########################
#################..############################### #########################
#################..############################### #########################
################################################## #########################

edexter
09-10-2007, 11:42 PM
I know it has been a long time since you have posted this... I was trying to compile your code and I was getting an error with it... I was hopeing to make some changes so that it was saved to disk...

Robert Kosek
10-10-2007, 01:24 AM
Well, what problems were you getting? I recently reformatted and I've yet to reinstall Delphi, but I can still help you.

User137
10-10-2007, 01:26 PM
StringGrid1.ExplicitLeft: Property ExplicitLeft does not exist.
and same for:
ExplicitTop
ExplicitHeight
ExplicitWidth

FIX:
open uMain AND uMessages forms, ignore warnings of missing properties, save, compile and run :wink: