RRGGLIB
Ruby Random Game Library
Introduction
This library is a set of tools to make easily a Rouguelike game in Ruby.
#======================================================================================================================
- ** 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
[Category: Ruby]
[Category: Libraries]