Graal Forums  

Go Back   Graal Forums > Development Forums > NPC Scripting > Code Gallery
FAQ Members List Calendar Today's Posts

Reply
 
Thread Tools Search this Thread Display Modes
  #1  
Old 10-06-2010, 05:13 AM
salesman salesman is offline
Finger lickin' good.
salesman's Avatar
Join Date: Nov 2008
Location: Colorado
Posts: 1,865
salesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud of
MazeGenerator

I had an assignment for school to write a maze generator in Java, and decided to convert it to GS2. The purpose of the assignment was to apply a disjoint set data structure to something practical, like maze generating. It might not be the most efficient algorithm, and it might not generate the most difficult mazes, but it is fast and it works. The algorithm is essentially a randomized Kruskal's algorithm, and has an O(n) running time proportional to the number of spaces in your maze.

How it works:
  • You start out with a grid where each space is closed off with walls.
  • You then look at each wall in some random order and have two cases to consider:
    1. There is already some path that connects the two spaces on either side of this wall, leave this wall where it is.
    2. There is no path that connects the two spaces, remove this wall to make a path.
  • We can tell if there is a path between two spaces if they are in the same set, so whenever we remove a wall, we union the two sets containing each space.
  • After looking at each wall, you should be able to reach any space from any other space in the maze because they are all in the same set.

See it in action:

When generating a maze in the video, I'm actually generating 10 mazes with a .05 second sleep between each one to give it a cool "loading" effect. So it really is fast...seemingly instant when generating a full-level maze.

Suggested Reading:
Implementation:
Simply put the code in a DBNPC and call this function:
PHP Code:
  MazeGenerator.generateMaze(level_objstart_xstart_ywidthheight);

  
// EXAMPLE:

  // This code will generate a maze in salesman's level
  // The maze will cover the entire level
  
temp.pl findplayer("salesman");
  
MazeGenerator.generateMaze(temp.pl.level006464); 
When the maze is generated, there will be exactly one path between any two points in the maze. Because of this, you can pick any two points to be your start and end locations of the maze. I recommend choosing opposite corners!

The code:
MazeGenerator.txt

NOTE: I have not tested this on a gmap!
__________________
Reply With Quote
  #2  
Old 10-06-2010, 06:28 AM
TheJames TheJames is offline
Atrius Manager
TheJames's Avatar
Join Date: Dec 2004
Posts: 581
TheJames can only hope to improve
Badass

Remind me why you arnt staff on Era/Manager?
__________________
Contact me on Skype: "skype.sam.lorenz"

James,
Manager of Atrius
Reply With Quote
  #3  
Old 10-06-2010, 06:45 AM
Tigairius Tigairius is offline
The Cat
Tigairius's Avatar
Join Date: Jan 2007
Location: Missouri, USA
Posts: 4,240
Tigairius has a brilliant futureTigairius has a brilliant futureTigairius has a brilliant futureTigairius has a brilliant futureTigairius has a brilliant futureTigairius has a brilliant futureTigairius has a brilliant futureTigairius has a brilliant future
Quote:
Originally Posted by salesman View Post
has an O(n) running time proportional to the number of spaces in your maze.
Is the number of spaces in your maze something user-defined?
__________________


“Shoot for the moon. Even if you miss, you'll land among the stars.”
Reply With Quote
  #4  
Old 10-06-2010, 06:47 AM
DustyPorViva DustyPorViva is offline
Will work for food. Maybe
DustyPorViva's Avatar
Join Date: Sep 2003
Location: Maryland, USA
Posts: 9,589
DustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond reputeDustyPorViva has a reputation beyond repute
Send a message via AIM to DustyPorViva Send a message via MSN to DustyPorViva
Quote:
Originally Posted by TheJames View Post
Badass

Remind me why you arnt staff on Era/Manager?
Because Era isn't a giant server of mazes, duh!
Reply With Quote
  #5  
Old 10-06-2010, 06:51 AM
MrOmega MrOmega is offline
One More Time
MrOmega's Avatar
Join Date: Aug 2010
Location: TN, USA
Posts: 631
MrOmega is an unknown quantity at this point
Send a message via AIM to MrOmega Send a message via MSN to MrOmega Send a message via Yahoo to MrOmega
Dude.. my mind is blown... o_o
__________________
Time is the fire in which we burn...
Up, Up, Down, Down, Left, Right, Left, Right, B, A, Select, Start! Now I got 99 LIVES!!!
Reply With Quote
  #6  
Old 10-06-2010, 07:54 AM
Seich Seich is offline
Noctorious' NeoHunter
Seich's Avatar
Join Date: Jun 2008
Location: Honduras
Posts: 193
Seich will become famous soon enough
Send a message via MSN to Seich Send a message via Yahoo to Seich
That's awesome... ._.
Reply With Quote
  #7  
Old 10-06-2010, 08:28 AM
xAndrewx xAndrewx is offline
Registered User
xAndrewx's Avatar
Join Date: Sep 2004
Posts: 5,260
xAndrewx has much to be proud ofxAndrewx has much to be proud ofxAndrewx has much to be proud ofxAndrewx has much to be proud ofxAndrewx has much to be proud ofxAndrewx has much to be proud ofxAndrewx has much to be proud of
very cool- but there isn't an exit!!
Reply With Quote
  #8  
Old 10-06-2010, 12:54 PM
xXziroXx xXziroXx is offline
Master of Puppets
xXziroXx's Avatar
Join Date: May 2004
Location: Sweden
Posts: 5,288
xXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant futurexXziroXx has a brilliant future
Send a message via AIM to xXziroXx Send a message via MSN to xXziroXx
Reminds me of this, nice job though.
__________________

"A delayed game is eventually good, but a rushed game is forever bad." - Shigeru Miyamoto
Reply With Quote
  #9  
Old 10-06-2010, 01:26 PM
Dnegel Dnegel is offline
Rjax Shizibz
Join Date: Jul 2009
Posts: 1,334
Dnegel will become famous soon enough
Badass music.

But really cool Generator Sales.
Reply With Quote
  #10  
Old 10-06-2010, 04:16 PM
cbkbud cbkbud is offline
Chris Vimes' Brother
cbkbud's Avatar
Join Date: Aug 2008
Posts: 5
cbkbud is on a distinguished road
Very nice work Sales .
__________________
Reply With Quote
  #11  
Old 10-06-2010, 04:32 PM
salesman salesman is offline
Finger lickin' good.
salesman's Avatar
Join Date: Nov 2008
Location: Colorado
Posts: 1,865
salesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud of
Quote:
Originally Posted by Tigairius View Post
Is the number of spaces in your maze something user-defined?
The width and height are user-defined, and number of spaces = width * height. I actually meant to say number of walls (because the important part of the algorithm looks at each wall once), but the number of walls is directly proportional to the number of spaces, regardless.

Quote:
Originally Posted by xAndrewx View Post
very cool- but there isn't an exit!!
lol, that's your job.
Quote:
Originally Posted by salesman View Post
When the maze is generated, there will be exactly one path between any two points in the maze. Because of this, you can pick any two points to be your start and end locations of the maze. I recommend choosing opposite corners!
__________________
Reply With Quote
  #12  
Old 10-06-2010, 07:34 PM
Bell Bell is offline
Registered User
Bell's Avatar
Join Date: Feb 2007
Posts: 1,824
Bell has much to be proud ofBell has much to be proud ofBell has much to be proud ofBell has much to be proud ofBell has much to be proud ofBell has much to be proud ofBell has much to be proud of
One maze server coming up.
Reply With Quote
  #13  
Old 10-07-2010, 10:46 AM
smirt362 smirt362 is offline
Tee Hee
smirt362's Avatar
Join Date: Feb 2005
Location: Texas
Posts: 2,101
smirt362 has a brilliant futuresmirt362 has a brilliant futuresmirt362 has a brilliant futuresmirt362 has a brilliant futuresmirt362 has a brilliant futuresmirt362 has a brilliant futuresmirt362 has a brilliant future
Send a message via AIM to smirt362 Send a message via MSN to smirt362
Really cool, randomly generated dungeon levels anyone?
__________________

Don Hertzfeldt <3
Reply With Quote
  #14  
Old 10-11-2010, 08:19 AM
fowlplay4 fowlplay4 is offline
team canada
fowlplay4's Avatar
Join Date: Jul 2004
Location: Canada
Posts: 5,200
fowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond reputefowlplay4 has a reputation beyond repute
You inspired me to take a shot at the maze generator thing.

Uses.. Depth-first search algorithm

The grid output is a fairly simple array.

grid[y][x][wall direction (0 to 3)] == wall alive boolean

My implementation does have it's limitations however I narrowed out the maximum size to be 35 by 35 but depending on your application (random dungeon level generator for example) that should be more than enough.

PHP Code:
function onCreated() {
  
// Generate Maze
  
temp.maze generateMaze(3535);
  
// For added fun!
  
randomDestruction(temp.maze.link(), 20);
  
// Example output
  
drawGrid(temp.maze.link());
}

public function 
generateMaze(temp.widthtemp.height) {
  
// Generate Blank Grid
  
temp.cells  = new[temp.height][temp.width][5];
  
// Begin Adding Rows
  
for (temp.0temp.temp.widthtemp.y++) {
    
// Begin Adding Columns
    
for (temp.0temp.temp.heighttemp.x++) {
      
// Generate Cells
      
temp.cell = {1111false};
      
// Add Cell to Column
      
temp.cells[temp.y][temp.x] = temp.cell;
      
temp.i++;
    }
  }
  
// Pick Exit
  
temp.exit_x int(random(0temp.width));
  
temp.exit_y int(random(0temp.height));
  
// Mark Exit
  
temp.cells[temp.exit_y][temp.exit_x][4] = true;
  
// Begin Carving
  
carveMaze(temp.cells.link(), temp.exit_xtemp.exit_y);
  
// Trim Visited Fat
  
for (temp.0temp.temp.widthtemp.y++) {
    for (
temp.0temp.temp.heighttemp.x++) {
      
temp.cells[temp.y][temp.x].delete(4);
    }
  }
  
// Return Grid
  
return temp.cells;
}

function 
carveMaze(cellscxcy) {
  
// Determine Grids Width and Height
  
temp.height cells.size();
  
temp.width  cells[0].size();
  
// Find Unvisited Neighbour Cells
  
for (temp.0temp.4temp.i++) {
    
temp.tx cx vecx(temp.i);
    
temp.ty cy vecy(temp.i);
    if (
cells[ty][tx].size() == 5) {
      if (
cells[ty][tx][4] == false) {
        
temp.unvisited.add(temp.i);
      }
    }
  }
  
// Check if has Unvisited Neighbours
  
temp.neighbours temp.unvisited.size();
  if (
temp.neighbours 0) {
    
// Pick Unvisited Neighbour
    
temp.neighbour temp.unvisited[int(random(0temp.neighbours))];
    
temp.tx cx vecx(temp.neighbour);
    
temp.ty cy vecy(temp.neighbour);
    
// Remove Wall between Neighbours
    
cells[cy][cx][temp.neighbour] = 0;
    
cells[ty][tx][(temp.neighbour 2) % 4] = 0;
    
// Set Neighbour Cell to Visited
    
cells[ty][tx][4] = true;
    
// Continue Carving as Neighbour
    
carveMaze(cells.link(), temp.txtemp.ty);
    
// Continue Carving Local Neighbours
    
if (temp.neighbours 0carveMaze(cells.link(), cxcy);
  }
}

public function 
randomDestruction(cellswalls) {
  
// Determine Grids Width and Height
  
temp.height cells.size();
  
temp.width  cells[0].size();
  
// Check for Proper Cell
  
if (cells[0][0].size() != 4) return false;
  
// Begin Random Destruction
  
while (walls 0) {
    
// Pick a Random Cell
    
temp.cx int(random(0temp.width));
    
temp.cy int(random(0temp.height));
    
// Pick a Random Neighbour
    
temp.neighbour int(random(04));
    
temp.tx cx vecx(temp.neighbour);
    
temp.ty cy vecy(temp.neighbour);
    
// Skip Non-Cells
    
if (cells[temp.tx][temp.ty].size() != 4) continue;
    
// Check if Wall Exists
    
if (cells[temp.cy][temp.cx][temp.neighbour] == true) {
      
// Remove Wall
      
cells[cy][cx][temp.neighbour] = 0;
      
cells[ty][tx][(temp.neighbour 2) % 4] = 0;
      
walls--;
    }
  }  
}

/*
   Example Rendering of a Maze
*/

public function drawGrid(cells) {
  
// Determine Grids Width and Height
  
temp.height cells.size();
  
temp.width  cells[0].size();
  
// Check for Proper Cell
  
if (cells[0][0].size() != 4) return false;
  
// Drawing Configuration
  
temp.cell_width  24;
  
temp.cell_height 24;
  
temp.lw          1;  // Line Width
  // Create Drawing Panel
  
temp.panel = new TDrawingPanel();
  
// Line Drawing Functionality (drawline is a jerk)
  
temp.panel.drawhoriz = function (x1y1width) {
    
this.drawimagestretched(params[0], params[1], width1"whitefill.png"0011);
  };
  
temp.panel.drawvert = function (x1y1height) {
    
this.drawimagestretched(params[0], params[1], 1height"whitefill.png"0011);
  };
  
// Set Panel Size
  
temp.panel.setsize(temp.width temp.cell_widthtemp.height temp.cell_height);
  
// Fill Image
  
temp.panel.drawimagestretched(00temp.width temp.cell_widthtemp.height temp.cell_height"blackfill.png"0011);
  
// Begin Drawing Cells
  
for (temp.0temp.temp.widthtemp.y++) {
    for (
temp.0temp.temp.heighttemp.x++) {
      
temp.cell cells[temp.y][temp.x];
      for (
temp.0temp.4temp.i++) {
        
// Check if Wall on that Direction
        
if (temp.cell[temp.i]) {
          
// Determine Top Corner
          
temp.tx temp.temp.cell_width;
          
temp.ty temp.temp.cell_height;
          
// Draw Walls
          
if (temp.== 0temp.panel.drawhoriz(temp.txtemp.tytemp.cell_width);
          else if (
temp.== 1temp.panel.drawvert(temp.txtemp.tytemp.cell_height);
          else if (
temp.== 2temp.panel.drawhoriz(temp.txtemp.ty temp.cell_height 1temp.cell_width);
          else if (
temp.== 3temp.panel.drawvert(temp.tx temp.cell_width 1temp.tytemp.cell_height);
        }
      }
    }
  }
  
// Save Image (change this to somewhere meaningful to yourself)
  
temp.panel.saveimage("cartridge/testmaze.png");
  
// Destroy Panel
  
temp.panel.destroy();

Attached, images used in script and an example output.

http://img40.imageshack.us/img40/7562/whitefill.png
http://img259.imageshack.us/img259/9659/blackfill.png
Attached Thumbnails
Click image for larger version

Name:	testmaze.png
Views:	329
Size:	14.8 KB
ID:	51825  
__________________
Quote:

Last edited by fowlplay4; 10-11-2010 at 08:51 AM..
Reply With Quote
  #15  
Old 10-11-2010, 07:57 PM
salesman salesman is offline
Finger lickin' good.
salesman's Avatar
Join Date: Nov 2008
Location: Colorado
Posts: 1,865
salesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud ofsalesman has much to be proud of
Very nice, I was actually planning on implementing a depth-first search version myself!
__________________
Reply With Quote
Reply


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +2. The time now is 12:03 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2025, vBulletin Solutions Inc.
Copyright (C) 1998-2019 Toonslab All Rights Reserved.