karnowski-ruby-warrior 0.0.1

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 (108) hide show
  1. data/.gitignore +3 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +156 -0
  4. data/Rakefile +36 -0
  5. data/VERSION +1 -0
  6. data/bin/rubywarrior +8 -0
  7. data/features/levels.feature +50 -0
  8. data/features/profiles.feature +46 -0
  9. data/features/step_definitions/common_steps.rb +7 -0
  10. data/features/step_definitions/interaction_steps.rb +53 -0
  11. data/features/support/env.rb +8 -0
  12. data/features/support/mockio.rb +57 -0
  13. data/features/towers.feature +14 -0
  14. data/lib/ruby_warrior.rb +41 -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 +11 -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 +160 -0
  35. data/lib/ruby_warrior/level.rb +93 -0
  36. data/lib/ruby_warrior/level_loader.rb +52 -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 +86 -0
  40. data/lib/ruby_warrior/space.rb +63 -0
  41. data/lib/ruby_warrior/tower.rb +14 -0
  42. data/lib/ruby_warrior/turn.rb +38 -0
  43. data/lib/ruby_warrior/ui.rb +64 -0
  44. data/lib/ruby_warrior/units/archer.rb +34 -0
  45. data/lib/ruby_warrior/units/base.rb +95 -0
  46. data/lib/ruby_warrior/units/captive.rb +30 -0
  47. data/lib/ruby_warrior/units/sludge.rb +30 -0
  48. data/lib/ruby_warrior/units/thick_sludge.rb +13 -0
  49. data/lib/ruby_warrior/units/warrior.rb +58 -0
  50. data/lib/ruby_warrior/units/wizard.rb +34 -0
  51. data/ruby-warrior.gemspec +191 -0
  52. data/script/console +8 -0
  53. data/spec/fixtures/short-tower/level_001.rb +14 -0
  54. data/spec/fixtures/short-tower/level_002.rb +14 -0
  55. data/spec/fixtures/walking_player.rb +5 -0
  56. data/spec/ruby_warrior/abilities/attack_spec.rb +51 -0
  57. data/spec/ruby_warrior/abilities/base_spec.rb +24 -0
  58. data/spec/ruby_warrior/abilities/bind_spec.rb +19 -0
  59. data/spec/ruby_warrior/abilities/direction_of_spec.rb +13 -0
  60. data/spec/ruby_warrior/abilities/direction_of_stairs_spec.rb +13 -0
  61. data/spec/ruby_warrior/abilities/distance_spec.rb +13 -0
  62. data/spec/ruby_warrior/abilities/explode_spec.rb +21 -0
  63. data/spec/ruby_warrior/abilities/feel_spec.rb +13 -0
  64. data/spec/ruby_warrior/abilities/health_spec.rb +13 -0
  65. data/spec/ruby_warrior/abilities/listen_spec.rb +17 -0
  66. data/spec/ruby_warrior/abilities/look_spec.rb +15 -0
  67. data/spec/ruby_warrior/abilities/pivot_spec.rb +18 -0
  68. data/spec/ruby_warrior/abilities/rescue_spec.rb +40 -0
  69. data/spec/ruby_warrior/abilities/rest_spec.rb +29 -0
  70. data/spec/ruby_warrior/abilities/shoot_spec.rb +22 -0
  71. data/spec/ruby_warrior/abilities/walk_spec.rb +25 -0
  72. data/spec/ruby_warrior/floor_spec.rb +81 -0
  73. data/spec/ruby_warrior/game_spec.rb +96 -0
  74. data/spec/ruby_warrior/level_loader_spec.rb +54 -0
  75. data/spec/ruby_warrior/level_spec.rb +163 -0
  76. data/spec/ruby_warrior/player_generator_spec.rb +12 -0
  77. data/spec/ruby_warrior/position_spec.rb +103 -0
  78. data/spec/ruby_warrior/profile_spec.rb +125 -0
  79. data/spec/ruby_warrior/space_spec.rb +165 -0
  80. data/spec/ruby_warrior/tower_spec.rb +15 -0
  81. data/spec/ruby_warrior/turn_spec.rb +42 -0
  82. data/spec/ruby_warrior/ui_spec.rb +96 -0
  83. data/spec/ruby_warrior/units/archer_spec.rb +23 -0
  84. data/spec/ruby_warrior/units/base_spec.rb +133 -0
  85. data/spec/ruby_warrior/units/captive_spec.rb +34 -0
  86. data/spec/ruby_warrior/units/sludge_spec.rb +27 -0
  87. data/spec/ruby_warrior/units/thick_sludge_spec.rb +19 -0
  88. data/spec/ruby_warrior/units/warrior_spec.rb +60 -0
  89. data/spec/ruby_warrior/units/wizard_spec.rb +23 -0
  90. data/spec/spec_helper.rb +7 -0
  91. data/templates/README.erb +20 -0
  92. data/templates/player.rb +5 -0
  93. data/towers/beginner/level_001.rb +15 -0
  94. data/towers/beginner/level_002.rb +17 -0
  95. data/towers/beginner/level_003.rb +20 -0
  96. data/towers/beginner/level_004.rb +17 -0
  97. data/towers/beginner/level_005.rb +21 -0
  98. data/towers/beginner/level_006.rb +18 -0
  99. data/towers/beginner/level_007.rb +17 -0
  100. data/towers/beginner/level_008.rb +20 -0
  101. data/towers/beginner/level_009.rb +19 -0
  102. data/towers/intermediate/level_001.rb +17 -0
  103. data/towers/intermediate/level_002.rb +19 -0
  104. data/towers/intermediate/level_003.rb +22 -0
  105. data/towers/intermediate/level_004.rb +23 -0
  106. data/towers/intermediate/level_005.rb +18 -0
  107. data/towers/intermediate/level_006.rb +23 -0
  108. metadata +218 -0
@@ -0,0 +1,160 @@
1
+ module RubyWarrior
2
+ class Game
3
+
4
+ def start
5
+ UI.puts "Welcome to Ruby Warrior"
6
+
7
+ if File.exists?(Config.path_prefix + '/.profile')
8
+ @profile = Profile.load(Config.path_prefix + '/.profile')
9
+ else
10
+ make_game_directory unless File.exists?(Config.path_prefix + '/ruby-warrior')
11
+ end
12
+
13
+ if profile.epic?
14
+ UI.delay /= 2 if UI.delay # speed up UI since we're going to be doing a lot here
15
+ profile.current_epic_score = 0
16
+ playing = true
17
+ while playing
18
+ @current_level = @next_level = nil
19
+ profile.level_number += 1
20
+ playing = play_current_level
21
+ end
22
+ profile.save # saves the score for epic mode
23
+ else
24
+ if current_level.number.zero?
25
+ prepare_next_level
26
+ UI.puts "First level has been generated. See the ruby-warrior directory for instructions."
27
+ else
28
+ play_current_level
29
+ end
30
+ end
31
+ end
32
+
33
+ def make_game_directory
34
+ if UI.ask("No ruby-warrior directory found, would you like to create one?")
35
+ Dir.mkdir(Config.path_prefix + '/ruby-warrior')
36
+ else
37
+ UI.puts "Unable to continue without directory."
38
+ exit
39
+ end
40
+ end
41
+
42
+ def play_current_level
43
+ continue = true
44
+ current_level.load_player
45
+ UI.puts "Starting Level #{current_level.number}"
46
+ current_level.play
47
+ if current_level.passed?
48
+ if next_level.exists?
49
+ UI.puts "Success! You have found the stairs."
50
+ else
51
+ UI.puts "CONGRATULATIONS! You have climbed to the top of the tower and rescue the fair maiden Ruby."
52
+ continue = false
53
+ end
54
+ current_level.tally_points
55
+ request_next_level unless profile.epic?
56
+ else
57
+ continue = false
58
+ UI.puts "Sorry, you failed the level. Change your script and try again."
59
+ if current_level.clue && UI.ask("Would you like to read the additional clues for this level?")
60
+ UI.puts current_level.clue
61
+ end
62
+ end
63
+ continue
64
+ end
65
+
66
+ def request_next_level
67
+ if (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?"))
68
+ if next_level.exists?
69
+ prepare_next_level
70
+ UI.puts "See the ruby-warrior directory for the next level README."
71
+ else
72
+ prepare_epic_mode
73
+ UI.puts "Run rubywarrior again to play epic mode."
74
+ end
75
+ else
76
+ UI.puts "Staying on current level. Try to earn more points next time."
77
+ end
78
+ end
79
+
80
+ def prepare_next_level
81
+ next_level.generate_player_files
82
+ profile.level_number += 1
83
+ profile.save # this saves score and new abilities too
84
+ end
85
+
86
+ def prepare_epic_mode
87
+ profile.enable_epic_mode
88
+ profile.level_number = 0
89
+ profile.save # this saves score too
90
+ end
91
+
92
+
93
+ # profiles
94
+
95
+ def profiles
96
+ profile_paths.map { |profile| Profile.load(profile) }
97
+ end
98
+
99
+ def profile_paths
100
+ Dir[Config.path_prefix + '/ruby-warrior/**/.profile']
101
+ end
102
+
103
+ def profile
104
+ @profile ||= choose_profile
105
+ end
106
+
107
+ def new_profile
108
+ profile = Profile.new
109
+ profile.tower_path = UI.choose('tower', towers).path
110
+ profile.warrior_name = UI.request('Enter a name for your warrior: ')
111
+ profile
112
+ end
113
+
114
+
115
+ # towers
116
+
117
+ def towers
118
+ tower_paths.map { |path| Tower.new(path) }
119
+ end
120
+
121
+ def tower_paths
122
+ Dir[File.expand_path(File.dirname(__FILE__) + '/../../towers/*')]
123
+ end
124
+
125
+
126
+ # levels
127
+
128
+ def current_level
129
+ @current_level ||= profile.current_level
130
+ end
131
+
132
+ def next_level
133
+ @next_level ||= profile.next_level
134
+ end
135
+
136
+
137
+ private
138
+
139
+ def choose_profile # REFACTORME
140
+ profile = UI.choose('profile', profiles + [[:new, 'New Profile']])
141
+ if profile == :new
142
+ profile = new_profile
143
+ if profiles.any? { |p| p.player_path == profile.player_path }
144
+ if UI.ask("Are you sure you want to replace your existing profile for this tower?")
145
+ UI.puts("Replacing existing profile.")
146
+ profile
147
+ else
148
+ UI.puts("Not replacing profile.")
149
+ exit
150
+ end
151
+ else
152
+ profile
153
+ end
154
+ else
155
+ profile
156
+ end
157
+ end
158
+
159
+ end
160
+ end
@@ -0,0 +1,93 @@
1
+ module RubyWarrior
2
+ class Level
3
+ attr_reader :profile, :number
4
+ attr_accessor :description, :tip, :clue, :warrior, :floor, :time_bonus
5
+
6
+ def initialize(profile, number)
7
+ @profile = profile
8
+ @number = number
9
+ @time_bonus = 0
10
+ end
11
+
12
+ def player_path
13
+ @profile.player_path
14
+ end
15
+
16
+ def load_path
17
+ @profile.tower_path + "/level_" + @number.to_s.rjust(3, '0') + ".rb"
18
+ end
19
+
20
+ def load_level
21
+ LevelLoader.new(self).instance_eval(File.read(load_path))
22
+ end
23
+
24
+ def load_player
25
+ $: << player_path
26
+ load 'player.rb'
27
+ end
28
+
29
+ def generate_player_files
30
+ load_level
31
+ PlayerGenerator.new(self).generate
32
+ end
33
+
34
+ def play(turns = 1000)
35
+ load_level
36
+ turns.times do |n|
37
+ return if passed? || failed?
38
+ UI.puts "- turn #{n+1} -"
39
+ UI.print @floor.to_map
40
+ @floor.units.each { |unit| unit.prepare_turn }
41
+ @floor.units.each { |unit| unit.perform_turn }
42
+ yield if block_given?
43
+ @time_bonus -= 1 if @time_bonus > 0
44
+ end
45
+ end
46
+
47
+ def tally_points
48
+ score = 0
49
+
50
+ UI.puts "Level Score: #{warrior.score}"
51
+ score += warrior.score
52
+
53
+ UI.puts "Time Bonus: #{time_bonus}"
54
+ score += @time_bonus
55
+
56
+ if floor.other_units.empty?
57
+ UI.puts "Clear Bonus: #{clear_bonus}"
58
+ score += clear_bonus
59
+ end
60
+
61
+ if @profile.epic?
62
+ @profile.current_epic_score += score
63
+ UI.puts "Total Score: #{@profile.current_epic_score}"
64
+ else
65
+ @profile.score += score
66
+ @profile.abilities = warrior.abilities.keys
67
+ UI.puts "Total Score: #{@profile.score}"
68
+ end
69
+ end
70
+
71
+ def clear_bonus
72
+ ((warrior.score + time_bonus)*0.2).round
73
+ end
74
+
75
+ def passed?
76
+ @floor.stairs_space.warrior?
77
+ end
78
+
79
+ def failed?
80
+ !@floor.units.include?(warrior)
81
+ end
82
+
83
+ def exists?
84
+ File.exist? load_path
85
+ end
86
+
87
+ def setup_warrior(warrior)
88
+ @warrior = warrior
89
+ @warrior.add_abilities(*profile.abilities)
90
+ @warrior.name = profile.warrior_name
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,52 @@
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 size(width, height)
26
+ @floor.width = width
27
+ @floor.height = height
28
+ end
29
+
30
+ def stairs(x, y)
31
+ @floor.place_stairs(x, y)
32
+ end
33
+
34
+ def unit(unit, x, y, facing = :north)
35
+ unit = unit_to_constant(unit).new unless unit.kind_of? Units::Base
36
+ @floor.add(unit, x, y, facing)
37
+ yield unit if block_given?
38
+ unit
39
+ end
40
+
41
+ def warrior(*args, &block)
42
+ @level.setup_warrior unit(Units::Warrior.new, *args, &block)
43
+ end
44
+
45
+ private
46
+
47
+ def unit_to_constant(name)
48
+ camel = name.to_s.split('_').map { |s| s.capitalize }.join
49
+ eval("Units::#{camel}")
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'erb'
4
+
5
+ module RubyWarrior
6
+ class PlayerGenerator
7
+ def initialize(level)
8
+ @level = level
9
+ end
10
+
11
+ def level
12
+ @level
13
+ end
14
+
15
+ def previous_level
16
+ @previous_level ||= Level.new(level.profile, level.number-1)
17
+ end
18
+
19
+ # TODO refactor and test this method
20
+ def generate
21
+ if level.number == 1
22
+ FileUtils.mkdir_p(level.player_path)
23
+ FileUtils.cp(templates_path + '/player.rb', level.player_path)
24
+ end
25
+
26
+ File.open(level.player_path + '/README', 'w') do |f|
27
+ f.write read_template(templates_path + '/README.erb')
28
+ end
29
+ end
30
+
31
+ def templates_path
32
+ File.expand_path(File.dirname(__FILE__) + "/../../templates")
33
+ end
34
+
35
+ private
36
+
37
+ def read_template(path)
38
+ ERB.new(File.read(path), nil, '-').result(binding)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,80 @@
1
+ module RubyWarrior
2
+ class Position
3
+ attr_reader :floor
4
+ DIRECTIONS = [:north, :east, :south, :west]
5
+ RELATIVE_DIRECTIONS = [:forward, :right, :backward, :left]
6
+
7
+ def initialize(floor, x, y, direction = nil)
8
+ @floor = floor
9
+ @x = x
10
+ @y = y
11
+ @direction_index = DIRECTIONS.index(direction || :north)
12
+ end
13
+
14
+ def at?(x, y)
15
+ @x == x && @y == y
16
+ end
17
+
18
+ def direction
19
+ DIRECTIONS[@direction_index]
20
+ end
21
+
22
+ def rotate(amount)
23
+ @direction_index += amount
24
+ @direction_index -= 4 while @direction_index > 3
25
+ @direction_index += 4 while @direction_index < 0
26
+ end
27
+
28
+ def relative_space(forward, right = 0)
29
+ @floor.space(*translate_offset(forward, right))
30
+ end
31
+
32
+ def space
33
+ @floor.space(@x, @y)
34
+ end
35
+
36
+ def move(forward, right = 0)
37
+ @x, @y = *translate_offset(forward, right)
38
+ end
39
+
40
+ def distance_from_stairs
41
+ stairs_x, stairs_y = *@floor.stairs_location
42
+ (@x - stairs_x).abs + (@y - stairs_y).abs
43
+ end
44
+
45
+ def relative_direction_of_stairs
46
+ relative_direction_of(@floor.stairs_space)
47
+ end
48
+
49
+ def relative_direction_of(space)
50
+ relative_direction(direction_of(space))
51
+ end
52
+
53
+ def direction_of(space)
54
+ space_x, space_y = *space.location
55
+ if (@x - space_x).abs > (@y - space_y).abs
56
+ space_x > @x ? :east : :west
57
+ else
58
+ space_y > @y ? :south : :north
59
+ end
60
+ end
61
+
62
+ def relative_direction(direction)
63
+ offset = DIRECTIONS.index(direction) - @direction_index
64
+ offset -= 4 while offset > 3
65
+ offset += 4 while offset < 0
66
+ RELATIVE_DIRECTIONS[offset]
67
+ end
68
+
69
+ private
70
+
71
+ def translate_offset(forward, right)
72
+ case direction
73
+ when :north then [@x + right, @y - forward]
74
+ when :east then [@x + forward, @y + right]
75
+ when :south then [@x - right, @y + forward]
76
+ when :west then [@x - forward, @y - right]
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,86 @@
1
+ require 'base64'
2
+
3
+ module RubyWarrior
4
+ class Profile
5
+ attr_accessor :score, :epic_score, :current_epic_score, :abilities, :level_number, :tower_path, :warrior_name, :player_path
6
+
7
+ def initialize
8
+ @tower_path = nil
9
+ @warrior_name = nil
10
+ @score = 0
11
+ @current_epic_score = 0
12
+ @epic_score = 0
13
+ @abilities = []
14
+ @level_number = 0
15
+ end
16
+
17
+ def encode
18
+ Base64.encode64(Marshal.dump(self))
19
+ end
20
+
21
+ def save
22
+ update_epic_score
23
+ @level_number = 0 if epic?
24
+ File.open(player_path + '/.profile', 'w') { |f| f.write(encode) }
25
+ end
26
+
27
+ def self.decode(str)
28
+ Marshal.load(Base64.decode64(str))
29
+ end
30
+
31
+ def self.load(path)
32
+ player = decode(File.read(path))
33
+ player.player_path = File.dirname(path)
34
+ player
35
+ end
36
+
37
+ def player_path
38
+ @player_path || Config.path_prefix + "/ruby-warrior/#{directory_name}"
39
+ end
40
+
41
+ def directory_name
42
+ [warrior_name.downcase.gsub(/[^a-z0-9]+/, '-'), tower.name].join('-')
43
+ end
44
+
45
+ def to_s
46
+ if epic?
47
+ [warrior_name, tower.name, "first score #{score}", "epic score #{epic_score}"].join(' - ')
48
+ else
49
+ [warrior_name, tower.name, "level #{level_number}", "score #{score}"].join(' - ')
50
+ end
51
+ end
52
+
53
+ def tower
54
+ Tower.new(@tower_path)
55
+ end
56
+
57
+ def current_level
58
+ Level.new(self, level_number)
59
+ end
60
+
61
+ def next_level
62
+ Level.new(self, level_number+1)
63
+ end
64
+
65
+ def add_abilities(*abilities)
66
+ @abilities += abilities
67
+ @abilities.uniq!
68
+ end
69
+
70
+ def enable_epic_mode
71
+ @epic = true
72
+ @epic_score ||= 0
73
+ @current_epic_score ||= 0
74
+ end
75
+
76
+ def epic?
77
+ @epic
78
+ end
79
+
80
+ def update_epic_score
81
+ if @current_epic_score > @epic_score
82
+ @epic_score = @current_epic_score
83
+ end
84
+ end
85
+ end
86
+ end