RubyMan 0.0.2

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 (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +6 -0
  4. data/Rakefile +2 -0
  5. data/RubyMan.gemspec +24 -0
  6. data/bin/rubyman +4 -0
  7. data/lib/ruby_man.rb +5 -0
  8. data/lib/ruby_man/GameObjects/door.rb +11 -0
  9. data/lib/ruby_man/GameObjects/food.rb +24 -0
  10. data/lib/ruby_man/GameObjects/game_object.rb +29 -0
  11. data/lib/ruby_man/GameObjects/game_over.rb +44 -0
  12. data/lib/ruby_man/GameObjects/ghost.rb +123 -0
  13. data/lib/ruby_man/GameObjects/ghost_corpse.rb +30 -0
  14. data/lib/ruby_man/GameObjects/grid_object.rb +69 -0
  15. data/lib/ruby_man/GameObjects/hud.rb +26 -0
  16. data/lib/ruby_man/GameObjects/menu.rb +82 -0
  17. data/lib/ruby_man/GameObjects/moving_object.rb +50 -0
  18. data/lib/ruby_man/GameObjects/player.rb +90 -0
  19. data/lib/ruby_man/GameObjects/super_food.rb +16 -0
  20. data/lib/ruby_man/GameObjects/wall.rb +24 -0
  21. data/lib/ruby_man/aabb.rb +17 -0
  22. data/lib/ruby_man/direction.rb +102 -0
  23. data/lib/ruby_man/game_main.rb +213 -0
  24. data/lib/ruby_man/level_loader.rb +49 -0
  25. data/lib/ruby_man/resource_manager.rb +103 -0
  26. data/lib/ruby_man/sprite.rb +26 -0
  27. data/lib/ruby_man/version.rb +4 -0
  28. data/media/door_strip2.png +0 -0
  29. data/media/gem_strip9.png +0 -0
  30. data/media/ghost_death_strip15.png +0 -0
  31. data/media/ghost_down_strip2.png +0 -0
  32. data/media/ghost_left_strip2.png +0 -0
  33. data/media/ghost_right_strip2.png +0 -0
  34. data/media/ghost_scared_strip2.png +0 -0
  35. data/media/ghost_stopscared_strip3.png +0 -0
  36. data/media/ghost_up_strip2.png +0 -0
  37. data/media/levellist.txt +4 -0
  38. data/media/levels/level_1.txt +19 -0
  39. data/media/levels/level_2.txt +19 -0
  40. data/media/levels/level_3.txt +19 -0
  41. data/media/levels/level_4.txt +19 -0
  42. data/media/levels/level_empty.txt +19 -0
  43. data/media/logo.png +0 -0
  44. data/media/resource.cfg +18 -0
  45. data/media/rubyman_strip11.png +0 -0
  46. data/media/sfx/pacman_beginning.wav +0 -0
  47. data/media/sfx/pacman_chomp.wav +0 -0
  48. data/media/sfx/pacman_death.wav +0 -0
  49. data/media/sfx/pacman_eatghost.wav +0 -0
  50. data/media/walls_strip16.png +0 -0
  51. data/test/aabb_spec.rb +41 -0
  52. data/test/direction_spec.rb +48 -0
  53. data/test/game_main_spec.rb +126 -0
  54. metadata +142 -0
@@ -0,0 +1,49 @@
1
+ require 'ruby_man/GameObjects/wall'
2
+ require 'ruby_man/GameObjects/player'
3
+ require 'ruby_man/GameObjects/food'
4
+ require 'ruby_man/GameObjects/ghost'
5
+ require 'ruby_man/GameObjects/hud'
6
+ require 'ruby_man/GameObjects/door'
7
+ require 'ruby_man/GameObjects/super_food'
8
+
9
+ module RubyMan
10
+ # mixin for loading levels from files
11
+ # required method - add_object, level_loaded
12
+ module LevelLoader
13
+ def load_level_string(level_string, restart)
14
+ y = 0
15
+ level_string.lines do |line|
16
+ x = -1
17
+ line.each_char do |char|
18
+ x += 1
19
+ obj = LevelLoader.object_from_char(char)
20
+ next unless obj
21
+ next if restart && obj.class.persistent == true
22
+ obj.set_pos(x, y)
23
+ add_object(obj)
24
+ end
25
+ y += 1
26
+ end
27
+
28
+ add_object(Hud.new)
29
+
30
+ level_loaded
31
+ end
32
+
33
+ def load_level_file(level_name, restart)
34
+ f = File.open(ResourceManager.media(level_name))
35
+ load_level_string(f.read, restart)
36
+ end
37
+
38
+ def self.object_from_char(char)
39
+ case char.downcase
40
+ when 'p' then return Player.new
41
+ when '.' then return Food.new
42
+ when ':' then return SuperFood.new
43
+ when 'g' then return Ghost.new
44
+ when '_' then return Door.new
45
+ end
46
+ Wall.from_char(char) unless Wall.chars.index(char).nil?
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,103 @@
1
+ require 'gosu'
2
+ require 'ruby_man/sprite'
3
+
4
+ module Gosu
5
+ # extension of Font for easier centered drawing
6
+ class Font
7
+ def draw_centered(text, x, y, z = 0,
8
+ factor_x = 1, factor_y = 1,
9
+ color = 0xffffffff, mode = :default)
10
+ w = text_width(text, factor_x)
11
+ draw(text, x - w / 2, y, z, factor_x, factor_y, color, mode)
12
+ end
13
+ end
14
+ end
15
+
16
+ module RubyMan
17
+ # Handles loading of resources for faster and centralized access.
18
+ class ResourceManager
19
+ class << self
20
+ attr_accessor :inst
21
+ end
22
+
23
+ def initialize(window)
24
+ ResourceManager.inst = self
25
+ @window = window
26
+ @resources = {}
27
+ @media_dir = "#{__dir__}/../../media"
28
+ load_config
29
+ end
30
+
31
+ def media_file_path(media_path)
32
+ "#{@media_dir}/#{media_path}"
33
+ end
34
+
35
+ def load_config
36
+ File.open(media_file_path('resource.cfg')).each do |line|
37
+ cfg = line.split(';').map!(&:strip)
38
+ next unless cfg.size >= 3
39
+ case cfg[1]
40
+ when 'image' then load_image(cfg)
41
+ when 'animation' then load_animation(cfg)
42
+ when 'font' then load_font(cfg)
43
+ when 'sound' then load_sound(cfg)
44
+ end
45
+ end
46
+ end
47
+
48
+ def load_animation(cfg)
49
+ return unless cfg.size >= 7
50
+
51
+ sprite = Sprite.new
52
+ x_offset = cfg[3].to_i
53
+ y_offset = cfg[4].to_i
54
+ sprite.width = cfg[5].to_i
55
+ sprite.height = cfg[6].to_i
56
+ frames = cfg[7].to_i
57
+
58
+ (0...frames).each do |i|
59
+ image = Gosu::Image.new(@window, media_file_path(cfg[2]), true,
60
+ x_offset + i * sprite.width, y_offset,
61
+ sprite.width, sprite.height)
62
+ sprite.frames.push(image)
63
+ end
64
+
65
+ @resources[cfg[0]] = sprite
66
+ end
67
+
68
+ def load_image(cfg)
69
+ sprite = Sprite.new
70
+ sprite.frames.push(Gosu::Image.new(@window, media_file_path(cfg[2]), false))
71
+ sprite.width = sprite.frames[0].width
72
+ sprite.height = sprite.frames[0].height
73
+ @resources[cfg[0]] = sprite
74
+ end
75
+
76
+ def load_sound(cfg)
77
+ sound = Gosu::Sample.new(@window, media_file_path(cfg[2]))
78
+ @resources[cfg[0]] = sound
79
+ end
80
+
81
+ def load_font(cfg)
82
+ begin
83
+ font = Gosu::Font.new(@window, media_file_path(cfg[2]), cfg[3].to_i)
84
+ font.text_width('font found test')
85
+ rescue
86
+ font = Gosu::Font.new(@window, Gosu::default_font_name, cfg[3].to_i)
87
+ end
88
+ @resources[cfg[0]] = font
89
+ end
90
+
91
+ def resource(name)
92
+ @resources[name]
93
+ end
94
+
95
+ def self.[](resource_name)
96
+ @inst.resource(resource_name)
97
+ end
98
+
99
+ def self.media(media_path)
100
+ @inst.media_file_path(media_path)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,26 @@
1
+ require 'ruby_man/resource_manager'
2
+
3
+ module RubyMan
4
+ # drawable animated image
5
+ class Sprite
6
+ attr_accessor :frames
7
+ attr_accessor :width, :height
8
+
9
+ def initialize
10
+ @frames = []
11
+ end
12
+
13
+ def self.[](resource_name)
14
+ ResourceManager[resource_name]
15
+ end
16
+
17
+ def draw(x, y, depth = 0, rotation = 0, frame = 0, scale = 1.0)
18
+ frames[frame].draw_rot(x + @width / 2, y + @height / 2, -depth,
19
+ rotation, 0.5, 0.5, scale, scale)
20
+ end
21
+
22
+ def draw_centered(x, y, depth = 0, rotation = 0, frame = 0, scale = 1.0)
23
+ frames[frame].draw_rot(x, y, -depth, rotation, 0.5, 0.5, scale, scale)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ # Common RubyMan namespace
2
+ module RubyMan
3
+ VERSION = '0.0.2'
4
+ end
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ levels/level_1.txt
2
+ levels/level_2.txt
3
+ levels/level_3.txt
4
+ levels/level_4.txt
@@ -0,0 +1,19 @@
1
+ /-----------------------\
2
+ |...........:...........|
3
+ |./----\./-----\./----\.|
4
+ |.| |.| |.| |.|
5
+ |.| |.| |.| |.|
6
+ |.L----J.L-----J.L----J.|
7
+ |.......................|
8
+ |.o.o.o.o./-_-\.o.o.o.o.|
9
+ |....:....|GGG|....:....|
10
+ |.o.o.o.o.L---J.o.o.o.o.|
11
+ |.......................|
12
+ |./----\./-----\./----\.|
13
+ |.| |.| |.| |.|
14
+ |.| |.| |.| |.|
15
+ |.L----J.L-----J.L----J.|
16
+ |.......................|
17
+ |.<-------->P<-------->.|
18
+ |:.....................:|
19
+ L-----------------------J
@@ -0,0 +1,19 @@
1
+ /-----------------------\
2
+ |:.....................:|
3
+ |./-----\./---\./-----\.|
4
+ |.| /---J.L\ /J.L---\ |.|
5
+ |.| |:.....L-J.....:| |.|
6
+ |.L-J.<-->.....<-->.L-J.|
7
+ |........../_\..........|
8
+ F-------\./JGL\./-------3
9
+ | G |.|GGG|.| G |
10
+ | |.L---J.| |
11
+ F-------J.......L-------3
12
+ |........./\P/\.........|
13
+ |./-\.<---3L-JF--->./-\.|
14
+ |.| |:....| |....:| |.|
15
+ |.| L---\.| G |./---J |.|
16
+ |.L-----J.| |.L-----J.|
17
+ |.........L---J.........|
18
+ L-------\...:.../-------J
19
+ L-------J
@@ -0,0 +1,19 @@
1
+ /-----------T-----------\
2
+ |...........|...........|
3
+ |./--\./--\.|./--\./--\.|
4
+ |:| |.| |.|.| |.| |:|
5
+ |.L--J.L--J.|.L--J.L--J.|
6
+ |...........:...........|
7
+ |.<--\./\./-_-\./\./-->.|
8
+ |....|.||.| GG|.||.|....|
9
+ F--\.|.||.|GGG|.||.|./--3
10
+ | |.|.||.L-T-J.||.|.| |
11
+ F--J.V.LJ...|...LJ.V.L--3
12
+ |.........A.P.A.........|
13
+ |./\./--\.V.|.V./--\./\.|
14
+ |.||.L--3...|...F--J.||.|
15
+ |:||....L->.V.<-J....||:|
16
+ |.|L--\.........../--J|.|
17
+ |.L---J.<>./-\.<>.L---J.|
18
+ |..........| |..........|
19
+ L----------4-4----------J
@@ -0,0 +1,19 @@
1
+ /----T-T---------T-T----\
2
+ |...:|G|....:....|G|:...|
3
+ |.<--4_4--->.<---4_4-->.|
4
+ |.......................|
5
+ F----\./-\./-\./-\./----3
6
+ | |.| |.L-J.| |.| |
7
+ | |.| |.....| |.| |
8
+ F----J.L-J./-\.L-J.L----3
9
+ |..........|G|..........|
10
+ |./--\.<---3 F--->./--\.|
11
+ |.| |.....L-J.....| |.|
12
+ |.| L\./\..P../\./J |.|
13
+ |.L---J.|L-----J|.L---J.|
14
+ |.......| |.......|
15
+ |./-\./-J/-----\L-\./-\.|
16
+ |.|G|.| |.....| |.|G|.|
17
+ |.L_J.L--J./_\.L--J.L_J.|
18
+ |:.........|G|.........:|
19
+ L----------4-4----------J
@@ -0,0 +1,19 @@
1
+ /-----------------------\
2
+ | |
3
+ | |
4
+ | |
5
+ | |
6
+ | |
7
+ | |
8
+ | |
9
+ | |
10
+ | |
11
+ | |
12
+ | |
13
+ | |
14
+ | |
15
+ | |
16
+ | |
17
+ | |
18
+ | |
19
+ L-----------------------J
Binary file
@@ -0,0 +1,18 @@
1
+ rubyman;animation;rubyman_strip11.png;0;0;32;32;11
2
+ walls;animation;walls_strip16.png;0;0;32;32;16
3
+ door;animation;door_strip2.png;0;0;32;32;2
4
+ ghost_left;animation;ghost_left_strip2.png;0;0;32;32;2
5
+ ghost_right;animation;ghost_right_strip2.png;0;0;32;32;2
6
+ ghost_up;animation;ghost_up_strip2.png;0;0;32;32;2
7
+ ghost_down;animation;ghost_down_strip2.png;0;0;32;32;2
8
+ ghost_scared;animation;ghost_scared_strip2.png;0;0;32;32;2
9
+ ghost_stop_scared;animation;ghost_stopscared_strip3.png;0;0;32;32;3
10
+ ghost_death;animation;ghost_death_strip15.png;0;0;32;32;15
11
+ gem;animation;gem_strip9.png;0;0;32;32;9
12
+ logo;image;logo.png
13
+ sfx_begin;sound;sfx/pacman_beginning.wav
14
+ sfx_death;sound;sfx/pacman_death.wav
15
+ sfx_eatghost;sound;sfx/pacman_eatghost.wav
16
+ sfx_eat;sound;sfx/pacman_chomp.wav
17
+ text_normal;font;Arial;22
18
+ text_large;font;Arial;48
Binary file
Binary file
@@ -0,0 +1,41 @@
1
+ require 'rspec'
2
+ require 'ruby_man/aabb'
3
+
4
+ # Common RubyMan namespace
5
+ module RubyMan
6
+ describe 'AABB' do
7
+ context 'intersection' do
8
+ subject(:box) { AABB.new(2, 2, 8, 8) }
9
+ it 'intersects basic cases' do
10
+ expect(box.intersects?(AABB.new(0, 0, 4, 4))).to be true
11
+ expect(box.intersects?(AABB.new(4, 4, 10, 10))).to be true
12
+ expect(box.intersects?(AABB.new(0, 4, 5, 10))).to be true
13
+ expect(box.intersects?(AABB.new(4, 0, 10, 5))).to be true
14
+ end
15
+
16
+ it 'intersects edges' do
17
+ expect(box.intersects?(AABB.new(3, 0, 4, 10))).to be true
18
+ expect(box.intersects?(AABB.new(0, 3, 10, 4))).to be true
19
+ end
20
+
21
+ it 'accepts floats' do
22
+ expect(box.intersects?(AABB.new(2.1, 0, 7.9, 10))).to be true
23
+ end
24
+
25
+ it 'does not intersect' do
26
+ expect(box.intersects?(AABB.new(-1000, -1000, 1, 1))).to be false
27
+ expect(box.intersects?(AABB.new(9, 9, 1000, 1000))).to be false
28
+ expect(box.intersects?(AABB.new(-1000, -1000, 1000, 1))).to be false
29
+ expect(box.intersects?(AABB.new(-1000, 9, 1000, 1000))).to be false
30
+ end
31
+
32
+ it 'intersects when contained inside other' do
33
+ expect(box.intersects?(AABB.new(0, 0, 10, 10))).to be true
34
+ end
35
+
36
+ it 'intersects when containing other' do
37
+ expect(box.intersects?(AABB.new(4, 4, 5, 5))).to be true
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ require 'rspec'
2
+ require 'ruby_man/direction'
3
+
4
+ # Common RubyMan namespace
5
+ module RubyMan
6
+ describe 'Direction' do
7
+ context 'should work with Array booleans' do
8
+ subject(:arr) { [Direction.left, Direction.right, Direction.up] }
9
+
10
+ it 'works with intersection' do
11
+ expect(arr & [Direction.right,
12
+ Direction.down,
13
+ Direction.up]).to eq [Direction.right, Direction.up]
14
+ end
15
+
16
+ it 'works with subtraction' do
17
+ expect(arr - [Direction.right,
18
+ Direction.down,
19
+ Direction.up]).to eq [Direction.left]
20
+ end
21
+
22
+ it 'works with equal' do
23
+ expect(arr).to eq [Direction.left, Direction.right, Direction.up]
24
+ end
25
+ end
26
+
27
+ context 'functionality' do
28
+ subject(:dir) { Direction.up }
29
+ it { expect(dir).to eq Direction.up }
30
+ it { expect(dir.opposite).to eq Direction.down }
31
+ it do
32
+ expect(dir.inverted).to eq [Direction.right,
33
+ Direction.down,
34
+ Direction.left]
35
+ end
36
+ it do
37
+ expect(Direction.each.to_a).to eq [Direction.right,
38
+ Direction.down,
39
+ Direction.left,
40
+ Direction.up]
41
+ end
42
+ it { expect(Direction.each.to_a.map(&:to_i)).to eq [0, 1, 2, 3] }
43
+ it { expect(Direction.each.to_a.map(&:to_angle)).to eq [0, 90, 180, 270] }
44
+ it { expect(Direction.each.to_a.map(&:rel_x)).to eq [1, 0, -1, 0] }
45
+ it { expect(Direction.each.to_a.map(&:rel_y)).to eq [0, 1, 0, -1] }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,126 @@
1
+ require 'rspec'
2
+ require 'ruby_man/game_main'
3
+ require 'fileutils'
4
+
5
+ # Common RubyMan namespace
6
+ module RubyMan
7
+ describe 'GameMain' do
8
+ context 'game' do
9
+ subject(:game) { GameMain.new }
10
+
11
+ def select_type(arr, type)
12
+ arr.select { |obj| obj.is_a?(type) }
13
+ end
14
+
15
+ it 'starts in menu' do
16
+ expect(select_type(game.objects, Menu).size).to eq 1
17
+ end
18
+
19
+ it 'has at least one level' do
20
+ expect(game.instance_variable_get(:@levels).size).to be > 0
21
+ end
22
+
23
+ context 'first level' do
24
+ before { game.start }
25
+
26
+ it 'has 3 lives' do
27
+ expect(game.lives).to eq 3
28
+ end
29
+
30
+ it 'spawns correct number of required game objects' do
31
+ expect(select_type(game.objects, Player).size).to eq 1
32
+ expect(select_type(game.moving_objects, Player).size).to eq 1
33
+ expect(select_type(game.objects, Food).size).to be > 0
34
+ expect(select_type(game.objects, Hud).size).to eq 1
35
+ # ghosts aren't mandatory for the game to work
36
+ end
37
+
38
+ it 'spawns player on a free spot' do
39
+ expect(game.place_free(game.player.grid_x,
40
+ game.player.grid_y)).to be true
41
+ end
42
+
43
+ it 'does not crash after start' do
44
+ expect { game.update_delta(0.01) }.to_not raise_error
45
+ end
46
+
47
+ it 'sets player variable' do
48
+ expect(game.player).to be_a Player
49
+ end
50
+
51
+ after { game.main_menu }
52
+ end
53
+
54
+ context 'test level' do
55
+ def setup
56
+ level = '|G..:' + "\n" \
57
+ 'L_P| '
58
+ game.end_level
59
+ game.load_level_string(level, false)
60
+ # verify test sanity - the player should be at (2,1)
61
+ raise_error unless game.player.grid_x == 2 && game.player.grid_y == 1
62
+ end
63
+
64
+ before { setup }
65
+
66
+ it 'player detects walls' do
67
+ expect(game.player.direction_free(Direction.up)).to be true
68
+ expect(game.player.direction_free(Direction.left)).to be false
69
+ expect(game.player.direction_free(Direction.right)).to be false
70
+ end
71
+
72
+ context 'player is killed by ghost' do
73
+ before do
74
+ game.lives = 3
75
+ ghost = game.objects_at(1, 0).first
76
+ ghost.set_pos(2, 1)
77
+ # process collision
78
+ game.update_delta(0)
79
+ # skip time after pause
80
+ game.update_delta(2.5)
81
+ # process level restart
82
+ game.update_delta(0)
83
+ end
84
+
85
+ it 'reduces player lives' do
86
+ expect(game.lives).to eq 2
87
+ end
88
+
89
+ after do
90
+ game.lives = 3
91
+ setup
92
+ end
93
+ end
94
+
95
+ context 'ghost' do
96
+ subject(:ghost) { game.objects_at(1, 0).first }
97
+
98
+ before { ghost.change_direction(Direction.up) }
99
+
100
+ it 'starts in chase mode' do
101
+ expect(instance_variable_get(:@scared)).to be_nil
102
+ end
103
+
104
+ it 'knows where it can go' do
105
+ # down would be turning back, left would be into the wall
106
+ expect(ghost.possible_directions).to match_array [Direction.right,
107
+ Direction.up]
108
+ end
109
+
110
+ it 'knows directions to player' do
111
+ expect(ghost.directions_to_player).to match_array [Direction.right,
112
+ Direction.down]
113
+ end
114
+
115
+ it 'knows directions in which to run away from player' do
116
+ expect(ghost.scared_directions).to eq [Direction.up]
117
+ end
118
+ end
119
+
120
+ after do
121
+ game.main_menu
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end