colstrom-rtanque 0.1.3

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 (133) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +10 -0
  7. data/.yardopts +4 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.ci +6 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +168 -0
  12. data/Rakefile +1 -0
  13. data/bin/rtanque +117 -0
  14. data/lib/rtanque.rb +31 -0
  15. data/lib/rtanque/arena.rb +8 -0
  16. data/lib/rtanque/bot.rb +117 -0
  17. data/lib/rtanque/bot/brain.rb +50 -0
  18. data/lib/rtanque/bot/brain_helper.rb +23 -0
  19. data/lib/rtanque/bot/command.rb +23 -0
  20. data/lib/rtanque/bot/radar.rb +54 -0
  21. data/lib/rtanque/bot/sensors.rb +37 -0
  22. data/lib/rtanque/bot/turret.rb +14 -0
  23. data/lib/rtanque/configuration.rb +47 -0
  24. data/lib/rtanque/explosion.rb +23 -0
  25. data/lib/rtanque/gui.rb +25 -0
  26. data/lib/rtanque/gui/bot.rb +71 -0
  27. data/lib/rtanque/gui/bot/health_color_calculator.rb +37 -0
  28. data/lib/rtanque/gui/draw_group.rb +30 -0
  29. data/lib/rtanque/gui/explosion.rb +25 -0
  30. data/lib/rtanque/gui/shell.rb +31 -0
  31. data/lib/rtanque/gui/window.rb +64 -0
  32. data/lib/rtanque/heading.rb +162 -0
  33. data/lib/rtanque/match.rb +73 -0
  34. data/lib/rtanque/match/tick_group.rb +50 -0
  35. data/lib/rtanque/movable.rb +51 -0
  36. data/lib/rtanque/normalized_attr.rb +69 -0
  37. data/lib/rtanque/point.rb +140 -0
  38. data/lib/rtanque/runner.rb +88 -0
  39. data/lib/rtanque/shell.rb +44 -0
  40. data/lib/rtanque/version.rb +3 -0
  41. data/resources/images/body.png +0 -0
  42. data/resources/images/bullet.png +0 -0
  43. data/resources/images/explosions/explosion2-1.png +0 -0
  44. data/resources/images/explosions/explosion2-10.png +0 -0
  45. data/resources/images/explosions/explosion2-11.png +0 -0
  46. data/resources/images/explosions/explosion2-12.png +0 -0
  47. data/resources/images/explosions/explosion2-13.png +0 -0
  48. data/resources/images/explosions/explosion2-14.png +0 -0
  49. data/resources/images/explosions/explosion2-15.png +0 -0
  50. data/resources/images/explosions/explosion2-16.png +0 -0
  51. data/resources/images/explosions/explosion2-17.png +0 -0
  52. data/resources/images/explosions/explosion2-18.png +0 -0
  53. data/resources/images/explosions/explosion2-19.png +0 -0
  54. data/resources/images/explosions/explosion2-2.png +0 -0
  55. data/resources/images/explosions/explosion2-20.png +0 -0
  56. data/resources/images/explosions/explosion2-21.png +0 -0
  57. data/resources/images/explosions/explosion2-22.png +0 -0
  58. data/resources/images/explosions/explosion2-23.png +0 -0
  59. data/resources/images/explosions/explosion2-24.png +0 -0
  60. data/resources/images/explosions/explosion2-25.png +0 -0
  61. data/resources/images/explosions/explosion2-26.png +0 -0
  62. data/resources/images/explosions/explosion2-27.png +0 -0
  63. data/resources/images/explosions/explosion2-28.png +0 -0
  64. data/resources/images/explosions/explosion2-29.png +0 -0
  65. data/resources/images/explosions/explosion2-3.png +0 -0
  66. data/resources/images/explosions/explosion2-30.png +0 -0
  67. data/resources/images/explosions/explosion2-31.png +0 -0
  68. data/resources/images/explosions/explosion2-32.png +0 -0
  69. data/resources/images/explosions/explosion2-33.png +0 -0
  70. data/resources/images/explosions/explosion2-34.png +0 -0
  71. data/resources/images/explosions/explosion2-35.png +0 -0
  72. data/resources/images/explosions/explosion2-36.png +0 -0
  73. data/resources/images/explosions/explosion2-37.png +0 -0
  74. data/resources/images/explosions/explosion2-38.png +0 -0
  75. data/resources/images/explosions/explosion2-39.png +0 -0
  76. data/resources/images/explosions/explosion2-4.png +0 -0
  77. data/resources/images/explosions/explosion2-40.png +0 -0
  78. data/resources/images/explosions/explosion2-41.png +0 -0
  79. data/resources/images/explosions/explosion2-42.png +0 -0
  80. data/resources/images/explosions/explosion2-43.png +0 -0
  81. data/resources/images/explosions/explosion2-44.png +0 -0
  82. data/resources/images/explosions/explosion2-45.png +0 -0
  83. data/resources/images/explosions/explosion2-46.png +0 -0
  84. data/resources/images/explosions/explosion2-47.png +0 -0
  85. data/resources/images/explosions/explosion2-48.png +0 -0
  86. data/resources/images/explosions/explosion2-49.png +0 -0
  87. data/resources/images/explosions/explosion2-5.png +0 -0
  88. data/resources/images/explosions/explosion2-50.png +0 -0
  89. data/resources/images/explosions/explosion2-51.png +0 -0
  90. data/resources/images/explosions/explosion2-52.png +0 -0
  91. data/resources/images/explosions/explosion2-53.png +0 -0
  92. data/resources/images/explosions/explosion2-54.png +0 -0
  93. data/resources/images/explosions/explosion2-55.png +0 -0
  94. data/resources/images/explosions/explosion2-56.png +0 -0
  95. data/resources/images/explosions/explosion2-57.png +0 -0
  96. data/resources/images/explosions/explosion2-58.png +0 -0
  97. data/resources/images/explosions/explosion2-59.png +0 -0
  98. data/resources/images/explosions/explosion2-6.png +0 -0
  99. data/resources/images/explosions/explosion2-60.png +0 -0
  100. data/resources/images/explosions/explosion2-61.png +0 -0
  101. data/resources/images/explosions/explosion2-62.png +0 -0
  102. data/resources/images/explosions/explosion2-63.png +0 -0
  103. data/resources/images/explosions/explosion2-64.png +0 -0
  104. data/resources/images/explosions/explosion2-65.png +0 -0
  105. data/resources/images/explosions/explosion2-66.png +0 -0
  106. data/resources/images/explosions/explosion2-67.png +0 -0
  107. data/resources/images/explosions/explosion2-68.png +0 -0
  108. data/resources/images/explosions/explosion2-69.png +0 -0
  109. data/resources/images/explosions/explosion2-7.png +0 -0
  110. data/resources/images/explosions/explosion2-70.png +0 -0
  111. data/resources/images/explosions/explosion2-71.png +0 -0
  112. data/resources/images/explosions/explosion2-8.png +0 -0
  113. data/resources/images/explosions/explosion2-9.png +0 -0
  114. data/resources/images/grass.png +0 -0
  115. data/resources/images/radar.png +0 -0
  116. data/resources/images/turret.png +0 -0
  117. data/rtanque.gemspec +34 -0
  118. data/sample_bots/camper.rb +79 -0
  119. data/sample_bots/keyboard.rb +50 -0
  120. data/sample_bots/seek_and_destroy.rb +53 -0
  121. data/screenshots/battle_1.png +0 -0
  122. data/screenshots/battle_2.png +0 -0
  123. data/spec/rtanque/bot_spec.rb +239 -0
  124. data/spec/rtanque/heading_spec.rb +279 -0
  125. data/spec/rtanque/match_spec.rb +46 -0
  126. data/spec/rtanque/normalized_attr_spec.rb +90 -0
  127. data/spec/rtanque/point_spec.rb +196 -0
  128. data/spec/rtanque/radar_spec.rb +87 -0
  129. data/spec/rtanque/shell_spec.rb +35 -0
  130. data/spec/rtanque_spec.rb +34 -0
  131. data/spec/spec_helper.rb +51 -0
  132. data/templates/bot.erb +17 -0
  133. metadata +315 -0
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rtanque/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "colstrom-rtanque"
8
+ gem.version = RTanque::VERSION
9
+ gem.authors = ["Adam Williams"]
10
+ gem.email = ["pwnfactory@gmail.com"]
11
+ gem.summary = %q{RTanque is a game for programmers. Players program the brain of a tank and then send their tank into battle against other tanks.}
12
+ gem.description = %q{RTanque is a game for programmers. Players program the brain of a tank and then send their tank+brain into battle with other tanks. All tanks are otherwise equal.
13
+
14
+ Rules of the game are simple: Last bot standing wins. Gameplay is also pretty simple. Each tank has a base, turret and radar, each of which rotate independently. The base moves the tank, the turret has a gun mounted to it which can fire at other tanks, and the radar detects other tanks in its field of vision.
15
+
16
+ Have fun competing against friends' tanks or the sample ones included. Maybe you'll start a small league at your local Ruby meetup.}
17
+ gem.homepage = "https://github.com/awilliams/RTanque"
18
+ gem.license = 'MIT'
19
+
20
+ gem.files = `git ls-files`.split($/)
21
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23
+ gem.require_paths = ["lib"]
24
+
25
+ gem.add_dependency 'gosu', '~> 0.10', '>= 0.7.45'
26
+ gem.add_dependency 'configuration', '~> 1.3.2'
27
+ gem.add_dependency 'octokit', '~> 2.7.0'
28
+ gem.add_dependency 'thor', '~> 0.17.0'
29
+ gem.add_dependency 'texplay', '~> 0.4', '>= 0.4.3'
30
+
31
+ gem.add_development_dependency 'pry'
32
+ gem.add_development_dependency 'rspec', '~> 2.13.0'
33
+ gem.add_development_dependency 'rspec-mocks', '~> 2.13.0'
34
+ end
@@ -0,0 +1,79 @@
1
+ # Camper: Sample Bot
2
+ #
3
+ # Enjoys sitting in the corners and firing powerful shots
4
+ class Camper < RTanque::Bot::Brain
5
+ NAME = 'Camper'
6
+ include RTanque::Bot::BrainHelper
7
+
8
+ CORNERS = [:NW, :NE, :SE, :SW]
9
+ TURRET_FIRE_RANGE = RTanque::Heading::ONE_DEGREE * 1.0
10
+ SWITCH_CORNER_TICK_RANGE = (600..1000)
11
+
12
+ def tick!
13
+ self.hide_in_corners
14
+ if (target = self.nearest_target)
15
+ self.fire_upon(target)
16
+ else
17
+ self.detect_targets
18
+ end
19
+ end
20
+
21
+ def fire_upon(target)
22
+ self.command.radar_heading = target.heading
23
+ self.command.turret_heading = target.heading
24
+ if self.sensors.turret_heading.delta(target.heading).abs < TURRET_FIRE_RANGE
25
+ self.command.fire(MAX_FIRE_POWER)
26
+ end
27
+ end
28
+
29
+ def nearest_target
30
+ self.sensors.radar.min { |a,b| a.distance <=> b.distance }
31
+ end
32
+
33
+ def detect_targets
34
+ self.command.radar_heading = self.sensors.radar_heading + MAX_RADAR_ROTATION
35
+ self.command.turret_heading = self.sensors.heading + RTanque::Heading::HALF_ANGLE
36
+ end
37
+
38
+ def hide_in_corners
39
+ @corner_cycle ||= CORNERS.shuffle.cycle
40
+ self.at_tick_interval(self.camp_interval) {
41
+ self.corner = @corner_cycle.next
42
+ self.reset_camp_interval
43
+ }
44
+ self.corner ||= @corner_cycle.next
45
+ self.move_to_corner
46
+ end
47
+
48
+ def move_to_corner
49
+ if self.corner
50
+ command.heading = self.sensors.position.heading(RTanque::Point.new(*self.corner, self.arena))
51
+ command.speed = MAX_BOT_SPEED
52
+ end
53
+ end
54
+
55
+ def corner=(corner_name)
56
+ @corner = case corner_name
57
+ when :NE
58
+ [self.arena.width, self.arena.height]
59
+ when :SE
60
+ [self.arena.width, 0]
61
+ when :SW
62
+ [0, 0]
63
+ else
64
+ [0, self.arena.height]
65
+ end
66
+ end
67
+
68
+ def corner
69
+ @corner
70
+ end
71
+
72
+ def camp_interval
73
+ @camp_interval ||= self.reset_camp_interval
74
+ end
75
+
76
+ def reset_camp_interval
77
+ @camp_interval = rand(SWITCH_CORNER_TICK_RANGE.max - SWITCH_CORNER_TICK_RANGE.min) + SWITCH_CORNER_TICK_RANGE.min
78
+ end
79
+ end
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # Keyboard: Sample Bot
3
+ #
4
+ # Special bot controlled with the keyboard
5
+ #
6
+ # Controls:
7
+ # w drive forward
8
+ # s drive backwards
9
+ # a rotate left
10
+ # d rotate right
11
+ #
12
+ # ↑ fire powerful shot
13
+ # ↓ fire week shot
14
+ # ← rotate turret left
15
+ # → rotate turret right
16
+ class Keyboard < RTanque::Bot::Brain
17
+ include RTanque::Bot::BrainHelper
18
+
19
+ NAME = 'Keyboard'
20
+
21
+ def tick!
22
+ command.radar_heading = sensors.radar_heading + (RTanque::Heading::ONE_DEGREE * 30)
23
+
24
+ if sensors.button_down?(Gosu::KbLeft)
25
+ command.turret_heading = sensors.turret_heading - (RTanque::Heading::ONE_DEGREE * 10)
26
+ elsif sensors.button_down?(Gosu::KbRight)
27
+ command.turret_heading = sensors.turret_heading + (RTanque::Heading::ONE_DEGREE * 10)
28
+ end
29
+
30
+ if sensors.button_down?('a')
31
+ command.heading = sensors.heading - (RTanque::Heading::ONE_DEGREE * 10)
32
+ elsif sensors.button_down?('d')
33
+ command.heading = sensors.heading + (RTanque::Heading::ONE_DEGREE * 10)
34
+ end
35
+
36
+ if sensors.button_down?('w')
37
+ command.speed = 10
38
+ elsif sensors.button_down?('s')
39
+ command.speed = -10
40
+ else
41
+ command.speed = 0
42
+ end
43
+
44
+ if sensors.button_down?(Gosu::KbUp)
45
+ command.fire_power = 5
46
+ elsif sensors.button_down?(Gosu::KbDown)
47
+ command.fire_power = 1
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,53 @@
1
+ # Seek&Destroy: Sample Bot
2
+ #
3
+ # Enjoys following and target and firing many shots
4
+ class SeekAndDestroy < RTanque::Bot::Brain
5
+ NAME = 'Seek&Destroy'
6
+ include RTanque::Bot::BrainHelper
7
+
8
+ TURRET_FIRE_RANGE = RTanque::Heading::ONE_DEGREE * 5.0
9
+
10
+ def tick!
11
+ @desired_heading ||= nil
12
+
13
+ if (lock = self.get_radar_lock)
14
+ self.destroy_lock(lock)
15
+ @desired_heading = nil
16
+ else
17
+ self.seek_lock
18
+ end
19
+ end
20
+
21
+ def destroy_lock(reflection)
22
+ command.heading = reflection.heading
23
+ command.radar_heading = reflection.heading
24
+ command.turret_heading = reflection.heading
25
+ command.speed = reflection.distance > 200 ? MAX_BOT_SPEED : MAX_BOT_SPEED / 2.0
26
+ if (reflection.heading.delta(sensors.turret_heading)).abs < TURRET_FIRE_RANGE
27
+ command.fire(reflection.distance > 200 ? MAX_FIRE_POWER : MIN_FIRE_POWER)
28
+ end
29
+ end
30
+
31
+ def seek_lock
32
+ if sensors.position.on_wall?
33
+ @desired_heading = sensors.heading + RTanque::Heading::HALF_ANGLE
34
+ end
35
+ command.radar_heading = sensors.radar_heading + MAX_RADAR_ROTATION
36
+ command.speed = 1
37
+ if @desired_heading
38
+ command.heading = @desired_heading
39
+ command.turret_heading = @desired_heading
40
+ end
41
+ end
42
+
43
+ def get_radar_lock
44
+ @locked_on ||= nil
45
+ lock = if @locked_on
46
+ sensors.radar.find { |reflection| reflection.name == @locked_on } || sensors.radar.first
47
+ else
48
+ sensors.radar.first
49
+ end
50
+ @locked_on = lock.name if lock
51
+ lock
52
+ end
53
+ end
Binary file
Binary file
@@ -0,0 +1,239 @@
1
+ require "spec_helper"
2
+
3
+ describe RTanque::Bot do
4
+ before(:each) { @brain_tick_lambda = lambda { } }
5
+ let(:bot){ brain_bot(&@brain_tick_lambda) }
6
+
7
+ context '#dead?' do
8
+ it 'should not initially be dead' do
9
+ expect(bot.dead?).to be_false
10
+ end
11
+
12
+ it 'should be true if health is below min' do
13
+ bot.health = RTanque::Configuration.bot.health.min - 1
14
+ expect(bot.dead?).to be_true
15
+ end
16
+ end
17
+
18
+ context '#sensors' do
19
+ it 'should return a Sensors instance' do
20
+ expect(bot.sensors).to be_instance_of RTanque::Bot::Sensors
21
+ end
22
+
23
+ it 'should correctly transfer values from bot' do
24
+ bot.health = 5
25
+ bot.speed = -2
26
+ expect(bot.sensors.health).to eq 5
27
+ expect(bot.sensors.speed).to eq -2
28
+ end
29
+ end
30
+
31
+ context '#tick' do
32
+ context 'no commands' do
33
+ it 'should not update bot position, heading on tick' do
34
+ bot.tick
35
+ expect(bot.position.x).to eq 0.0
36
+ expect(bot.position.y).to eq 0.0
37
+ expect(bot.heading).to eq 0
38
+ end
39
+ end
40
+
41
+ context 'command with speed' do
42
+ before(:each) do
43
+ bot.speed = 1
44
+ bot.heading = 0
45
+ end
46
+
47
+ it 'should update bot position on tick' do
48
+ bot.tick
49
+ expect(bot.position.x).to eq 0.0
50
+ expect(bot.position.y).to eq 1.0
51
+ end
52
+
53
+ it 'should keep updating bot position' do
54
+ 5.times { bot.tick }
55
+ expect(bot.position.x).to eq 0.0
56
+ expect(bot.position.y).to eq 5.0
57
+ end
58
+
59
+ it 'should stop at arena limit' do
60
+ (@arena.height + 2).times { bot.tick }
61
+ expect(bot.position.x).to eq 0.0
62
+ expect(bot.position.y).to eq @arena.height
63
+ end
64
+ end
65
+
66
+ context 'command with heading' do
67
+ before(:each) do
68
+ bot.heading = RTanque::Heading::EAST
69
+ end
70
+
71
+ it 'should have heading east' do
72
+ bot.tick
73
+ expect(bot.heading).to eq RTanque::Heading::EAST
74
+ end
75
+
76
+ it 'should keep heading east' do
77
+ 5.times { bot.tick }
78
+ expect(bot.heading).to eq RTanque::Heading::EAST
79
+ end
80
+
81
+ it 'should maintain heading given null heading' do
82
+ bot.tick
83
+ @brain_tick_lambda = lambda { command.heading = nil }
84
+ expect(bot.heading).to eq RTanque::Heading::EAST
85
+ end
86
+
87
+ it 'should not change radar and turret headings' do
88
+ bot.tick
89
+ expect(bot.radar.heading).to eq 0.0
90
+ expect(bot.turret.heading).to eq 0.0
91
+ end
92
+ end
93
+
94
+ context 'radar heading' do
95
+ before(:each) do
96
+ bot.radar.heading = RTanque::Heading::EAST
97
+ bot.tick
98
+ end
99
+
100
+ it 'should change radar heading' do
101
+ expect(bot.radar.heading).to eq RTanque::Heading::EAST
102
+ end
103
+
104
+ it 'should not change bot heading' do
105
+ expect(bot.heading).to eq 0
106
+ end
107
+ end
108
+
109
+ context 'turret heading' do
110
+ before(:each) do
111
+ bot.turret.heading = RTanque::Heading::EAST
112
+ bot.tick
113
+ end
114
+
115
+ it 'should change radar heading' do
116
+ expect(bot.turret.heading).to eq RTanque::Heading::EAST
117
+ end
118
+
119
+ it 'should not change bot heading' do
120
+ expect(bot.heading).to eq 0
121
+ end
122
+ end
123
+
124
+ context 'fire power' do
125
+ it 'bot should have 0 fire_power' do
126
+ bot.tick
127
+ expect(bot.fire_power).to eq 0
128
+ end
129
+
130
+ it 'bot should have fire_power reset' do
131
+ bot.fire_power = 1
132
+ bot.tick
133
+ expect(bot.fire_power).to eq 0
134
+ end
135
+ end
136
+
137
+ context 'command with error' do
138
+ before(:each) do
139
+ @brain_tick_lambda = lambda { raise 'oops' }
140
+ end
141
+
142
+ it 'should capture error' do
143
+ expect{ bot.tick }.not_to raise_exception
144
+ end
145
+
146
+ it 'should reduce bot health' do
147
+ original_health = bot.health
148
+ bot.tick
149
+ expect(bot.health).to be < original_health
150
+ end
151
+ end
152
+ end
153
+
154
+ context 'bot command speed' do
155
+ before(:each) do
156
+ @brain_tick_lambda = lambda { command.speed = RTanque::Bot::MAX_SPEED + 1 }
157
+ bot.tick
158
+ end
159
+
160
+ it 'should respect step size' do
161
+ expect(bot.speed).to eq RTanque::Configuration.bot.speed_step
162
+ end
163
+
164
+ it 'should respect max speed' do
165
+ times = RTanque::Bot::MAX_SPEED / RTanque::Configuration.bot.speed_step
166
+ (times + 1).to_i.times { bot.tick }
167
+ expect(bot.speed).to eq RTanque::Bot::MAX_SPEED
168
+ end
169
+
170
+ it 'should respect min speed' do
171
+ @brain_tick_lambda = lambda { command.speed = -(RTanque::Bot::MAX_SPEED + 1) }
172
+ times = RTanque::Bot::MAX_SPEED / RTanque::Configuration.bot.speed_step
173
+ (times + 1).to_i.times { bot.tick }
174
+ expect(bot.speed).to eq RTanque::Bot::MAX_SPEED
175
+ end
176
+ end
177
+
178
+ context 'bot command heading' do
179
+ it 'should respect step size' do
180
+ @brain_tick_lambda = lambda { command.heading = RTanque::Heading::S }
181
+ bot.tick
182
+ expect(bot.heading).to eq RTanque::Heading.new(RTanque::Configuration.bot.turn_step)
183
+ end
184
+
185
+ it 'should respect step size in negative' do
186
+ @brain_tick_lambda = lambda { command.heading = -RTanque::Heading::S }
187
+ bot.tick
188
+ expect(bot.heading).to eq RTanque::Heading.new(-RTanque::Configuration.bot.turn_step)
189
+ end
190
+ end
191
+
192
+ context 'bot command radar heading' do
193
+ it 'should respect step size' do
194
+ @brain_tick_lambda = lambda { command.radar_heading = RTanque::Heading::S }
195
+ bot.tick
196
+ expect(bot.radar.heading).to eq RTanque::Heading.new(RTanque::Configuration.radar.turn_step)
197
+ end
198
+
199
+ it 'should respect step size in negative' do
200
+ @brain_tick_lambda = lambda { command.radar_heading = -RTanque::Heading::S }
201
+ bot.tick
202
+ expect(bot.radar.heading).to eq RTanque::Heading.new(-RTanque::Configuration.radar.turn_step)
203
+ end
204
+ end
205
+
206
+ context 'bot command turret heading' do
207
+ it 'should respect step size' do
208
+ @brain_tick_lambda = lambda { self.command.turret_heading = RTanque::Heading::S }
209
+ bot.tick
210
+ expect(bot.turret.heading).to eq RTanque::Heading.new(RTanque::Configuration.turret.turn_step)
211
+ end
212
+
213
+ it 'should respect step size in negative' do
214
+ @brain_tick_lambda = lambda { self.command.turret_heading = -RTanque::Heading::S }
215
+ bot.tick
216
+ expect(bot.turret.heading).to eq RTanque::Heading.new(-RTanque::Configuration.turret.turn_step)
217
+ end
218
+ end
219
+
220
+ context 'bot command fire_power' do
221
+ it 'should respect max' do
222
+ @brain_tick_lambda = lambda { command.fire_power = RTanque::Bot::MAX_FIRE_POWER + 1 }
223
+ bot.tick
224
+ expect(bot.fire_power).to eq RTanque::Bot::MAX_FIRE_POWER
225
+ end
226
+
227
+ it 'should respect min' do
228
+ @brain_tick_lambda = lambda { command.fire_power = RTanque::Bot::MIN_FIRE_POWER - 1 }
229
+ bot.tick
230
+ expect(bot.fire_power).to eq RTanque::Bot::MIN_FIRE_POWER
231
+ end
232
+
233
+ it 'should not allow constant shooting' do
234
+ @brain_tick_lambda = lambda { command.fire_power = RTanque::Bot::MAX_FIRE_POWER }
235
+ 5.times { bot.tick }
236
+ expect(bot.fire_power).not_to eq RTanque::Bot::MAX_FIRE_POWER
237
+ end
238
+ end
239
+ end