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 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
  - - ">="