square-dungeon-gen 1.0.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +57 -0
- data/lib/dungeon/dungeon.rb +82 -0
- data/lib/dungeon/dungeon_draw.rb +57 -0
- data/lib/dungeon/dungeon_generator.rb +102 -0
- data/lib/dungeon/dungeon_walker.rb +51 -0
- data/lib/hallways/hallway.rb +51 -0
- data/lib/hallways/hallways_list.rb +73 -0
- data/lib/hallways/horizontal_hallway.rb +42 -0
- data/lib/hallways/vertical_hallway.rb +42 -0
- data/lib/misc/drawable_object.rb +9 -0
- data/lib/misc/walker.rb +26 -0
- data/lib/rooms/room.rb +74 -0
- data/lib/rooms/room_content.rb +75 -0
- data/lib/rooms/room_draw.rb +47 -0
- data/lib/rooms/room_traps.rb +15 -0
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02ee3558715e0c0bcdee350923901d21bce2af45
|
4
|
+
data.tar.gz: '08248f4ae13933c8c8a717827b2f4b5ef7617c39'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b7355fb3e50b8dc6f53bc59cff5f3b29fa6ceb8f9b7a566bfc22d9711832c01cd86ae68bebdd02d02099d3e13b6dcf1a90e928dcd550732d05079f06d5d0c19e
|
7
|
+
data.tar.gz: aa41cbff21d6098e8bc1c0004bc63433e94f2aa2607bbf235aeed86c8002351a0eba9ee856cde46358493e50863413a5ab2b41e6dbafc336d641e895ef49ef8e
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
[](https://travis-ci.org/czuger/square-dungeon-gen)
|
2
|
+
[](https://codeclimate.com/github/czuger/square-dungeon-gen/maintainability)
|
3
|
+
[](https://codeclimate.com/github/czuger/square-dungeon-gen/test_coverage)
|
4
|
+
|
5
|
+
# Square dungeon gen
|
6
|
+
This repository contain a library for generating square simple dungeons
|
7
|
+
|
8
|
+
## Compatibility
|
9
|
+
|
10
|
+
Require ruby 2.4.0 or higher.
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
### Basics
|
15
|
+
------
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
# Create a dungeon for four 1 level players
|
19
|
+
d=Dungeon.new
|
20
|
+
d.generate( dungeon_size, party_array )
|
21
|
+
|
22
|
+
# Exemple :
|
23
|
+
d=Dungeon.new
|
24
|
+
d.generate( 4, [ 1, 1, 1, 1 ] )
|
25
|
+
# This will not create a dungeon of 4 rooms size, but a dungeon of 4**2*0.3 rooms (rounded up)
|
26
|
+
# The dungeon constructor accept another parameter wich is the amount of rooms to remove from the dungeon
|
27
|
+
# This parameter is a number between 0 and 1. By default it is set to 0.3 which mean that it will remove 30% of the rooms.
|
28
|
+
# The [ 1, 1, 1, 1 ] array mean that the dungeon is designed for four 1 level players.
|
29
|
+
|
30
|
+
d=Dungeon.new
|
31
|
+
d.generate( 4, [ 1, 1, 1, 1 ], 0.5 )
|
32
|
+
# Will remove 50% of the rooms
|
33
|
+
|
34
|
+
# Draw your dungeon
|
35
|
+
d.draw( 'out/dungeon.jpg' )
|
36
|
+
# Or only the curren room
|
37
|
+
d.draw_current_room( 'out/current_room.jpg' )
|
38
|
+
|
39
|
+
# You can get the directions available from the current room
|
40
|
+
d.available_directions
|
41
|
+
# => [ :left, :right ] # according to your dungeon which is random
|
42
|
+
|
43
|
+
# Then you can move the current room by moving in a direction
|
44
|
+
d.set_next_room( :left )
|
45
|
+
# Will move the current_room to the left.
|
46
|
+
```
|
47
|
+
|
48
|
+
### Examples
|
49
|
+
------
|
50
|
+
|
51
|
+
This is an example of the current room :
|
52
|
+
|
53
|
+

|
54
|
+
|
55
|
+
This is an example of the full dungeon room :
|
56
|
+
|
57
|
+

|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative '../rooms/room'
|
2
|
+
require_relative 'dungeon_walker'
|
3
|
+
require_relative 'dungeon_generator'
|
4
|
+
require_relative 'dungeon_draw'
|
5
|
+
require_relative '../hallways/horizontal_hallway'
|
6
|
+
require_relative '../hallways/vertical_hallway'
|
7
|
+
require_relative '../hallways/hallways_list'
|
8
|
+
require 'dd-next-encounters'
|
9
|
+
require 'rmagick'
|
10
|
+
require 'pp'
|
11
|
+
require 'json'
|
12
|
+
|
13
|
+
class Dungeon
|
14
|
+
|
15
|
+
attr_reader :current_room, :hallways, :rooms
|
16
|
+
|
17
|
+
include DungeonGenerator
|
18
|
+
include DungeonDraw
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@dungeon_size = @rooms_removal_coef = @rooms = @hallways = @dungeon_generated = @current_room = @lair = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_next_room( direction )
|
25
|
+
assert_dungeon_generated
|
26
|
+
# puts 'Current room = ' + @current_room.id.to_s
|
27
|
+
room_id = @hallways.get_room_id_from_direction( @current_room, direction )
|
28
|
+
# puts 'Connected room id = ' + room_id.to_s
|
29
|
+
# puts 'Rooms = ' + @rooms.keys.to_s
|
30
|
+
@current_room = @rooms[ room_id ]
|
31
|
+
end
|
32
|
+
|
33
|
+
def available_directions
|
34
|
+
assert_dungeon_generated
|
35
|
+
@hallways.directions( @current_room )
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_json
|
39
|
+
assert_dungeon_generated
|
40
|
+
{
|
41
|
+
dungeon_size: @dungeon_size,
|
42
|
+
rooms_removal_coef: @rooms_removal_coef,
|
43
|
+
entry_room_id: @entry.id,
|
44
|
+
current_room_id: @current_room.id,
|
45
|
+
dungeon_generated: @dungeon_generated,
|
46
|
+
rooms: @rooms.values.map{ |r| r.to_json_hash( @hallways ) },
|
47
|
+
hallways: @hallways.to_hash,
|
48
|
+
lair: @lair.to_hash
|
49
|
+
}.to_json
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.from_json( json_string )
|
53
|
+
data = JSON.parse( json_string )
|
54
|
+
dungeon = Dungeon.new
|
55
|
+
dungeon.from_json(data)
|
56
|
+
dungeon
|
57
|
+
end
|
58
|
+
|
59
|
+
def from_json( data )
|
60
|
+
raise 'You must parse the json string before calling this method' if data.is_a? String
|
61
|
+
|
62
|
+
@dungeon_size = data['dungeon_size']
|
63
|
+
@rooms_removal_coef = data['rooms_removal_coef']
|
64
|
+
@dungeon_generated = data['dungeon_generated']
|
65
|
+
|
66
|
+
@lair = Lairs.from_hash( data['lair'] )
|
67
|
+
@rooms = Hash[ data['rooms'].map{ |dr| [ dr['id'], Room.new( dr['top'], dr['left'], @lair, dr ) ] } ]
|
68
|
+
|
69
|
+
@hallways = HallwaysList.new
|
70
|
+
@hallways.from_json(data['hallways'], @rooms)
|
71
|
+
|
72
|
+
@entry = @rooms[data['entry_room_id']]
|
73
|
+
@current_room = @rooms[data['current_room_id']]
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def check_params( dungeon_size, party_levels, encounters_difficulty, rooms_removal_coef )
|
79
|
+
raise "dungeon_size should not be null" if dungeon_size == 0
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module DungeonDraw
|
2
|
+
|
3
|
+
def draw( output_file )
|
4
|
+
assert_dungeon_generated
|
5
|
+
width = height = ( @dungeon_size * Room::ROOM_SQUARE_SIZE +
|
6
|
+
( ( @dungeon_size-1 ) * Room::SQUARES_BETWEEN_ROOMS ) ) * Room::SQUARE_SIZE_IN_PIXELS +
|
7
|
+
( Room::ROOM_SQUARE_SIZE * Room::SQUARE_SIZE_IN_PIXELS )
|
8
|
+
|
9
|
+
create_gc( width, height )
|
10
|
+
@rooms.each_pair do |_, r|
|
11
|
+
r.compute_coords
|
12
|
+
r.draw( @gc )
|
13
|
+
end
|
14
|
+
@hallways.draw_from_base_room @gc
|
15
|
+
draw_gc( output_file )
|
16
|
+
end
|
17
|
+
|
18
|
+
def draw_current_room( output_file )
|
19
|
+
assert_dungeon_generated
|
20
|
+
width = height = ( Room::ROOM_SQUARE_SIZE + Room::SQUARES_BETWEEN_ROOMS * 2 ) * Room::SQUARE_SIZE_IN_PIXELS
|
21
|
+
|
22
|
+
create_gc( width, height )
|
23
|
+
@current_room.compute_coords_at_origin
|
24
|
+
@current_room.draw( @gc )
|
25
|
+
|
26
|
+
@hallways.draw_hallways_connected_to_given_room_at_origin( @gc, @current_room )
|
27
|
+
|
28
|
+
draw_gc( output_file )
|
29
|
+
end
|
30
|
+
|
31
|
+
def print_dungeon( output_file )
|
32
|
+
assert_dungeon_generated
|
33
|
+
rooms = {}
|
34
|
+
@rooms.each do |_, v|
|
35
|
+
rooms[ v.id ] = v.to_hash(hallways )
|
36
|
+
end
|
37
|
+
File.open(output_file,'w') do |f|
|
38
|
+
PP.pp(rooms,f)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def create_gc( width, height )
|
45
|
+
@canvas = Magick::Image.new( width, height )
|
46
|
+
|
47
|
+
@gc = Magick::Draw.new
|
48
|
+
@gc.stroke( DrawableObject::GRID_COLOR )
|
49
|
+
@gc.fill( DrawableObject::BACKGROUND_COLOR )
|
50
|
+
end
|
51
|
+
|
52
|
+
def draw_gc( output_file )
|
53
|
+
@gc.draw( @canvas )
|
54
|
+
@canvas.write( output_file )
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
|
3
|
+
module DungeonGenerator
|
4
|
+
|
5
|
+
def generate( dungeon_size, party_levels, encounters_difficulty: :medium, rooms_removal_coef: 0.3, lair: nil, output: false )
|
6
|
+
check_params( dungeon_size, party_levels, encounters_difficulty, rooms_removal_coef )
|
7
|
+
@dungeon_size = dungeon_size
|
8
|
+
@rooms_removal_coef = rooms_removal_coef
|
9
|
+
@rooms = {}
|
10
|
+
@hallways = HallwaysList.new
|
11
|
+
@dungeon_generated = false
|
12
|
+
@current_room = nil
|
13
|
+
|
14
|
+
@lair = lair ? lair : Lairs.new( encounters_difficulty, party_levels )
|
15
|
+
|
16
|
+
@output = output
|
17
|
+
create_dungeon
|
18
|
+
create_entry
|
19
|
+
connect_hallways
|
20
|
+
delete_rooms
|
21
|
+
generate_treasure
|
22
|
+
@dungeon_generated = true
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def assert_dungeon_generated
|
28
|
+
raise "Dungeon hasn't been generated" unless @dungeon_generated
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_treasure
|
32
|
+
rooms_distances = { }
|
33
|
+
@rooms.keys.each do |room_id|
|
34
|
+
rooms_distances[ room_id ] = distance_between_rooms_ids(@entry.id, room_id )
|
35
|
+
end
|
36
|
+
max_distance = rooms_distances.values.max
|
37
|
+
rooms_distances.delete_if {|_, value| value != max_distance }
|
38
|
+
treasure_room_id = rooms_distances.keys.sample
|
39
|
+
@rooms[treasure_room_id].set_treasure_room
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_entry
|
43
|
+
@entry = random_entry_room
|
44
|
+
@entry.set_entry_room
|
45
|
+
@current_room = @entry
|
46
|
+
end
|
47
|
+
|
48
|
+
def distance_between_rooms_ids( r_id_1, r_id_2 )
|
49
|
+
Math.sqrt( (r_id_1[0] - r_id_2[0])**2 + (r_id_1[1] - r_id_2[1])**2 ).ceil
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_dungeon
|
53
|
+
Matrix.build( @dungeon_size ){ |r, c| [ r+1, c+1 ] }.to_a.flatten(1).each do |top, left|
|
54
|
+
@rooms[ [ top, left ] ] = Room.new( top, left, @lair )
|
55
|
+
@rooms[ [ top, left ] ].id = [ top, left ]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def connect_hallways
|
60
|
+
Matrix.build( @dungeon_size ){ |r, c| [ r+1, c+1 ] }.to_a.flatten(1).each do |top, left|
|
61
|
+
@hallways.connect_rooms( @rooms[ [ top, left ] ],@rooms[ [ top, left+1 ] ], HorizontalHallway.new ) unless left == @dungeon_size
|
62
|
+
@hallways.connect_rooms( @rooms[ [ top, left ] ],@rooms[ [ top+1, left ] ], VerticalHallway.new ) unless top == @dungeon_size
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Coef must be a number between 0 -> 1
|
67
|
+
# for example 1/3 mean that we will delete 1/3 of the rooms
|
68
|
+
def delete_rooms
|
69
|
+
|
70
|
+
to_delete_rooms_keys = @rooms.keys.shuffle
|
71
|
+
puts "Current dungeon size = #{@rooms.count}" if @output
|
72
|
+
target_dungeon_size = @rooms.count - ((@dungeon_size**2)*@rooms_removal_coef).ceil
|
73
|
+
puts "Target dungeon size = #{target_dungeon_size}" if @output
|
74
|
+
|
75
|
+
while @rooms.count > target_dungeon_size && !to_delete_rooms_keys.empty?
|
76
|
+
|
77
|
+
to_delete_room_key = to_delete_rooms_keys.shift
|
78
|
+
tmp_rooms = @rooms.clone
|
79
|
+
tmp_rooms.delete( to_delete_room_key )
|
80
|
+
|
81
|
+
dw = DungeonWalker.new( tmp_rooms, @dungeon_size, @entry )
|
82
|
+
# If we can walk to all the rooms in the dungeon, then the room deletion is validated
|
83
|
+
if dw.walk_rooms.count == tmp_rooms.count
|
84
|
+
@hallways.disable_hallways!( to_delete_room_key )
|
85
|
+
@rooms.delete( to_delete_room_key )
|
86
|
+
end
|
87
|
+
# Otherwise we try with the next room
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
puts "Final dungeon size = #{@rooms.count}" if @output
|
92
|
+
end
|
93
|
+
|
94
|
+
def external_rooms
|
95
|
+
@rooms.values.select{ |r| r.top == 1 || r.left == 1 || r.top == @dungeon_size || r.left == @dungeon_size }
|
96
|
+
end
|
97
|
+
|
98
|
+
def random_entry_room
|
99
|
+
external_rooms.sample
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class DungeonWalker
|
4
|
+
|
5
|
+
WALKING_POSITIONS = [ [ -1, 0 ], [ 1, 0 ], [ 0, -1 ], [ 0, 1 ] ]
|
6
|
+
|
7
|
+
def initialize( rooms, dungeon_size, entry_room )
|
8
|
+
@rooms = rooms
|
9
|
+
@dungeon_size = dungeon_size
|
10
|
+
@entry_room = entry_room
|
11
|
+
end
|
12
|
+
|
13
|
+
def walk_rooms()
|
14
|
+
walking_room_queue = [ @entry_room.top_left_array ]
|
15
|
+
walked_rooms_positions = Set.new
|
16
|
+
|
17
|
+
until walking_room_queue.empty?
|
18
|
+
current_room_position = walking_room_queue.shift
|
19
|
+
walked_rooms_positions << current_room_position
|
20
|
+
|
21
|
+
connected_rooms_positions = get_connected_rooms_positions( current_room_position )
|
22
|
+
connected_rooms_positions.each do |room_position|
|
23
|
+
# p room_position
|
24
|
+
next if walked_rooms_positions.include?( room_position )
|
25
|
+
walked_rooms_positions << room_position
|
26
|
+
walking_room_queue << room_position
|
27
|
+
# p walked_rooms_positions
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
walked_rooms_positions
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_connected_rooms_positions( room_position )
|
35
|
+
connected_positions = []
|
36
|
+
WALKING_POSITIONS.each do |wp|
|
37
|
+
|
38
|
+
top = room_position[0] + wp[0]
|
39
|
+
left = room_position[1] + wp[1]
|
40
|
+
|
41
|
+
if( room_position != [ top, left ] && top >= 1 && left >= 1 && top <= @dungeon_size && left <= @dungeon_size &&
|
42
|
+
@rooms.has_key?( [ top, left ] ) )
|
43
|
+
connected_positions << [ top, left ]
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
# p connected_positions
|
48
|
+
connected_positions
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative '../misc/drawable_object'
|
2
|
+
|
3
|
+
class Hallway < DrawableObject
|
4
|
+
|
5
|
+
attr_reader :disabled, :rooms, :hallway_id
|
6
|
+
attr_accessor :hallway_id
|
7
|
+
|
8
|
+
# Should be half room size
|
9
|
+
HALLWAYS_LENGTH=6
|
10
|
+
HALLWAYS_WIDTH=2
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@disabled = false
|
14
|
+
@hallway_id = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def disable!
|
18
|
+
@disabled = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_draw_base_room( draw_base_room )
|
22
|
+
@draw_base_room = draw_base_room
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
{ hallway_id: @hallway_id, klass: self.class.name, disabled: @disabled, draw_base_room: @draw_base_room.id }
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_direction_array( rooms_keys, room, input_output )
|
30
|
+
return [ nil, nil ] if disabled
|
31
|
+
return [ input_output[0], self ] if rooms_keys[0] == room.top_left_array
|
32
|
+
return [ input_output[1], self ] if rooms_keys[1] == room.top_left_array
|
33
|
+
[ nil, nil ]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def draw( gc, width, height, min_x, max_x, min_y, max_y, x_decal: 0, y_decal: 0 )
|
39
|
+
gc.rectangle( min_x, min_y, max_x, max_y )
|
40
|
+
|
41
|
+
# Squares
|
42
|
+
1.upto( width ).each do |t|
|
43
|
+
gc.line( min_x + SQUARE_SIZE_IN_PIXELS*t - x_decal, min_y, min_x + SQUARE_SIZE_IN_PIXELS*t - x_decal, max_y )
|
44
|
+
end
|
45
|
+
|
46
|
+
1.upto( height ).each do |t|
|
47
|
+
gc.line( min_x, min_y + SQUARE_SIZE_IN_PIXELS*t - y_decal, max_x, min_y + SQUARE_SIZE_IN_PIXELS*t - y_decal )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
class HallwaysList
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@hallways = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def connect_rooms( r1, r2, hallway )
|
8
|
+
@hallways[ [ r1.top_left_array, r2.top_left_array ] ] = hallway
|
9
|
+
hallway.hallway_id = [ r1.top_left_array, r2.top_left_array ]
|
10
|
+
hallway.set_draw_base_room( r1 )
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_room_id_from_direction( room, direction )
|
14
|
+
connections = connected_hallways( room )
|
15
|
+
# puts "Connected hallways = " + connections.map{ |k, h| [ k, h.hallway_id ] }.to_s
|
16
|
+
connected_hallway = connections[direction]
|
17
|
+
unless connected_hallway
|
18
|
+
raise "Can't find a connected hallway. direction = #{direction.inspect}, connections = #{connections.inspect}"
|
19
|
+
end
|
20
|
+
connected_hallway.get_connected_room(direction)
|
21
|
+
end
|
22
|
+
|
23
|
+
def connected_hallways( room )
|
24
|
+
hallways = {}
|
25
|
+
@hallways.each_pair do |rooms_keys, hallway|
|
26
|
+
d, h = hallway.get_direction_array( rooms_keys, room )
|
27
|
+
hallways[d] = h if d
|
28
|
+
end
|
29
|
+
hallways
|
30
|
+
end
|
31
|
+
|
32
|
+
def directions( room )
|
33
|
+
connected_hallways(room).keys.sort
|
34
|
+
end
|
35
|
+
|
36
|
+
def draw_from_base_room( gc )
|
37
|
+
@hallways.each_pair{ |_, h| h.draw_from_base_room( gc ) unless h.disabled }
|
38
|
+
end
|
39
|
+
|
40
|
+
def draw_hallways_connected_to_given_room_at_origin( gc, room )
|
41
|
+
connections = connected_hallways( room )
|
42
|
+
connections.each do |direction, hallway|
|
43
|
+
hallway.draw_from_given_room( gc, room, direction )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def disable_hallways!( room_key )
|
48
|
+
@hallways.each_pair do |rooms_keys, hallway|
|
49
|
+
if rooms_keys[0] == room_key || rooms_keys[1] == room_key
|
50
|
+
hallway.disable!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_hash
|
56
|
+
@hallways.values.map{ |v| v.to_hash }
|
57
|
+
end
|
58
|
+
|
59
|
+
def from_json( data, rooms )
|
60
|
+
data.each do |hallway_data|
|
61
|
+
# pp hallway_data
|
62
|
+
r1 = rooms[hallway_data['hallway_id'][0]]
|
63
|
+
r2 = rooms[hallway_data['hallway_id'][1]]
|
64
|
+
if r1 && r2
|
65
|
+
hallway = Object.const_get(hallway_data['klass']).new
|
66
|
+
hallway.disable! if hallway_data['disabled']
|
67
|
+
hallway.set_draw_base_room(r1)
|
68
|
+
connect_rooms( r1, r2, hallway )
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'hallway'
|
2
|
+
|
3
|
+
class HorizontalHallway < Hallway
|
4
|
+
|
5
|
+
HALLWAY_HEIGHT=HALLWAYS_WIDTH
|
6
|
+
HALLWAY_WIDTH=HALLWAYS_LENGTH
|
7
|
+
|
8
|
+
def get_direction_array( rooms_keys, room )
|
9
|
+
super( rooms_keys, room, [ :right, :left ] )
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_connected_room( direction )
|
13
|
+
if direction == :right
|
14
|
+
@hallway_id[1]
|
15
|
+
else
|
16
|
+
@hallway_id[0]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def draw( gc, base_room, x_decal = 0 )
|
21
|
+
min_x = base_room.max_x - x_decal
|
22
|
+
max_x = min_x + HALLWAY_WIDTH * SQUARE_SIZE_IN_PIXELS - x_decal
|
23
|
+
|
24
|
+
min_y = base_room.min_y + ( Room::ROOM_SQUARE_SIZE/2-1 ) * SQUARE_SIZE_IN_PIXELS
|
25
|
+
max_y = min_y + HALLWAY_HEIGHT * SQUARE_SIZE_IN_PIXELS
|
26
|
+
|
27
|
+
super( gc, HALLWAY_WIDTH, HALLWAY_HEIGHT, min_x, max_x, min_y, max_y, x_decal: x_decal/2 )
|
28
|
+
end
|
29
|
+
|
30
|
+
def draw_from_base_room( gc )
|
31
|
+
draw( gc, @draw_base_room )
|
32
|
+
end
|
33
|
+
|
34
|
+
def draw_from_given_room( gc, room, direction )
|
35
|
+
if direction == :right
|
36
|
+
draw( gc, room )
|
37
|
+
else
|
38
|
+
draw( gc, room, ( Room::ROOM_SQUARE_SIZE ) * SQUARE_SIZE_IN_PIXELS )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'hallway'
|
2
|
+
|
3
|
+
class VerticalHallway < Hallway
|
4
|
+
|
5
|
+
HALLWAY_HEIGHT=HALLWAYS_LENGTH
|
6
|
+
HALLWAY_WIDTH=HALLWAYS_WIDTH
|
7
|
+
|
8
|
+
def get_direction_array( rooms_keys, room )
|
9
|
+
super( rooms_keys, room, [ :bottom, :top ] )
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_connected_room( direction )
|
13
|
+
if direction == :bottom
|
14
|
+
@hallway_id[1]
|
15
|
+
else
|
16
|
+
@hallway_id[0]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def draw( gc, base_room, y_decal = 0 )
|
21
|
+
min_x = base_room.min_x + ( Room::ROOM_SQUARE_SIZE/2-1 ) * SQUARE_SIZE_IN_PIXELS
|
22
|
+
max_x = min_x + HALLWAY_WIDTH * SQUARE_SIZE_IN_PIXELS
|
23
|
+
|
24
|
+
min_y = base_room.max_y - y_decal
|
25
|
+
max_y = min_y + HALLWAY_HEIGHT * SQUARE_SIZE_IN_PIXELS - y_decal
|
26
|
+
|
27
|
+
super( gc, HALLWAY_WIDTH, HALLWAY_HEIGHT, min_x, max_x, min_y, max_y, y_decal: y_decal/2 )
|
28
|
+
end
|
29
|
+
|
30
|
+
def draw_from_base_room( gc )
|
31
|
+
draw( gc, @draw_base_room )
|
32
|
+
end
|
33
|
+
|
34
|
+
def draw_from_given_room( gc, room, direction )
|
35
|
+
if direction == :bottom
|
36
|
+
draw( gc, room )
|
37
|
+
else
|
38
|
+
draw( gc, room, ( Room::ROOM_SQUARE_SIZE ) * SQUARE_SIZE_IN_PIXELS )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/lib/misc/walker.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Walker
|
2
|
+
|
3
|
+
def initialize( dungeon )
|
4
|
+
@dungeon = dungeon
|
5
|
+
end
|
6
|
+
|
7
|
+
def directions
|
8
|
+
Hash[ @dungeon.hallways.directions( @dungeon.current_room ).map{ |e| [ e.to_s[0], e ] } ]
|
9
|
+
end
|
10
|
+
|
11
|
+
def main_loop
|
12
|
+
loop do
|
13
|
+
_directions = directions
|
14
|
+
p _directions
|
15
|
+
|
16
|
+
direction = gets.chomp
|
17
|
+
direction = _directions[direction]
|
18
|
+
p direction
|
19
|
+
|
20
|
+
p @dungeon.set_next_room( direction )
|
21
|
+
@dungeon.draw_current_room( 'out/room.jpg' )
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/lib/rooms/room.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require_relative '../misc/drawable_object'
|
2
|
+
require_relative '../hallways/hallway'
|
3
|
+
require_relative 'room_content'
|
4
|
+
require_relative 'room_draw'
|
5
|
+
require 'hazard'
|
6
|
+
|
7
|
+
class Room < DrawableObject
|
8
|
+
|
9
|
+
SQUARES_BETWEEN_ROOMS = Hallway::HALLWAYS_LENGTH
|
10
|
+
ROOM_SQUARE_SIZE = 12
|
11
|
+
|
12
|
+
attr_reader :top, :left, :entry_room, :min_x, :min_y, :max_x, :max_y, :content, :content_description, :decorations
|
13
|
+
attr_accessor :id
|
14
|
+
|
15
|
+
include RoomContent
|
16
|
+
include RoomDraw
|
17
|
+
|
18
|
+
def initialize( top, left, lair, room_data = {} )
|
19
|
+
@top = room_data['top'] ? room_data['top'].to_i : top
|
20
|
+
@left = room_data['left'] ? room_data['left'].to_i : left
|
21
|
+
@content = room_data['content']
|
22
|
+
@content_description = room_data['content_description']
|
23
|
+
@entry_room = room_data['entry_room']
|
24
|
+
@id = room_data['id'] ? room_data['id'] : [ top, left ]
|
25
|
+
|
26
|
+
@decorations = []
|
27
|
+
if room_data['decorations']
|
28
|
+
load_decoration_from_json(room_data['decorations'] )
|
29
|
+
else
|
30
|
+
create_decorations
|
31
|
+
end
|
32
|
+
|
33
|
+
create_encounters( lair ) unless room_data['content_description']
|
34
|
+
end
|
35
|
+
|
36
|
+
def top_left_array
|
37
|
+
[ @top, @left ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def decal_at_origin
|
41
|
+
@top = 1
|
42
|
+
@left = 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def compute_coords
|
46
|
+
@min_x = ( ( @left-1 ) * ROOM_SQUARE_SIZE + @left * SQUARES_BETWEEN_ROOMS ) * SQUARE_SIZE_IN_PIXELS
|
47
|
+
@max_x = @min_x + ROOM_SQUARE_SIZE * SQUARE_SIZE_IN_PIXELS
|
48
|
+
@min_y = ( ( @top-1 ) * ROOM_SQUARE_SIZE + @top * SQUARES_BETWEEN_ROOMS ) * SQUARE_SIZE_IN_PIXELS
|
49
|
+
@max_y = @min_y + ROOM_SQUARE_SIZE * SQUARE_SIZE_IN_PIXELS
|
50
|
+
end
|
51
|
+
|
52
|
+
def compute_coords_at_origin
|
53
|
+
@min_x = SQUARES_BETWEEN_ROOMS * SQUARE_SIZE_IN_PIXELS
|
54
|
+
@max_x = @min_x + ROOM_SQUARE_SIZE * SQUARE_SIZE_IN_PIXELS
|
55
|
+
@min_y = SQUARES_BETWEEN_ROOMS * SQUARE_SIZE_IN_PIXELS
|
56
|
+
@max_y = @min_y + ROOM_SQUARE_SIZE * SQUARE_SIZE_IN_PIXELS
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_hash( hallways )
|
60
|
+
{id: @id, entry_room: @entry_room, content: @content, content_description: @content_description,
|
61
|
+
top: @top, left: @left, decorations: @decorations.map{ |d| d[:decoration_type] },
|
62
|
+
connected_hallways: hallways.connected_hallways( self ).map{ |k, h| { k => h.to_hash } } }
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_json_hash( hallways )
|
66
|
+
if @decorations.count > 1
|
67
|
+
a = :true
|
68
|
+
end
|
69
|
+
h = to_hash( hallways )
|
70
|
+
h.delete(:connected_hallways)
|
71
|
+
h
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'hazard'
|
2
|
+
require_relative 'room_traps'
|
3
|
+
|
4
|
+
module RoomContent
|
5
|
+
|
6
|
+
attr_accessor :party_levels, :encounters_difficulty
|
7
|
+
|
8
|
+
include RoomTraps
|
9
|
+
|
10
|
+
CONTENT_DESC_EMPTY='Nothing in this room.'
|
11
|
+
CONTENT_DESC_MONSTERS_CLEARED='Slain monsters lying on the floor.'
|
12
|
+
CONTENT_DESC_TRAP_CLEARED='A deactivated trap. Be carefull.'
|
13
|
+
|
14
|
+
def set_entry_room
|
15
|
+
@entry_room = true
|
16
|
+
@content = 'E'
|
17
|
+
@content_description = 'This room is the entry room.'
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_treasure_room
|
21
|
+
@content = 'H'
|
22
|
+
@content_description = 'The treasure, you find it.'
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear
|
26
|
+
case @content
|
27
|
+
when 'T'
|
28
|
+
@content = nil
|
29
|
+
@content_description = CONTENT_DESC_TRAP_CLEARED
|
30
|
+
when 'M'
|
31
|
+
@content = nil
|
32
|
+
@content_description = CONTENT_DESC_MONSTERS_CLEARED
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def create_encounters( lair )
|
39
|
+
@content_description = CONTENT_DESC_EMPTY
|
40
|
+
roll = Hazard.d6
|
41
|
+
generate_trap if roll == 1
|
42
|
+
generate_monster( lair ) if roll > 1 && roll < 6
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_decoration_from_json( decorations_types )
|
46
|
+
decorations_types.each do |decoration_type|
|
47
|
+
if decoration_type == 'four_columns'
|
48
|
+
create_four_columns
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_decorations
|
54
|
+
roll = Hazard.d6
|
55
|
+
create_four_columns if roll >= 5
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_four_columns
|
59
|
+
near = (Room::ROOM_SQUARE_SIZE/8.0).ceil
|
60
|
+
distant = Room::ROOM_SQUARE_SIZE-[ (Room::ROOM_SQUARE_SIZE/8.0).ceil*2, 3 ].max
|
61
|
+
|
62
|
+
column_1 = { top: near, left: near }
|
63
|
+
column_2 = { top: near, left: distant }
|
64
|
+
column_3 = { top: distant, left: near }
|
65
|
+
column_4 = { top: distant, left: distant }
|
66
|
+
|
67
|
+
@decorations << { decoration_type: :four_columns, decoration_data: [ column_1, column_2, column_3, column_4 ] }
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_monster( lair )
|
71
|
+
@content = 'M'
|
72
|
+
@content_description = lair.encounter.to_s + '.'
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RoomDraw
|
2
|
+
|
3
|
+
def draw( gc )
|
4
|
+
gc.rectangle( @min_x, @min_y, @max_x, @max_y )
|
5
|
+
|
6
|
+
# Squares
|
7
|
+
1.upto( Room::ROOM_SQUARE_SIZE ).each do |t|
|
8
|
+
gc.line( @min_x + DrawableObject::SQUARE_SIZE_IN_PIXELS*t, @min_y, @min_x + DrawableObject::SQUARE_SIZE_IN_PIXELS*t, @max_y )
|
9
|
+
gc.line( @min_x, @min_y + DrawableObject::SQUARE_SIZE_IN_PIXELS*t, @max_x, @min_y + DrawableObject::SQUARE_SIZE_IN_PIXELS*t )
|
10
|
+
end
|
11
|
+
|
12
|
+
if @content
|
13
|
+
print_text( gc, @content )
|
14
|
+
end
|
15
|
+
|
16
|
+
@decorations.each do |decoration|
|
17
|
+
draw_four_columns(gc, decoration[:decoration_data] ) if decoration[:decoration_type] == :four_columns
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def print_text( gc, text )
|
24
|
+
x = @min_x + (Room::ROOM_SQUARE_SIZE/2.35) * DrawableObject::SQUARE_SIZE_IN_PIXELS
|
25
|
+
y = @min_y + (Room::ROOM_SQUARE_SIZE/1.73) * DrawableObject::SQUARE_SIZE_IN_PIXELS
|
26
|
+
gc.pointsize( (Room::ROOM_SQUARE_SIZE*100)/8 )
|
27
|
+
gc.fill( DrawableObject::TEXT_COLOR )
|
28
|
+
gc.text( x, y, text )
|
29
|
+
gc.fill( DrawableObject::BACKGROUND_COLOR )
|
30
|
+
end
|
31
|
+
|
32
|
+
def draw_four_columns(gc, columns_data )
|
33
|
+
gc.stroke( DrawableObject::GRID_COLOR )
|
34
|
+
gc.fill( DrawableObject::GRID_COLOR )
|
35
|
+
|
36
|
+
columns_data.each do |c_data|
|
37
|
+
min_x = (c_data[:left]+1)*DrawableObject::SQUARE_SIZE_IN_PIXELS + @min_x
|
38
|
+
min_y = (c_data[:top]+1)*DrawableObject::SQUARE_SIZE_IN_PIXELS + @min_y
|
39
|
+
perim_x = min_x+3
|
40
|
+
perim_y = min_y - DrawableObject::SQUARE_SIZE_IN_PIXELS+3
|
41
|
+
|
42
|
+
gc.circle( min_x, min_y, perim_x, perim_y )
|
43
|
+
end
|
44
|
+
gc.fill( DrawableObject::BACKGROUND_COLOR )
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# This is very basic, need to improve
|
2
|
+
|
3
|
+
module RoomTraps
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def generate_trap
|
8
|
+
dd = 10+Hazard.d4
|
9
|
+
degats = Hazard.m2d10
|
10
|
+
@content = 'T'
|
11
|
+
@content_description = "The room contains a pit. Make a perception check against #{dd}. In case of failure the first character takes #{degats} hp."
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: square-dungeon-gen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cédric Zuger
|
@@ -38,13 +38,48 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '2.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: dd-next-encounters
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 2.0.2
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '2.0'
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 2.0.2
|
41
61
|
description: A simple gem that generate square dungeons
|
42
62
|
email: zuger.cedric@gmail.com
|
43
63
|
executables: []
|
44
64
|
extensions: []
|
45
65
|
extra_rdoc_files: []
|
46
66
|
files:
|
67
|
+
- README.md
|
47
68
|
- lib/dungeon.rb
|
69
|
+
- lib/dungeon/dungeon.rb
|
70
|
+
- lib/dungeon/dungeon_draw.rb
|
71
|
+
- lib/dungeon/dungeon_generator.rb
|
72
|
+
- lib/dungeon/dungeon_walker.rb
|
73
|
+
- lib/hallways/hallway.rb
|
74
|
+
- lib/hallways/hallways_list.rb
|
75
|
+
- lib/hallways/horizontal_hallway.rb
|
76
|
+
- lib/hallways/vertical_hallway.rb
|
77
|
+
- lib/misc/drawable_object.rb
|
78
|
+
- lib/misc/walker.rb
|
79
|
+
- lib/rooms/room.rb
|
80
|
+
- lib/rooms/room_content.rb
|
81
|
+
- lib/rooms/room_draw.rb
|
82
|
+
- lib/rooms/room_traps.rb
|
48
83
|
homepage: https://github.com/czuger/sqare-dungeon-gen
|
49
84
|
licenses:
|
50
85
|
- MIT
|
@@ -57,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
92
|
requirements:
|
58
93
|
- - ">="
|
59
94
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
95
|
+
version: 2.4.0
|
61
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
97
|
requirements:
|
63
98
|
- - ">="
|