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,50 @@
1
+ require 'ruby_man/GameObjects/grid_object'
2
+ require 'ruby_man/direction'
3
+
4
+ module RubyMan
5
+ # GridObject with the ability to move smoothly in 4 directions
6
+ class MovingObject < GridObject
7
+ attr_accessor :direction, :phase, :speed
8
+
9
+ def initialize
10
+ super
11
+ @direction = Direction.none
12
+ @phase = 0
13
+ @speed = 0
14
+ end
15
+
16
+ def update(delta)
17
+ super
18
+ @phase += @speed * delta
19
+ while @phase > 1
20
+ @phase -= 1
21
+ @grid_x += @direction.rel_x
22
+ @grid_y += @direction.rel_y
23
+ grid_step
24
+ end
25
+ end
26
+
27
+ def grid_step
28
+ end
29
+
30
+ def collision(_other)
31
+ end
32
+
33
+ def change_direction(direction)
34
+ @direction = direction
35
+ @image_angle = direction.to_angle
36
+ end
37
+
38
+ def direction_free(direction)
39
+ @game.place_free(@grid_x + direction.rel_x, @grid_y + direction.rel_y)
40
+ end
41
+
42
+ def pixel_x
43
+ (@grid_x + @direction.rel_x * @phase) * 32
44
+ end
45
+
46
+ def pixel_y
47
+ (@grid_y + @direction.rel_y * @phase) * 32
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,90 @@
1
+ require 'gosu'
2
+ require 'ruby_man/GameObjects/moving_object'
3
+ require 'ruby_man/GameObjects/food'
4
+ require 'ruby_man/GameObjects/ghost'
5
+ require 'ruby_man/GameObjects/door'
6
+
7
+ module RubyMan
8
+ # player (RubyMan) object
9
+ class Player < MovingObject
10
+ def created
11
+ self.sprite = Sprite['rubyman']
12
+ @image_speed = 3
13
+ @next_direction = Direction.none
14
+ @speed = 3
15
+ @is_dead = false
16
+ @game.player = self
17
+ end
18
+
19
+ def button_down(id)
20
+ case id
21
+ when Gosu::KbLeft then @next_direction = Direction.left
22
+ when Gosu::KbUp then @next_direction = Direction.up
23
+ when Gosu::KbRight then @next_direction = Direction.right
24
+ when Gosu::KbDown then @next_direction = Direction.down
25
+ end
26
+ end
27
+
28
+ def update(delta)
29
+ dead if @is_dead
30
+ super
31
+ end
32
+
33
+ def grid_step
34
+ eat_food
35
+ change_direction(@next_direction)
36
+ @direction = Direction.none unless direction_free(@direction)
37
+ end
38
+
39
+ def direction_free(direction)
40
+ x = @grid_x + direction.rel_x
41
+ y = @grid_y + direction.rel_y
42
+ @game.objects_at(x, y).each do |obj|
43
+ return false if obj.solid || obj.is_a?(Door)
44
+ end
45
+ true
46
+ end
47
+
48
+ def collision(obj)
49
+ return unless obj.is_a?(Ghost)
50
+ return unless distance_to(obj) <= 16
51
+ if obj.scared?
52
+ obj.die
53
+ else
54
+ die
55
+ end
56
+ end
57
+
58
+ def die
59
+ ResourceManager['sfx_death'].play
60
+ @game.pause(2000)
61
+ @is_dead = true
62
+ end
63
+
64
+ def dead
65
+ if @game.lives > 0
66
+ @game.lives -= 1
67
+ @game.restart
68
+ else
69
+ @game.game_over
70
+ end
71
+ end
72
+
73
+ def eat_food
74
+ @game.objects_at(@grid_x, @grid_y).each do |obj|
75
+ next unless obj.is_a?(Food)
76
+ obj.destroy
77
+ @game.score += 10
78
+ end
79
+ end
80
+
81
+ def change_direction(direction)
82
+ dir_x = @grid_x + direction.rel_x
83
+ dir_y = @grid_y + direction.rel_y
84
+ @game.objects_at(dir_x, dir_y).each do |obj|
85
+ return if obj.solid || obj.is_a?(Door)
86
+ end
87
+ super
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,16 @@
1
+ require 'ruby_man/GameObjects/food'
2
+
3
+ module RubyMan
4
+ # food that makes ghosts vulnerable
5
+ class SuperFood < Food
6
+ def created
7
+ super
8
+ @scale = 1
9
+ end
10
+
11
+ def destroyed
12
+ super
13
+ @game.moving_objects.select { |obj| obj.is_a?(Ghost) }.each(&:scare)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'ruby_man/GameObjects/grid_object'
2
+
3
+ module RubyMan
4
+ # impassable wall
5
+ class Wall < GridObject
6
+ def initialize(type)
7
+ super()
8
+ self.sprite = Sprite['walls']
9
+ @image_index = type
10
+ @image_speed = 0
11
+ @solid = true
12
+ end
13
+
14
+ def self.chars
15
+ '+T34F/\\JL-|oAV<>'
16
+ end
17
+
18
+ def self.from_char(char)
19
+ index = chars.index(char)
20
+ return if index.nil?
21
+ Wall.new(index)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module RubyMan
2
+ # Axis-Aligned Bounding Box
3
+ class AABB
4
+ attr_reader :x_beg, :y_beg
5
+ attr_reader :x_end, :y_end
6
+
7
+ def initialize(x_beg, y_beg, x_end, y_end)
8
+ @x_beg, @y_beg = x_beg, y_beg
9
+ @x_end, @y_end = x_end, y_end
10
+ end
11
+
12
+ def intersects?(other)
13
+ @x_beg < other.x_end && @x_end > other.x_beg &&
14
+ @y_beg < other.y_end && @y_end > other.y_beg
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,102 @@
1
+ module RubyMan
2
+ # Class representing left/right/up/down/none direction in 2D space
3
+ class Direction
4
+ def initialize(dir = :none)
5
+ @direction = dir
6
+ end
7
+
8
+ def self.none
9
+ Direction.new
10
+ end
11
+
12
+ def self.left
13
+ Direction.new(:left)
14
+ end
15
+
16
+ def self.right
17
+ Direction.new(:right)
18
+ end
19
+
20
+ def self.up
21
+ Direction.new(:up)
22
+ end
23
+
24
+ def self.down
25
+ Direction.new(:down)
26
+ end
27
+
28
+ def self.each
29
+ return to_enum unless block_given?
30
+ yield Direction.right
31
+ yield Direction.down
32
+ yield Direction.left
33
+ yield Direction.up
34
+ end
35
+
36
+ def to_i
37
+ case @direction
38
+ when :right
39
+ return 0
40
+ when :down
41
+ return 1
42
+ when :left
43
+ return 2
44
+ when :up
45
+ return 3
46
+ end
47
+ end
48
+
49
+ def self.from_i(num)
50
+ case num % 4
51
+ when 0
52
+ return Direction.right
53
+ when 1
54
+ return Direction.down
55
+ when 2
56
+ return Direction.left
57
+ when 3
58
+ return Direction.up
59
+ end
60
+ end
61
+
62
+ def rel_x
63
+ i = to_i
64
+ return 0 unless i
65
+ (1 - i) * ((i + 1) % 2)
66
+ end
67
+
68
+ def rel_y
69
+ i = to_i
70
+ return 0 unless i
71
+ (2 - i) * (i % 2)
72
+ end
73
+
74
+ def opposite
75
+ i = to_i
76
+ return Direction.none unless i
77
+ Direction.from_i(to_i + 2)
78
+ end
79
+
80
+ def inverted
81
+ Direction.each.select { |dir| dir != self }
82
+ end
83
+
84
+ def to_angle
85
+ i = to_i
86
+ return 0 unless i
87
+ i * 90
88
+ end
89
+
90
+ def ==(other)
91
+ to_i == other.to_i
92
+ end
93
+
94
+ def eql?(other)
95
+ self == other
96
+ end
97
+
98
+ def hash
99
+ @direction.hash
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,213 @@
1
+ require 'gosu'
2
+ require 'ruby_man/resource_manager'
3
+ require 'ruby_man/level_loader'
4
+ require 'ruby_man/GameObjects/menu'
5
+ require 'ruby_man/GameObjects/game_over'
6
+
7
+ module RubyMan
8
+ # Game window and the game itself
9
+ class GameMain < Gosu::Window
10
+ include LevelLoader
11
+
12
+ attr_reader :x_res, :y_res
13
+ attr_accessor :lives, :score
14
+ attr_reader :scores
15
+ attr_accessor :player
16
+ attr_reader :objects, :moving_objects
17
+
18
+ def initialize
19
+ @x_res, @y_res = 800, 640
20
+ super(@x_res, @y_res, false)
21
+ @resmgr = ResourceManager.new(self)
22
+ @objects, @moving_objects = [], []
23
+ @last_milliseconds = Gosu.milliseconds
24
+ @food_left = 0
25
+ @pause_time = -0.5
26
+ self.caption = 'RubyMan'
27
+ load_scores
28
+ load_level_list
29
+ main_menu
30
+ end
31
+
32
+ def load_scores
33
+ @config_file_name = "#{Dir.home}/.rubyman.cfg"
34
+ @scores = []
35
+ @score = 0
36
+ return unless File.exist?(@config_file_name)
37
+ File.open(@config_file_name).each do |line|
38
+ entry = line.strip.split(';')
39
+ entry[2] = entry[2].to_i
40
+ @scores.push(entry)
41
+ end
42
+ end
43
+
44
+ def save_scores
45
+ file = File.open("#{Dir.home}/.rubyman.cfg", 'w')
46
+ count = 0
47
+ @scores.each do |entry|
48
+ count += 1
49
+ break if count > 10
50
+ line = "#{entry[0]};#{entry[1]};#{entry[2]}"
51
+ file.puts(line)
52
+ end
53
+ end
54
+
55
+ def add_score(name)
56
+ entry = [name, Time.new.strftime('%Y-%m-%d %H:%M'), @score]
57
+ @scores.push(entry)
58
+ sort_scores
59
+ save_scores
60
+ end
61
+
62
+ def sort_scores
63
+ @scores.sort! do |a, b|
64
+ a[2] <=> b[2]
65
+ end
66
+ @scores.reverse!
67
+ @scores = @scores.first(10)
68
+ end
69
+
70
+ def load_level_list
71
+ @levels = []
72
+ @level = 0
73
+ File.open(ResourceManager.media('levellist.txt')).each do |line|
74
+ @levels.push(line.strip)
75
+ end
76
+ end
77
+
78
+ def start
79
+ @lives = 3
80
+ @score = 0
81
+ @level = -1
82
+ next_level
83
+ end
84
+
85
+ def restart
86
+ persistent = @objects.select { |obj| obj.class.persistent }
87
+ persistent_moving = @moving_objects.select { |obj| obj.class.persistent }
88
+ load_level(@level, true)
89
+ @objects += persistent
90
+ @moving_objects += persistent_moving
91
+ pause(1000)
92
+ end
93
+
94
+ def next_level
95
+ if @level < @levels.size - 1
96
+ @level += 1
97
+ load_level(@level, false)
98
+ ResourceManager['sfx_begin'].play
99
+ pause(3000)
100
+ else
101
+ game_over
102
+ end
103
+ end
104
+
105
+ def end_level
106
+ @objects.clear
107
+ @moving_objects.clear
108
+ end
109
+
110
+ def load_level(num, restart)
111
+ end_level
112
+ load_level_file(@levels[num], restart)
113
+ end
114
+
115
+ def level_loaded
116
+ # reevaluate ghost directions after level is fully loaded
117
+ moving_objects.each { |obj| obj.select_direction! if obj.is_a?(Ghost) }
118
+ end
119
+
120
+ def game_over
121
+ end_level
122
+ add_object(GameOver.new)
123
+ end
124
+
125
+ def main_menu
126
+ end_level
127
+ add_object(Menu.new)
128
+ end
129
+
130
+ def add_object(obj)
131
+ @objects.push(obj)
132
+ obj.game = self
133
+ @moving_objects.push(obj) if obj.is_a?(MovingObject)
134
+ obj.created
135
+ end
136
+
137
+ def destroy_object(obj)
138
+ obj.destroyed
139
+ @objects.delete(obj)
140
+ @moving_objects.delete(obj) if obj.is_a?(MovingObject)
141
+ end
142
+
143
+ def button_down(id)
144
+ close if id == Gosu::KbEscape
145
+ pause if id == Gosu::KbSpace
146
+ @objects.each { |obj| obj.button_down(id) }
147
+ end
148
+
149
+ def pause(ms = -1000.0)
150
+ if paused
151
+ @pause_time = -0.5
152
+ else
153
+ @pause_time = ms / 1000.0
154
+ end
155
+ end
156
+
157
+ def paused
158
+ return true if @pause_time > 0 || @pause_time == -1
159
+ false
160
+ end
161
+
162
+ def update
163
+ current_time = Gosu.milliseconds / 1000.0
164
+ delta = [current_time - @last_milliseconds, 0.25].min
165
+ @last_milliseconds = current_time
166
+
167
+ update_delta(delta)
168
+ end
169
+
170
+ def update_delta(delta)
171
+ @pause_time -= delta if @pause_time > 0
172
+
173
+ return if paused
174
+ @objects.each { |obj| obj.update(delta) }
175
+ check_collisions
176
+ end
177
+
178
+ def draw
179
+ super
180
+ @objects.each(&:draw)
181
+ end
182
+
183
+ def check_collisions
184
+ @moving_objects.each do |obj1|
185
+ bbox = obj1.bbox
186
+ @moving_objects.each do |obj2|
187
+ obj2.collision(obj1) if bbox.intersects?(obj2.bbox)
188
+ end
189
+ end
190
+ end
191
+
192
+ def place_free(x, y)
193
+ objects_at(x, y).each { |obj| return false if obj.solid }
194
+ true
195
+ end
196
+
197
+ def objects_at(x, y)
198
+ @objects.select do |obj|
199
+ next unless obj.is_a?(GridObject)
200
+ obj.grid_x == x && obj.grid_y == y
201
+ end
202
+ end
203
+
204
+ def food_created
205
+ @food_left += 1
206
+ end
207
+
208
+ def food_destroyed
209
+ @food_left -= 1
210
+ next_level if @food_left == 0
211
+ end
212
+ end
213
+ end