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
@@ -0,0 +1,122 @@
1
+ module RoadToRubykaigi
2
+ module EventDispatcher
3
+ class << self
4
+ def subscribe(event, &block)
5
+ subscribers[event] << block
6
+ end
7
+
8
+ def publish(event, *args)
9
+ subscribers[event].each { |block| block.call(*args) }
10
+ end
11
+
12
+ private
13
+
14
+ def subscribers
15
+ @subscribers ||= Hash.new { |hash, key| hash[key] = [] }
16
+ end
17
+ end
18
+ end
19
+
20
+ class EventHander
21
+ def self.subscribe(attacks:, bonuses:, effects:, enemies:, player:, game_manager:)
22
+ new(attacks: attacks, bonuses: bonuses, effects: effects, enemies: enemies, player: player, game_manager: game_manager).subscribe
23
+ end
24
+
25
+ def subscribe
26
+ EventDispatcher.subscribe(:input) { |action| handle_input(action) }
27
+ EventDispatcher.subscribe(:collision) { |collision| handle_collision(collision) }
28
+ EventDispatcher.subscribe(:ending) do
29
+ Manager::AudioManager.instance.fanfare
30
+ @game_manager.ending
31
+ end
32
+ EventDispatcher.subscribe(:finish) do
33
+ until Manager::AudioManager.instance.fanfare_finished?
34
+ sleep 0.5
35
+ end
36
+ @game_manager.finish
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def initialize(attacks:, bonuses:, effects:, enemies:, player:, game_manager:)
43
+ @attacks = attacks
44
+ @bonuses = bonuses
45
+ @effects = effects
46
+ @enemies = enemies
47
+ @player = player
48
+ @game_manager = game_manager
49
+ end
50
+
51
+ def handle_input(action)
52
+ return if @player.stunned?
53
+
54
+ case action
55
+ when :jump; @player.jump
56
+ when :crouch
57
+ @player.crouch
58
+ Manager::AudioManager.instance.crouch
59
+ when :right; @player.right
60
+ when :left; @player.left
61
+ when :attack
62
+ if @player.can_attack?(@attacks)
63
+ @player.attack(@attacks)
64
+ Manager::AudioManager.instance.attack
65
+ end
66
+ end
67
+ end
68
+
69
+ def handle_collision(collision)
70
+ __send__(collision[:type], *collision[:pair])
71
+ end
72
+
73
+ def attack_bonus(attack, bonus)
74
+ @attacks.delete(attack)
75
+ @bonuses.delete(bonus)
76
+ @effects.heart(@player.x + @player.width - 1, @player.y)
77
+ @game_manager.increment_score
78
+ Manager::AudioManager.instance.bonus
79
+ end
80
+
81
+ def attack_enemy(attack, enemy)
82
+ @attacks.delete(attack)
83
+ @effects.note(@player.x + @player.width - 1, @player.y)
84
+ @enemies.delete(enemy)
85
+ @game_manager.increment_score
86
+ Manager::AudioManager.instance.defeat
87
+ end
88
+
89
+ def player_bonus(_, bonus)
90
+ @bonuses.delete(bonus)
91
+ @effects.heart(@player.x + @player.width - 1, @player.y)
92
+ @game_manager.increment_score
93
+ if bonus.type == :laptop
94
+ @player.can_attack!
95
+ Manager::AudioManager.instance.laptop
96
+ else
97
+ Manager::AudioManager.instance.bonus
98
+ end
99
+ end
100
+
101
+ def player_deadline(*args)
102
+ @player.stun
103
+ @game_manager.game_over
104
+ Manager::AudioManager.instance.game_over
105
+ end
106
+
107
+ def player_enemy(_, enemy)
108
+ if @player.stompable?
109
+ @effects.note(@player.x + @player.width - 1, @player.y)
110
+ @enemies.delete(enemy)
111
+ @player.jump
112
+ @game_manager.increment_score
113
+ Manager::AudioManager.instance.defeat
114
+ else
115
+ @effects.lightning(@player.x + @player.width - 1, @player.y)
116
+ @enemies.delete(enemy)
117
+ @player.stun
118
+ Manager::AudioManager.instance.stun
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,6 +1,7 @@
1
1
  module RoadToRubykaigi
2
2
  class Fireworks
3
3
  START_X = 632
4
+ DEMO_START_X = 524
4
5
  DURATION_SECOND = 0.1
5
6
 
6
7
  def shoot
@@ -10,7 +11,7 @@ module RoadToRubykaigi
10
11
  def update
11
12
  return unless shooting?
12
13
  if finished?
13
- return @game_manager.finish
14
+ return EventDispatcher.publish(:finish)
14
15
  end
15
16
 
16
17
  if Time.now - @last_frame_time >= DURATION_SECOND
@@ -37,10 +38,9 @@ module RoadToRubykaigi
37
38
 
38
39
  private
39
40
 
40
- def initialize(game_manager)
41
- @x = START_X
41
+ def initialize
42
+ @x = RoadToRubykaigi.demo? ? DEMO_START_X : START_X
42
43
  @y = 3
43
- @game_manager = game_manager
44
44
  @start_time = Time.now
45
45
  @frame_index = 0
46
46
  @last_frame_time = Time.now
@@ -1,34 +1,34 @@
1
1
  module RoadToRubykaigi
2
2
  class Game
3
3
  def run
4
- @start_time = Time.now
5
4
  ANSI.clear
5
+ last_time = Time.now
6
+ accumulator = 0.0
6
7
  $stdin.raw do
7
8
  loop do
8
9
  RoadToRubykaigi.debug.clear
9
10
  process_input($stdin.read_nonblock(4, exception: false))
10
11
 
11
- if @game_manager.finished?
12
- result_time = (Time.now - @start_time).round(2)
13
- print(["CLEAR!", @score_board.render.strip, "Time: #{result_time} seconds"].map.with_index do |message, i|
14
- ANSI::RESULT_DATA[i] + message
15
- end.join)
12
+ if @game_manager.result?
13
+ print(@game_manager.render_result)
16
14
  exit
17
15
  else
18
- @game_manager.update
19
- @update_manager.update(offset_x: @scroll_offset_x)
20
- @scroll_offset_x = (@player.x - Map::VIEWPORT_WIDTH / 2).clamp(0, @background.width - Map::VIEWPORT_WIDTH).to_i
21
- case @collision_manager.process
22
- when :game_over
23
- game_over
24
- when :bonus
25
- @score_board.increment
16
+ current_time = Time.now
17
+ accumulator += current_time - last_time
18
+ last_time = current_time
19
+ while accumulator >= Manager::GameManager::UPDATE_RATE
20
+ @game_manager.update
21
+ @physics_engine.simulate
22
+ @update_manager.update(offset_x: @game_manager.offset_x)
23
+ @collision_manager.process
24
+ accumulator -= Manager::GameManager::UPDATE_RATE
26
25
  end
27
- @drawing_manager.draw(offset_x: @scroll_offset_x)
26
+
27
+ @drawing_manager.draw(offset_x: @game_manager.offset_x) unless @game_manager.game_over?
28
28
  end
29
29
 
30
30
  puts RoadToRubykaigi.debug
31
- sleep 1.0/10
31
+ sleep Manager::GameManager::FRAME_RATE
32
32
  end
33
33
  end
34
34
  end
@@ -36,62 +36,51 @@ module RoadToRubykaigi
36
36
  private
37
37
 
38
38
  def initialize
39
- @background = Map.new
40
- @score_board = ScoreBoard.new
41
- @player = Sprite::Player.new
39
+ map = Map.new
40
+ player = Sprite::Player.new
42
41
  bonuses = Sprite::Bonuses.new
43
42
  enemies = Sprite::Enemies.new
44
- @attacks = Sprite::Attacks.new
43
+ attacks = Sprite::Attacks.new
45
44
  effects = Sprite::Effects.new
46
- deadline = Sprite::Deadline.new(@background.height)
45
+ deadline = Sprite::Deadline.new
47
46
 
48
- @foreground = Layer.new(
49
- player: @player,
50
- deadline: deadline,
51
- bonuses: bonuses,
52
- enemies: enemies,
53
- attacks: @attacks,
54
- effects: effects,
47
+ @game_manager = Manager::GameManager.new(
48
+ map: map, deadline: deadline, enemies: enemies, player: player,
49
+ )
50
+ @physics_engine = Manager::PhysicsEngine.new(
51
+ attacks: attacks, deadline: deadline, enemies: enemies, player: player,
52
+ )
53
+ @update_manager = Manager::UpdateManager.new(
54
+ map: map, attacks: attacks, effects: effects, enemies: enemies, player: player, fireworks: @game_manager.fireworks,
55
+ )
56
+ @collision_manager = Manager::CollisionManager.new(
57
+ attacks: attacks, bonuses: bonuses, deadline: deadline, enemies: enemies, player: player,
58
+ )
59
+ @drawing_manager = Manager::DrawingManager.new(
60
+ map: map, player: player, deadline: deadline, bonuses: bonuses, enemies: enemies, attacks: attacks, effects: effects, game_manager: @game_manager,
55
61
  )
56
- @game_manager = Manager::GameManager.new(@player, deadline, enemies)
57
- @update_manager = Manager::UpdateManager.new(@background, @foreground, @game_manager.fireworks)
58
- @collision_manager = Manager::CollisionManager.new(@background, @foreground)
59
- @drawing_manager = Manager::DrawingManager.new(@score_board, @background, @foreground, @game_manager.fireworks)
60
- @scroll_offset_x = 0
62
+ EventHander.subscribe(
63
+ attacks: attacks, bonuses: bonuses, effects: effects, enemies: enemies, player: player, game_manager: @game_manager,
64
+ )
65
+ Manager::AudioManager.instance
61
66
  end
62
67
 
63
68
  def process_input(input)
64
- return if @player.stunned?
65
-
66
- up = "\e[A"
67
- right = "\e[C"
68
- left = "\e[D"
69
- attack = " "
69
+ actions = {
70
+ "\e[A" => :jump,
71
+ "\e[B" => :crouch,
72
+ "\e[C" => :right,
73
+ "\e[D" => :left,
74
+ " " => :attack,
75
+ }
70
76
  stop = %W[q \x03]
71
77
 
72
- case input
73
- when up
74
- @player.jump
75
- when right
76
- @player.right
77
- when left
78
- @player.left
79
- when attack
80
- # @attacks.add(
81
- # @player.x + @player.width,
82
- # @player.y + 1,
83
- # )
84
- when *stop
78
+ case
79
+ when actions[input]
80
+ EventDispatcher.publish(:input, actions[input])
81
+ when stop.include?(input)
85
82
  exit
86
83
  end
87
84
  end
88
-
89
- def game_over
90
- result_time = (Time.now - @start_time).round(2)
91
- print([ANSI::RED + "Game Over", ANSI::DEFAULT_TEXT_COLOR + @score_board.render.strip, "Time: #{result_time} seconds"].map.with_index do |message, i|
92
- ANSI::RESULT_DATA[i] + " #{message} "
93
- end.join)
94
- exit
95
- end
96
85
  end
97
86
  end
@@ -0,0 +1,30 @@
1
+ ┌──────────────────────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────┬────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
2
+ │ │ │ │ ★ ° . *   ° . °☆  . * ● ¸ │
3
+ │ │ │ │ .    ★  ° :. ★  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ │
4
+ │ ┌──────────────────────────────────────────────┐ .─┴─. .─┴─. .─┴─. ⣰⣾⣾⣿⣿⣿⣿⣆ .  *  .     ⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠳⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ │
5
+ │ │ │ ( ) ( ) ( ) ⣰⣾⣾⣾⣾⡀⣾⣾⣾⣾⣾⣷⣄ °  . ● . ★ ° . *⠀⠀⠀⠀⠀⠀⣀⡴⢧⣀⠀⠀⣀⣠⠤⠤⠤⠤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ │
6
+ │ │ Walk with ← → key │ '───' '───' '───' ⣴⣿⣿⣷⣾⣿⣿⣆ ⢀⣰⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣷⣄  . * ● ¸ .    ★ ⠀⠀⠀⠀⠀⠀⠀⠘⠏⢀⡴⠊⠁⠀⠀⠀⠀⠀⠀⠈⠙⠦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ¸ │
7
+ │ │ Jump with ↑ key │ ⢾⣿⡿⢿⣿⣿⣿⠟ ⣰⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣿⣷⣄ • ○ ° ★  .  *  ⠀⠀⠀⠀⠀⠀⠀⠀⣰⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢶⣶⣒⣶⠦⣤⣀⠀⠀ ★   ° . °☆  . * ●╔═══════╗ ║ ║ │
8
+ │ │ Stomp bugs for bonus │ ⢀⣰⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾⣾⣾⣾⣾⣷⣄   °  . ● . ★ ° ⠀⠀⠀⠀⠀⠀⢀⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣟⠲⡌⠙⢦⠈⢧⠀ :. ★  * • ○ ° ║  ║ ║ ║ │
9
+ │ │ Bumping into bugs stuns you │ ┌────────────────────────────────┐ ┌────────────────────────────────┐ ┌────────────────────────────────┐ ⣰⣾⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣶⣄⡀ °☆  . * ● ¸ .⠀⠀⠀⣠⢴⡾⢟⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡴⢃⡠⠋⣠⠋⠀°☆  .       .   ║ ║ ║ ║ ║ ║ ║ ╔═══════║ │
10
+ │ │ │ │ │ │ │ │ ▷▷ │ ⣴⣿⣿⣷⣾⣿⣿⣆ ⣴⣿⣿⣷⣾⣿⣿⣆ ⢀⣰⣾⣾⣾⣾⣾⣾ ⠟ ⡞ ⣾ ⣾⣾ ⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣷⣄ ⠀⠀⠞⣱⠋⢰⠁⢿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⠤⢖⣋⡥⢖⣫⠔⠋⠀⠀⠀  * ★ ° .    ° . ║☆ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ │
11
+ │ │ Escape the deadline to attend RubyKaigi! │ │ ╔═════╗ ═══╦═══ ╔══════╗ │ │ ╔═════╗ ═══╦═══ ║║ ║║ │ │ ▷ ▷▷ │ ⢾⣿⡿⢿⣿⣿⣿⠟ ⢾⣿⡿⢿⣿⣿⣿⠟ ⢀⣰⣾⣾⣾⣾⣾⣾⣾⠟ ⡞ ⣾ ⣾⣾⣾ ⣾ ⠈⠻⣾⣷⣦⣄ ⠀⠀⡀⠹⢤⣈⣙⠚⠶⠤⠤⠤⠴⠶⣒⣒⣚⣩⠭⢵⣒⣻⠭⢖⠏⠁⢀⣀⠀⠀⠀⠀ .    ★  ° :●.  ╠═════╦═╝ ║ ║ ╠══════╗ ║ ║ ╠═════╦═╝ ╔═══════║ ║ ║ │
12
+ │ │ │ │ ║ ║ ║ ║ ║ │ │ ║ ║ ║ ║║ ║║ │ │ ▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷ ▷▷ │ ┌──────────┐ ╔═══════╗ ⢀⣰⣾⣾⠟ ⠟ ⣾⡞ ⡌⢿⣷⣄ ⠠⠀⠈⠓⠒⠦⠭⠭⠭⣭⠭⠭⠭⠭⠿⠓⠒⠛⠉⠉⠀⠀⣠⠏⠀⠀⠘⠞⠀⠀⠀⠀ .  .   .       ║ ╚═╗ ║ ║ ║ ╚╗ ╚═══════╣ ║ ╚═╗ ║ ║ ║ ╚═══════╣ ║ │
13
+ │ └──────────────────────────────────────────────┘ │ ║ ║ ║ ║ │ │ ║ ║ ║ ║║ ║║ │ │ ▷ ▷▷ │ │ □ □ □ □ │ ║ ║ ⣀⣾⣾⣾⠟ ⡞ ⠈⢿⣷⣄ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢤⣀⠀⠀⠀⠀⠀⠀⣀⡤⠞⠁⠀⣰⣆⠀⠀⠀⠀⠀⠀ . ★ ° . *   ° ║  ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ │
14
+ │ │ ╚═════╗ ║ ╠══════╣ │ │ ╠═══╦═╝ ║ ║║ ║║ │ │ ▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷ ▷▷ │ │ │ ║ ║ ║ ⢀⣰⣾⣾⣾⣾⠟ ⠈⠻⣦⡀ ★ ° . *   ° . °☆  .⠀⠀⠀⠈⠉⠙⠒⠒⠛⠉⠁⠀⠀⠀⠉⢳⡞⠉⠀⠀⠀⠀⠁ .  ● ¸ .       ║ ║ ╚═══════║ ╚═══════╝ ════════╝ ║ ║ ╚═══════║ ║ ════════╝ ║ │
15
+ │ │ ║ ║ ║ ║ │ │ ║ ╚═╗ ║ │ │ ▷ ▷▷ │ └──────────┘ ║ ║ ║ ⢀⣰⣾⣾⣾⣾⠟ ⠈⠻⣷⣦⣄ * ● ¸ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀     ○ °   .   .  │
16
+ │ │ ╚═════╝ ║ ║ ║ │ │ ║ ║ ║ ║║ ║║ │ │ ▷▷ │ ╠═════╦═╝ ╔═══════╗ ╔═══════║ ╔══════╣ ⣀⣀⣀⣀⣀⠤⣾⣾⣾⠟ ⠈⠻⣷⣦⣄ .    ★  ° :. ★  * •  . .        │
17
+ │ └────────────────────────────────┘ └────────────────────────────────┘ └────────────────────────────────┘ ║ ╚═╗ ║ ║ ║ ║ ╔╝ ║ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ ☆  ¸ .      ╭────────────────────────────────────╮ │
18
+ │ ║ ║ ║ ║ ║ ║ ║ ║ ┌────────┐ ╭─┼─╮ ╭─┼─╮    ° .   . │ │ │
19
+ │ ┌──────────┐ ║ ║ ╚═══════╝ ╚═══════║ ╚═══════╝ │ │ ╭─╯ │ ╰──╮ ╭──╯ │ ╰─╮           ¸ ├▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─▄─┤ │
20
+ │ │ □ □ □ □ │ │ │ ╭─╯ ╰─╮ ╰─╮ ╭─────╮ ╭─────╮ ╭─╯ ╭─╯ ╰─╮       .   ○ ╭────────────┼─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀─▀┼────────────╮ │
21
+ │ ████████ ████████ ████████ ████████ │ │ │ │ ╭─╯ ╰─╮ ╰─╮ │╭───╮│ ╭────╮ ╭────╮ ╭────╮ ╭────╮ ╭────╮ ╭────╮ ╭────╮ │╭───╮│ ╭─╯ ╭─╯ ╰─╮ ⠀⠀⠀⠀⠀⠀⠀⠀   ° . │ │ ▗▄▄▄▄▄▄▖ │ │ │
22
+ │ ████████ ████████ ▓░▓ ████████ ████████ ░ └──────────┘ │ │ ╭─╯ ╰──╮ ╰──╮ ││ ││ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ││ ││ ╭──╯ ╭──╯ ╰─╮ │ ▯ ▯ ▯ │ ▐██████▌ │ ▯ ▯ ▯ │ │
23
+ │ ▀▀▀▜▛▀▀▀ ▀▀▀▜▛▀▀▀ █ ▓░▓ █ ▀▀▀▜▛▀▀▀ ▀▀▀▜▛▀▀▀ █▘ ░ ┌──────────┐ │ │ ╭─┴╮ ╰────╯ │╰───╯│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │╰───╯│ ╰────╯ ╭┴─╮ │ ▯ ▯ ▯ │ │ │ │ ▯ ▯ ▯ │ │
24
+ │ ══╦═════▝▘══════════╦══ ══╦═════▝▘════════════════════▝▘═════╦══ ══╦═════▝▘══════════╦══ │ □ □ □ □ │ ══╦═════════════════╦══ │ ◯ │ ╭─╯ ╰─╮ │ │ ╰────╯ ╰────╯ ╰────╯ ╰────╯ ╰────╯ ╰────╯ ╰────╯ │ │ ╭─╯ ╰─╮ │ │ └──────┘ │ │ │
25
+ │ ║ ║ ║ ║ ║ ║ ┌──────────┐ │ │ ║ ║ │ │ ╭─╯ ╰─╮ │ │ │ │ ╭─╯ ╰─╮ │ ▯ ▯ ▯ ├─▄─▄─▄┐ ┌▄─▄─▄─▄─▄─▄─▄─▄─▄─▖ ▗─▄─▄─▄┤ ▯ ▯ ▯ │ │
26
+ │ ║ ║ ║ ║ ║ ║ │ □ □ □ □ │ └──────────┘ ║ ║ │ │ │ ╰─╮ │ │ │ │ ╭─╯ │ │ ▯ ▯ ▯ ├▀─▀─▀─▘ ▝─▀─▀─▀─▀─▀─▀─▀─▀─▀┘ └▀─▀─▀─┤ ▯ ▯ ▯ │ │
27
+ │ ║ ║ ║ ║ ║ ║ │ │ ║ ║ │ │ │ ╰─╮ ╰─────╯ ╰─────╯ ╭─╯ │ │ ▯ ▯ ▯ │ ▄▄▄▄▄▖ ╔═══╗ ▗▄▄▄▄▄▄▖ ╔═══╗ ▗▄▄▄▄▄ │ ▯ ▯ ▯ │ │
28
+ │ ╨ ╨ ╨ ╨ ╨ ╨ └──────────┘ ╨ ╨ │ │ ╰──────────────┴┬───┬───┬───┬────────────────────────────────────────────────────────────────────────────────────────────────────────────┬───┬───┬───┬┴──────────────╯ │ ▯ ▯ ▯ │ █████▌ ║▐█▌║ ▐██████▌ ║▐█▌║ ▐█████ │ ▯ ▯ ▯ │ │
29
+ │ └────────┘ ╰◯◯◯╯ ╰◯◯◯╯ ╰◯◯◯╯ ╰◯◯◯╯ ╰────────────╯ ╰────────────╯ │
30
+ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
@@ -0,0 +1,30 @@
1
+ ###########################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################
2
+ # # # # ★ ° . *   ° . °☆  . * ● ¸ #
3
+ # # # # .    ★  ° :. ★  ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #
4
+ # ################################################ .---. .---. .---. ⣰⣾⣾⣿⣿⣿⣿⣆ .  *  .     ⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠳⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #
5
+ # # # ( ) ( ) ( ) ⣰⣾⣾⣾⣾⡀⣾⣾⣾⣾⣾⣷⣄ °  . ● . ★ ° . *⠀⠀⠀⠀⠀⠀⣀⡴⢧⣀⠀⠀⣀⣠⠤⠤⠤⠤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ #
6
+ # # Walk with ← → key # '---' '---' '---' ⣴⣿⣿⣷⣾⣿⣿⣆ ⢀⣰⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣷⣄  . * ● ¸ .    ★ ⠀⠀⠀⠀⠀⠀⠀⠘⠏⢀⡴⠊⠁⠀⠀⠀⠀⠀⠀⠈⠙⠦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ¸ #
7
+ # # Jump with ↑ key # ⢾⣿⡿⢿⣿⣿⣿⠟ ⣰⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣿⣷⣄ • ○ ° ★  .  *  ⠀⠀⠀⠀⠀⠀⠀⠀⣰⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢶⣶⣒⣶⠦⣤⣀⠀⠀ ★   ° . °☆  . * ●######### # # #
8
+ # # Stomp ugs for onus # ⢀⣰⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾⣾⣾⣾⣾⣷⣄   °  . ● . ★ ° ⠀⠀⠀⠀⠀⠀⢀⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣟⠲⡌⠙⢦⠈⢧⠀ :. ★  * • ○ ° #  # # # #
9
+ # # umping into ugs stuns you # ################################## ################################## ################################## ⣰⣾⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾⣾⣾⣾⣾⣾⣶⣄⡀ °☆  . * ● ¸ .⠀⠀⠀⣠⢴⡾⢟⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡴⢃⡠⠋⣠⠋⠀°☆  .       .   # # # # # # # ######### #
10
+ # # # - - - - - ▷▷ - ⣴⣿⣿⣷⣾⣿⣿⣆ ⣴⣿⣿⣷⣾⣿⣿⣆ ⢀⣰⣾⣾⣾⣾⣾⣾ ⠟ ⡞ ⣾ ⣾⣾ ⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣾ ⣾⣾⣾⣾⣷⣄ ⠀⠀⠞⣱⠋⢰⠁⢿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⠤⢖⣋⡥⢖⣫⠔⠋⠀⠀⠀  * ★ ° .    ° . #☆ # # # # # # # # # # #
11
+ # # Escape the deadline to attend Ru yKaigi! # - ------- ------- -------- - - ------- ------- -- -- - - ▷ ▷▷ - ⢾⣿⡿⢿⣿⣿⣿⠟ ⢾⣿⡿⢿⣿⣿⣿⠟ ⢀⣰⣾⣾⣾⣾⣾⣾⣾⠟ ⡞ ⣾ ⣾⣾⣾ ⣾ ⠈⠻⣾⣷⣦⣄ ⠀⠀⡀⠹⢤⣈⣙⠚⠶⠤⠤⠤⠴⠶⣒⣒⣚⣩⠭⢵⣒⣻⠭⢖⠏⠁⢀⣀⠀⠀⠀⠀ .    ★  ° :●.  ######### # # ######## # # ######### ######### # # #
12
+ # # # - - - - - - - - - - - -- -- - - ▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷ ▷▷ - ############ ######### ⢀⣰⣾⣾⠟ ⠟ ⣾⡞ ⡌⢿⣷⣄ ⠠⠀⠈⠓⠒⠦⠭⠭⠭⣭⠭⠭⠭⠭⠿⠓⠒⠛⠉⠉⠀⠀⣠⠏⠀⠀⠘⠞⠀⠀⠀⠀ .  .   .       # ### # # # ## ######### # ### # # # ######### # #
13
+ # ################################################ - - - - - - - - - - -- -- - - ▷ ▷▷ - # # # # ⣀⣾⣾⣾⠟ ⡞ ⠈⢿⣷⣄ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢤⣀⠀⠀⠀⠀⠀⠀⣀⡤⠞⠁⠀⣰⣆⠀⠀⠀⠀⠀⠀ . ★ ° . *   ° #  # # # # # # # # # # # # # #
14
+ # - ------- - -------- - - ------- - -- -- - - ▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷▷ ▷▷ - # # # # # ⢀⣰⣾⣾⣾⣾⠟ ⠈⠻⣦⡀ ★ ° . *   ° . °☆  .⠀⠀⠀⠈⠉⠙⠒⠒⠛⠉⠁⠀⠀⠀⠉⢳⡞⠉⠀⠀⠀⠀⠁ .  ● ¸ .       # # ######### ######### ######### # # ######### # ######### # #
15
+ # - - - - - - - - --- - - - ▷ ▷▷ - ############ # # # ⢀⣰⣾⣾⣾⣾⠟ ⠈⠻⣷⣦⣄ * ● ¸ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀     ○ °   .   .  #
16
+ # - ------- - - - - - - - - -- -- - - ▷▷ - ######### ######### ######### ######## ⣀⣀⣀⣀⣀⠤⣾⣾⣾⠟ ⠈⠻⣷⣦⣄ .    ★  ° :. ★  * •  . .        #
17
+ # ################################## ################################## ################################## # ### # # # # ## # ###################################################################################################################################### ☆  ¸ .      ###################################### #
18
+ # # # # # # # # # ########## ##########################################################################################################################################    ° .   . # # #
19
+ # ############ # # ######### ######### ######### ########## ### # #### #### # ###           ¸ ###################################### #
20
+ # # # ########## ######### ### ####### ####### ### ### ###       .   ○ ################################################################ #
21
+ # ######## ######## ######## ######## # # ########## ############# ### ####### ###### ###### ###### ###### ###### ###### ###### ####### ### ### ### ⠀⠀⠀⠀⠀⠀⠀⠀   ° . - - -------- # # #
22
+ # ######## ######## ### ######## ######## # ############ ########## ################## #### ## ## # # # # # # # # # # # # # # ## ## #### #### ### - ▯ ▯ ▯ - -------- # ▯ ▯ ▯ # #
23
+ # ######## ######## # ### # ######## ######## ## # ############ ########## ######################### ####### # # # # # # # # # # # # # # ####### ###### #### - ▯ ▯ ▯ - - - # ▯ ▯ ▯ # #
24
+ # ####################### ######################################## ####################### # # ####################### ########## ######## # # ###### ###### ###### ###### ###### ###### ###### # # ### ### - - -------- # # #
25
+ # # # # # # # ############ # # # # ########## ### ### # # # # ### ### - ▯ ▯ ▯ -------- -----------------### ######## ▯ ▯ ▯ # #
26
+ # # # # # # # # # ############ # # ########## # ### # # # # ### # - ▯ ▯ ▯ -------- -----------------### ######## ▯ ▯ ▯ # #
27
+ # # # # # # # # # # # ########## # ### ####### ####### ### # - ▯ ▯ ▯ - ------ ----- -------- --### ###### # ▯ ▯ ▯ # #
28
+ # # # # # # # ############ # # ########## ###################################################################################################################################################################### - ▯ ▯ ▯ - ------ ----- -------- --### ###### # ▯ ▯ ▯ # #
29
+ # ########## ##### ##### ##### ##### ############## ############## #
30
+ ###########################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################
@@ -2,9 +2,14 @@ module RoadToRubykaigi
2
2
  module Graphics
3
3
  module Map
4
4
  FILE_PATH = "map.txt"
5
+ DEMO_FILE_PATH = "demo-map.txt"
5
6
 
6
7
  def self.data
7
- File.read("#{__dir__}/#{FILE_PATH}").split("\n")
8
+ File.read("#{__dir__}/#{file_path}").split("\n")
9
+ end
10
+
11
+ def self.file_path
12
+ @file_path ||= RoadToRubykaigi.demo? ? DEMO_FILE_PATH : FILE_PATH
8
13
  end
9
14
  end
10
15
  end
@@ -2,9 +2,15 @@ module RoadToRubykaigi
2
2
  module Graphics
3
3
  module Mask
4
4
  FILE_PATH = "mask.txt"
5
+ DEMO_FILE_PATH = "demo-mask.txt"
5
6
  MASK_CHARAS = "[╭─╮╰│╯┬◯╔═║╠╚╦╝╗╣┤┴┼├╽▐▗▖▌◻▥▞▟█▙◺◸┌┐┘└╨▝▘▄▜▛▀░▓]"
7
+
6
8
  def self.data
7
- File.read("#{__dir__}/#{FILE_PATH}").split("\n")
9
+ File.read("#{__dir__}/#{file_path}").split("\n")
10
+ end
11
+
12
+ def self.file_path
13
+ @file_path ||= RoadToRubykaigi.demo? ? DEMO_FILE_PATH : FILE_PATH
8
14
  end
9
15
  end
10
16
  end
@@ -3,77 +3,68 @@ module RoadToRubykaigi
3
3
  module Player
4
4
  RIGHT = RoadToRubykaigi::Sprite::Player::RIGHT
5
5
  LEFT = RoadToRubykaigi::Sprite::Player::LEFT
6
-
7
- CHARACTERS = {
8
- normal: {
9
- RIGHT => [
10
- <<~SPRITE,
11
- ╭──────╮
12
- │。・◡・│
13
- ╰ᜊ───ᜊ─╯
14
- SPRITE
15
- <<~SPRITE,
16
- ╭──────╮
17
- │。・◡・│
18
- ╰─∪───∪╯
19
- SPRITE
20
- ],
21
- LEFT => [
22
- <<~SPRITE,
23
- ╭──────╮
24
- │・◡・。│
25
- ╰─ᜊ───ᜊ╯
26
- SPRITE
27
- <<~SPRITE,
28
- ╭──────╮
29
- │・◡・。│
30
- ╰∪───∪─╯
31
- SPRITE
32
- ],
33
- },
34
- stunned: {
35
- RIGHT => [
36
- <<~SPRITE,
37
- ╭──────╮
38
- │ ´×⌓× │
39
- ╰─ᜊ───ᜊ╯
40
- SPRITE
41
- <<~SPRITE,
42
- ╭──────╮
43
- │ ´×⌓× │
44
- ╰─∪───∪╯
45
- SPRITE
46
- ],
47
- LEFT => [
48
- <<~SPRITE,
49
- ╭──────╮
50
- │ ×⌓×` │
51
- ╰─ᜊ───ᜊ╯
52
- SPRITE
53
- <<~SPRITE,
54
- ╭──────╮
55
- │ ×⌓×` │
56
- ╰─∪───∪╯
57
- SPRITE
58
- ],
59
- },
60
- }
6
+ FILE_PATH = "player.txt"
61
7
 
62
8
  class << self
63
- def character(status, direction)
64
- CHARACTERS[status][direction].map do |lines|
65
- lines.split("\n").map do |line|
66
- line.chars.map do |character|
67
- fullwidth?(character) ? [character, ANSI::NULL] : character
68
- end.flatten
69
- end
70
- end
9
+ def character(posture:, status:, direction:, attack_mode:)
10
+ load_data
11
+ characters = attack_mode ? @attack_characters : @default_characters
12
+ characters[posture][status][direction]
71
13
  end
72
14
 
73
15
  private
74
16
 
75
- def fullwidth?(character)
76
- %w[・].include? character
17
+ def load_data
18
+ return if @default_characters
19
+ index = { "RIGHT" => Sprite::Player::RIGHT, "LEFT" => Sprite::Player::LEFT }
20
+ hash = Hash.new { |h, k| h[k] = [] }
21
+ @default_characters = {
22
+ standup: { normal: hash.dup, stunned: hash.dup },
23
+ crouching: { normal: hash.dup, stunned: hash.dup },
24
+ }
25
+ @attack_characters = {
26
+ standup: { normal: hash.dup, stunned: hash.dup },
27
+ crouching: { normal: hash.dup, stunned: hash.dup },
28
+ }
29
+ set = {}
30
+ File.read("#{__dir__}/#{FILE_PATH}").scan(/# (\w+)\n(.*)\n/) do |type, line|
31
+ set[type.to_sym] = line.chars.map do |char|
32
+ fullwidth?(char) ? [char, ANSI::NULL] : char
33
+ end.flatten
34
+ end
35
+
36
+ %i[standup crouching].each do |posture|
37
+ %i[normal stunned].each do |status|
38
+ index.each do |(direction, direction_value)|
39
+ (1..2).each do |i|
40
+ @default_characters[posture][status][direction_value] << (
41
+ [
42
+ set[:head],
43
+ set[:"face_#{posture}_#{status}_#{direction}"],
44
+ posture == :crouching ? nil : set[:"foot_#{status}_#{i}"],
45
+ ].compact
46
+ )
47
+ @attack_characters[posture][status][direction_value] << (
48
+ direction == "RIGHT" ?
49
+ [
50
+ set[:head] + " ".chars,
51
+ set[:"face_#{posture}_#{status}_#{direction}"] + "_◢◤".chars,
52
+ posture == :crouching ? nil : set[:"foot_#{status}_#{i}"] + " ".chars,
53
+ ].compact :
54
+ [
55
+ " ".chars + set[:head],
56
+ "◥◣_".chars + set[:"face_#{posture}_#{status}_#{direction}"],
57
+ posture == :crouching ? nil : " ".chars + set[:"foot_#{status}_#{i}"],
58
+ ].compact
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def fullwidth?(char)
67
+ %w[・].include? char
77
68
  end
78
69
  end
79
70
  end
@@ -0,0 +1,26 @@
1
+ # head
2
+ ╭──────╮
3
+ # foot_normal_1
4
+ ╰─∪───∪╯
5
+ # foot_normal_2
6
+ ╰ᜊ───ᜊ─╯
7
+ # foot_stunned_1
8
+ ╰─ᜊ───ᜊ╯
9
+ # foot_stunned_2
10
+ ╰ᜊ───ᜊ─╯
11
+ # face_standup_normal_RIGHT
12
+ │。・◡・│
13
+ # face_standup_normal_LEFT
14
+ │・◡・。│
15
+ # face_standup_stunned_RIGHT
16
+ │ ´×⌓× │
17
+ # face_standup_stunned_LEFT
18
+ │ ×⌓×` │
19
+ # face_crouching_normal_RIGHT
20
+ ╰ᜊ・◡・╯
21
+ # face_crouching_normal_LEFT
22
+ ╰・◡・ᜊ╯
23
+ # face_crouching_stunned_RIGHT
24
+ ╰ ´×⌓× ╯
25
+ # face_crouching_stunned_LEFT
26
+ ╰ ×⌓×` ╯