rubywarrior 0.1.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 (107) hide show
  1. data/CHANGELOG.rdoc +3 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +170 -0
  4. data/Rakefile +21 -0
  5. data/bin/rubywarrior +5 -0
  6. data/features/command_options.feature +46 -0
  7. data/features/levels.feature +51 -0
  8. data/features/profiles.feature +56 -0
  9. data/features/step_definitions/common_steps.rb +19 -0
  10. data/features/step_definitions/interaction_steps.rb +65 -0
  11. data/features/support/env.rb +12 -0
  12. data/features/support/mockio.rb +57 -0
  13. data/features/towers.feature +14 -0
  14. data/lib/ruby_warrior.rb +44 -0
  15. data/lib/ruby_warrior/abilities/attack.rb +24 -0
  16. data/lib/ruby_warrior/abilities/base.rb +36 -0
  17. data/lib/ruby_warrior/abilities/bind.rb +19 -0
  18. data/lib/ruby_warrior/abilities/direction_of.rb +13 -0
  19. data/lib/ruby_warrior/abilities/direction_of_stairs.rb +13 -0
  20. data/lib/ruby_warrior/abilities/distance.rb +13 -0
  21. data/lib/ruby_warrior/abilities/explode.rb +16 -0
  22. data/lib/ruby_warrior/abilities/feel.rb +13 -0
  23. data/lib/ruby_warrior/abilities/health.rb +13 -0
  24. data/lib/ruby_warrior/abilities/listen.rb +15 -0
  25. data/lib/ruby_warrior/abilities/look.rb +15 -0
  26. data/lib/ruby_warrior/abilities/pivot.rb +16 -0
  27. data/lib/ruby_warrior/abilities/rescue.rb +23 -0
  28. data/lib/ruby_warrior/abilities/rest.rb +20 -0
  29. data/lib/ruby_warrior/abilities/shoot.rb +23 -0
  30. data/lib/ruby_warrior/abilities/walk.rb +20 -0
  31. data/lib/ruby_warrior/config.rb +22 -0
  32. data/lib/ruby_warrior/core_additions.rb +25 -0
  33. data/lib/ruby_warrior/floor.rb +71 -0
  34. data/lib/ruby_warrior/game.rb +193 -0
  35. data/lib/ruby_warrior/level.rb +119 -0
  36. data/lib/ruby_warrior/level_loader.rb +56 -0
  37. data/lib/ruby_warrior/player_generator.rb +41 -0
  38. data/lib/ruby_warrior/position.rb +80 -0
  39. data/lib/ruby_warrior/profile.rb +104 -0
  40. data/lib/ruby_warrior/runner.rb +33 -0
  41. data/lib/ruby_warrior/space.rb +63 -0
  42. data/lib/ruby_warrior/tower.rb +14 -0
  43. data/lib/ruby_warrior/turn.rb +38 -0
  44. data/lib/ruby_warrior/ui.rb +54 -0
  45. data/lib/ruby_warrior/units/archer.rb +34 -0
  46. data/lib/ruby_warrior/units/base.rb +95 -0
  47. data/lib/ruby_warrior/units/captive.rb +30 -0
  48. data/lib/ruby_warrior/units/sludge.rb +30 -0
  49. data/lib/ruby_warrior/units/thick_sludge.rb +13 -0
  50. data/lib/ruby_warrior/units/warrior.rb +58 -0
  51. data/lib/ruby_warrior/units/wizard.rb +34 -0
  52. data/spec/fixtures/short-tower/level_001.rb +15 -0
  53. data/spec/fixtures/short-tower/level_002.rb +15 -0
  54. data/spec/fixtures/walking_player.rb +5 -0
  55. data/spec/ruby_warrior/abilities/attack_spec.rb +51 -0
  56. data/spec/ruby_warrior/abilities/base_spec.rb +24 -0
  57. data/spec/ruby_warrior/abilities/bind_spec.rb +19 -0
  58. data/spec/ruby_warrior/abilities/direction_of_spec.rb +13 -0
  59. data/spec/ruby_warrior/abilities/direction_of_stairs_spec.rb +13 -0
  60. data/spec/ruby_warrior/abilities/distance_spec.rb +13 -0
  61. data/spec/ruby_warrior/abilities/explode_spec.rb +21 -0
  62. data/spec/ruby_warrior/abilities/feel_spec.rb +13 -0
  63. data/spec/ruby_warrior/abilities/health_spec.rb +13 -0
  64. data/spec/ruby_warrior/abilities/listen_spec.rb +17 -0
  65. data/spec/ruby_warrior/abilities/look_spec.rb +15 -0
  66. data/spec/ruby_warrior/abilities/pivot_spec.rb +18 -0
  67. data/spec/ruby_warrior/abilities/rescue_spec.rb +40 -0
  68. data/spec/ruby_warrior/abilities/rest_spec.rb +29 -0
  69. data/spec/ruby_warrior/abilities/shoot_spec.rb +22 -0
  70. data/spec/ruby_warrior/abilities/walk_spec.rb +25 -0
  71. data/spec/ruby_warrior/floor_spec.rb +81 -0
  72. data/spec/ruby_warrior/game_spec.rb +119 -0
  73. data/spec/ruby_warrior/level_loader_spec.rb +54 -0
  74. data/spec/ruby_warrior/level_spec.rb +203 -0
  75. data/spec/ruby_warrior/player_generator_spec.rb +12 -0
  76. data/spec/ruby_warrior/position_spec.rb +103 -0
  77. data/spec/ruby_warrior/profile_spec.rb +144 -0
  78. data/spec/ruby_warrior/space_spec.rb +165 -0
  79. data/spec/ruby_warrior/tower_spec.rb +15 -0
  80. data/spec/ruby_warrior/turn_spec.rb +42 -0
  81. data/spec/ruby_warrior/ui_spec.rb +93 -0
  82. data/spec/ruby_warrior/units/archer_spec.rb +23 -0
  83. data/spec/ruby_warrior/units/base_spec.rb +133 -0
  84. data/spec/ruby_warrior/units/captive_spec.rb +34 -0
  85. data/spec/ruby_warrior/units/sludge_spec.rb +27 -0
  86. data/spec/ruby_warrior/units/thick_sludge_spec.rb +19 -0
  87. data/spec/ruby_warrior/units/warrior_spec.rb +60 -0
  88. data/spec/ruby_warrior/units/wizard_spec.rb +23 -0
  89. data/spec/spec_helper.rb +10 -0
  90. data/templates/README.erb +20 -0
  91. data/templates/player.rb +5 -0
  92. data/towers/beginner/level_001.rb +16 -0
  93. data/towers/beginner/level_002.rb +18 -0
  94. data/towers/beginner/level_003.rb +21 -0
  95. data/towers/beginner/level_004.rb +18 -0
  96. data/towers/beginner/level_005.rb +22 -0
  97. data/towers/beginner/level_006.rb +19 -0
  98. data/towers/beginner/level_007.rb +18 -0
  99. data/towers/beginner/level_008.rb +21 -0
  100. data/towers/beginner/level_009.rb +20 -0
  101. data/towers/intermediate/level_001.rb +18 -0
  102. data/towers/intermediate/level_002.rb +20 -0
  103. data/towers/intermediate/level_003.rb +23 -0
  104. data/towers/intermediate/level_004.rb +24 -0
  105. data/towers/intermediate/level_005.rb +19 -0
  106. data/towers/intermediate/level_006.rb +24 -0
  107. metadata +167 -0
@@ -0,0 +1,22 @@
1
+ module RubyWarrior
2
+ class Config
3
+ class << self
4
+ attr_accessor :delay, :in_stream, :out_stream, :practice_level
5
+ attr_writer :path_prefix, :skip_input
6
+
7
+ def path_prefix
8
+ @path_prefix || "."
9
+ end
10
+
11
+ def skip_input?
12
+ @skip_input
13
+ end
14
+
15
+ def reset
16
+ [:@path_prefix, :@skip_input, :@delay, :@in_stream, :@out_stream, :@practice_level].each do |i|
17
+ remove_instance_variable(i) if instance_variable_defined?(i)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ class String
2
+ def camelize
3
+ gsub(/(?:^|_)(.)/) { $1.upcase }
4
+ end
5
+
6
+ def constantize
7
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
8
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
9
+ end
10
+
11
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
12
+ end
13
+
14
+ def underscore
15
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
16
+ end
17
+
18
+ def humanize
19
+ gsub(/_/, " ").capitalize
20
+ end
21
+
22
+ def titleize
23
+ underscore.humanize.gsub(/\b('?[a-z])/) { $1.capitalize }
24
+ end
25
+ end
@@ -0,0 +1,71 @@
1
+ module RubyWarrior
2
+ class Floor
3
+ attr_accessor :width, :height, :grid
4
+ attr_reader :stairs_location
5
+
6
+ def initialize
7
+ @width = 0
8
+ @height = 0
9
+ @units = []
10
+ @stairs_location = [-1, -1]
11
+ end
12
+
13
+ def add(unit, x, y, direction = nil)
14
+ @units << unit
15
+ unit.position = Position.new(self, x, y, direction)
16
+ end
17
+
18
+ def place_stairs(x, y)
19
+ @stairs_location = [x, y]
20
+ end
21
+
22
+ def stairs_space
23
+ space(*@stairs_location)
24
+ end
25
+
26
+ def units
27
+ @units.reject { |u| u.position.nil? }
28
+ end
29
+
30
+ def other_units
31
+ units.reject { |u| u.kind_of? Units::Warrior }
32
+ end
33
+
34
+ def get(x, y)
35
+ units.detect do |unit|
36
+ unit.position.at?(x, y)
37
+ end
38
+ end
39
+
40
+ def space(x, y)
41
+ Space.new(self, x, y)
42
+ end
43
+
44
+ def out_of_bounds?(x, y)
45
+ x < 0 || y < 0 || x > @width-1 || y > @height-1
46
+ end
47
+
48
+ def character
49
+ rows = []
50
+ rows << " " + ("-" * @width)
51
+ @height.times do |y|
52
+ row = "|"
53
+ @width.times do |x|
54
+ row << space(x, y).character
55
+ end
56
+ row << "|"
57
+ rows << row
58
+ end
59
+ rows << " " + ("-" * @width)
60
+ rows.join("\n") + "\n"
61
+ end
62
+
63
+ def unique_units
64
+ unique_units = []
65
+ units.each do |unit|
66
+ unique_units << unit unless unique_units.map { |u| u.class }.include?(unit.class)
67
+ end
68
+ unique_units
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,193 @@
1
+ module RubyWarrior
2
+ class Game
3
+
4
+ def start
5
+ UI.puts "Welcome to Ruby Warrior"
6
+
7
+ if File.exist?(Config.path_prefix + '/.profile')
8
+ @profile = Profile.load(Config.path_prefix + '/.profile')
9
+ else
10
+ if File.exist?(Config.path_prefix + '/ruby-warrior')
11
+ FileUtils.mv(Config.path_prefix + '/ruby-warrior', Config.path_prefix + '/rubywarrior')
12
+ end
13
+ make_game_directory unless File.exist?(Config.path_prefix + '/rubywarrior')
14
+ end
15
+
16
+ profile.epic? ? play_epic_mode : play_normal_mode
17
+ end
18
+
19
+ def make_game_directory
20
+ if UI.ask("No rubywarrior directory found, would you like to create one?")
21
+ Dir.mkdir(Config.path_prefix + '/rubywarrior')
22
+ else
23
+ UI.puts "Unable to continue without directory."
24
+ exit
25
+ end
26
+ end
27
+
28
+ def play_epic_mode
29
+ Config.delay /= 2 if Config.delay # speed up UI since we're going to be doing a lot here
30
+ profile.current_epic_score = 0
31
+ profile.current_epic_grades = {}
32
+ if Config.practice_level
33
+ @current_level = @next_level = nil
34
+ profile.level_number = Config.practice_level
35
+ play_current_level
36
+ else
37
+ playing = true
38
+ while playing
39
+ @current_level = @next_level = nil
40
+ profile.level_number += 1
41
+ playing = play_current_level
42
+ end
43
+ profile.save # saves the score for epic mode
44
+ end
45
+ end
46
+
47
+ def play_normal_mode
48
+ if Config.practice_level
49
+ UI.puts "Unable to practice level while not in epic mode, remove -l option."
50
+ else
51
+ if current_level.number.zero?
52
+ prepare_next_level
53
+ UI.puts "First level has been generated. See the rubywarrior/#{profile.directory_name}/README for instructions."
54
+ else
55
+ play_current_level
56
+ end
57
+ end
58
+ end
59
+
60
+ def play_current_level
61
+ continue = true
62
+ current_level.load_player
63
+ UI.puts "Starting Level #{current_level.number}"
64
+ current_level.play
65
+ if current_level.passed?
66
+ if next_level.exists?
67
+ UI.puts "Success! You have found the stairs."
68
+ else
69
+ UI.puts "CONGRATULATIONS! You have climbed to the top of the tower and rescued the fair maiden Ruby."
70
+ continue = false
71
+ end
72
+ current_level.tally_points
73
+ if profile.epic?
74
+ UI.puts final_report if final_report && !continue
75
+ else
76
+ request_next_level
77
+ end
78
+ else
79
+ continue = false
80
+ UI.puts "Sorry, you failed level #{current_level.number}. Change your script and try again."
81
+ if !Config.skip_input? && current_level.clue && UI.ask("Would you like to read the additional clues for this level?")
82
+ UI.puts current_level.clue
83
+ end
84
+ end
85
+ continue
86
+ end
87
+
88
+ def request_next_level
89
+ if !Config.skip_input? && (next_level.exists? ? UI.ask("Would you like to continue on to the next level?") : UI.ask("Would you like to continue on to epic mode?"))
90
+ if next_level.exists?
91
+ prepare_next_level
92
+ UI.puts "See the updated README in the rubywarrior/#{profile.directory_name} directory."
93
+ else
94
+ prepare_epic_mode
95
+ UI.puts "Run rubywarrior again to play epic mode."
96
+ end
97
+ else
98
+ UI.puts "Staying on current level. Try to earn more points next time."
99
+ end
100
+ end
101
+
102
+ def prepare_next_level
103
+ next_level.generate_player_files
104
+ profile.level_number += 1
105
+ profile.save # this saves score and new abilities too
106
+ end
107
+
108
+ def prepare_epic_mode
109
+ profile.enable_epic_mode
110
+ profile.level_number = 0
111
+ profile.save # this saves score too
112
+ end
113
+
114
+
115
+ # profiles
116
+
117
+ def profiles
118
+ profile_paths.map { |profile| Profile.load(profile) }
119
+ end
120
+
121
+ def profile_paths
122
+ Dir[Config.path_prefix + '/rubywarrior/**/.profile']
123
+ end
124
+
125
+ def profile
126
+ @profile ||= choose_profile
127
+ end
128
+
129
+ def new_profile
130
+ profile = Profile.new
131
+ profile.tower_path = UI.choose('tower', towers).path
132
+ profile.warrior_name = UI.request('Enter a name for your warrior: ')
133
+ profile
134
+ end
135
+
136
+
137
+ # towers
138
+
139
+ def towers
140
+ tower_paths.map { |path| Tower.new(path) }
141
+ end
142
+
143
+ def tower_paths
144
+ Dir[File.expand_path(File.dirname(__FILE__) + '/../../towers/*')]
145
+ end
146
+
147
+
148
+ # levels
149
+
150
+ def current_level
151
+ @current_level ||= profile.current_level
152
+ end
153
+
154
+ def next_level
155
+ @next_level ||= profile.next_level
156
+ end
157
+
158
+ def final_report
159
+ if profile.calculate_average_grade && !Config.practice_level
160
+ report = ""
161
+ report << "Your average grade for this tower is: #{Level.grade_letter(profile.calculate_average_grade)}\n\n"
162
+ profile.current_epic_grades.keys.sort.each do |level|
163
+ report << " Level #{level}: #{Level.grade_letter(profile.current_epic_grades[level])}\n"
164
+ end
165
+ report << "\nTo practice a level, use the -l option:\n\n rubywarrior -l 3"
166
+ report
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ def choose_profile # REFACTORME
173
+ profile = UI.choose('profile', profiles + [[:new, 'New Profile']])
174
+ if profile == :new
175
+ profile = new_profile
176
+ if profiles.any? { |p| p.player_path == profile.player_path }
177
+ if UI.ask("Are you sure you want to replace your existing profile for this tower?")
178
+ UI.puts "Replacing existing profile."
179
+ profile
180
+ else
181
+ UI.puts "Not replacing profile."
182
+ exit
183
+ end
184
+ else
185
+ profile
186
+ end
187
+ else
188
+ profile
189
+ end
190
+ end
191
+
192
+ end
193
+ end
@@ -0,0 +1,119 @@
1
+ module RubyWarrior
2
+ class Level
3
+ attr_reader :profile, :number
4
+ attr_accessor :description, :tip, :clue, :warrior, :floor, :time_bonus, :ace_score
5
+
6
+ def self.grade_letter(percent)
7
+ if percent >= 1.0 then "S"
8
+ elsif percent >= 0.9 then "A"
9
+ elsif percent >= 0.8 then "B"
10
+ elsif percent >= 0.7 then "C"
11
+ elsif percent >= 0.6 then "D"
12
+ else "F"
13
+ end
14
+ end
15
+
16
+ def initialize(profile, number)
17
+ @profile = profile
18
+ @number = number
19
+ @time_bonus = 0
20
+ end
21
+
22
+ def player_path
23
+ @profile.player_path
24
+ end
25
+
26
+ def load_path
27
+ @profile.tower_path + "/level_" + @number.to_s.rjust(3, '0') + ".rb"
28
+ end
29
+
30
+ def load_level
31
+ LevelLoader.new(self).instance_eval(File.read(load_path))
32
+ end
33
+
34
+ def load_player
35
+ $: << player_path
36
+ load 'player.rb'
37
+ end
38
+
39
+ def generate_player_files
40
+ load_level
41
+ PlayerGenerator.new(self).generate
42
+ end
43
+
44
+ def play(turns = 1000)
45
+ load_level
46
+ turns.times do |n|
47
+ return if passed? || failed?
48
+ UI.puts "- turn #{n+1} -"
49
+ UI.print @floor.character
50
+ @floor.units.each { |unit| unit.prepare_turn }
51
+ @floor.units.each { |unit| unit.perform_turn }
52
+ yield if block_given?
53
+ @time_bonus -= 1 if @time_bonus > 0
54
+ end
55
+ end
56
+
57
+ def tally_points
58
+ score = 0
59
+
60
+ UI.puts "Level Score: #{warrior.score}"
61
+ score += warrior.score
62
+
63
+ UI.puts "Time Bonus: #{time_bonus}"
64
+ score += @time_bonus
65
+
66
+ if floor.other_units.empty?
67
+ UI.puts "Clear Bonus: #{clear_bonus}"
68
+ score += clear_bonus
69
+ end
70
+
71
+ if @profile.epic?
72
+ UI.puts "Level Grade: #{grade_for(score)}" if grade_for(score)
73
+ UI.puts "Total Score: " + score_calculation(@profile.current_epic_score, score)
74
+ @profile.current_epic_grades[@number] = (score / ace_score.to_f) if ace_score
75
+ @profile.current_epic_score += score
76
+ else
77
+ UI.puts "Total Score: " + score_calculation(@profile.score, score)
78
+ @profile.score += score
79
+ @profile.abilities = warrior.abilities.keys
80
+ end
81
+ end
82
+
83
+ def grade_for(score)
84
+ if ace_score
85
+ self.class.grade_letter(score / ace_score.to_f)
86
+ end
87
+ end
88
+
89
+ def clear_bonus
90
+ ((warrior.score + time_bonus)*0.2).round
91
+ end
92
+
93
+ def score_calculation(current_score, addition)
94
+ if current_score.zero?
95
+ addition.to_s
96
+ else
97
+ "#{current_score} + #{addition} = #{current_score + addition}"
98
+ end
99
+ end
100
+
101
+ def passed?
102
+ @floor.stairs_space.warrior?
103
+ end
104
+
105
+ def failed?
106
+ !@floor.units.include?(warrior)
107
+ end
108
+
109
+ def exists?
110
+ File.exist? load_path
111
+ end
112
+
113
+ def setup_warrior(warrior)
114
+ @warrior = warrior
115
+ @warrior.add_abilities(*profile.abilities)
116
+ @warrior.name = profile.warrior_name
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,56 @@
1
+ module RubyWarrior
2
+ class LevelLoader
3
+ def initialize(level)
4
+ @floor = RubyWarrior::Floor.new
5
+ @level = level
6
+ @level.floor = @floor
7
+ end
8
+
9
+ def description(desc)
10
+ @level.description = desc
11
+ end
12
+
13
+ def tip(tip)
14
+ @level.tip = tip
15
+ end
16
+
17
+ def clue(clue)
18
+ @level.clue = clue
19
+ end
20
+
21
+ def time_bonus(bonus)
22
+ @level.time_bonus = bonus
23
+ end
24
+
25
+ def ace_score(score)
26
+ @level.ace_score = score
27
+ end
28
+
29
+ def size(width, height)
30
+ @floor.width = width
31
+ @floor.height = height
32
+ end
33
+
34
+ def stairs(x, y)
35
+ @floor.place_stairs(x, y)
36
+ end
37
+
38
+ def unit(unit, x, y, facing = :north)
39
+ unit = unit_to_constant(unit).new unless unit.kind_of? Units::Base
40
+ @floor.add(unit, x, y, facing)
41
+ yield unit if block_given?
42
+ unit
43
+ end
44
+
45
+ def warrior(*args, &block)
46
+ @level.setup_warrior unit(Units::Warrior.new, *args, &block)
47
+ end
48
+
49
+ private
50
+
51
+ def unit_to_constant(name)
52
+ camel = name.to_s.split('_').map { |s| s.capitalize }.join
53
+ eval("Units::#{camel}")
54
+ end
55
+ end
56
+ end