delve 0.0.7
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +24 -0
- data/LICENSE +25 -0
- data/README.md +6 -0
- data/ROADMAP.md +92 -0
- data/Rakefile +25 -0
- data/bin/delve +15 -0
- data/delve.gemspec +17 -0
- data/doc/examples/ascii.rb +34 -0
- data/doc/examples/astar.rb +39 -0
- data/doc/examples/border.rb +13 -0
- data/doc/examples/cellular.rb +15 -0
- data/doc/examples/display.rb +27 -0
- data/doc/examples/engine.rb +29 -0
- data/doc/examples/menu.rb +22 -0
- data/doc/examples/noise.rb +37 -0
- data/doc/examples/progress.rb +25 -0
- data/doc/examples/rogue.rb +15 -0
- data/lib/delve.rb +109 -0
- data/lib/delve/component/collision.rb +17 -0
- data/lib/delve/component/movement.rb +78 -0
- data/lib/delve/component/position.rb +30 -0
- data/lib/delve/component/symbol.rb +23 -0
- data/lib/delve/display/curses_renderer.rb +63 -0
- data/lib/delve/display/display.rb +51 -0
- data/lib/delve/engine.rb +30 -0
- data/lib/delve/entity.rb +23 -0
- data/lib/delve/event_queue.rb +48 -0
- data/lib/delve/fov/discrete_shadowcasting.rb +78 -0
- data/lib/delve/fov/fov.rb +83 -0
- data/lib/delve/game.rb +22 -0
- data/lib/delve/generator/cellular.rb +111 -0
- data/lib/delve/generator/dungeon.rb +19 -0
- data/lib/delve/generator/map.rb +30 -0
- data/lib/delve/generator/noise.rb +33 -0
- data/lib/delve/generator/rogue.rb +403 -0
- data/lib/delve/input/curses_input.rb +28 -0
- data/lib/delve/input/input.rb +12 -0
- data/lib/delve/path/astar.rb +83 -0
- data/lib/delve/path/path.rb +70 -0
- data/lib/delve/scheduler/action_scheduler.rb +38 -0
- data/lib/delve/scheduler/scheduler.rb +37 -0
- data/lib/delve/scheduler/simple_scheduler.rb +21 -0
- data/lib/delve/screen_manager.rb +41 -0
- data/lib/delve/widgets/border.rb +44 -0
- data/lib/delve/widgets/key_value.rb +36 -0
- data/lib/delve/widgets/menu.rb +84 -0
- data/lib/delve/widgets/multi_line.rb +51 -0
- data/lib/delve/widgets/progress.rb +37 -0
- data/lib/delve/widgets/text.rb +37 -0
- data/lib/delve/widgets/viewport.rb +67 -0
- data/rot.js.LICENSE +25 -0
- data/templates/Gemfile.erb +4 -0
- data/templates/README.md.erb +22 -0
- data/templates/binfile.erb +21 -0
- data/templates/game_screen.rb.erb +51 -0
- data/templates/gemspec.erb +27 -0
- data/templates/loading_screen.rb.erb +46 -0
- data/templates/player_factory.rb.erb +19 -0
- data/templates/title_screen.rb.erb +42 -0
- data/templates/world.rb.erb +63 -0
- data/test/component/collision_test.rb +39 -0
- data/test/component/movement_test.rb +155 -0
- data/test/component/position_test.rb +43 -0
- data/test/component/symbol_test.rb +30 -0
- data/test/delve_test.rb +61 -0
- data/test/display/curses_renderer_test.rb +60 -0
- data/test/display/display_test.rb +86 -0
- data/test/engine_test.rb +36 -0
- data/test/entity_test.rb +48 -0
- data/test/event_queue_test.rb +86 -0
- data/test/game_test.rb +47 -0
- data/test/generator/dungeon_test.rb +21 -0
- data/test/generator/map_test.rb +31 -0
- data/test/generator/noise_test.rb +49 -0
- data/test/input/curses_input_test.rb +24 -0
- data/test/input/input_test.rb +21 -0
- data/test/path/astar_test.rb +22 -0
- data/test/path/path_test.rb +41 -0
- data/test/scheduler/action_scheduler_test.rb +54 -0
- data/test/scheduler/scheduler_test.rb +50 -0
- data/test/scheduler/simple_scheduler_test.rb +33 -0
- data/test/screen_manager_test.rb +92 -0
- data/test/widgets/border_test.rb +60 -0
- data/test/widgets/key_value_test.rb +68 -0
- data/test/widgets/menu_test.rb +103 -0
- data/test/widgets/multi_line_test.rb +73 -0
- data/test/widgets/progress_test.rb +96 -0
- data/test/widgets/text_test.rb +66 -0
- data/test/widgets/viewport_test.rb +154 -0
- metadata +193 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'delve/display/display'
|
2
|
+
require 'delve/display/curses_renderer'
|
3
|
+
require 'delve/widgets/progress'
|
4
|
+
|
5
|
+
display = Display.new CursesRenderer.new
|
6
|
+
progress = ProgressWidget.new 3, 3, 20, 10, 10, :green, :white
|
7
|
+
|
8
|
+
progress.draw display
|
9
|
+
display.render
|
10
|
+
|
11
|
+
sleep 2
|
12
|
+
|
13
|
+
progress.current = 5
|
14
|
+
progress.draw display
|
15
|
+
display.render
|
16
|
+
|
17
|
+
sleep 2
|
18
|
+
|
19
|
+
progress.current = 2
|
20
|
+
progress.draw display
|
21
|
+
display.render
|
22
|
+
|
23
|
+
sleep 2
|
24
|
+
|
25
|
+
display.exit
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'delve/generator/rogue'
|
2
|
+
require 'delve/display/curses_renderer'
|
3
|
+
require 'delve/display/display'
|
4
|
+
|
5
|
+
display = Display.new CursesRenderer.new
|
6
|
+
|
7
|
+
rogue = RogueGenerator.new 100, 40
|
8
|
+
rogue.generate do |x, y, value|
|
9
|
+
display.draw(x, y, value == 1 ? '#' : '.')
|
10
|
+
end
|
11
|
+
display.render
|
12
|
+
|
13
|
+
sleep 4
|
14
|
+
|
15
|
+
display.exit
|
data/lib/delve.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
class Delve
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
raise 'Cannot create a new roguelike without a name' unless name
|
8
|
+
@name = name
|
9
|
+
|
10
|
+
@renderers = [:curses]
|
11
|
+
@generators = [:noise, :rogue, :cellular]
|
12
|
+
@renderer = :curses
|
13
|
+
@generator = :rogue
|
14
|
+
|
15
|
+
@statements = {
|
16
|
+
:curses => {
|
17
|
+
:renderer => 'delve/display/curses_renderer',
|
18
|
+
:input => 'delve/input/curses_input',
|
19
|
+
:new_renderer => 'CursesRenderer.new',
|
20
|
+
:new_input => 'CursesInput.new'
|
21
|
+
},
|
22
|
+
:noise => {
|
23
|
+
:generator => 'delve/generator/noise',
|
24
|
+
:new_generator => 'NoiseGenerator.new(128, 64, :fine)'
|
25
|
+
},
|
26
|
+
:rogue => {
|
27
|
+
:generator => 'delve/generator/rogue',
|
28
|
+
:new_generator => 'RogueGenerator.new(128, 64)'
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def use_renderer(renderer)
|
34
|
+
raise "Cannot use unknown renderer: #{renderer}" unless @renderers.include? renderer
|
35
|
+
@renderer = renderer
|
36
|
+
end
|
37
|
+
|
38
|
+
def use_generator(generator)
|
39
|
+
raise "Cannot use unknown generator: #{generator}" unless @generators.include? generator
|
40
|
+
@generator = generator
|
41
|
+
end
|
42
|
+
|
43
|
+
def renderer
|
44
|
+
@renderer
|
45
|
+
end
|
46
|
+
|
47
|
+
def generator
|
48
|
+
@generator
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_game path
|
52
|
+
raise "Cannot create roguelike when path already exists: #{path}/#{@name}" if Dir.exist?("#{path}/#{@name}")
|
53
|
+
|
54
|
+
@base_path = File.join(path, @name).to_s
|
55
|
+
create_directories
|
56
|
+
create_gem_files
|
57
|
+
create_bin_file
|
58
|
+
create_screens
|
59
|
+
create_game_classes
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def create_game_classes
|
64
|
+
create_file_from_template 'world.rb.erb', "lib/#{@name}/world.rb"
|
65
|
+
create_file_from_template 'player_factory.rb.erb', "lib/#{@name}/factory/player.rb"
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_screens
|
69
|
+
@generator_import = @statements[@generator][:generator]
|
70
|
+
@new_generator = @statements[@generator][:new_generator]
|
71
|
+
create_file_from_template 'title_screen.rb.erb', "lib/#{@name}/screens/title.rb"
|
72
|
+
create_file_from_template 'loading_screen.rb.erb', "lib/#{@name}/screens/loading.rb"
|
73
|
+
create_file_from_template 'game_screen.rb.erb', "lib/#{@name}/screens/game.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_bin_file
|
77
|
+
@renderer_import = @statements[@renderer][:renderer]
|
78
|
+
@input_import = @statements[@renderer][:input]
|
79
|
+
@new_renderer_statement = @statements[@renderer][:new_renderer]
|
80
|
+
@new_input_statement = @statements[@renderer][:new_input]
|
81
|
+
|
82
|
+
create_file_from_template 'binfile.erb', "bin/#{@name}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def create_gem_files
|
86
|
+
create_file_from_template 'Gemfile.erb', 'Gemfile'
|
87
|
+
create_file_from_template 'gemspec.erb', "#{@name}.gemspec"
|
88
|
+
create_file_from_template 'README.md.erb', "README.md"
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_file_from_template(template_name, target)
|
92
|
+
dir = File.expand_path(File.dirname(__FILE__))
|
93
|
+
content = File.read(File.join(dir, "../templates/#{template_name}"))
|
94
|
+
erb = ERB.new content
|
95
|
+
File.open(File.join(@base_path, target), 'w') do |file|
|
96
|
+
file.write erb.result(binding)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_directories
|
101
|
+
FileUtils.mkdir(@base_path)
|
102
|
+
FileUtils.mkdir(@base_path + '/bin')
|
103
|
+
FileUtils.mkdir(@base_path + '/lib')
|
104
|
+
FileUtils.mkdir(@base_path + '/lib/' + @name)
|
105
|
+
FileUtils.mkdir(@base_path + '/lib/' + @name + '/screens')
|
106
|
+
FileUtils.mkdir(@base_path + '/lib/' + @name + '/factory')
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CollisionComponent
|
2
|
+
def initialize(parent, detector)
|
3
|
+
raise 'Cannot create collision component when parent is nil' unless parent
|
4
|
+
raise 'Cannot create collision component when detector is nil' unless detector
|
5
|
+
|
6
|
+
@parent = parent
|
7
|
+
@detector = detector
|
8
|
+
end
|
9
|
+
|
10
|
+
def id
|
11
|
+
:collision
|
12
|
+
end
|
13
|
+
|
14
|
+
def free?(x, y)
|
15
|
+
@detector.free(x, y)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class MovementComponent
|
2
|
+
|
3
|
+
@@styles = [:fourway, :eightway]
|
4
|
+
@@diagonal_error = 'Style is not sufficient to move diagonally'
|
5
|
+
|
6
|
+
def initialize(parent, movement_style=:fourway)
|
7
|
+
raise 'Cannot create movement component when parent is nil' unless parent
|
8
|
+
@parent = parent
|
9
|
+
@style = movement_style || :fourway
|
10
|
+
raise 'Cannot create movement component with unknown style' unless @@styles.include?(@style)
|
11
|
+
end
|
12
|
+
|
13
|
+
def id
|
14
|
+
:movement
|
15
|
+
end
|
16
|
+
|
17
|
+
def style
|
18
|
+
@style
|
19
|
+
end
|
20
|
+
|
21
|
+
def north_west
|
22
|
+
raise @@diagonal_error unless diagonal_movement_is_valid
|
23
|
+
move -1, -1
|
24
|
+
end
|
25
|
+
|
26
|
+
def north_east
|
27
|
+
raise @@diagonal_error unless diagonal_movement_is_valid
|
28
|
+
move 1, -1
|
29
|
+
end
|
30
|
+
|
31
|
+
def south_west
|
32
|
+
raise @@diagonal_error unless diagonal_movement_is_valid
|
33
|
+
move -1, 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def south_east
|
37
|
+
raise @@diagonal_error unless diagonal_movement_is_valid
|
38
|
+
move 1, 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def north
|
42
|
+
move 0, -1
|
43
|
+
end
|
44
|
+
|
45
|
+
def east
|
46
|
+
move 1, 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def south
|
50
|
+
move 0, 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def west
|
54
|
+
move -1, 0
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def diagonal_movement_is_valid
|
59
|
+
@style == :eightway
|
60
|
+
end
|
61
|
+
|
62
|
+
def move(dx, dy)
|
63
|
+
if @parent.has? :position
|
64
|
+
pos = @parent.get :position
|
65
|
+
|
66
|
+
if @parent.has? :collision
|
67
|
+
col = @parent.get :collision
|
68
|
+
|
69
|
+
if col.free?(pos.x + dx, pos.y + dy)
|
70
|
+
pos.displace(dx, dy)
|
71
|
+
end
|
72
|
+
else
|
73
|
+
pos.displace(dx, dy)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class PositionComponent
|
2
|
+
def initialize(parent)
|
3
|
+
raise 'Cannot create position comonent when parent is nil' unless parent
|
4
|
+
|
5
|
+
@x = 0
|
6
|
+
@y = 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def id
|
10
|
+
:position
|
11
|
+
end
|
12
|
+
|
13
|
+
def x
|
14
|
+
@x
|
15
|
+
end
|
16
|
+
|
17
|
+
def y
|
18
|
+
@y
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(x, y)
|
22
|
+
@x = x
|
23
|
+
@y = y
|
24
|
+
end
|
25
|
+
|
26
|
+
def displace(dx, dy)
|
27
|
+
@x += dx
|
28
|
+
@y += dy
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class SymbolComponent
|
2
|
+
def initialize(parent, symbol, color=:white)
|
3
|
+
raise 'Cannot create symbol component when parent is nil' unless parent
|
4
|
+
raise 'Cannot create symbol component when symbol is nil' unless symbol
|
5
|
+
|
6
|
+
@parent = parent
|
7
|
+
@symbol = symbol
|
8
|
+
@color = color
|
9
|
+
end
|
10
|
+
|
11
|
+
def id
|
12
|
+
:symbol
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(symbol, color=:white)
|
16
|
+
@symbol = symbol
|
17
|
+
@color = color
|
18
|
+
end
|
19
|
+
|
20
|
+
def symbol
|
21
|
+
{ :char => @symbol, :color => @color }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
class CursesRenderer
|
4
|
+
|
5
|
+
@@colors = {
|
6
|
+
:black => Curses::COLOR_BLACK,
|
7
|
+
:red => Curses::COLOR_RED,
|
8
|
+
:blue => Curses::COLOR_BLUE,
|
9
|
+
:white => Curses::COLOR_WHITE,
|
10
|
+
:cyan => Curses::COLOR_CYAN,
|
11
|
+
:green => Curses::COLOR_GREEN,
|
12
|
+
:magenta => Curses::COLOR_MAGENTA,
|
13
|
+
:yellow => Curses::COLOR_YELLOW
|
14
|
+
}
|
15
|
+
|
16
|
+
def init
|
17
|
+
Curses.init_screen
|
18
|
+
Curses.start_color
|
19
|
+
Curses.nl
|
20
|
+
Curses.noecho
|
21
|
+
Curses.curs_set 0
|
22
|
+
@pairs = Array.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def exit
|
26
|
+
Curses.close_screen
|
27
|
+
end
|
28
|
+
|
29
|
+
def render(data)
|
30
|
+
Curses.clear
|
31
|
+
data.keys.each do |key|
|
32
|
+
draw_data = data[key]
|
33
|
+
draw(draw_data[:x], draw_data[:y], draw_data[:char], draw_data[:foreground], draw_data[:background])
|
34
|
+
end
|
35
|
+
Curses.refresh
|
36
|
+
end
|
37
|
+
|
38
|
+
def draw(x, y, char, fg, bg)
|
39
|
+
pair = "#{fg.to_s},#{bg.to_s}"
|
40
|
+
if !@pairs.include? pair
|
41
|
+
@pairs.push pair
|
42
|
+
Curses.init_pair(@pairs.index(pair)+1, @@colors[fg], @@colors[bg])
|
43
|
+
end
|
44
|
+
# Seem to need to flip x and y
|
45
|
+
index = @pairs.index(pair)+1
|
46
|
+
Curses.setpos(y, x)
|
47
|
+
Curses.attron(Curses.color_pair(index)) do
|
48
|
+
begin
|
49
|
+
Curses.addstr(char)
|
50
|
+
rescue TypeError => e
|
51
|
+
raise 'The char was ' + char.to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def width
|
57
|
+
Curses.cols
|
58
|
+
end
|
59
|
+
|
60
|
+
def height
|
61
|
+
Curses.lines
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Display
|
2
|
+
|
3
|
+
def initialize(renderer, options=Hash.new)
|
4
|
+
raise 'Cannot initialize display with no renderer' unless renderer
|
5
|
+
|
6
|
+
@renderer = renderer
|
7
|
+
@dirty = false
|
8
|
+
@render_data = Hash.new
|
9
|
+
|
10
|
+
default_options = {
|
11
|
+
:width => 80,
|
12
|
+
:height => 24,
|
13
|
+
:background => :black,
|
14
|
+
:foreground => :white
|
15
|
+
}
|
16
|
+
|
17
|
+
options.keys.each { |k| default_options[k] = options[k] }
|
18
|
+
@options = default_options
|
19
|
+
@renderer.init
|
20
|
+
end
|
21
|
+
|
22
|
+
def draw(x, y, char, foreground=nil, background=nil)
|
23
|
+
fg = foreground || @options[:foreground]
|
24
|
+
bg = background || @options[:background]
|
25
|
+
@render_data["#{x},#{y}"] = { :x => x, :y => y, :char => char, :foreground => fg, :background => bg }
|
26
|
+
@dirty = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def options
|
30
|
+
return @options
|
31
|
+
end
|
32
|
+
|
33
|
+
def render
|
34
|
+
if @dirty
|
35
|
+
@renderer.render @render_data
|
36
|
+
@render_data = Hash.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def exit
|
41
|
+
@renderer.exit
|
42
|
+
end
|
43
|
+
|
44
|
+
def height
|
45
|
+
@renderer.height
|
46
|
+
end
|
47
|
+
|
48
|
+
def width
|
49
|
+
@renderer.width
|
50
|
+
end
|
51
|
+
end
|
data/lib/delve/engine.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
class Engine
|
2
|
+
|
3
|
+
def initialize scheduler
|
4
|
+
raise 'Cannot initialize engine when scheduler is nil' unless scheduler
|
5
|
+
@scheduler = scheduler
|
6
|
+
@lock = 1
|
7
|
+
end
|
8
|
+
|
9
|
+
def locked?
|
10
|
+
@lock > 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def lock
|
14
|
+
@lock += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def unlock
|
18
|
+
@lock -= 1
|
19
|
+
|
20
|
+
while !locked? do
|
21
|
+
actor = @scheduler.next
|
22
|
+
if !actor
|
23
|
+
lock
|
24
|
+
break
|
25
|
+
end
|
26
|
+
actor.act
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|