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,403 @@
1
+ require 'delve/generator/map'
2
+
3
+ class RogueGenerator < Map
4
+
5
+ def initialize(width=nil, height=nil, opts=Hash.new)
6
+ super width, height
7
+
8
+ @options = {
9
+ :cell_width => 3,
10
+ :cell_height => 3,
11
+ }
12
+
13
+ opts.keys.each do |key|
14
+ @options[key] = opts[key]
15
+ end
16
+
17
+ @options[:room_width] ||= calculate_room_size width, @options[:cell_width]
18
+ @options[:room_height] ||= calculate_room_size height, @options[:cell_height]
19
+ end
20
+
21
+ def generate
22
+ @map = fill 1
23
+ @rooms = Array.new
24
+ @connected_cells = Array.new
25
+
26
+ init_rooms
27
+ connect_rooms
28
+ connect_unconnected_rooms
29
+ create_rooms
30
+ create_corridors
31
+
32
+ if block_given?
33
+ (0..@map.length-1).each do |x|
34
+ (0..@map[0].length-1).each do |y|
35
+ yield x, y, @map[x][y]
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+ def random_int(min, max)
43
+ (rand * (max - min + 1)).floor + min
44
+ end
45
+
46
+ def calculate_room_size(size, cell)
47
+ max = ((size/cell) * 0.8).floor
48
+ min = ((size/cell) * 0.25).floor
49
+ min = 2 if min < 2
50
+ max = 2 if max < 2
51
+ [min, max]
52
+ end
53
+
54
+ def init_rooms
55
+ (0..@options[:cell_width]-1).each do |i|
56
+ @rooms << Array.new
57
+ (0..@options[:cell_height]-1).each do |j|
58
+ @rooms[i] << {
59
+ :x => 0,
60
+ :y => 0,
61
+ :width => 0,
62
+ :height => 0,
63
+ :connections => Array.new,
64
+ :cell_x => i,
65
+ :cell_y => j
66
+ }
67
+ end
68
+ end
69
+ end
70
+
71
+ def connect_rooms
72
+ cgx = random_int 0, @options[:cell_width]-1
73
+ cgy = random_int 0, @options[:cell_height]-1
74
+
75
+ found = false
76
+
77
+ loop do
78
+
79
+ dir_to_check = [0, 2, 4, 6]
80
+
81
+ loop do
82
+ found = false
83
+ idx = dir_to_check.sample
84
+ break if idx.nil?
85
+ dir_to_check.delete idx
86
+
87
+ new_cgx = determine_ncgx(idx)
88
+ ncgx = cgx + new_cgx[:x]
89
+ ncgy = cgx + new_cgx[:y]
90
+
91
+ next if ncgx < 0 or ncgx >= @options[:cell_width]-1
92
+ next if ncgy < 0 or ncgy >= @options[:cell_height]-1
93
+
94
+ room = @rooms[cgx][cgy]
95
+
96
+ if room[:connections].length > 0
97
+ if room[:connections][0][0] == ncgx and room[:connections][0][1] == ncgy
98
+ break
99
+ end
100
+ end
101
+
102
+ begin
103
+ other_room = @rooms[ncgx][ncgy]
104
+ rescue
105
+ raise "ncgx: #{ncgx}, ncgy: #{ncgy}, rooms: #{@rooms.length}"
106
+ end
107
+
108
+ if other_room and other_room[:connections].length == 0
109
+ other_room[:connections] << [cgx, cgy]
110
+
111
+ @connected_cells << [ncgx, ncgy]
112
+ cgx = ncgx
113
+ cgy = ncgy
114
+ found = true
115
+ end
116
+
117
+ break if dir_to_check.length <= 0 || found
118
+ end
119
+
120
+ break if dir_to_check.length <= 0
121
+ end
122
+ end
123
+
124
+ def determine_ncgx(idx)
125
+ return { :x => 0, :y => -1 } if idx == 0
126
+ return { :x => 1, :y => 0 } if idx == 2
127
+ return { :x => 0, :y => 1 } if idx == 4
128
+ return { :x => -1, :y => 0 } if idx == 6
129
+ raise "Error determining ncgx with #{idx}."
130
+ end
131
+
132
+ def connect_unconnected_rooms
133
+ cw = @options[:cell_width]
134
+ ch = @options[:cell_height]
135
+
136
+ (0..@options[:cell_width]-1).each do |i|
137
+ (0..@options[:cell_height]-1).each do |j|
138
+ room = @rooms[i][j]
139
+
140
+ if room[:connections].length == 0
141
+ directions = [0, 2, 4, 6]
142
+ valid_room = false
143
+ other_room = nil
144
+
145
+ loop do
146
+
147
+ dir_idx = directions.sample
148
+ break if dir_idx.nil?
149
+ directions.delete dir_idx
150
+
151
+ ncgx = determine_ncgx dir_idx
152
+ new_i = i + ncgx[:x]
153
+ new_j = j + ncgx[:y]
154
+
155
+ if new_i < 0 or new_i >= cw or new_j < 0 or new_j >= ch
156
+ next
157
+ end
158
+
159
+ other_room = @rooms[new_i][new_j]
160
+
161
+ valid_room = true
162
+
163
+ if other_room[:connections].length == 0
164
+ break
165
+ end
166
+
167
+ (0..other_room[:connections].length-1).each do |k|
168
+ if other_room[:connections][k][0] == i and other_room[:connections][k][1] == j
169
+ valid_room = false
170
+ break
171
+ end
172
+ end
173
+
174
+ break if valid_room
175
+ break if directions.length == 0
176
+ end
177
+
178
+ room[:connections] << [other_room[:cell_x], other_room[:cell_y]] if valid_room
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ def create_rooms
185
+ w = @width
186
+ h = @height
187
+
188
+ cw = @options[:cell_width]
189
+ ch = @options[:cell_height]
190
+
191
+ cwp = (w / cw).floor
192
+ chp = (h / ch).floor
193
+
194
+ roomw, roomh, sx, sy, tx, ty, other_room = nil
195
+
196
+ room_width = @options[:room_width]
197
+ room_height = @options[:room_height]
198
+
199
+ (0..cw-1).each do |i|
200
+ (0..ch-1).each do |j|
201
+ sx = cwp * i
202
+ sy = chp * j
203
+
204
+ sx = 1 if sx == 0
205
+ sy = 1 if sy == 0
206
+
207
+ roomw = random_int room_width[0], room_width[1]
208
+ roomh = random_int room_height[0], room_height[1]
209
+
210
+ if j > 0
211
+ other_room = @rooms[i][j-1]
212
+ while (sy - (other_room[:y] + other_room[:height]) < 3)
213
+ sy += 1
214
+ end
215
+ end
216
+ if i > 0
217
+ other_room = @rooms[i-1][j]
218
+ while (sx - (other_room[:x] + other_room[:width]) < 3)
219
+ sx += 1
220
+ end
221
+ end
222
+
223
+ sx_offset = ((random_int 0, cwp-roomw) / 2).round
224
+ sy_offset = ((random_int 0, chp-roomh) / 2).round
225
+
226
+ while sx + sx_offset + roomw >= w
227
+ if sx_offset > 0
228
+ sx_offset -= 1
229
+ else
230
+ roomw -= 1
231
+ end
232
+ end
233
+
234
+ while sy + sy_offset + roomh >= h
235
+ if sy_offset > 0
236
+ sy_offset -= 1
237
+ else
238
+ roomh -= 1
239
+ end
240
+ end
241
+
242
+ sx = sx + sx_offset
243
+ sy = sy + sy_offset
244
+
245
+ @rooms[i][j][:x] = sx
246
+ @rooms[i][j][:y] = sy
247
+ @rooms[i][j][:width] = roomw
248
+ @rooms[i][j][:height] = roomh
249
+
250
+ (sx..sx + roomw - 1).each do |ii|
251
+ (sy..sy + roomh - 1).each do |jj|
252
+ @map[ii][jj] = 0
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ def get_wall_position(room, direction)
260
+ rx = nil
261
+ ry = nil
262
+ door = nil
263
+
264
+ if direction == 1 || direction == 3
265
+ rx = random_int(room[:x] + 1, room[:x] + room[:width] - 2)
266
+ if direction == 1
267
+ ry = room[:y] - 2
268
+ door = ry + 1
269
+ else
270
+ ry = room[:y] + room[:height] + 1
271
+ door = ry - 1
272
+ end
273
+
274
+ @map[rx][door] = 2
275
+ elsif direction == 2 || direction == 4
276
+ ry = random_int(room[:y] + 1, room[:y] + room[:height] - 2)
277
+ if direction == 2
278
+ rx = room[:x] + room[:width] + 1
279
+ door = rx - 1
280
+ else
281
+ rx = room[:x] - 2
282
+ door = rx + 1
283
+ end
284
+
285
+ @map[door][ry] = 2
286
+ end
287
+
288
+ [rx, ry]
289
+ end
290
+
291
+ def draw_corridor(start_pos, end_pos)
292
+ x_offset = end_pos[0] - start_pos[0]
293
+ y_offset = end_pos[1] - start_pos[1]
294
+
295
+ xpos = start_pos[0]
296
+ ypos = start_pos[1]
297
+
298
+ temp_dist, xdir, ydir = nil
299
+
300
+ move = nil
301
+ moves = Array.new
302
+
303
+ xabs = x_offset.abs
304
+ yabs = y_offset.abs
305
+
306
+ percent = rand
307
+ first_half = percent
308
+ second_half = 1 - percent
309
+
310
+ xdir = x_offset > 0 ? 2 : 6
311
+ ydir = y_offset > 0 ? 4 : 0
312
+
313
+ if xabs < yabs
314
+ temp_dist = (yabs * first_half).ceil
315
+ moves << [ydir, temp_dist]
316
+ moves << [xdir, xabs]
317
+ temp_dist = (yabs * second_half).floor
318
+ moves << [ydir, temp_dist]
319
+ else
320
+ temp_dist = (xabs * first_half).ceil
321
+ moves << [xdir, temp_dist]
322
+ moves << [ydir, yabs]
323
+ temp_dist = (xabs * second_half).floor
324
+ moves << [xdir, temp_dist]
325
+ end
326
+
327
+ @map[xpos][ypos] = 0;
328
+
329
+ while moves.length > 0
330
+ move = moves.pop
331
+ while move[1] > 0
332
+ xpos += directions(:eight)[move[0]][0]
333
+ ypos += directions(:eight)[move[0]][1]
334
+ @map[xpos][ypos] = 0
335
+ move[1] = move[1] - 1
336
+ end
337
+ end
338
+ end
339
+
340
+ def create_corridors
341
+ cw = @options[:cell_width]
342
+ ch = @options[:cell_height]
343
+ room, connection, other_room, wall, other_wall = nil
344
+
345
+ (0..cw - 1).each do |i|
346
+ (0..ch - 1).each do |j|
347
+ room = @rooms[i][j]
348
+
349
+ room[:connections].each do |connection|
350
+ other_room = @rooms[connection[0]][connection[1]]
351
+
352
+ if other_room[:cell_x] > room[:cell_x]
353
+ wall = 2
354
+ other_wall = 4
355
+ elsif other_room[:cell_x] < room[:cell_x]
356
+ wall = 4
357
+ other_wall = 2
358
+ elsif other_room[:cell_y] > room[:cell_y]
359
+ wall = 3
360
+ other_wall = 1
361
+ elsif other_room[:cell_y] < room[:cell_y]
362
+ wall = 1
363
+ other_wall = 3
364
+ end
365
+
366
+ draw_corridor get_wall_position(room, wall), get_wall_position(other_room, other_wall)
367
+ end
368
+ end
369
+ end
370
+ end
371
+
372
+ def directions(v)
373
+ dirs = {
374
+ :four => [
375
+ [ 0, -1],
376
+ [ 1, 0],
377
+ [ 0, 1],
378
+ [-1, 0]
379
+ ],
380
+ :eight => [
381
+ [ 0, -1],
382
+ [ 1, -1],
383
+ [ 1, 0],
384
+ [ 1, 1],
385
+ [ 0, 1],
386
+ [-1, 1],
387
+ [-1, 0],
388
+ [-1, -1]
389
+ ],
390
+ :six => [
391
+ [-1, -1],
392
+ [ 1, -1],
393
+ [ 2, 0],
394
+ [ 1, 1],
395
+ [-1, 1],
396
+ [-2, 0]
397
+ ]
398
+ }
399
+
400
+ dirs[v]
401
+ end
402
+
403
+ end
@@ -0,0 +1,28 @@
1
+ require 'curses'
2
+
3
+ class CursesInput
4
+
5
+ @@keys = {
6
+ 263 => :backspace,
7
+ 330 => :delete,
8
+ 258 => :down_arrow,
9
+ 360 => :end,
10
+ 343 => :enter,
11
+ 262 => :home,
12
+ 331 => :insert,
13
+ 260 => :left_arrow,
14
+ 338 => :page_down,
15
+ 339 => :page_up,
16
+ 261 => :right_arrrow,
17
+ 259 => :up_arrow
18
+ }
19
+
20
+ def wait_for_input
21
+ value = Curses.getch
22
+ if @@keys.include? value
23
+ value = @@keys[value]
24
+ end
25
+ value
26
+ end
27
+
28
+ end
@@ -0,0 +1,12 @@
1
+ class Input
2
+
3
+ def initialize(handler)
4
+ raise 'Cannot initialize display with no renderer' unless handler
5
+ @handler = handler
6
+ end
7
+
8
+ def wait_for_input
9
+ @handler.wait_for_input
10
+ end
11
+
12
+ end
@@ -0,0 +1,83 @@
1
+ require 'delve/path/path'
2
+
3
+ class AStar < Path
4
+
5
+ def initialize(to_x, to_y, free_checker, options = Hash.new)
6
+ super to_x, to_y, free_checker, options
7
+
8
+ @todo = Array.new
9
+ @done = Hash.new
10
+ @from_x = nil
11
+ @from_y = nil
12
+ end
13
+
14
+ def compute(from_x, from_y)
15
+ raise 'Cannot compute path without a block' unless block_given?
16
+
17
+ @todo = Array.new
18
+ @done = Hash.new
19
+ @from_x = from_x
20
+ @from_y = from_y
21
+ add @to_x, @to_y, nil
22
+
23
+ while @todo.length > 0
24
+ item = @todo.shift
25
+ break if item[:x] == from_x and item[:y] == from_y
26
+ neighbours = get_neighbours item[:x], item[:y]
27
+
28
+ (0..neighbours.length-1).each do |i|
29
+ neighbour = neighbours[i]
30
+ x = neighbour[0]
31
+ y = neighbour[1]
32
+ id = "#{x},#{y}"
33
+ next if @done.include? id
34
+ add x, y, item
35
+ end
36
+ end
37
+
38
+ item = @done["#{from_x},#{from_y}"]
39
+ return nil if item.nil?
40
+
41
+ until item.nil?
42
+ yield item[:x], item[:y]
43
+ item = item[:prev]
44
+ end
45
+ end
46
+
47
+ private
48
+ def add(x, y, prev)
49
+ obj = {
50
+ x: x,
51
+ y: y,
52
+ prev: prev,
53
+ g: (prev ? prev[:g]+1 : 0),
54
+ h: distance(x, y)
55
+ }
56
+
57
+ @done["#{x},#{y}"] = obj
58
+
59
+ f = obj[:g] + obj[:h]
60
+ (0..@todo.length-1).each do |i|
61
+ item = @todo[i]
62
+ if f < item[:g] + item[:h]
63
+ @todo.insert(i, obj)
64
+ return
65
+ end
66
+ end
67
+
68
+ @todo << obj
69
+ end
70
+
71
+ def distance(x, y)
72
+ if [:four, :eight].include? @options[:topology]
73
+ return (x - @from_x).abs + (y - @from_y).abs
74
+ end
75
+
76
+ if @options[:topology] == :six
77
+ dx = (x - @from_x).abs
78
+ dy = (y - @from_y).abs
79
+ dy + [0, ((dx-dy)/2)].max
80
+ end
81
+ end
82
+
83
+ end