square-dungeon-gen 1.0.0 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/czuger/square-dungeon-gen.svg?branch=master)](https://travis-ci.org/czuger/square-dungeon-gen)
|
2
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/a72af1658fd0931463a5/maintainability)](https://codeclimate.com/github/czuger/square-dungeon-gen/maintainability)
|
3
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/a72af1658fd0931463a5/test_coverage)](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
|
+
![test picture](/images/entry-room.jpg)
|
54
|
+
|
55
|
+
This is an example of the full dungeon room :
|
56
|
+
|
57
|
+
![test picture](/images/dungeon.jpg)
|
@@ -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
|
- - ">="
|