road_to_rubykaigi 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.standard.yml +11 -0
- data/CHANGELOG.md +6 -0
- data/README.md +15 -1
- data/Rakefile +1 -0
- data/lib/road_to_rubykaigi/audio/audio_engine.rb +70 -0
- data/lib/road_to_rubykaigi/audio/oscillator.rb +159 -0
- data/lib/road_to_rubykaigi/audio/sequencer.rb +263 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_01.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_02.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_03.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_04.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/attack_05.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/bonus.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/crouch.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/defeat.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/game_over.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/jump.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/laptop.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/stun.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/walk_01.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav/walk_02.wav +0 -0
- data/lib/road_to_rubykaigi/audio/wav_source.rb +55 -0
- data/lib/road_to_rubykaigi/event_dispatcher.rb +122 -0
- data/lib/road_to_rubykaigi/fireworks.rb +4 -4
- data/lib/road_to_rubykaigi/game.rb +49 -60
- data/lib/road_to_rubykaigi/graphics/demo-map.txt +30 -0
- data/lib/road_to_rubykaigi/graphics/demo-mask.txt +30 -0
- data/lib/road_to_rubykaigi/graphics/map.rb +6 -1
- data/lib/road_to_rubykaigi/graphics/mask.rb +7 -1
- data/lib/road_to_rubykaigi/graphics/player.rb +56 -65
- data/lib/road_to_rubykaigi/graphics/player.txt +26 -0
- data/lib/road_to_rubykaigi/manager/audio_manager.rb +81 -0
- data/lib/road_to_rubykaigi/manager/collision_manager.rb +23 -108
- data/lib/road_to_rubykaigi/manager/drawing_manager.rb +7 -6
- data/lib/road_to_rubykaigi/manager/game_manager.rb +50 -13
- data/lib/road_to_rubykaigi/manager/physics_engine.rb +21 -0
- data/lib/road_to_rubykaigi/manager/update_manager.rb +15 -12
- data/lib/road_to_rubykaigi/map.rb +1 -15
- data/lib/road_to_rubykaigi/score_board.rb +18 -1
- data/lib/road_to_rubykaigi/sprite/attack.rb +13 -6
- data/lib/road_to_rubykaigi/sprite/bonus.rb +16 -0
- data/lib/road_to_rubykaigi/sprite/deadline.rb +3 -3
- data/lib/road_to_rubykaigi/sprite/enemy.rb +12 -8
- data/lib/road_to_rubykaigi/sprite/player.rb +110 -29
- data/lib/road_to_rubykaigi/version.rb +1 -1
- data/lib/road_to_rubykaigi.rb +20 -4
- metadata +55 -3
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module RoadToRubykaigi
|
4
|
+
module Manager
|
5
|
+
class AudioManager
|
6
|
+
include Singleton
|
7
|
+
SOUND_FILES = {
|
8
|
+
attack: %w[
|
9
|
+
lib/road_to_rubykaigi/audio/wav/attack_03.wav
|
10
|
+
lib/road_to_rubykaigi/audio/wav/attack_04.wav
|
11
|
+
lib/road_to_rubykaigi/audio/wav/attack_05.wav
|
12
|
+
],
|
13
|
+
bonus: %w[lib/road_to_rubykaigi/audio/wav/bonus.wav],
|
14
|
+
crouch: %w[lib/road_to_rubykaigi/audio/wav/crouch.wav],
|
15
|
+
defeat: %w[lib/road_to_rubykaigi/audio/wav/defeat.wav],
|
16
|
+
game_over: %w[lib/road_to_rubykaigi/audio/wav/game_over.wav],
|
17
|
+
jump: %w[lib/road_to_rubykaigi/audio/wav/jump.wav],
|
18
|
+
laptop: %w[lib/road_to_rubykaigi/audio/wav/laptop.wav],
|
19
|
+
stun: %w[lib/road_to_rubykaigi/audio/wav/stun.wav],
|
20
|
+
walk: %w[
|
21
|
+
lib/road_to_rubykaigi/audio/wav/walk_01.wav
|
22
|
+
lib/road_to_rubykaigi/audio/wav/walk_02.wav
|
23
|
+
],
|
24
|
+
}
|
25
|
+
WALK_SOUND_INTERVAL = 0.25
|
26
|
+
|
27
|
+
SOUND_FILES.keys.each do |action|
|
28
|
+
define_method(action) {
|
29
|
+
@audio_engine.add_source(@sources[action].sample)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def fanfare
|
34
|
+
@audio_engine.mute
|
35
|
+
@audio_engine.remove_source(@bass_sequencer)
|
36
|
+
@audio_engine.remove_source(@melody_sequencer)
|
37
|
+
@audio_engine.unmute
|
38
|
+
@audio_engine.add_source(@fanfare_sequencer)
|
39
|
+
end
|
40
|
+
|
41
|
+
def fanfare_finished?
|
42
|
+
@fanfare_sequencer.finished?
|
43
|
+
end
|
44
|
+
|
45
|
+
def game_over
|
46
|
+
@sources[:game_over].first.tap do |source|
|
47
|
+
@audio_engine.remove_source(@bass_sequencer)
|
48
|
+
@audio_engine.remove_source(@melody_sequencer)
|
49
|
+
@audio_engine.add_source(source)
|
50
|
+
until source.finished?
|
51
|
+
sleep 0.1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def walk
|
57
|
+
now = Time.now
|
58
|
+
if (now - @last_walk_time) >= WALK_SOUND_INTERVAL
|
59
|
+
@audio_engine.add_source(@sources[:walk][@walk_index])
|
60
|
+
@last_walk_time = now
|
61
|
+
@walk_index = (@walk_index + 1) % @sources[:walk].size
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def initialize
|
68
|
+
@bass_sequencer = Audio::BassSequencer.new
|
69
|
+
@melody_sequencer = Audio::MelodySequencer.new
|
70
|
+
@fanfare_sequencer = Audio::FanfareSequencer.new
|
71
|
+
@audio_engine = Audio::AudioEngine.new(@bass_sequencer, @melody_sequencer)
|
72
|
+
@sources = SOUND_FILES
|
73
|
+
@sources.each do |action, file_paths|
|
74
|
+
@sources[action] = file_paths.map { |file_path| Audio::WavSource.new(file_path) }
|
75
|
+
end
|
76
|
+
@walk_index = 0
|
77
|
+
@last_walk_time = Time.now - 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -2,122 +2,37 @@ module RoadToRubykaigi
|
|
2
2
|
module Manager
|
3
3
|
class CollisionManager
|
4
4
|
def process
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
if event.include?(:game_over)
|
16
|
-
:game_over
|
17
|
-
elsif event.include?(:bonus)
|
18
|
-
:bonus
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def initialize(background, foreground)
|
25
|
-
@map = background
|
26
|
-
@player, @deadline, @bonuses, @enemies, @attacks, @effects = foreground.layers
|
27
|
-
end
|
28
|
-
|
29
|
-
def player_fall
|
30
|
-
bounding_box = @player.bounding_box
|
31
|
-
foot_y = bounding_box[:y] + bounding_box[:height]
|
32
|
-
center_x = bounding_box[:x] + bounding_box[:width] / 2.0
|
33
|
-
if @map.passable_at?(center_x, foot_y + 1)
|
34
|
-
@player.fall
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def player_land
|
39
|
-
bounding_box = @player.bounding_box
|
40
|
-
foot_y = bounding_box[:y] + bounding_box[:height]
|
41
|
-
foot_y = foot_y.clamp(bounding_box[:height], RoadToRubykaigi::Sprite::Player::BASE_Y)
|
42
|
-
(bounding_box[:x]...(bounding_box[:x] + bounding_box[:width])).each do |col|
|
43
|
-
unless @map.passable_at?(col, foot_y)
|
44
|
-
break @player.land(foot_y)
|
5
|
+
{
|
6
|
+
attack_bonus: [@attacks, @bonuses],
|
7
|
+
attack_enemy: [@attacks, @enemies],
|
8
|
+
player_bonus: [[@player], @bonuses],
|
9
|
+
player_deadline: [[@player], [@deadline]],
|
10
|
+
player_enemy: [[@player], @enemies],
|
11
|
+
}.each do |type, pair|
|
12
|
+
collided_pair = find_collided_pair(*pair)
|
13
|
+
unless collided_pair.empty?
|
14
|
+
EventDispatcher.publish(:collision, { type: type, pair: collided_pair })
|
45
15
|
end
|
46
16
|
end
|
47
17
|
end
|
48
18
|
|
49
|
-
|
50
|
-
def player_meet_deadline
|
51
|
-
find_collision_item(@player, @deadline) && :game_over
|
52
|
-
end
|
53
|
-
|
54
|
-
def player_meet_bonus
|
55
|
-
if (collided_item = find_collision_item(@player, @bonuses))
|
56
|
-
@effects.heart(
|
57
|
-
@player.x + @player.width - 1,
|
58
|
-
@player.y,
|
59
|
-
)
|
60
|
-
@bonuses.delete(collided_item)
|
61
|
-
:bonus
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# @returns [:bonus, false]
|
66
|
-
def attack_hit_bonus
|
67
|
-
collided = !@attacks.dup.select do |attack|
|
68
|
-
if (collided_item = find_collision_item(attack, @bonuses))
|
69
|
-
@effects.heart(
|
70
|
-
@player.x + @player.width - 1,
|
71
|
-
@player.y,
|
72
|
-
)
|
73
|
-
@bonuses.delete(collided_item)
|
74
|
-
@attacks.delete(attack)
|
75
|
-
end
|
76
|
-
end.empty?
|
77
|
-
collided && :bonus
|
78
|
-
end
|
19
|
+
private
|
79
20
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
@player.y,
|
87
|
-
)
|
88
|
-
@enemies.delete(collided_item)
|
89
|
-
@attacks.delete(attack)
|
90
|
-
end
|
91
|
-
end.empty?
|
92
|
-
collided && :bonus
|
21
|
+
def initialize(attacks:, bonuses:, deadline:, enemies:, player:)
|
22
|
+
@attacks = attacks
|
23
|
+
@bonuses = bonuses
|
24
|
+
@deadline = deadline
|
25
|
+
@enemies = enemies
|
26
|
+
@player = player
|
93
27
|
end
|
94
28
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
@effects.note(
|
100
|
-
@player.x + @player.width - 1,
|
101
|
-
@player.y,
|
102
|
-
)
|
103
|
-
@enemies.delete(collided_item)
|
104
|
-
@player.vy = @player.class::JUMP_INITIAL_VELOCITY
|
105
|
-
:bonus
|
106
|
-
else
|
107
|
-
@effects.lightning(
|
108
|
-
@player.x + @player.width - 1,
|
109
|
-
@player.y,
|
110
|
-
)
|
111
|
-
@enemies.delete(collided_item)
|
112
|
-
@player.stun
|
29
|
+
def find_collided_pair(entities, others)
|
30
|
+
entities.map do |entity|
|
31
|
+
found = others.find do |other|
|
32
|
+
collided?(entity.bounding_box, other.bounding_box)
|
113
33
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
def find_collision_item(entity, others)
|
118
|
-
others.find do |other|
|
119
|
-
collided?(entity.bounding_box, other.bounding_box)
|
120
|
-
end
|
34
|
+
break [entity, found] if found
|
35
|
+
end.compact
|
121
36
|
end
|
122
37
|
|
123
38
|
def collided?(box1, box2)
|
@@ -13,7 +13,7 @@ module RoadToRubykaigi
|
|
13
13
|
ANSI.home
|
14
14
|
ANSI.background_color
|
15
15
|
ANSI.default_text_color
|
16
|
-
print @
|
16
|
+
print @game_manager.render_score_board
|
17
17
|
@viewport_height.times do |row|
|
18
18
|
@viewport_width.times do |col|
|
19
19
|
unless buffer[row][col] == @preview_buffer[row][col]
|
@@ -26,14 +26,15 @@ module RoadToRubykaigi
|
|
26
26
|
|
27
27
|
private
|
28
28
|
|
29
|
-
def initialize(
|
29
|
+
def initialize(map:, attacks:, bonuses:, deadline:, effects:, enemies:, player:, game_manager:)
|
30
30
|
@viewport_width = Map::VIEWPORT_WIDTH
|
31
31
|
@viewport_height = Map::VIEWPORT_HEIGHT
|
32
32
|
@preview_buffer = Array.new(@viewport_height) { Array.new(@viewport_width) { "" } }
|
33
|
-
@
|
34
|
-
@
|
35
|
-
|
36
|
-
|
33
|
+
@game_manager = game_manager
|
34
|
+
@layers = [
|
35
|
+
# From bottom to top
|
36
|
+
map, player, deadline, bonuses, enemies, attacks, effects, game_manager.fireworks,
|
37
|
+
]
|
37
38
|
end
|
38
39
|
|
39
40
|
def merge_buffer(buffer, layer, offset_x:)
|
@@ -1,7 +1,10 @@
|
|
1
1
|
module RoadToRubykaigi
|
2
2
|
module Manager
|
3
3
|
class GameManager
|
4
|
+
UPDATE_RATE = 1.0 / 10
|
5
|
+
FRAME_RATE = 1.0 / 60
|
4
6
|
GOAL_X = 650
|
7
|
+
DEMO_GOAL_X = 540
|
5
8
|
STATE = {
|
6
9
|
playing: 0,
|
7
10
|
pause: 1,
|
@@ -11,29 +14,68 @@ module RoadToRubykaigi
|
|
11
14
|
}
|
12
15
|
attr_reader :fireworks
|
13
16
|
|
17
|
+
def self.goal_x
|
18
|
+
@goal_x ||= RoadToRubykaigi.demo? ? DEMO_GOAL_X : GOAL_X
|
19
|
+
end
|
20
|
+
|
21
|
+
def offset_x
|
22
|
+
(@player.x - Map::VIEWPORT_WIDTH / 2).clamp(0, @map.width - Map::VIEWPORT_WIDTH).to_i
|
23
|
+
end
|
24
|
+
|
14
25
|
def update
|
15
26
|
@deadline.activate(player_x: @player.x)
|
16
27
|
@enemies.activate if player_moved?
|
17
|
-
if @player.x >=
|
18
|
-
|
28
|
+
if @player.x >= GameManager.goal_x && playing?
|
29
|
+
EventDispatcher.publish(:ending)
|
19
30
|
end
|
20
31
|
end
|
21
32
|
|
22
|
-
def
|
23
|
-
@
|
33
|
+
def increment_score
|
34
|
+
@score_board.increment
|
35
|
+
end
|
36
|
+
|
37
|
+
def render_score_board
|
38
|
+
@score_board.render_score_board
|
39
|
+
end
|
40
|
+
|
41
|
+
def render_result
|
42
|
+
if game_over?
|
43
|
+
@score_board.render_game_over_result
|
44
|
+
else
|
45
|
+
@score_board.render_clear_result
|
46
|
+
end
|
24
47
|
end
|
25
48
|
|
26
|
-
def
|
27
|
-
@state == STATE[:finished]
|
49
|
+
def result?
|
50
|
+
@state == STATE[:game_over] || @state == STATE[:finished]
|
51
|
+
end
|
52
|
+
|
53
|
+
def game_over
|
54
|
+
@state = STATE[:game_over]
|
55
|
+
end
|
56
|
+
|
57
|
+
def game_over?
|
58
|
+
@state == STATE[:game_over]
|
59
|
+
end
|
60
|
+
|
61
|
+
def ending
|
62
|
+
@state = STATE[:ending]
|
63
|
+
@fireworks.shoot
|
64
|
+
end
|
65
|
+
|
66
|
+
def finish
|
67
|
+
@state = STATE[:finished]
|
28
68
|
end
|
29
69
|
|
30
70
|
private
|
31
71
|
|
32
|
-
def initialize(
|
72
|
+
def initialize(map:, deadline:, enemies:, player:)
|
73
|
+
@map = map
|
33
74
|
@player = player
|
34
75
|
@deadline = deadline
|
35
76
|
@enemies = enemies
|
36
|
-
@
|
77
|
+
@score_board = ScoreBoard.new
|
78
|
+
@fireworks = RoadToRubykaigi::Fireworks.new
|
37
79
|
@state = STATE[:playing]
|
38
80
|
end
|
39
81
|
|
@@ -45,11 +87,6 @@ module RoadToRubykaigi
|
|
45
87
|
def playing?
|
46
88
|
@state == STATE[:playing]
|
47
89
|
end
|
48
|
-
|
49
|
-
def game_clear
|
50
|
-
@state = STATE[:ending]
|
51
|
-
@fireworks.shoot
|
52
|
-
end
|
53
90
|
end
|
54
91
|
end
|
55
92
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RoadToRubykaigi
|
2
|
+
module Manager
|
3
|
+
class PhysicsEngine
|
4
|
+
def simulate
|
5
|
+
@attacks.simulate_physics
|
6
|
+
@deadline.simulate_physics
|
7
|
+
@enemies.simulate_physics
|
8
|
+
@player.simulate_physics
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def initialize(attacks:, deadline:, enemies:, player:)
|
14
|
+
@attacks = attacks
|
15
|
+
@deadline = deadline
|
16
|
+
@enemies = enemies
|
17
|
+
@player = player
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,25 +2,28 @@ module RoadToRubykaigi
|
|
2
2
|
module Manager
|
3
3
|
class UpdateManager
|
4
4
|
def update(offset_x:)
|
5
|
-
enemies
|
6
|
-
enemies.each do |enemy|
|
5
|
+
@enemies.each do |enemy|
|
7
6
|
enemy.activate_with_offset(offset_x)
|
8
7
|
end
|
9
|
-
@
|
10
|
-
|
8
|
+
@effects.update
|
9
|
+
@enemies.update
|
10
|
+
@player.update
|
11
|
+
@fireworks.update
|
12
|
+
@player.enforce_boundary(@map, offset_x: offset_x)
|
13
|
+
@attacks.enforce_boundary(@map, offset_x: offset_x)
|
14
|
+
@player.fall_if_ground_is_passable(@map)
|
15
|
+
@player.land_unless_ground_is_passable(@map)
|
11
16
|
end
|
12
17
|
|
13
18
|
private
|
14
19
|
|
15
|
-
def initialize(map
|
20
|
+
def initialize(map:, attacks:, effects:, enemies:, player:, fireworks:)
|
16
21
|
@map = map
|
17
|
-
@
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
@
|
22
|
-
entity.respond_to?(:enforce_boundary) && entity.enforce_boundary(@map, offset_x:)
|
23
|
-
end
|
22
|
+
@attacks = attacks
|
23
|
+
@effects = effects
|
24
|
+
@enemies = enemies
|
25
|
+
@player = player
|
26
|
+
@fireworks = fireworks
|
24
27
|
end
|
25
28
|
end
|
26
29
|
end
|
@@ -12,7 +12,7 @@ module RoadToRubykaigi
|
|
12
12
|
|
13
13
|
def clamp_position(x:, y:, width:, height:, dx:, dy:)
|
14
14
|
clamped_x = x.clamp(2, @width - width)
|
15
|
-
clamped_y = y.clamp(2, @height - height)
|
15
|
+
clamped_y = y.clamp(2, @height - (Sprite::Player::BASE_HEIGHT - height))
|
16
16
|
return [clamped_x, clamped_y] if box_passable?(clamped_x, clamped_y, width, height)
|
17
17
|
|
18
18
|
attempt_count = 10
|
@@ -85,20 +85,6 @@ module RoadToRubykaigi
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
|
88
|
-
class Layer
|
89
|
-
attr_reader :layers
|
90
|
-
|
91
|
-
def build_buffer(offset_x:)
|
92
|
-
@layers.map { |layer| layer.build_buffer(offset_x: offset_x) }
|
93
|
-
end
|
94
|
-
|
95
|
-
private
|
96
|
-
|
97
|
-
def initialize(player:, deadline:, bonuses:, enemies:, attacks:, effects:)
|
98
|
-
@layers = [player, deadline, bonuses, enemies, attacks, effects]
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
88
|
class Tile
|
103
89
|
MASK_CHAR = "#"
|
104
90
|
|
@@ -4,14 +4,31 @@ module RoadToRubykaigi
|
|
4
4
|
@score += 1
|
5
5
|
end
|
6
6
|
|
7
|
-
def
|
7
|
+
def render_score_board
|
8
8
|
"Score: #{@score}".ljust(10).rjust(Map::VIEWPORT_WIDTH)
|
9
9
|
end
|
10
10
|
|
11
|
+
def render_clear_result
|
12
|
+
[ANSI::BLUE + "CLEAR!" + ANSI::DEFAULT_TEXT_COLOR, "Score: #{@score}", "Time: #{result_time} seconds"].map.with_index do |message, i|
|
13
|
+
ANSI::RESULT_DATA[i] + " #{message} "
|
14
|
+
end.join
|
15
|
+
end
|
16
|
+
|
17
|
+
def render_game_over_result
|
18
|
+
[ANSI::RED + "Game Over" + ANSI::DEFAULT_TEXT_COLOR, "Score: #{@score}", "Time: #{result_time} seconds"].map.with_index do |message, i|
|
19
|
+
ANSI::RESULT_DATA[i] + " #{message} "
|
20
|
+
end.join
|
21
|
+
end
|
22
|
+
|
11
23
|
private
|
12
24
|
|
13
25
|
def initialize
|
14
26
|
@score = 0
|
27
|
+
@start_time = Time.now
|
28
|
+
end
|
29
|
+
|
30
|
+
def result_time
|
31
|
+
(Time.now - @start_time).round(2)
|
15
32
|
end
|
16
33
|
end
|
17
34
|
end
|
@@ -4,13 +4,18 @@ module RoadToRubykaigi
|
|
4
4
|
module Sprite
|
5
5
|
class Attacks
|
6
6
|
extend Forwardable
|
7
|
-
def_delegators :@attacks, :each, :delete, :select
|
7
|
+
def_delegators :@attacks, :each, :map, :delete, :select
|
8
|
+
ATTACK_COUNT = 13
|
8
9
|
|
9
|
-
def
|
10
|
-
@attacks
|
10
|
+
def remain_attack?
|
11
|
+
@attacks.size < ATTACK_COUNT
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
+
def add(player)
|
15
|
+
@attacks << Attack.new(*player.attack_position, player.current_direction)
|
16
|
+
end
|
17
|
+
|
18
|
+
def simulate_physics
|
14
19
|
@attacks.each(&:move)
|
15
20
|
end
|
16
21
|
|
@@ -43,9 +48,10 @@ module RoadToRubykaigi
|
|
43
48
|
|
44
49
|
class Attack < Sprite
|
45
50
|
SYMBOL = ".˖"
|
51
|
+
SPEED = 3
|
46
52
|
|
47
53
|
def move
|
48
|
-
@x +=
|
54
|
+
@x += @direction * SPEED
|
49
55
|
end
|
50
56
|
|
51
57
|
def characters
|
@@ -63,9 +69,10 @@ module RoadToRubykaigi
|
|
63
69
|
|
64
70
|
private
|
65
71
|
|
66
|
-
def initialize(x, y)
|
72
|
+
def initialize(x, y, direction)
|
67
73
|
@x = x
|
68
74
|
@y = y
|
75
|
+
@direction = direction
|
69
76
|
end
|
70
77
|
end
|
71
78
|
end
|
@@ -98,6 +98,22 @@ module RoadToRubykaigi
|
|
98
98
|
sake: "🍶",
|
99
99
|
laptop: "💻",
|
100
100
|
}
|
101
|
+
TYPE = {
|
102
|
+
ruby: :basic,
|
103
|
+
money: :basic,
|
104
|
+
coffee: :basic,
|
105
|
+
book: :basic,
|
106
|
+
sushi: :basic,
|
107
|
+
meat: :basic,
|
108
|
+
fish: :basic,
|
109
|
+
beer: :alcohol,
|
110
|
+
sake: :alcohol,
|
111
|
+
laptop: :laptop,
|
112
|
+
}
|
113
|
+
|
114
|
+
def type
|
115
|
+
TYPE[@character]
|
116
|
+
end
|
101
117
|
|
102
118
|
def bounding_box
|
103
119
|
{ x: @x, y: @y, width: width, height: height }
|
@@ -10,7 +10,7 @@ module RoadToRubykaigi
|
|
10
10
|
yield self || nil
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def simulate_physics
|
14
14
|
return unless active?
|
15
15
|
now = Time.now
|
16
16
|
if (now - @last_update) > DEADLINE_SPEED
|
@@ -40,11 +40,11 @@ module RoadToRubykaigi
|
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
|
-
def initialize
|
43
|
+
def initialize
|
44
44
|
@x = 2
|
45
45
|
@y = 1
|
46
46
|
@width = 1
|
47
|
-
@height =
|
47
|
+
@height = Map::VIEWPORT_HEIGHT
|
48
48
|
@last_update = Time.now
|
49
49
|
@waiting = true
|
50
50
|
end
|
@@ -43,10 +43,14 @@ module RoadToRubykaigi
|
|
43
43
|
buffer
|
44
44
|
end
|
45
45
|
|
46
|
-
def
|
46
|
+
def simulate_physics
|
47
47
|
if activated?
|
48
|
-
@enemies.each(&:
|
49
|
-
|
48
|
+
@enemies.each(&:move)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def update
|
53
|
+
unless activated?
|
50
54
|
@enemies.each(&:reset_last_update_time)
|
51
55
|
end
|
52
56
|
end
|
@@ -101,10 +105,10 @@ module RoadToRubykaigi
|
|
101
105
|
super { [CHARACTER[@character]] }
|
102
106
|
end
|
103
107
|
|
104
|
-
def
|
108
|
+
def move
|
105
109
|
elapsed_time = Time.now - @last_update_time
|
106
110
|
@last_update_time = Time.now
|
107
|
-
@strategy.
|
111
|
+
@strategy.move(self, elapsed_time)
|
108
112
|
end
|
109
113
|
|
110
114
|
def reset_last_update_time
|
@@ -153,7 +157,7 @@ module RoadToRubykaigi
|
|
153
157
|
@speed = speed
|
154
158
|
end
|
155
159
|
|
156
|
-
def
|
160
|
+
def move(enemy, elapsed_time)
|
157
161
|
end
|
158
162
|
end
|
159
163
|
|
@@ -161,7 +165,7 @@ module RoadToRubykaigi
|
|
161
165
|
end
|
162
166
|
|
163
167
|
class HorizontalPatrolStrategy < PatrolStrategy
|
164
|
-
def
|
168
|
+
def move(enemy, elapsed_time)
|
165
169
|
enemy.x += @speed * elapsed_time * enemy.direction
|
166
170
|
enemy.x = enemy.x.clamp(@left_bound, @right_bound)
|
167
171
|
enemy.reverse_direction if enemy.x == @left_bound || enemy.x == @right_bound
|
@@ -169,7 +173,7 @@ module RoadToRubykaigi
|
|
169
173
|
end
|
170
174
|
|
171
175
|
class ScreenEntryPatrolStrategy < PatrolStrategy
|
172
|
-
def
|
176
|
+
def move(enemy, elapsed_time)
|
173
177
|
return unless enemy.active?
|
174
178
|
enemy.x += @speed * elapsed_time * enemy.direction
|
175
179
|
end
|