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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +11 -0
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +15 -1
  5. data/Rakefile +1 -0
  6. data/lib/road_to_rubykaigi/audio/audio_engine.rb +70 -0
  7. data/lib/road_to_rubykaigi/audio/oscillator.rb +159 -0
  8. data/lib/road_to_rubykaigi/audio/sequencer.rb +263 -0
  9. data/lib/road_to_rubykaigi/audio/wav/attack_01.wav +0 -0
  10. data/lib/road_to_rubykaigi/audio/wav/attack_02.wav +0 -0
  11. data/lib/road_to_rubykaigi/audio/wav/attack_03.wav +0 -0
  12. data/lib/road_to_rubykaigi/audio/wav/attack_04.wav +0 -0
  13. data/lib/road_to_rubykaigi/audio/wav/attack_05.wav +0 -0
  14. data/lib/road_to_rubykaigi/audio/wav/bonus.wav +0 -0
  15. data/lib/road_to_rubykaigi/audio/wav/crouch.wav +0 -0
  16. data/lib/road_to_rubykaigi/audio/wav/defeat.wav +0 -0
  17. data/lib/road_to_rubykaigi/audio/wav/game_over.wav +0 -0
  18. data/lib/road_to_rubykaigi/audio/wav/jump.wav +0 -0
  19. data/lib/road_to_rubykaigi/audio/wav/laptop.wav +0 -0
  20. data/lib/road_to_rubykaigi/audio/wav/stun.wav +0 -0
  21. data/lib/road_to_rubykaigi/audio/wav/walk_01.wav +0 -0
  22. data/lib/road_to_rubykaigi/audio/wav/walk_02.wav +0 -0
  23. data/lib/road_to_rubykaigi/audio/wav_source.rb +55 -0
  24. data/lib/road_to_rubykaigi/event_dispatcher.rb +122 -0
  25. data/lib/road_to_rubykaigi/fireworks.rb +4 -4
  26. data/lib/road_to_rubykaigi/game.rb +49 -60
  27. data/lib/road_to_rubykaigi/graphics/demo-map.txt +30 -0
  28. data/lib/road_to_rubykaigi/graphics/demo-mask.txt +30 -0
  29. data/lib/road_to_rubykaigi/graphics/map.rb +6 -1
  30. data/lib/road_to_rubykaigi/graphics/mask.rb +7 -1
  31. data/lib/road_to_rubykaigi/graphics/player.rb +56 -65
  32. data/lib/road_to_rubykaigi/graphics/player.txt +26 -0
  33. data/lib/road_to_rubykaigi/manager/audio_manager.rb +81 -0
  34. data/lib/road_to_rubykaigi/manager/collision_manager.rb +23 -108
  35. data/lib/road_to_rubykaigi/manager/drawing_manager.rb +7 -6
  36. data/lib/road_to_rubykaigi/manager/game_manager.rb +50 -13
  37. data/lib/road_to_rubykaigi/manager/physics_engine.rb +21 -0
  38. data/lib/road_to_rubykaigi/manager/update_manager.rb +15 -12
  39. data/lib/road_to_rubykaigi/map.rb +1 -15
  40. data/lib/road_to_rubykaigi/score_board.rb +18 -1
  41. data/lib/road_to_rubykaigi/sprite/attack.rb +13 -6
  42. data/lib/road_to_rubykaigi/sprite/bonus.rb +16 -0
  43. data/lib/road_to_rubykaigi/sprite/deadline.rb +3 -3
  44. data/lib/road_to_rubykaigi/sprite/enemy.rb +12 -8
  45. data/lib/road_to_rubykaigi/sprite/player.rb +110 -29
  46. data/lib/road_to_rubykaigi/version.rb +1 -1
  47. data/lib/road_to_rubykaigi.rb +20 -4
  48. metadata +55 -3
@@ -8,13 +8,17 @@ module RoadToRubykaigi
8
8
  WALK_MAX_SPEED = 20.0
9
9
  WALK_FRICTION = 1.0
10
10
 
11
+ INITIAL_X = 10
11
12
  BASE_Y = 26
13
+ BASE_HEIGHT = 3
12
14
  JUMP_INITIAL_VELOCITY = -40.0
13
15
  JUMP_GRAVITY = 80.0
14
16
 
17
+ ATTACK_COOLDOWN_SECOND = 0.1
15
18
  KEY_INPUT_THRESHOLD = 0.5
16
- ANIMETION_FRAME_SECOND = 0.5
19
+ ANIMETION_FRAME_SECOND = 0.25
17
20
  STUN_SECOND = 2.0
21
+ CROUCH_SECOND = 1.0
18
22
 
19
23
  RIGHT = 1
20
24
  LEFT = -1
@@ -28,25 +32,40 @@ module RoadToRubykaigi
28
32
  end
29
33
 
30
34
  def jump
31
- unless jumping?
35
+ unless jumping? || crouching?
32
36
  @jumping = true
33
37
  @vy = JUMP_INITIAL_VELOCITY
38
+ Manager::AudioManager.instance.jump
34
39
  end
35
40
  end
36
41
 
37
- def land(land_y)
38
- @y = land_y - height
39
- @vy = 0
40
- @jumping = false
41
- # Not change stompable since stompable will not change from true
42
+ def crouch
43
+ unless jumping? || crouching?
44
+ @crouching_until = Time.now + CROUCH_SECOND
45
+ end
42
46
  end
43
47
 
44
- def fall
45
- unless @jumping
46
- @jumping = true
47
- @vy = 0
48
- @stompable = true
49
- end
48
+ def crouching?
49
+ @crouching_until && Time.now < @crouching_until
50
+ end
51
+
52
+ def can_attack!
53
+ @attack_mode = true
54
+ end
55
+
56
+ def can_attack?(attacks)
57
+ @attack_mode && attacks.remain_attack? && (Time.now - @last_attack_time >= ATTACK_COOLDOWN_SECOND)
58
+ end
59
+
60
+ def attack(attacks)
61
+ attacks.add(self)
62
+ @last_attack_time = Time.now
63
+ end
64
+
65
+ def attack_position
66
+ x_position = current_direction == RIGHT ? x + width : x - 2
67
+ y_position = crouching? ? y + 2 : y + 1
68
+ [x_position, y_position]
50
69
  end
51
70
 
52
71
  def stun
@@ -62,27 +81,55 @@ module RoadToRubykaigi
62
81
  end
63
82
 
64
83
  def update
65
- return @coordinate_updated_time = Time.now if stunned?
84
+ return if stunned?
66
85
 
67
86
  now = Time.now
87
+ standup
68
88
  if (now - @animetion_updated_time) >= ANIMETION_FRAME_SECOND
69
89
  @walking_frame = (@walking_frame + 1) % current_character.size
70
90
  @animetion_updated_time = now
71
91
  end
92
+ if jumping?
93
+ @stompable = @vy.positive? || (@y == BASE_Y && @vy.zero?) # stompable also when the moment just land
94
+ if @vy.zero?
95
+ @jumping = false
96
+ end
97
+ else
98
+ @stompable = false
99
+ end
100
+ end
101
+
102
+ def fall_if_ground_is_passable(map)
103
+ foot_y = @y + BASE_HEIGHT
104
+ center_x = @x + width / 2.0
105
+ if map.passable_at?(center_x, foot_y + 1)
106
+ fall
107
+ end
108
+ end
109
+
110
+ def land_unless_ground_is_passable(map)
111
+ foot_y = @y + BASE_HEIGHT
112
+ (x...(x + width)).each do |col|
113
+ unless map.passable_at?(col, foot_y)
114
+ break land(foot_y)
115
+ end
116
+ end
117
+ end
72
118
 
119
+ def simulate_physics
120
+ return @coordinate_updated_time = Time.now if stunned?
121
+
122
+ now = Time.now
73
123
  elapsed_time = now - @coordinate_updated_time
74
124
  @coordinate_updated_time = now
75
125
  if jumping?
76
126
  @vy += JUMP_GRAVITY * elapsed_time
77
127
  @y += @vy * elapsed_time
78
- @stompable = @vy.positive?
79
128
  if @y >= BASE_Y
80
129
  @y = BASE_Y
81
130
  @vy = 0
82
- @jumping = false
83
131
  end
84
132
  else
85
- @stompable = false
86
133
  if current_direction == RIGHT
87
134
  @vx -= friction * elapsed_time
88
135
  @vx = [@vx, 0].max # vx must be positive
@@ -92,7 +139,6 @@ module RoadToRubykaigi
92
139
  end
93
140
  end
94
141
  @x += @vx * elapsed_time
95
-
96
142
  @x = @x.round.to_i
97
143
  @y = @y.round.to_i
98
144
  end
@@ -100,7 +146,7 @@ module RoadToRubykaigi
100
146
  def build_buffer(offset_x:)
101
147
  buffer = Array.new(Map::VIEWPORT_HEIGHT) { Array.new(Map::VIEWPORT_WIDTH) { "" } }
102
148
  relative_x = @x - offset_x - 1
103
- relative_y = @y - 1
149
+ relative_y = @y + (BASE_HEIGHT - height) - 1
104
150
  current_character[@walking_frame].each_with_index do |row, i|
105
151
  row.each_with_index do |character, j|
106
152
  buffer[relative_y+i][relative_x+j] = character
@@ -122,27 +168,36 @@ module RoadToRubykaigi
122
168
  end
123
169
 
124
170
  def width
125
- @width ||= current_character.first.map(&:size).max
171
+ @width[@attack_mode] ||= current_character.first.map(&:size).max
126
172
  end
127
173
 
128
174
  def height
129
- @height ||= current_character.first.size
175
+ @height[crouching?] ||= current_character.first.size
176
+ end
177
+
178
+ def current_direction
179
+ (@vx >= 0) ? RIGHT : LEFT
130
180
  end
131
181
 
132
182
  private
133
183
 
134
- def initialize(x = 10, y = BASE_Y)
135
- @x = x
136
- @y = y
184
+ def initialize
185
+ @x = INITIAL_X
186
+ @y = BASE_Y
137
187
  @vx = 0.0
138
188
  @vy = 0.0
189
+ @width ||= {}
190
+ @height ||= {}
139
191
  @walking_frame = 0
140
192
  @coordinate_updated_time = Time.now
141
193
  @animetion_updated_time = Time.now
142
194
  @key_input_time = Time.now
143
195
  @jumping = false
196
+ @crouching_until = nil
144
197
  @stompable = false
145
198
  @stunned_until = Time.now
199
+ @attack_mode = false
200
+ @last_attack_time = Time.now
146
201
  end
147
202
 
148
203
  def move(dx)
@@ -151,11 +206,41 @@ module RoadToRubykaigi
151
206
  end
152
207
  @vx += WALK_ACCEL * dx
153
208
  @vx = @vx.clamp(-WALK_MAX_SPEED, WALK_MAX_SPEED)
209
+ Manager::AudioManager.instance.walk
210
+ end
211
+
212
+ def fall
213
+ unless @jumping
214
+ @jumping = true
215
+ @vy = 0
216
+ @stompable = true
217
+ end
218
+ end
219
+
220
+ def land(land_y)
221
+ @y = land_y - BASE_HEIGHT
222
+ @vy = 0
223
+ @jumping = false
224
+ # Not change stompable since stompable will not change from true
225
+ end
226
+
227
+ def standup
228
+ if @crouching_until && Time.now > @crouching_until
229
+ @crouching_until = nil
230
+ # Standup with jumping
231
+ @y -= 1
232
+ @jumping = true
233
+ @vy = -0.1 # Avoid 0 to keep @jumping true for 1 frame
234
+ Manager::AudioManager.instance.jump
235
+ end
154
236
  end
155
237
 
156
238
  def current_character
239
+ posture = crouching? ? :crouching : :standup
157
240
  status = stunned? ? :stunned : :normal
158
- Graphics::Player.character(status, current_direction)
241
+ Graphics::Player.character(
242
+ posture: posture, status: status, direction: current_direction, attack_mode: @attack_mode
243
+ )
159
244
  end
160
245
 
161
246
  def jumping?
@@ -169,10 +254,6 @@ module RoadToRubykaigi
169
254
  WALK_FRICTION
170
255
  end
171
256
  end
172
-
173
- def current_direction
174
- (@vx >= 0) ? RIGHT : LEFT
175
- end
176
257
  end
177
258
  end
178
259
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RoadToRubykaigi
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -1,13 +1,20 @@
1
1
  require_relative "road_to_rubykaigi/version"
2
2
  require_relative "road_to_rubykaigi/ansi"
3
+ require_relative "road_to_rubykaigi/audio/audio_engine"
4
+ require_relative "road_to_rubykaigi/audio/oscillator"
5
+ require_relative "road_to_rubykaigi/audio/sequencer"
6
+ require_relative "road_to_rubykaigi/audio/wav_source"
7
+ require_relative "road_to_rubykaigi/event_dispatcher"
3
8
  require_relative "road_to_rubykaigi/fireworks"
4
9
  require_relative "road_to_rubykaigi/game"
5
10
  require_relative "road_to_rubykaigi/map"
6
11
  require_relative "road_to_rubykaigi/opening_screen"
7
12
  require_relative "road_to_rubykaigi/score_board"
13
+ require_relative "road_to_rubykaigi/manager/audio_manager"
8
14
  require_relative "road_to_rubykaigi/manager/collision_manager"
9
15
  require_relative "road_to_rubykaigi/manager/drawing_manager"
10
16
  require_relative "road_to_rubykaigi/manager/game_manager"
17
+ require_relative "road_to_rubykaigi/manager/physics_engine"
11
18
  require_relative "road_to_rubykaigi/manager/update_manager"
12
19
  require_relative "road_to_rubykaigi/sprite/sprite"
13
20
  require_relative "road_to_rubykaigi/sprite/attack"
@@ -24,16 +31,25 @@ require "io/console"
24
31
 
25
32
  module RoadToRubykaigi
26
33
  class Error < StandardError; end
27
- END_POSITION = Map.new.height + 2
34
+ END_POSITION = Map::VIEWPORT_HEIGHT + 2
28
35
 
29
- def self.start
36
+ def self.start(game_mode = :normal)
30
37
  ANSI.cursor_off
31
38
  at_exit do
32
39
  print "\e[#{END_POSITION};1H"
33
40
  ANSI.cursor_on
34
41
  end
35
42
 
36
- OpeningScreen.new.display && Game.new.run
43
+ @game_mode = game_mode
44
+ if demo?
45
+ Game.new.run
46
+ else
47
+ OpeningScreen.new.display && Game.new.run
48
+ end
49
+ end
50
+
51
+ def self.demo?
52
+ @game_mode != :normal
37
53
  end
38
54
 
39
55
  def self.debug
@@ -41,6 +57,6 @@ module RoadToRubykaigi
41
57
  end
42
58
 
43
59
  def self.debug_add(string)
44
- @debug << "\e[#{END_POSITION+@debug.size};1H" + string
60
+ debug << "\e[#{END_POSITION+debug.size};1H" + string
45
61
  end
46
62
  end
metadata CHANGED
@@ -1,14 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: road_to_rubykaigi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - makicamel
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-27 00:00:00.000000000 Z
11
- dependencies: []
10
+ date: 2025-04-15 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ffi-portaudio
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: wavefile
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
12
40
  description: Road to RubyKaigi is a Ruby gem that delivers a nostalgic, text-based
13
41
  action game experience. Dodge obstacles, overcome bugs, and beat the deadline to
14
42
  reach RubyKaigi. All in your terminal.
@@ -29,8 +57,29 @@ files:
29
57
  - bin/road_to_rubykaigi
30
58
  - lib/road_to_rubykaigi.rb
31
59
  - lib/road_to_rubykaigi/ansi.rb
60
+ - lib/road_to_rubykaigi/audio/audio_engine.rb
61
+ - lib/road_to_rubykaigi/audio/oscillator.rb
62
+ - lib/road_to_rubykaigi/audio/sequencer.rb
63
+ - lib/road_to_rubykaigi/audio/wav/attack_01.wav
64
+ - lib/road_to_rubykaigi/audio/wav/attack_02.wav
65
+ - lib/road_to_rubykaigi/audio/wav/attack_03.wav
66
+ - lib/road_to_rubykaigi/audio/wav/attack_04.wav
67
+ - lib/road_to_rubykaigi/audio/wav/attack_05.wav
68
+ - lib/road_to_rubykaigi/audio/wav/bonus.wav
69
+ - lib/road_to_rubykaigi/audio/wav/crouch.wav
70
+ - lib/road_to_rubykaigi/audio/wav/defeat.wav
71
+ - lib/road_to_rubykaigi/audio/wav/game_over.wav
72
+ - lib/road_to_rubykaigi/audio/wav/jump.wav
73
+ - lib/road_to_rubykaigi/audio/wav/laptop.wav
74
+ - lib/road_to_rubykaigi/audio/wav/stun.wav
75
+ - lib/road_to_rubykaigi/audio/wav/walk_01.wav
76
+ - lib/road_to_rubykaigi/audio/wav/walk_02.wav
77
+ - lib/road_to_rubykaigi/audio/wav_source.rb
78
+ - lib/road_to_rubykaigi/event_dispatcher.rb
32
79
  - lib/road_to_rubykaigi/fireworks.rb
33
80
  - lib/road_to_rubykaigi/game.rb
81
+ - lib/road_to_rubykaigi/graphics/demo-map.txt
82
+ - lib/road_to_rubykaigi/graphics/demo-mask.txt
34
83
  - lib/road_to_rubykaigi/graphics/fireworks.rb
35
84
  - lib/road_to_rubykaigi/graphics/fireworks.txt
36
85
  - lib/road_to_rubykaigi/graphics/map.rb
@@ -38,9 +87,12 @@ files:
38
87
  - lib/road_to_rubykaigi/graphics/mask.rb
39
88
  - lib/road_to_rubykaigi/graphics/mask.txt
40
89
  - lib/road_to_rubykaigi/graphics/player.rb
90
+ - lib/road_to_rubykaigi/graphics/player.txt
91
+ - lib/road_to_rubykaigi/manager/audio_manager.rb
41
92
  - lib/road_to_rubykaigi/manager/collision_manager.rb
42
93
  - lib/road_to_rubykaigi/manager/drawing_manager.rb
43
94
  - lib/road_to_rubykaigi/manager/game_manager.rb
95
+ - lib/road_to_rubykaigi/manager/physics_engine.rb
44
96
  - lib/road_to_rubykaigi/manager/update_manager.rb
45
97
  - lib/road_to_rubykaigi/map.rb
46
98
  - lib/road_to_rubykaigi/opening_screen.rb