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,203 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'set'
3
+
4
+ describe RubyWarrior::Level do
5
+ before(:each) do
6
+ @profile = RubyWarrior::Profile.new
7
+ @floor = RubyWarrior::Floor.new
8
+ @level = RubyWarrior::Level.new(@profile, 1)
9
+ @level.floor = @floor
10
+ @level.stubs(:failed?).returns(false)
11
+ end
12
+
13
+ it "should consider passed when warrior is on stairs" do
14
+ @level.warrior = RubyWarrior::Units::Warrior.new
15
+ @floor.add(@level.warrior, 0, 0, :north)
16
+ @floor.place_stairs(0, 0)
17
+ @level.should be_passed
18
+ end
19
+
20
+ it "should default time bonus to zero" do
21
+ @level.time_bonus.should be_zero
22
+ end
23
+
24
+ it "should have a grade relative to ace score" do
25
+ @level.ace_score = 100
26
+ @level.grade_for(110).should == "S"
27
+ @level.grade_for(100).should == "S"
28
+ @level.grade_for(99).should == "A"
29
+ @level.grade_for(89).should == "B"
30
+ @level.grade_for(79).should == "C"
31
+ @level.grade_for(69).should == "D"
32
+ @level.grade_for(59).should == "F"
33
+ end
34
+
35
+ it "should have no grade if there is no ace score" do
36
+ @level.ace_score.should be_nil
37
+ @level.grade_for(100).should be_nil
38
+ end
39
+
40
+ it "should load file contents into level" do
41
+ @level.stubs(:load_path).returns('path/to/level.rb')
42
+ File.expects(:read).with('path/to/level.rb').returns("description 'foo'")
43
+ @level.load_level
44
+ @level.description.should == 'foo'
45
+ end
46
+
47
+ it "should have a player path from profile" do
48
+ @profile.stubs(:player_path).returns('path/to/player')
49
+ @level.player_path.should == 'path/to/player'
50
+ end
51
+
52
+ it "should have a load path from profile tower with level number in it" do
53
+ @profile.stubs(:tower_path).returns('path/to/tower')
54
+ @level.load_path.should == 'path/to/tower/level_001.rb'
55
+ end
56
+
57
+ it "should exist if file exists" do
58
+ @level.stubs(:load_path).returns('/foo/bar')
59
+ File.expects(:exist?).with('/foo/bar').returns(true)
60
+ @level.exists?.should be_true
61
+ end
62
+
63
+ it "should load player and player path" do
64
+ @level.stubs(:player_path).returns('player/path')
65
+ $:.expects(:<<).with('player/path')
66
+ @level.expects(:load).with('player.rb')
67
+ @level.load_player
68
+ end
69
+
70
+ it "should generate player files" do
71
+ @level.expects(:load_level)
72
+ generator = stub
73
+ generator.expects(:generate)
74
+ RubyWarrior::PlayerGenerator.expects(:new).with(@level).returns(generator)
75
+ @level.generate_player_files
76
+ end
77
+
78
+ it "should setup warrior with profile abilities" do
79
+ @profile.abilities = [:foo, :bar]
80
+ warrior = stub_everything
81
+ warrior.expects(:add_abilities).with(:foo, :bar)
82
+ @level.setup_warrior(warrior)
83
+ end
84
+
85
+ it "should setup warrior with profile name" do
86
+ @profile.warrior_name = "Joe"
87
+ warrior = stub_everything
88
+ warrior.expects(:name=).with("Joe")
89
+ @level.setup_warrior(warrior)
90
+ end
91
+
92
+ describe "playing" do
93
+ before(:each) do
94
+ @level.stubs(:load_level)
95
+ end
96
+
97
+ it "should load level once when playing multiple turns" do
98
+ @level.expects(:load_level)
99
+ @level.play(2)
100
+ end
101
+
102
+ it "should call prepare_turn and play_turn on each object specified number of times" do
103
+ object = RubyWarrior::Units::Base.new
104
+ object.expects(:prepare_turn).times(2)
105
+ object.expects(:perform_turn).times(2)
106
+ @floor.add(object, 0, 0, :north)
107
+ @level.play(2)
108
+ end
109
+
110
+ it "should return immediately when passed" do
111
+ object = RubyWarrior::Units::Base.new
112
+ object.expects(:turn).times(0)
113
+ @floor.add(object, 0, 0, :north)
114
+ @level.stubs(:passed?).returns(true)
115
+ @level.play(2)
116
+ end
117
+
118
+ it "should yield to block in play method for each turn" do
119
+ int = 0
120
+ @level.play(2) do
121
+ int += 1
122
+ end
123
+ int.should == 2
124
+ end
125
+
126
+ it "should count down time_bonus once each turn" do
127
+ @level.time_bonus = 10
128
+ @level.play(3)
129
+ @level.time_bonus.should == 7
130
+ end
131
+
132
+ it "should count down time_bonus below 0" do
133
+ @level.time_bonus = 2
134
+ @level.play(5)
135
+ @level.time_bonus.should be_zero
136
+ end
137
+
138
+ it "should have a pretty score calculation" do
139
+ @level.score_calculation(123, 45).should == "123 + 45 = 168"
140
+ end
141
+
142
+ it "should not have a score calculation when starting score is zero" do
143
+ @level.score_calculation(0, 45).should == "45"
144
+ end
145
+ end
146
+
147
+ describe "tallying points" do
148
+ before(:each) do
149
+ @warrior = stub(:score => 0, :abilities => {})
150
+ @level.stubs(:warrior).returns(@warrior)
151
+ @level.floor.stubs(:other_units).returns([stub])
152
+ end
153
+
154
+ it "should add warrior score to profile" do
155
+ @warrior.stubs(:score).returns(30)
156
+ @level.tally_points
157
+ @profile.score.should == 30
158
+ end
159
+
160
+ it "should add warrior score to profile for epic mode" do
161
+ @profile.enable_epic_mode
162
+ @warrior.stubs(:score).returns(30)
163
+ @level.tally_points
164
+ @profile.current_epic_score.should == 30
165
+ end
166
+
167
+ it "should add level grade percent to profile for epic mode" do
168
+ @level.ace_score = 100
169
+ @profile.enable_epic_mode
170
+ @warrior.stubs(:score).returns(30)
171
+ @level.tally_points
172
+ @profile.current_epic_grades.should == { 1 => 0.3 }
173
+ end
174
+
175
+ it "should not add level grade if ace score is not set" do
176
+ @level.ace_score = nil
177
+ @profile.enable_epic_mode
178
+ @warrior.stubs(:score).returns(30)
179
+ @level.tally_points
180
+ @profile.current_epic_grades.should == {}
181
+ end
182
+
183
+ it "should apply warrior abilities to profile" do
184
+ @warrior.stubs(:abilities).returns({:foo => nil, :bar => nil})
185
+ @level.tally_points
186
+ @profile.abilities.to_set.should == [:foo, :bar].to_set
187
+ end
188
+
189
+ it "should apply time bonus to profile score" do
190
+ @level.time_bonus = 20
191
+ @level.tally_points
192
+ @profile.score.should == 20
193
+ end
194
+
195
+ it "should give 20% bonus when no other units left" do
196
+ @level.floor.stubs(:other_units).returns([])
197
+ @warrior.stubs(:score).returns(10)
198
+ @level.time_bonus = 10
199
+ @level.tally_points
200
+ @profile.score.should == 24
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe RubyWarrior::PlayerGenerator do
4
+ before(:each) do
5
+ @level = RubyWarrior::Level.new(RubyWarrior::Profile.new, 15)
6
+ @generator = RubyWarrior::PlayerGenerator.new(@level)
7
+ end
8
+
9
+ it "should know templates path" do
10
+ @generator.templates_path.should == File.expand_path(File.dirname(__FILE__) + "/../../templates")
11
+ end
12
+ end
@@ -0,0 +1,103 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe RubyWarrior::Position do
4
+ before(:each) do
5
+ @unit = RubyWarrior::Units::Base.new
6
+ @floor = RubyWarrior::Floor.new
7
+ @floor.width = 6
8
+ @floor.height = 5
9
+ @floor.add(@unit, 1, 2, :north)
10
+ @position = @unit.position
11
+ end
12
+
13
+ it "should rotate clockwise" do
14
+ @position.direction.should == :north
15
+ [:east, :south, :west, :north, :east].each do |dir|
16
+ @position.rotate(1)
17
+ @position.direction.should == dir
18
+ end
19
+ end
20
+
21
+ it "should rotate counterclockwise" do
22
+ @position.direction.should == :north
23
+ [:west, :south, :east, :north, :west].each do |dir|
24
+ @position.rotate(-1)
25
+ @position.direction.should == dir
26
+ end
27
+ end
28
+
29
+ it "should get relative space in front" do
30
+ unit = RubyWarrior::Units::Base.new
31
+ @floor.add(unit, 1, 1)
32
+ @position.relative_space(1).should_not be_empty
33
+ end
34
+
35
+ it "should get relative object in front when rotated" do
36
+ unit = RubyWarrior::Units::Base.new
37
+ @floor.add(unit, 2, 2)
38
+ @position.rotate(1)
39
+ @position.relative_space(1).should_not be_empty
40
+ end
41
+
42
+ it "should get relative object diagonally" do
43
+ unit = RubyWarrior::Units::Base.new
44
+ @floor.add(unit, 0, 1)
45
+ @position.relative_space(1, -1).should_not be_empty
46
+ end
47
+
48
+ it "should get relative object diagonally when rotating" do
49
+ unit = RubyWarrior::Units::Base.new
50
+ @floor.add(unit, 0, 1)
51
+ @position.rotate(2)
52
+ @position.relative_space(-1, 1).should_not be_empty
53
+ end
54
+
55
+ it "should move object on floor relatively" do
56
+ @floor.get(1, 2).should == @unit
57
+ @position.move(-1, 2)
58
+ @floor.get(1, 2).should be_nil
59
+ @floor.get(3, 3).should == @unit
60
+ @position.rotate(1)
61
+ @position.move(-1)
62
+ @floor.get(3, 3).should be_nil
63
+ @floor.get(2, 3).should == @unit
64
+ end
65
+
66
+ it "should return distance from stairs as 0 when on stairs" do
67
+ @floor.place_stairs(1, 2)
68
+ @position.distance_from_stairs.should == 0
69
+ end
70
+
71
+ it "should return distance from stairs in both directions" do
72
+ @floor.place_stairs(0, 3)
73
+ @position.distance_from_stairs.should == 2
74
+ end
75
+
76
+ it "should return relative direction of stairs" do
77
+ @floor.place_stairs(0, 0)
78
+ @position.relative_direction_of_stairs.should == :forward
79
+ end
80
+
81
+ it "should return relative direction of given space" do
82
+ @position.relative_direction_of(@floor.space(5, 3)).should == :right
83
+ @position.rotate 1
84
+ @position.relative_direction_of(@floor.space(1, 4)).should == :right
85
+ end
86
+
87
+ it "should be able to determine relative direction" do
88
+ @position.relative_direction(:north).should == :forward
89
+ @position.relative_direction(:south).should == :backward
90
+ @position.relative_direction(:west).should == :left
91
+ @position.relative_direction(:east).should == :right
92
+ @position.rotate 1
93
+ @position.relative_direction(:north).should == :left
94
+ @position.rotate 1
95
+ @position.relative_direction(:north).should == :backward
96
+ @position.rotate 1
97
+ @position.relative_direction(:north).should == :right
98
+ end
99
+
100
+ it "should return a space at the same location as position" do
101
+ @position.space.location.should == [1, 2]
102
+ end
103
+ end
@@ -0,0 +1,144 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe RubyWarrior::Profile do
4
+ before(:each) do
5
+ @profile = RubyWarrior::Profile.new
6
+ end
7
+
8
+ it "should have warrior name" do
9
+ @profile.warrior_name = 'Joe'
10
+ @profile.warrior_name.should == 'Joe'
11
+ end
12
+
13
+ it "should start level number at 0" do
14
+ @profile.level_number.should be_zero
15
+ end
16
+
17
+ it "should start score at 0 and allow it to increment" do
18
+ @profile.score.should be_zero
19
+ @profile.score += 5
20
+ @profile.score.should == 5
21
+ end
22
+
23
+ it "should have no abilities and allow adding" do
24
+ @profile.abilities.should == []
25
+ @profile.abilities += [:foo, :bar]
26
+ @profile.abilities.should == [:foo, :bar]
27
+ end
28
+
29
+ it "should encode with marshal + base64" do
30
+ @profile.encode.should == Base64.encode64(Marshal.dump(@profile))
31
+ end
32
+
33
+ it "should decode with marshal + base64" do
34
+ RubyWarrior::Profile.decode(@profile.encode).warrior_name.should == @profile.warrior_name
35
+ end
36
+
37
+ it "load should read file, decode and set player path" do
38
+ profile = 'profile'
39
+ profile.expects(:player_path=).with('path/to')
40
+ File.expects(:read).with('path/to/.profile').returns('encoded_profile')
41
+ RubyWarrior::Profile.expects(:decode).with('encoded_profile').returns(profile)
42
+ RubyWarrior::Profile.load('path/to/.profile').should == profile
43
+ end
44
+
45
+
46
+ it "should add abilities and remove duplicates" do
47
+ @profile.add_abilities(:foo, :bar, :blah, :bar)
48
+ @profile.abilities.should == [:foo, :bar, :blah]
49
+ end
50
+
51
+ it "should fetch new level with current number" do
52
+ @profile.level_number = 1
53
+ @profile.current_level.number.should == 1
54
+ end
55
+
56
+ it "should fetch next level" do
57
+ @profile.level_number = 1
58
+ @profile.next_level.number.should == 2
59
+ end
60
+
61
+ it "should enable epic mode and reset scores if nil" do
62
+ @profile.epic_score = nil
63
+ @profile.current_epic_score = nil
64
+ @profile.enable_epic_mode
65
+ @profile.should be_epic
66
+ @profile.epic_score.should be_zero
67
+ @profile.current_epic_score.should be_zero
68
+ end
69
+
70
+ it "should override epic score with current one if it is higher" do
71
+ @profile.enable_epic_mode
72
+ @profile.epic_score.should be_zero
73
+ @profile.average_grade.should be_nil
74
+ @profile.current_epic_score = 123
75
+ @profile.current_epic_grades = { 1 => 0.7, 2 => 0.9 }
76
+ @profile.update_epic_score
77
+ @profile.epic_score.should == 123
78
+ @profile.average_grade.should == 0.8
79
+ end
80
+
81
+ it "should not override epic score with current one if it is lower" do
82
+ @profile.enable_epic_mode
83
+ @profile.epic_score = 124
84
+ @profile.average_grade = 0.9
85
+ @profile.current_epic_score = 123
86
+ @profile.current_epic_grades = { 1 => 0.7, 2 => 0.9 }
87
+ @profile.update_epic_score
88
+ @profile.epic_score.should == 124
89
+ @profile.average_grade.should == 0.9
90
+ end
91
+
92
+ it "should not calculate average grade if no grades are present" do
93
+ @profile.enable_epic_mode
94
+ @profile.current_epic_grades = {}
95
+ @profile.calculate_average_grade.should be_nil
96
+ end
97
+
98
+ describe "with tower path" do
99
+ before(:each) do
100
+ @profile.warrior_name = "John Smith"
101
+ @profile.tower_path = 'path/to/tower'
102
+ end
103
+
104
+ it "save should write file with encoded profile" do
105
+ file = stub
106
+ file.expects(:write).with('encoded_profile')
107
+ File.expects(:open).with(@profile.player_path + '/.profile', 'w').yields(file)
108
+ @profile.expects(:encode).returns('encoded_profile')
109
+ @profile.save
110
+ end
111
+
112
+ it "should have a nice string representation" do
113
+ @profile.warrior_name = 'Joe'
114
+ @profile.to_s.should == "Joe - tower - level 0 - score 0"
115
+ end
116
+
117
+ it "should include epic score in string representation" do
118
+ @profile.warrior_name = 'Joe'
119
+ @profile.enable_epic_mode
120
+ @profile.to_s.should == "Joe - tower - first score 0 - epic score 0"
121
+ end
122
+
123
+ it "should include epic score with grade in string representation" do
124
+ @profile.warrior_name = 'Joe'
125
+ @profile.enable_epic_mode
126
+ @profile.average_grade = 0.7
127
+ @profile.to_s.should == "Joe - tower - first score 0 - epic score 0 (C)"
128
+ end
129
+
130
+ it "should guess at the player path" do
131
+ @profile.player_path.should == './rubywarrior/john-smith-tower'
132
+ end
133
+
134
+ it "should use specified player path" do
135
+ @profile.player_path = "path/to/player"
136
+ @profile.player_path.should == "path/to/player"
137
+ end
138
+
139
+ it "should load tower from path" do
140
+ RubyWarrior::Tower.expects(:new).with('path/to/tower').returns('tower')
141
+ @profile.tower.should == 'tower'
142
+ end
143
+ end
144
+ end