RRGGLIB
Jump to navigation
Jump to search
Ruby Random Game Library
Introduction
This library is a set of tools to make easily a Rouguelike game in Ruby.
Usage
It will make a data called Table wich is a two dimensional array with the data of each kind of dungeon. I suggest you should read each kind of dungeon to make sure you are trying to make the one you want.
These are the kind of dungeons:
#============================================================================================================== # ** RGG::Dungeons::grid_based(width, height[,wall_height, cell_width, cell_height]) #-------------------------------------------------------------------------------------------------------------- # * Creates a dungeon based on grids #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAUL_WALL_HEIGHT) # - cell_width : The width of each cell (default 9) # - cell_height : the height of each cell (default 9) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system is easy to use, and easy to maintain, since it can create big rooms easier than # other methods. # - This system ensures the connections of all rooms. # Disadvantages: # - Maps doesn't have to be completelly filled. # - Not too realisitc for cave layouts. # - Cells are predecible because they are the same size. #============================================================================================================== #============================================================================================================== # ** RGG::Dungeons::maze(width, height[,wall_height]) #-------------------------------------------------------------------------------------------------------------- # * Creates a perfect maze, trying to take all the possible space. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAUL_WALL_HEIGHT) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system ensures that every space is rechable. # Disadvantages: # - Small size to put enemies, obejects, chests. # - Boring arquitecture. # - Quite slow. #============================================================================================================== #============================================================================================================== # ** RGG::Dungeons::bsp(width, height[,wall_height last_mov]) #-------------------------------------------------------------------------------------------------------------- # * Creates a Maze with the BSP method. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAULT_WALL_HEIGHT) # - last_mov : The starting maze direction (random by default, 0 = horizontal, else vertical) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system ensures that every space is rechable. # - Easy maintained rooms. # - Better method than a normal cell based. # Disadvantages: # - This method would make easier dungeons than other ones. #============================================================================================================== #============================================================================================================== # ** RGG::Dungeons::cellular_automata(width, height[,wall_height last_mov]) #-------------------------------------------------------------------------------------------------------------- # * Creates a Dungeon with the BSP method. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAULT_WALL_HEIGHT) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system ensures that every space is rechable. # - Ganarates a great irregular effect. (Specially good for caves) # Disadvantages: # - This method would make easier dungeons than other ones. # - Extremly slow method. # - May create "Big areas" filled with nothing. #============================================================================================================== #============================================================================================================== # ** RGG::Dungeons::mirrored(dungeon[, mode]) #-------------------------------------------------------------------------------------------------------------- # * Creates a Dungeon using a mirror method. # The width and height of the newly created dungeon is automatically recognized. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - dungeon : The dungeon data to mirror # - mode : The mirror mode (default is X and Y) # - wall_height : The height of the wall. (default = DEFAULT_WALL_HEIGHT) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - Great effect for some places. # Disadvantages: # - Needs previous data to work, so it is just #==============================================================================================================
Code
#====================================================================================================================== # ** RANDOM GAME GENERATOR (RGG) #---------------------------------------------------------------------------------------------------------------------- # * Library for Rouguelike Generation in Ruby. #---------------------------------------------------------------------------------------------------------------------- # * Author: Ramiro Rojo. # * Version 1.0 #---------------------------------------------------------------------------------------------------------------------- # * Licence (Free BSD Licence): # Copyright 2011 Ramiro Rojo. All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are # permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this list of # conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, this list # of conditions and the following disclaimer in the documentation and/or other materials # provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # The views and conclusions contained in the software and documentation are those of the # authors and should not be interpreted as representing official policies, either expressed # or implied, of Ramiro Rojo. #---------------------------------------------------------------------------------------------------------------------- # * Thanks: # - Jim Babcock for the Cellular Automata code and tehory. # - Rougue Basin (http://roguebasin.roguelikedevelopment.org) for the BSP Generation, and normal rouguelike information. # - Emanuele Feronato(http://www.emanueleferonato.com) for the perfect maze generator. # - Alejandro Marzini (aka vgvgf) for the Table and Color classes. #====================================================================================================================== #====================================================================================================================== # ** Table Class #---------------------------------------------------------------------------------------------------------------------- # * A class for two or three dimensional arrays. #====================================================================================================================== class Table #------------------------------------------------------------------------------------------------------------------ # * Public Instance Variables #------------------------------------------------------------------------------------------------------------------ attr_reader :xsize # the x dimension of the table. attr_reader :ysize # the y dimension of the table. attr_reader :zsize # the z imension of the table. def initialize(x, y = 1, z = 1) @xsize, @ysize, @zsize = x, y, z @data = Array.new(x * y * z, 0) end def [](x, y = 0, z = 0) @data[x + y * @xsize + z * @xsize * @ysize] end def []=(*args) x = args[0] y = args.size > 2 ? args[1] :0 z = args.size > 3 ? args[2] :0 v = args.pop @data[x + y * @xsize + z * @xsize * @ysize] = v end def _dump(d = 0) s = [3].pack('L') s += [@xsize].pack('L') + [@ysize].pack('L') + [@zsize].pack('L') s += [@xsize * @ysize * @zsize].pack('L') for z in 0...@zsize for y in 0...@ysize for x in 0...@xsize s += [@data[x + y * @xsize + z * @xsize * @ysize]].pack('S') end end end s end def self._load(s) size = s[0, 4].unpack('L')[0] nx = s[4, 4].unpack('L')[0] ny = s[8, 4].unpack('L')[0] nz = s[12, 4].unpack('L')[0] data = [] pointer = 20 loop do data.push(*s[pointer, 2].unpack('S')) pointer += 2 break if pointer > s.size - 1 end t = Table.new(nx, ny, nz) n = 0 for z in 0...nz for y in 0...ny for x in 0...nx t[x, y, z] = data[n] n += 1 end end end t end end #====================================================================================================================== # ** Color Class #---------------------------------------------------------------------------------------------------------------------- # * The class containing color data in RGBA format. #====================================================================================================================== class Color #------------------------------------------------------------------------------------------------------------------ # * Public Instance Variables #------------------------------------------------------------------------------------------------------------------ attr_reader :red # the red component. attr_reader :green # the green component. attr_reader :blue # the blue component. attr_reader :alpha # the alpha (transparency) component. #================================================================================================================== # * Color#initialize(r, g, b[, a]) #------------------------------------------------------------------------------------------------------------------ # * Instantiates a new color #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - r : The red component of the color # - g : The green component of the color # - b : The blue component of the color # - a : the alpha component of the color (default = 255) #================================================================================================================== def initialize(r, g, b, a = 255.0) self.red = r.to_f self.green = g.to_f self.blue = b.to_f self.alpha = a.to_f end #================================================================================================================== # * Color#set(r, g, b[, a]) #------------------------------------------------------------------------------------------------------------------ # * Sets all the data of the color directly #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - r : The red component of the color # - g : The green component of the color # - b : The blue component of the color # - a : the alpha component of the color ( default = 255) #================================================================================================================== def set(r, g, b, a = 255.0) self.red = r.to_f self.green = g.to_f self.blue = b.to_f self.alpha = a.to_f end #================================================================================================================== # * Color#red =(val) #------------------------------------------------------------------------------------------------------------------ # * Sets the red component of the color #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - val : The red component of the color #================================================================================================================== def red=(val) @red = [[val.to_f, 0.0].max, 255.0].min end #================================================================================================================== # * Color#green =(val) #------------------------------------------------------------------------------------------------------------------ # * Sets the green component of the color #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - val : The green component of the color #================================================================================================================== def green=(val) @green = [[val.to_f, 0.0].max, 255.0].min end #================================================================================================================== # * Color#blue =(val) #------------------------------------------------------------------------------------------------------------------ # * Sets the blue component of the color #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - val : The blue component of the color #================================================================================================================== def blue=(val) @blue = [[val.to_f, 0.0].max, 255.0].min end #================================================================================================================== # * Color#alpha =(val) #------------------------------------------------------------------------------------------------------------------ # * Sets the alpha component of the color #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - val : The alpha component of the color #================================================================================================================== def alpha=(val) @alpha = [[val.to_f, 0.0].max, 255.0].min end #================================================================================================================== # * Color#color #------------------------------------------------------------------------------------------------------------------ # * Creates a duplication of the current color #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - none. #================================================================================================================== def color Color.new(@red, @green, @blue, @alpha) end #================================================================================================================== # * Color::_dump #------------------------------------------------------------------------------------------------------------------ # * Stores the color in a binary file #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - none #================================================================================================================== def _dump(d = 0) [@red, @green, @blue, @alpha].pack('d4') end #================================================================================================================== # * Color::_load #------------------------------------------------------------------------------------------------------------------ # * Loads a color from a binary file #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - none. #================================================================================================================== def self._load(s) Color.new(*s.unpack('d4')) end #================================================================================================================== # * Color#==(other) #------------------------------------------------------------------------------------------------------------------ # * Cheks if the color is the same as other. #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - other: other Color to compare. #================================================================================================================== def ==(other) (@red == other.red && @green == other.green && @blue == other.blue && @alpha == other.alpha) end #================================================================================================================== # * Color#===(other) #------------------------------------------------------------------------------------------------------------------ # * Cheks if the color is the same as other. #------------------------------------------------------------------------------------------------------------------ # * Parameters: # - other: other Color to compare. #================================================================================================================== def ===(other) (@red == other.red && @green == other.green && @blue == other.blue && @alpha == other.alpha) end end class Point attr_accessor :x attr_accessor :y MANHATTAN = 0 HYPOT = 1 def initialize(x, y) @x = x @y = y end def effort(x, y, mode = MANHATTAN) case mode when MANHATTAN; return ((@x - x) + (@y - y)).abs when HYPOT; return Math.hypot((@x - x), (@y - y)) else; return ((@x - x) + (@y - y)).abs end end def ==(other) other.is_a?(Point) ? (self.x == other.x && self.y == other.y) : false end def ===(other) other.is_a?(Point) ? (self.x == other.x && self.y == other.y) : false end end #====================================================================================================================== # ** RGG Module (Random Game Generator Module) #---------------------------------------------------------------------------------------------------------------------- # * The module to control all the ganaration of the library. #====================================================================================================================== module RGG #================================================================================================================== # ** RGG::Dungeons Module #------------------------------------------------------------------------------------------------------------------ # * The sub module that controls the dungeon generation. #================================================================================================================== module Dungeons #-------------------------------------------------------------------------------------------------------------- # * Module Constants #-------------------------------------------------------------------------------------------------------------- # the tile id for the "floor of the dungeon" FLOOR = 0 # the tile id for the wall of the dungeon WALL = 1 # the tile id for roofs of the dungeons ROOF = 2 # mirror x and y axis BOTH_MIRROR = 1 # mirror x axis X_MIRROR = 2 # mirror y axis Y_MIRROR = 3 # The default size of the floors of a dungeon DEFAULT_WALL_HEIGHT = 1 # Color codes used for tiled data (for testing orpouses ) COLORS = { ROOF => Color.new(255, 255, 255), WALL => Color.new(180, 180, 180), FLOOR => Color.new(60, 60, 60), } #============================================================================================================== # ** RGG::Dungeons::grid_based(width, height[,wall_height, cell_width, cell_height]) #-------------------------------------------------------------------------------------------------------------- # * Creates a dungeon based on grids #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAUL_WALL_HEIGHT) # - cell_width : The width of each cell (default 9) # - cell_height : the height of each cell (default 9) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system is easy to use, and easy to maintain, since it can create big rooms easier than # other methods. # - This system ensures the connections of all rooms. # Disadvantages: # - Maps doesn't have to be completelly filled. # - Not too realisitc for cave layouts. # - Cells are predecible because they are the same size. #============================================================================================================== def self.grid_based(width, height,wall_height = DEFAULT_WALL_HEIGHT, cell_width=9, cell_height=9) # sets up the data data = Table.new(width, height) for x in 0...width for y in 0...height data[x, y] = ROOF end end # define the numbers of horizontal and vertical rooms h = data.xsize / cell_width v = data.ysize / cell_height # define an arbitary number of total rooms total_rooms = [h * v / 2, rand(h * v) + 1].max rooms = 0 # sets the current room as the midde one. current_room = Point.new(h / 2, v / 2) first_room = Point.new(current_room.x, current_room.y) connections = Table.new(h, v) # sets up the connections for i in 0...h for j in 0...v connections[i, j] = 0 end end now_x = h / 2 now_y = v / 2 connections[now_x, now_y] = 1 for x in (now_x * cell_width + 1)...((now_x + 1) * cell_width - 1) for y in (now_y * cell_height + 1 + wall_height)...((now_y + 1) * cell_height - 1) data[x, y] = FLOOR end end last_x = now_x last_y = now_y can_create_room = true next_pos = [] gone_room_count = 4 while can_create_room d = [] d.push("N") if now_y > 0 && connections[now_x, now_y-1] == 0 d.push("S") if now_y < (v - 1) && connections[now_x, now_y+1] == 0 d.push("W") if now_x > 0 && connections[now_x-1, now_y] == 0 d.push("E") if now_x < (h - 1) && connections[now_x+1, now_y] == 0 if d.size == 0 can_create_room = false else c1 = [now_x * cell_width + cell_width / 2, now_y * cell_height + (cell_height - 1 - wall_height) / 2 + wall_height ] t = d.size - 1 + rand(2) (t).times do |i| case d[rand(d.size)] when "N" temp_y = now_y - 1 temp_x = now_x d.delete("N") when "S" temp_y = now_y + 1 temp_x = now_x d.delete("S") when "E" temp_x = now_x + 1 temp_y = now_y d.delete("E") when "W" temp_x = now_x - 1 temp_y = now_y d.delete("W") end connections[temp_x, temp_y] = 1 c2 = [temp_x * cell_width + cell_width / 2, temp_y * cell_height + (cell_height - 1 - wall_height) / 2 + wall_height ] if rand(gone_room_count) != 0 for x in (temp_x * cell_width + 1)...((temp_x + 1) * 9 - 1) for y in (temp_y * cell_height + 1 + wall_height)...((temp_y + 1) * cell_height - 1) data[x, y] = FLOOR end end gone_room_count -= 1 else gone_room_count += 1 end next_pos.push(Point.new(temp_x, temp_y)) x1 = [c1[0], c2[0]].min x2 = [c1[0], c2[0]].max y1 = [c1[1], c2[1]].min y2 = [c1[1], c2[1]].max for x in x1..x2 for y in y1..y2 data[x, y] = FLOOR end end end point = next_pos[rand(next_pos.size)] next_pos.delete(point) now_x = point.x now_y = point.y end end # returns the current data created return generate_walls(data, wall_height) end #============================================================================================================== # ** RGG::Dungeons::maze(width, height[,wall_height]) #-------------------------------------------------------------------------------------------------------------- # * Creates a perfect maze, trying to take all the possible space. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAUL_WALL_HEIGHT) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system ensures that every space is rechable. # Disadvantages: # - Small size to put enemies, obejects, chests. # - Boring arquitecture. # - Quite slow. #============================================================================================================== def self.maze(width, height,wall_height = DEFAULT_WALL_HEIGHT) # sets up the data data = Table.new(width, height) for x in 0...width for y in 0...height data[x, y] = ROOF end end sparcing = (wall_height+2) for x in 0...width for y in 0...height data[x, y] = ROOF end end moves = [] moves.push(Point.new(rand(width - 1) + 1,rand(height - 1) + 1)) last_dir = nil while moves.size > 0 d = [] case rand(5) when 0; moves.sort! {|a, b| a.x<=>b.y} when 1; moves.sort! {|a, b| a.y<=>b.x} when 2; moves.sort! {|a, b| b.x<=>a.y} when 3; moves.sort! {|a, b| b.y<=>a.x} end move = moves.pop d.push("E") if data[move.x+sparcing, move.y] == ROOF && valid?(move.x+sparcing, move.y, data, wall_height) d.push("W") if data[move.x-sparcing, move.y] == ROOF && valid?(move.x-sparcing, move.y, data, wall_height) d.push("S") if data[move.x, move.y+sparcing] == ROOF && valid?(move.x, move.y+sparcing, data, wall_height) d.push("N") if data[move.x, move.y-sparcing] == ROOF && valid?(move.x, move.y-sparcing, data, wall_height) if d.size > 0 case d[rand(d.size)] when "N" for i in 0..sparcing data[move.x, move.y-+i] = FLOOR if valid?(move.x, move.y-i, data, wall_height) end moves.push(Point.new(move.x, move.y-sparcing)) when "S" for i in 0..sparcing data[move.x, move.y+i] = FLOOR if valid?(move.x, move.y+i, data, wall_height) end moves.push(Point.new(move.x, move.y+sparcing)) when "E" for i in 0..sparcing data[move.x+i, move.y] = FLOOR if valid?(move.x+i, move.y, data, wall_height) end moves.push(Point.new(move.x+sparcing, move.y)) when "W" for i in 0..sparcing data[move.x-i, move.y] = FLOOR if valid?(move.x-i, move.y, data, wall_height) end moves.push(Point.new(move.x-sparcing, move.y)) end end moves.push(move) if (d.size > 1 && !moves.include?(move)) moves.uniq! end return generate_walls(data, wall_height) end #============================================================================================================== # ** RGG::Dungeons::bsp(width, height[,wall_height last_mov]) #-------------------------------------------------------------------------------------------------------------- # * Creates a Maze with the BSP method. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAULT_WALL_HEIGHT) # - last_mov : The starting maze direction (random by default, 0 = horizontal, else vertical) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system ensures that every space is rechable. # - Easy maintained rooms. # - Better method than a normal cell based. # Disadvantages: # - This method would make easier dungeons than other ones. #============================================================================================================== def self.bsp(width, height,wall_height = DEFAULT_WALL_HEIGHT, last_mov = rand(2)) return generate_walls(DungeonDivision.new(0,0, width, height, wall_height, last_mov).data, wall_height) end #============================================================================================================== # ** RGG:: DungeonDivision class #-------------------------------------------------------------------------------------------------------------- # * A class to help making a BSP dungeon. #============================================================================================================== class DungeonDivision #---------------------------------------------------------------------------------------------------------- # * Public instance variables #---------------------------------------------------------------------------------------------------------- attr_accessor :x # the x coordinate of this division. attr_accessor :y # the y coordinate of this division. attr_accessor :width # the width of this division. attr_accessor :height # the height of this division. attr_accessor :divisions # the divisions of this division. attr_accessor :data # the data of this division #============================================================================================================== # ** RGG:: DungeonDivision#initialize(x, y, width, height[,wall_height last_mov]) #-------------------------------------------------------------------------------------------------------------- # * Creates a Maze with the BSP method. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate of the dungeon. # - y : the y coordinate of the dungeon. # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAULT_WALL_HEIGHT) # - last_mov : The starting maze direction (random by default, 0 = horizontal, else vertical) #============================================================================================================== def initialize(x, y, width, height,wall_height = 1, last_mov = 0) @x = x @y = y @width = width @height = height @divisions = [] @data = Table.new(width, height) for x in 0...width for y in 0...height @data[x, y] = ::RGG::Dungeon_Base::ROOF end end iterations = (width > 15) && (height > 14 + wall_height) ? 1 : 0 if (iterations > 0) r = rand(3) r = 1 - last_mov if r > 1 case rand(2) when 0 w = width * (rand(21) + 40) / 100 @divisions.push(DungeonDivision.new(0, 0, w, height, wall_height, 0)) w = width - w @divisions.push(DungeonDivision.new(w, 0, w, height, wall_height, 0)) for x in 0...@divisions[0].width for y in 0...@divisions[0].height @data[x, y] = @divisions[0].data[x, y] end end for x in 0...@divisions[1].width for y in 0...@divisions[1].height @data[x + @divisions[0].width, y] = @divisions[1].data[x, y] end end y = height / 2 for x in (@divisions[0].width / 2)..(@divisions[0].width + @divisions[1].width / 2) @data[x, y] = ::RGG::Dungeons::FLOOR end when 1 h = height * (rand(21) + 40) / 100 @divisions.push(DungeonDivision.new(0, 0, width, h, wall_height, 1)) h = height - h @divisions.push(DungeonDivision.new(0, h, width, h, wall_height, 1)) for x in 0...@divisions[0].width for y in 0...@divisions[0].height @data[x, y] = @divisions[0].data[x, y] end end for x in 0...@divisions[1].width for y in 0...@divisions[1].height @data[x, y + @divisions[0].height] = @divisions[1].data[x, y] end end x = width / 2 for y in (@divisions[0].height / 2)..(@divisions[0].height + @divisions[1].height / 2) @data[x, y] = ::RGG::Dungeons::FLOOR end end else w = [rand(width) + rand(width / 2) - rand(width / 4), width - 4].min h = [rand(height) + rand(height / 2) - rand(height / 4), height - 6].min mx = (width - w) / 2 my = (height - h) / 2 for x in mx...mx+w for y in my...my+h @data[x, y] = ::RGG::Dungeons::FLOOR end end end end end #============================================================================================================== # ** RGG::Dungeons::cellular_automata(width, height[,wall_height last_mov]) #-------------------------------------------------------------------------------------------------------------- # * Creates a Dungeon with the BSP method. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - width : The width (in tiles) for the dungeon # - height : The height (in tiles) for the dungeon # - wall_height : The wall height (in tiles) of the dungeon, set as 0, if you dont want any. # (default = DEFAULT_WALL_HEIGHT) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - This system ensures that every space is rechable. # - Ganarates a great irregular effect. (Specially good for caves) # Disadvantages: # - This method would make easier dungeons than other ones. # - Extremly slow method. # - May create "Big areas" filled with nothing. #============================================================================================================== def self.cellular_automata(width, height,wall_height = DEFAULT_WALL_HEIGHT) # sets up the data data = Table.new(width, height) for x in 0...data.xsize for y in 0...data.ysize data[x, y] = ROOF end end points = data.xsize * data.ysize * 10 / 100 while points > 0 rx = [rand(width ) / 2, 5].min ry = [rand(height) / 2, 5].min x = rand(width - 6 - rx * 2) + rx / 2 + 2 y = rand(height - 6 - ry * 2) + ry / 2 + 2 if rand(10) == 0 add_inverted_circle(x, y, rx, ry, data) else add_circle(x, y, rx, ry, data) end points -= rx * ry end points = width * height * 30 / 100 tries = 0 while points > 0 x = rand(data.xsize) y = rand(data.ysize) if check_walls(x, y,data, 1, wall_height+1) == 0 for i in y..(y + wall_height+1) data[x, i] = ROOF points -= wall_height + 2 tries = 0 end end tries += 1 points = 0 if tries > 10000 end leap = 1 for x in 1...((data.xsize - 1) / leap) for y in 1...((data.ysize-1) / leap) if data[x * leap, y * leap] == FLOOR join_points(Point.new(x * leap, y * leap), data, wall_height) end end end (2).times do |i| for x in 1...(data.xsize - 1) for y in 1...(data.ysize - 1) wall_count = check_walls(x, y, data) if wall_count > 5 data[x, y] = ROOF if data[x, y] elsif wall_count < 4 data[x, y] = FLOOR if data[x, y] end end end end # returns the current data created return generate_walls(data, wall_height) end #============================================================================================================== # ** RGG::Dungeons::mirrored(dungeon[, mode]) #-------------------------------------------------------------------------------------------------------------- # * Creates a Dungeon using a mirror method. # The width and height of the newly created dungeon is automatically recognized. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - dungeon : The dungeon data to mirror # - mode : The mirror mode (default is X and Y) # - wall_height : The height of the wall. (default = DEFAULT_WALL_HEIGHT) # * Returns: # - A Table Object(see Table) with sizes width x height containing the generated dungeon. #-------------------------------------------------------------------------------------------------------------- # * Generator Notes: # Advantages: # - Great effect for some places. # Disadvantages: # - Needs previous data to work, so it is just #============================================================================================================== def self.mirrored(dungeon, mode= Y_MIRROR, wall_height = DEFAULT_WALL_HEIGHT) # sets up the data width = (mode != Y_MIRROR) ? dungeon.xsize * 2 : dungeon.xsize height = (mode != X_MIRROR) ? dungeon.ysize * 2 : dungeon.ysize data = Table.new(width, height) for x in 0...width for y in 0...height data[x, y] = ROOF end end case mode when X_MIRROR for x in 0...(width / 2) for y in 0...(height) data[x, y] = dungeon[x, y] if valid_draw?(x, y, dungeon) end end case rand(2) when 0 x = width / 2 - 1 while x > 0 y = height - 1 while y > 0 if data[x, y] == FLOOR for i in x..(width / 2) data[i, y] = FLOOR end y = x = 0 end y -= 1 end x -= 1 end else x = width / 2 - 1 while x > 0 y = 0 while y < height - 1 if data[x, y] == FLOOR for i in x..(width / 2) data[i, y] = FLOOR end y = height x = 0 end y += 1 end x -= 1 end end for x in 0...(width / 2) for y in 0...(height) data[width - x - 1, y] = data[x, y] if valid_draw?(x, y, data) end end when Y_MIRROR for x in 0...(width) for y in 0...(height / 2) data[x, y] = dungeon[x, y] if valid_draw?(x, y, dungeon) end end case rand(2) when 0 x = width - 1 while x > 0 y = height / 2 - 1 while y > 0 if data[x, y] == FLOOR for i in y..(height / 2) data[x, i] = FLOOR end y = x = 0 end y -= 1 end x -= 1 end else x = 0 while x < width - 1 y = height / 2 - 1 while y > 0 if data[x, y] == FLOOR for i in y..(height / 2) data[x, i] = FLOOR end y = 0 x = width end y -= 1 end x += 1 end end for x in 0...(width) for y in 0...(height / 2) data[x,height - y - 2] = data[x, y] if valid_draw?(x, y, data) end end else for x in 0...(width / 2) for y in 0...(height / 2) data[x, y] = dungeon[x, y] = dungeon[x, y] if valid_draw?(x, y, dungeon) end end case rand(2) when 0 x = width / 2 - 1 while x > 0 y = height / 2 - 1 while y > 0 if data[x, y] == FLOOR for i in x..(width / 2) data[i, y] = FLOOR end y = x = 0 end y -= 1 end x -= 1 end else x = width / 2 - 1 while x > 0 y = 0 while y < height / 2 - 1 if data[x, y] == FLOOR for i in x..(width / 2) data[i, y] = FLOOR end y = height x = 0 end y += 1 end x -= 1 end end case rand(2) when 0 x = width / 2 - 1 while x > 0 y = height / 2 - 1 while y > 0 if data[x, y] == FLOOR for i in y..(height / 2) data[x, i] = FLOOR end y = x = 0 end y -= 1 end x -= 1 end else x = 0 while x < width / 2 - 1 y = height / 2 - 1 while y > 0 if data[x, y] == FLOOR for i in y..(height / 2) data[x, i] = FLOOR end y = 0 x = width end y -= 1 end x += 1 end end for x in 0...(width / 2) for y in 0...(height / 2) data[width - x - 1, y] = data[x, y] if valid_draw?(x, y, data) end end for x in 0...(width) for y in 0...(height / 2) data[x,height - y - 1] = data[x, y] if valid_draw?(x, y, data) end end end # returns the current data created return generate_walls(data, wall_height) end #============================================================================================================== # ** RGG::Dungeons::valid_draw?(x, y, data) #-------------------------------------------------------------------------------------------------------------- # * Checks if a point of the map is valid. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate to check. # - y : the y coordinate to check. # - data : the data to check. # * Returns: # - true if the position is valid, false elsewhere. #============================================================================================================== def self.valid_draw?(x, y, data) return false if x > data.xsize - 1 || x < 1 || y > data.ysize || y < 1 return false if !data[x, y] return false if data[x, y] != FLOOR return true end #============================================================================================================== # ** RGG::Dungeons::join_points(point, data[, wall_height]) #-------------------------------------------------------------------------------------------------------------- # * Join a point to the center of a dungeon. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - point : The point to join. # - data : The data where the point is. # - wall_height : The height of the wall. (default = DEFAULT_WALL_HEIGHT) #============================================================================================================== def self.join_points(point, data, wall_height= DEFAULT_WALL_HEIGHT) center_point = center(data) tries = 0 loop do dir = get_tunnel_dir(point, center_point) case rand(2) when 0 next_point = Point.new(dir.x + point.x, point.y) else next_point = Point.new(point.x, point.y + dir.y) end break if stop_drawing(point,next_point, data) || tries > 10000 || next_point.x < 1 || next_point.y < wall_height || next_point.x > (data.xsize - 1) || next_point.y > (data.ysize - 1) data[next_point.x, next_point.y] = FLOOR if valid?(next_point.x, next_point.y, data) point = next_point tries += 1 end end #============================================================================================================== # ** RGG::Dungeons::get_tunnel_dir(p1, p2) #-------------------------------------------------------------------------------------------------------------- # * Checks the direction between a "current point" and a "goal point". #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - p1 : the current point. # - p2 : the goal point. # * Returns: # - a Point with the x and y coordinates of direction. #============================================================================================================== def self.get_tunnel_dir(p1, p2) dir = Point.new(0, 0) if p1.x < p2.x dir.x += 1 elsif p1.x > p2.x dir.x -= 1 end if p1.y < p2.y dir.y += 1 elsif p1.y > p2.y dir.y -= 1 end return dir end #============================================================================================================== # ** RGG::Dungeons::stop_drawing(p1, p2, data) #-------------------------------------------------------------------------------------------------------------- # * Determines if a "current point" has not reached a "goal point". #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - p1 : the current point. # - p2 : the goal point. # - data : the data to check. # * Returns: # - true if it's needed to stop drawing, false elsewhere. #============================================================================================================== def self.stop_drawing(p1,p2, data) return true if p2 == center(data) return true if p1 != p2 && data[p2.x, p2.y] == FLOOR return false end #============================================================================================================== # ** RGG::Dungeons::check_walls(x, y, data[,distance, wall_height]) #-------------------------------------------------------------------------------------------------------------- # * hecks how many walls are arround a determined tile. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate to check. # - y : the y coordinate to check. # - data : the data to check. # - distance : the distance of checking (default = 1) # - wall_height : the height of the wall (default = DEFAULT_WALL_HEIGHT) # * Returns: # - a number representing the number of tiles in area # [x-distance ... x+distance; y-distance ... y+distance+wall_height] whow many tiles are walls. # including the centered tile as well. #============================================================================================================== def self.check_walls(x, y, data, distance = 1, wall_count = 0) sum = 0 for i in (x-distance)..(x+distance) for j in (y-distance)..(y+distance+wall_count) if (!valid?(x, y, data) || data[x, y] != FLOOR) sum += 1 end end end return sum end #============================================================================================================== # ** RGG::Dungeons::valid?(x, y, data[, wall_height]) #-------------------------------------------------------------------------------------------------------------- # * Checks if a determined point of the dungeon is able tobe drawn. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate to check. # - y : the y coordinate to check. # - data : the data to check. # - wall_height : the height of the wall (default = DEFAULT_WALL_HEIGHT) # * Returns: # - true if the position is valid, false elsewhere. #============================================================================================================== def self.valid?(x, y, data, wall_height = DEFAULT_WALL_HEIGHT) return (x > 0 && y > wall_height + 1 && x < data.xsize - 1 && y < data.ysize - 1) end #============================================================================================================== # ** RGG::Dungeons::center(data) #-------------------------------------------------------------------------------------------------------------- # * Gets the center position of the data. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - data : The data to check. # * Returns: # - a Point with the positions of the center of the data. #============================================================================================================== def self.center(data) return Point.new(data.xsize / 2, data.ysize / 2) end #============================================================================================================== # ** RGG::Dungeons::generate_walls(data[, wall_height]) #-------------------------------------------------------------------------------------------------------------- # * Generates the walls of a dungeon. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate to check. # - y : the y coordinate to check. # - data : the data to check. # - wall_height : the height of the wall (default = DEFAULT_WALL_HEIGHT) # * Returns: # - true if the position is valid, false elsewhere. #============================================================================================================== def self.generate_walls(data, wall_height = DEFAULT_WALL_HEIGHT) for x in 0...data.xsize for y in 0...data.ysize if data[x, y] == FLOOR need_wall = true for i in 1..(wall_height + 1) if !data[x, y-i] || data[x, y-i] != ROOF need_wall = false break end end if need_wall for i in 1..wall_height data[x, y-i] = WALL if data[x, y-i] end end end end end return revise_walls(data) end #============================================================================================================== # ** RGG::Dungeons::wall_complete?(x, y, data) #-------------------------------------------------------------------------------------------------------------- # * Checks if a wall can exist in that position. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate to check. # - y : the y coordinate to check. # - data : the data to check. # * Returns: # - true if the wall has to be there, false elsewhere. #============================================================================================================== def self.wall_complete?(x, y, data) for i in y...data.ysize return true if data[x, i] == WALL return false if data[x, i] != ROOF end return true end #============================================================================================================== # ** RGG::Dungeons::revise_walls(data) #-------------------------------------------------------------------------------------------------------------- # * Correct impossible walls. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - data : The data to check. # * Returns: # - the data with the corrections. #============================================================================================================== def self.revise_walls(data) for x in 0...data.xsize for y in 0...data.ysize if data[x, y] == ROOF data[x, y] = FLOOR if !wall_complete?(x, y, data) end end return data end end #============================================================================================================== # ** RGG::Dungeons::revise_walls(data) #-------------------------------------------------------------------------------------------------------------- # * Creates an inverted circular randomized space in a chunk of data. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate. # - y : the y coordinate. # - rx : the x radious of the circle. # - ry : the y radious of the circle. # - data : The data to add the circle. #============================================================================================================== def self.add_inverted_circle(x, y, rx, ry, data) cx = x + rx cy = y + ry x1 = (x - rx) x2 = (x + 2 * rx) y1 = (y - ry) y2 = (y + 2 * ry) r = Math.sqrt((rx * ry).abs) for i in x1..x2 for j in y1..y2 data[i, j] = FLOOR if valid?(i, j, data) && Math.hypot(i - cx, j - cy) >= r end end end #============================================================================================================== # ** RGG::Dungeons::revise_walls(data) #-------------------------------------------------------------------------------------------------------------- # * Creates a circular randomized space in a chunk of data. #-------------------------------------------------------------------------------------------------------------- # * Parameters: # - x : the x coordinate. # - y : the y coordinate. # - rx : the x radious of the circle. # - ry : the y radious of the circle. # - data : The data to add the circle. #============================================================================================================== def self.add_circle(x, y, rx, ry, data) cx = x + rx cy = y + ry x1 = (x - rx) x2 = (x + 2 * rx) y1 = (y - ry) y2 = (y + 2 * ry) r = Math.sqrt(rx * ry) for i in x1..x2 for j in y1..y2 data[i, j] = FLOOR if valid?(i, j, data) && Math.hypot(i - cx, j - cy) < r end end end end end