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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +24 -0
  5. data/LICENSE +25 -0
  6. data/README.md +6 -0
  7. data/ROADMAP.md +92 -0
  8. data/Rakefile +25 -0
  9. data/bin/delve +15 -0
  10. data/delve.gemspec +17 -0
  11. data/doc/examples/ascii.rb +34 -0
  12. data/doc/examples/astar.rb +39 -0
  13. data/doc/examples/border.rb +13 -0
  14. data/doc/examples/cellular.rb +15 -0
  15. data/doc/examples/display.rb +27 -0
  16. data/doc/examples/engine.rb +29 -0
  17. data/doc/examples/menu.rb +22 -0
  18. data/doc/examples/noise.rb +37 -0
  19. data/doc/examples/progress.rb +25 -0
  20. data/doc/examples/rogue.rb +15 -0
  21. data/lib/delve.rb +109 -0
  22. data/lib/delve/component/collision.rb +17 -0
  23. data/lib/delve/component/movement.rb +78 -0
  24. data/lib/delve/component/position.rb +30 -0
  25. data/lib/delve/component/symbol.rb +23 -0
  26. data/lib/delve/display/curses_renderer.rb +63 -0
  27. data/lib/delve/display/display.rb +51 -0
  28. data/lib/delve/engine.rb +30 -0
  29. data/lib/delve/entity.rb +23 -0
  30. data/lib/delve/event_queue.rb +48 -0
  31. data/lib/delve/fov/discrete_shadowcasting.rb +78 -0
  32. data/lib/delve/fov/fov.rb +83 -0
  33. data/lib/delve/game.rb +22 -0
  34. data/lib/delve/generator/cellular.rb +111 -0
  35. data/lib/delve/generator/dungeon.rb +19 -0
  36. data/lib/delve/generator/map.rb +30 -0
  37. data/lib/delve/generator/noise.rb +33 -0
  38. data/lib/delve/generator/rogue.rb +403 -0
  39. data/lib/delve/input/curses_input.rb +28 -0
  40. data/lib/delve/input/input.rb +12 -0
  41. data/lib/delve/path/astar.rb +83 -0
  42. data/lib/delve/path/path.rb +70 -0
  43. data/lib/delve/scheduler/action_scheduler.rb +38 -0
  44. data/lib/delve/scheduler/scheduler.rb +37 -0
  45. data/lib/delve/scheduler/simple_scheduler.rb +21 -0
  46. data/lib/delve/screen_manager.rb +41 -0
  47. data/lib/delve/widgets/border.rb +44 -0
  48. data/lib/delve/widgets/key_value.rb +36 -0
  49. data/lib/delve/widgets/menu.rb +84 -0
  50. data/lib/delve/widgets/multi_line.rb +51 -0
  51. data/lib/delve/widgets/progress.rb +37 -0
  52. data/lib/delve/widgets/text.rb +37 -0
  53. data/lib/delve/widgets/viewport.rb +67 -0
  54. data/rot.js.LICENSE +25 -0
  55. data/templates/Gemfile.erb +4 -0
  56. data/templates/README.md.erb +22 -0
  57. data/templates/binfile.erb +21 -0
  58. data/templates/game_screen.rb.erb +51 -0
  59. data/templates/gemspec.erb +27 -0
  60. data/templates/loading_screen.rb.erb +46 -0
  61. data/templates/player_factory.rb.erb +19 -0
  62. data/templates/title_screen.rb.erb +42 -0
  63. data/templates/world.rb.erb +63 -0
  64. data/test/component/collision_test.rb +39 -0
  65. data/test/component/movement_test.rb +155 -0
  66. data/test/component/position_test.rb +43 -0
  67. data/test/component/symbol_test.rb +30 -0
  68. data/test/delve_test.rb +61 -0
  69. data/test/display/curses_renderer_test.rb +60 -0
  70. data/test/display/display_test.rb +86 -0
  71. data/test/engine_test.rb +36 -0
  72. data/test/entity_test.rb +48 -0
  73. data/test/event_queue_test.rb +86 -0
  74. data/test/game_test.rb +47 -0
  75. data/test/generator/dungeon_test.rb +21 -0
  76. data/test/generator/map_test.rb +31 -0
  77. data/test/generator/noise_test.rb +49 -0
  78. data/test/input/curses_input_test.rb +24 -0
  79. data/test/input/input_test.rb +21 -0
  80. data/test/path/astar_test.rb +22 -0
  81. data/test/path/path_test.rb +41 -0
  82. data/test/scheduler/action_scheduler_test.rb +54 -0
  83. data/test/scheduler/scheduler_test.rb +50 -0
  84. data/test/scheduler/simple_scheduler_test.rb +33 -0
  85. data/test/screen_manager_test.rb +92 -0
  86. data/test/widgets/border_test.rb +60 -0
  87. data/test/widgets/key_value_test.rb +68 -0
  88. data/test/widgets/menu_test.rb +103 -0
  89. data/test/widgets/multi_line_test.rb +73 -0
  90. data/test/widgets/progress_test.rb +96 -0
  91. data/test/widgets/text_test.rb +66 -0
  92. data/test/widgets/viewport_test.rb +154 -0
  93. 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
@@ -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
@@ -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