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,37 @@
1
+ class TextWidget
2
+ def initialize(x, y, text)
3
+ raise 'Cannot initialize text widget when x is nil' unless x
4
+ raise 'Cannot initialize text widget when y is nil' unless y
5
+ raise 'Cannot initialize text widget when text is nil' unless text
6
+
7
+ @x = x
8
+ @y = y
9
+ @text = text
10
+ end
11
+
12
+ def draw(display)
13
+ raise 'Cannot draw text when display is nil' unless display
14
+
15
+ x = determine_x(display)
16
+ y = determine_y(display)
17
+ @text.each_char do |c|
18
+ display.draw x, y, c
19
+ x += 1
20
+ end
21
+ end
22
+
23
+ private
24
+ def determine_x display
25
+ if @x == :center
26
+ return (display.width / 2.0).ceil - (@text.length / 2.0).floor
27
+ end
28
+ @x
29
+ end
30
+
31
+ def determine_y display
32
+ if @y == :center
33
+ return ((display.height / 2.0).ceil)
34
+ end
35
+ @y
36
+ end
37
+ end
@@ -0,0 +1,67 @@
1
+ class ViewportWidget
2
+ def initialize(x, y, width, height, world)
3
+ raise 'Cannot initialize text widget when x is nil' unless x
4
+ raise 'Cannot initialize text widget when y is nil' unless y
5
+ raise 'Cannot initialize text widget when width is nil' unless width
6
+ raise 'Cannot initialize text widget when height is nil' unless height
7
+ raise 'Cannot initialize text widget when text is nil' unless world
8
+
9
+ @x = x
10
+ @y = y
11
+ @width = width
12
+ @height = height
13
+ @world = world
14
+ @rx = 0
15
+ @ry = 0
16
+ end
17
+
18
+ def width
19
+ @width
20
+ end
21
+
22
+ def height
23
+ @height
24
+ end
25
+
26
+ def focus(x, y)
27
+ raise 'Cannot focus viewport out of world' if x < 0 || y < 0 || x >= @world.width || y >= @world.height
28
+
29
+ @rx = x - (@width/2).floor
30
+ @ry = y - (@height/2).floor
31
+
32
+ @rx = 0 if @rx < 0
33
+ @ry = 0 if @ry < 0
34
+
35
+ far_east = @world.width - @width
36
+ far_south = @world.height - @height
37
+ @rx = far_east if @rx > far_east
38
+ @ry = far_south if @ry > far_south
39
+ end
40
+
41
+ def draw(display)
42
+ raise 'Cannot draw text when display is nil' unless display
43
+
44
+ dx = 0
45
+ (@x..(@x+@width-1)).each do |x|
46
+ dy = 0
47
+ (@y..(@y+@height-1)).each do |y|
48
+ tile = @world.at(@rx+dx, @ry+dy)
49
+ tile = { :char => '?', :color => :red } unless tile
50
+
51
+ if tile.respond_to?(:has?)
52
+ symbol = tile.get(:symbol).symbol
53
+ symbol[:walkable] = tile.get(:walkable).walkable
54
+ tile = symbol
55
+ end
56
+
57
+ display.draw x, y, tile[:char], tile[:color]
58
+ dy += 1
59
+ end
60
+ dx += 1
61
+ end
62
+ end
63
+
64
+ def top_left
65
+ { x: @rx, y: @ry }
66
+ end
67
+ end
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2012-now(), Ondrej Zara
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name of Ondrej Zara nor the names of its contributors may be used
13
+ to endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
25
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in <%= @name %>.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ ## <%= @name.upcase %>
2
+
3
+ TODO: Write a game description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem '<%= @name %>'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install <%= @name %>
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ require 'delve/display/curses_renderer'
3
+ require '<%= @renderer_import %>'
4
+ require 'delve/display/display'
5
+ require 'delve/input/input'
6
+ require '<%= @input_import %>'
7
+ require 'delve/screen_manager'
8
+ require 'delve/game'
9
+
10
+ require '<%= @name %>/screens/title'
11
+
12
+ display = Display.new <%= @new_renderer_statement %>
13
+ screen_manager = ScreenManager.new
14
+ title_screen = TitleScreen.new screen_manager
15
+ screen_manager.push_screen title_screen
16
+ input = Input.new <%= @new_input_statement %>
17
+
18
+ game = Game.new display, screen_manager, input
19
+ game.start
20
+
21
+ display.exit
@@ -0,0 +1,51 @@
1
+ require 'delve/widgets/border'
2
+ require 'delve/widgets/viewport'
3
+
4
+ class GameScreen
5
+
6
+ def initialize(world, player, screen_manager)
7
+ @world = world
8
+ @manager = screen_manager
9
+ @player = player
10
+ @boundary = nil
11
+ @game_border = nil
12
+ @viewport = nil
13
+
14
+ @world.add_entity @player
15
+ end
16
+
17
+ def render(display)
18
+ @boundary ||= BorderWidget.new(0, 0, display.width, display.height)
19
+ @game_border ||= BorderWidget.new(0, 0, display.width - 25, display.height, '<%= @name %>', :white, :black)
20
+ @viewport ||= ViewportWidget.new 2, 1, display.width - 28, display.height - 2, @world
21
+
22
+ @boundary.draw display
23
+ @game_border.draw display
24
+
25
+ x = @player.get(:position).x
26
+ y = @player.get(:position).y
27
+ @viewport.focus(x, y)
28
+ @viewport.draw display
29
+ end
30
+
31
+ def partial?
32
+ false
33
+ end
34
+
35
+ def update(input)
36
+ input = input.wait_for_input
37
+ move = @player.get(:movement)
38
+
39
+ move.north if input == 'k'
40
+ move.south if input == 'j'
41
+ move.east if input == 'l'
42
+ move.west if input == 'h'
43
+ move.north_east if input == 'u'
44
+ move.north_west if input == 'y'
45
+ move.south_east if input == 'n'
46
+ move.south_west if input == 'b'
47
+
48
+ input == 'x'
49
+ end
50
+
51
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "<%= @name %>"
7
+ spec.version = '0.0.1'
8
+ spec.authors = ["roguelike-developer"]
9
+ spec.description = %q{A roguelike named <%= @name %>}
10
+ spec.summary = %q{<%= @name %>}
11
+
12
+ spec.files = %w(
13
+ bin/<%= @name %>
14
+ lib/<%= @name %>/world.rb
15
+ lib/<%= @name %>/factory/player.rb
16
+ lib/<%= @name %>/screens/game.rb
17
+ lib/<%= @name %>/screens/loading.rb
18
+ lib/<%= @name %>/screens/title.rb
19
+ )
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_runtime_dependency "delve"
26
+ end
27
+
@@ -0,0 +1,46 @@
1
+ require 'delve/widgets/text'
2
+ require '<%= @generator_import %>'
3
+
4
+ require '<%= @name %>/world'
5
+ require '<%= @name %>/factory/player'
6
+ require '<%= @name %>/screens/game'
7
+
8
+ class LoadingScreen
9
+
10
+ def initialize(screen_manager)
11
+ @manager = screen_manager
12
+ @text = TextWidget.new :center, :center, 'Creating world (this may take a while)'
13
+ @player_factory = PlayerFactory.new
14
+ @player = nil
15
+ @world = nil
16
+ end
17
+
18
+ def render(display)
19
+ @text.draw display
20
+ end
21
+
22
+ def partial?
23
+ false
24
+ end
25
+
26
+ def update(input)
27
+ @world = World.new(<%= @new_generator %>)
28
+ @player = @player_factory.create @world
29
+ @manager.push_screen GameScreen.new(@world, @player, @manager)
30
+ false
31
+ end
32
+
33
+ private
34
+ def randomize_player_location
35
+ rx = (rand * @world.width).floor
36
+ ry = (rand * @world.height).floor
37
+
38
+ while !@world.free(rx, ry)
39
+ rx = (rand * @world.width).floor
40
+ ry = (rand * @world.height).floor
41
+ end
42
+
43
+ @player.get(:position).set(rx, ry)
44
+ end
45
+
46
+ end
@@ -0,0 +1,19 @@
1
+ require 'delve/entity'
2
+ require 'delve/component/position'
3
+ require 'delve/component/movement'
4
+ require 'delve/component/symbol'
5
+ require 'delve/component/collision'
6
+
7
+ class PlayerFactory
8
+
9
+ def create(world)
10
+ player = Entity.new
11
+ player.add PositionComponent.new(player)
12
+ player.add CollisionComponent.new(player, world)
13
+ player.add MovementComponent.new(player, :eightway)
14
+ player.add SymbolComponent.new(player, '@')
15
+ player
16
+ end
17
+
18
+ end
19
+
@@ -0,0 +1,42 @@
1
+ require 'delve/widgets/menu'
2
+ require 'delve/widgets/text'
3
+
4
+ require '<%= @name %>/screens/loading'
5
+
6
+ class TitleScreen
7
+
8
+ def initialize(screen_manager)
9
+ @title = TextWidget.new :center, 1, '<%= @name %> - the roguelike'
10
+
11
+ @menu = MenuWidget.new 1, 15, {
12
+ 'n' => 'New game',
13
+ 'x' => 'Exit'
14
+ }
15
+ @manager = screen_manager
16
+ end
17
+
18
+ def render(display)
19
+ @title.draw display
20
+ @menu.draw display
21
+ end
22
+
23
+ def partial?
24
+ false
25
+ end
26
+
27
+ def update(input)
28
+ input = input.wait_for_input
29
+
30
+ @menu.next if input == :down_arrow
31
+ @menu.prev if input == :up_arrow
32
+
33
+ @menu.select(input)
34
+
35
+ if @menu.selected_item == 'New game'
36
+ @manager.push_screen LoadingScreen.new(@manager)
37
+ end
38
+
39
+ @menu.selected_item == 'Exit'
40
+ end
41
+
42
+ end
@@ -0,0 +1,63 @@
1
+ class World
2
+
3
+ def initialize(generator)
4
+ raise 'Cannot create world when generator is nil' unless generator
5
+
6
+ @world = Hash.new
7
+ @entities = Array.new
8
+ @width = generator.width
9
+ @height = generator.height
10
+ generator.generate do |x, y, height|
11
+ @world[key_for(x.floor, y.floor)] = tile_for_height(height)
12
+ end
13
+ end
14
+
15
+ def at(x, y)
16
+ key = key_for(x, y)
17
+ # This is super inefficient
18
+ found = @entities.select { |e| e.get(:position).x == x and e.get(:position).y == y }
19
+ entity = found.first
20
+ if entity and entity.has?(:symbol)
21
+ return entity.get(:symbol).symbol
22
+ else
23
+ return @world[key_for(x, y)]
24
+ end
25
+ end
26
+
27
+ def entities
28
+ @entities
29
+ end
30
+
31
+ def add_entity(entity)
32
+ raise 'Cannot add entity that doesnt have a position' unless entity.has? :position
33
+ @entities << entity
34
+ end
35
+
36
+ def width
37
+ @width
38
+ end
39
+
40
+ def height
41
+ @height
42
+ end
43
+
44
+ def free(x, y)
45
+ at(x, y)[:walkable]
46
+ end
47
+
48
+ private
49
+ def key_for(x, y)
50
+ "#{x},#{y}"
51
+ end
52
+
53
+ def tile_for_height(height)
54
+ v = (height).floor
55
+ t = '.'
56
+ t = '#' if v == 1
57
+
58
+ c = :white
59
+
60
+ { char: t, color: c, walkable: (t == '.') }
61
+ end
62
+
63
+ end
@@ -0,0 +1,39 @@
1
+ require 'minitest'
2
+ require 'minitest/autorun'
3
+ require 'delve/component/collision'
4
+ require 'mocha/setup'
5
+
6
+ class CollisionComponentTest < Minitest::Test
7
+
8
+ def setup
9
+ @parent = mock('object')
10
+ @detector = mock('object')
11
+ @collision = CollisionComponent.new @parent, @detector
12
+ end
13
+
14
+ def test_initialize_component_with_nil_parent_fails
15
+ assert_raises RuntimeError do
16
+ CollisionComponent.new nil, @detector
17
+ end
18
+ end
19
+
20
+ def test_initialize_component_with_nil_world_fails
21
+ assert_raises RuntimeError do
22
+ CollisionComponent.new @parent, nil
23
+ end
24
+ end
25
+
26
+ def test_id
27
+ assert_equal :collision, @collision.id
28
+ end
29
+
30
+ def test_free_returns_true_if_world_tile_is_free
31
+ @detector.expects(:free).with(1, 2).returns(true)
32
+ assert @collision.free?(1, 2)
33
+ end
34
+
35
+ def test_free_returns_false_if_world_tile_is_not_free
36
+ @detector.expects(:free).with(2, 1).returns(false)
37
+ assert !@collision.free?(2, 1)
38
+ end
39
+ end