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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02c73e23d80259be80160c0218e6fe479845e2e7
4
- data.tar.gz: bb6c03c2e1c1d82a87e0a2e422245291406232b7
3
+ metadata.gz: 02ee3558715e0c0bcdee350923901d21bce2af45
4
+ data.tar.gz: '08248f4ae13933c8c8a717827b2f4b5ef7617c39'
5
5
  SHA512:
6
- metadata.gz: da20505c016e885ca759019e24e458fd8c4fc59a51e9aae88ce101d0b77cef99423326a05c9ea61bbb7a1b76aa6317a15b48fbdb60a20d0e609c823d5982fb7d
7
- data.tar.gz: 205739141a74ba5bf6b30cfa9b0a84dad93effbfc0e6413c0cdafe1c398acda9c43ca2c7d67526f1eab032e64df0682fe288de28f96e4d56323e9219a60a5e46
6
+ metadata.gz: b7355fb3e50b8dc6f53bc59cff5f3b29fa6ceb8f9b7a566bfc22d9711832c01cd86ae68bebdd02d02099d3e13b6dcf1a90e928dcd550732d05079f06d5d0c19e
7
+ data.tar.gz: aa41cbff21d6098e8bc1c0004bc63433e94f2aa2607bbf235aeed86c8002351a0eba9ee856cde46358493e50863413a5ab2b41e6dbafc336d641e895ef49ef8e
@@ -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
@@ -0,0 +1,9 @@
1
+ class DrawableObject
2
+
3
+ SQUARE_SIZE_IN_PIXELS = 60
4
+
5
+ GRID_COLOR = 'darkslateblue'
6
+ BACKGROUND_COLOR = 'white'
7
+ TEXT_COLOR = 'black'
8
+
9
+ end
@@ -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
@@ -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.0.0
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: '0'
95
+ version: 2.4.0
61
96
  required_rubygems_version: !ruby/object:Gem::Requirement
62
97
  requirements:
63
98
  - - ">="