zombie-chaser 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/History.txt +22 -0
  2. data/README.txt +57 -47
  3. data/Rakefile +26 -24
  4. data/bin/zombie-chaser +77 -79
  5. data/lib/{chaser.rb → zombie-chaser/chaser.rb} +392 -373
  6. data/lib/zombie-chaser/human.rb +312 -0
  7. data/{ui → lib/zombie-chaser}/icons/death.png +0 -0
  8. data/{ui → lib/zombie-chaser}/icons/robot.png +0 -0
  9. data/lib/zombie-chaser/interface.rb +153 -0
  10. data/{ui → lib/zombie-chaser}/sprites/robot-attacking.png +0 -0
  11. data/{ui → lib/zombie-chaser}/sprites/robot-dead.png +0 -0
  12. data/{ui → lib/zombie-chaser}/sprites/robot-dying.png +0 -0
  13. data/{ui → lib/zombie-chaser}/sprites/robot-idle.png +0 -0
  14. data/{ui → lib/zombie-chaser}/sprites/robot-moving.png +0 -0
  15. data/{ui → lib/zombie-chaser}/sprites/robot-turning.png +0 -0
  16. data/{ui → lib/zombie-chaser}/sprites/tank-attacking.png +0 -0
  17. data/{ui → lib/zombie-chaser}/sprites/tank-dead.png +0 -0
  18. data/{ui → lib/zombie-chaser}/sprites/tank-idle.png +0 -0
  19. data/{ui → lib/zombie-chaser}/sprites/tank-moving.png +0 -0
  20. data/{ui → lib/zombie-chaser}/sprites/tank-turning.png +0 -0
  21. data/{ui → lib/zombie-chaser}/sprites/witch-attacking.png +0 -0
  22. data/{ui → lib/zombie-chaser}/sprites/witch-dead.png +0 -0
  23. data/{ui → lib/zombie-chaser}/sprites/witch-idle.png +0 -0
  24. data/{ui → lib/zombie-chaser}/sprites/witch-moving.png +0 -0
  25. data/{ui → lib/zombie-chaser}/sprites/witch-turning.png +0 -0
  26. data/{ui → lib/zombie-chaser}/sprites/zombie-attacking.png +0 -0
  27. data/{ui → lib/zombie-chaser}/sprites/zombie-dead.png +0 -0
  28. data/{ui → lib/zombie-chaser}/sprites/zombie-dying.png +0 -0
  29. data/{ui → lib/zombie-chaser}/sprites/zombie-idle.png +0 -0
  30. data/{ui → lib/zombie-chaser}/sprites/zombie-moving.png +0 -0
  31. data/{ui → lib/zombie-chaser}/sprites/zombie-turning.png +0 -0
  32. data/lib/zombie-chaser/test_unit_handler.rb +78 -0
  33. data/{ui → lib/zombie-chaser}/tiles/grass.png +0 -0
  34. data/{ui → lib/zombie-chaser}/tiles/shrubbery.png +0 -0
  35. data/{ui → lib/zombie-chaser}/ui.rb +165 -127
  36. data/lib/{world.rb → zombie-chaser/world.rb} +105 -98
  37. data/lib/zombie-chaser/zombie_test_chaser.rb +139 -0
  38. data/test/fixtures/chased.rb +56 -56
  39. data/test/integration.rb +58 -0
  40. data/test/test_chaser.rb +150 -144
  41. data/test/test_unit.rb +2 -2
  42. data/test/test_zombie.rb +302 -108
  43. data/zombie-chaser.gemspec +88 -88
  44. metadata +40 -46
  45. data/lib/human.rb +0 -189
  46. data/lib/interface.rb +0 -86
  47. data/lib/test_unit_handler.rb +0 -43
  48. data/lib/zombie_test_chaser.rb +0 -133
@@ -0,0 +1,312 @@
1
+ require "zombie-chaser/test_unit_handler"
2
+ require "zombie-chaser/ui" #For actor superclass
3
+
4
+ class Human < Actor
5
+ private_class_method :new
6
+ attr_reader :successful_step_count
7
+
8
+ def self.new_using_test_unit_handler(test_pattern, world)
9
+ test_handler = TestUnitHandler.new(test_pattern)
10
+ human = new(test_handler, world)
11
+ human
12
+ end
13
+
14
+ def initialize(test_handler, world)
15
+ @status = nil #Currently only used by zombie
16
+ @world = world
17
+ @successful_step_count = 0
18
+ @health = :alive
19
+ @test_handler = test_handler
20
+ @view_queue = Queue.new
21
+ @angle = Math::PI * rand * 2.0
22
+ end
23
+
24
+ def run
25
+ test_running_thread = Thread.new do
26
+ run_tests
27
+ end
28
+ view_queue_updating_thread = Thread.new do
29
+ build_view_queue
30
+ end
31
+ status_updating_thread = Thread.new do
32
+ update_view
33
+ end
34
+ status_updating_thread.join
35
+ view_queue_updating_thread.join
36
+ test_running_thread.join
37
+ end
38
+
39
+ def run_tests
40
+ @test_handler.run
41
+ end
42
+
43
+ def build_view_queue
44
+ while true
45
+ result = @test_handler.result_queue.deq #Slight law of demeter violation
46
+ case result
47
+ when :pass
48
+ @view_queue.enq(:passing_step)
49
+ when :failure
50
+ @view_queue.enq(:start_dying)
51
+ @view_queue.enq(:finish_dying)
52
+ when :end_of_work
53
+ @view_queue.enq(:end_of_work)
54
+ break
55
+ else raise "Unknown result!"
56
+ end
57
+ end
58
+ @view_queue.enq(:end_of_work)
59
+ end
60
+
61
+ def update_view
62
+ notify_world
63
+ while true
64
+ result = @view_queue.deq
65
+ case result
66
+ when :passing_step
67
+ notify_passing_step
68
+ when :start_dying
69
+ notify_start_dying
70
+ when :finish_dying
71
+ notify_finish_dying
72
+ when :end_of_work
73
+ notify_finished
74
+ break
75
+ else raise "Unknown result!"
76
+ end
77
+ end
78
+ end
79
+
80
+ def current_symbol
81
+ case @health
82
+ when :alive
83
+ "@"
84
+ when :dying
85
+ "*"
86
+ when :dead
87
+ "+"
88
+ end
89
+ end
90
+
91
+ def actor_type
92
+ 'robot'
93
+ end
94
+
95
+ def actor_state
96
+ return "attacking" if @status == :attacking
97
+ case @health
98
+ when :alive
99
+ "moving"
100
+ when :dying
101
+ "dying"
102
+ when :dead
103
+ "dead"
104
+ end
105
+ end
106
+
107
+ def actor_direction
108
+ @angle * -360.0 / (2 * Math::PI)
109
+ end
110
+
111
+ def notify_passing_step
112
+ break_out_of_loop = false
113
+ while true
114
+ # This synchronization doesn't actually work. The unit test dont_test_zombies_do_not_collide seem to pass on 1.8 and fail on 1.9, though it could be coincidence.
115
+ # Fixme Find out why my use of synchronization doesn't work, while remembering that select is not broken.
116
+ @world.synchronize_for_collision_detection do
117
+ if no_other_living_zombies_in?(@successful_step_count + 1)
118
+ @successful_step_count += 1
119
+ break_out_of_loop = true
120
+ end
121
+ end
122
+ break if break_out_of_loop
123
+ shuffle_in_one_place
124
+ end
125
+ increase_angle_by(13) if defined?(@lurch_offset)
126
+ sleep([0.1, 2.0 / test_suite_size].min)
127
+ notify_world
128
+ end
129
+
130
+ def no_other_living_zombies_in?(desired_step_count)
131
+ @world.no_living_zombies_apart_from_me?(desired_step_count, self)
132
+ end
133
+
134
+ def notify_start_dying
135
+ @health = :dying
136
+ sleep 0.5
137
+ notify_world
138
+ end
139
+
140
+ def dying?
141
+ @health == :dying
142
+ end
143
+ private :dying?
144
+
145
+ def dead?
146
+ @health == :dead
147
+ end
148
+
149
+ def notify_finish_dying
150
+ sleep 0.5
151
+ raise "I'm not dead yet!" unless dying?
152
+ @health = :dead
153
+ notify_world
154
+ sleep 0.5
155
+ end
156
+
157
+ def notify_finished
158
+ #Do nothing in humans
159
+ end
160
+
161
+ def notify_world
162
+ @world.something_happened
163
+ end
164
+
165
+ def get_eaten
166
+ @health = :dead unless dead?
167
+ end
168
+
169
+ def test_suite_size
170
+ #This is valid from when @test_handler is initialized
171
+ #And that is done when human is initialized
172
+ @test_handler.test_suite_size
173
+ end
174
+
175
+ end
176
+
177
+ class MockHuman < Human
178
+ private_class_method :new
179
+
180
+ def self.new_using_results(results, world)
181
+ test_handler = MockTestHandler.new(results)
182
+ mock_human = new(test_handler, world)
183
+ mock_human
184
+ end
185
+
186
+ def sleep(duration)
187
+ @world.sleep(duration)
188
+ end
189
+ end
190
+
191
+ class ZombieFactory
192
+ def initialize(test_pattern, world)
193
+ @test_pattern, @world = test_pattern, world
194
+ end
195
+
196
+ def create_zombie
197
+ Zombie.new_using_test_unit_handler(@test_pattern, @world)
198
+ end
199
+ end
200
+
201
+ class ZombieList
202
+
203
+ def self.new_using_test_unit_handler(test_pattern, world)
204
+ zombie_factory = ZombieFactory.new(test_pattern, world)
205
+ new(zombie_factory)
206
+ end
207
+
208
+ def initialize(zombie_factory)
209
+ @zombie_factory = zombie_factory
210
+ @zombies = []
211
+ end
212
+
213
+ def supply_next_zombie
214
+ zombie = @zombie_factory.create_zombie
215
+ @zombies << zombie
216
+ zombie
217
+ end
218
+
219
+ def each_zombie
220
+ @zombies.each{|zombie| yield zombie}
221
+ end
222
+
223
+ def draw_zombies
224
+ each_zombie {|zombie| zombie.draw}
225
+ end
226
+ end
227
+
228
+ class MockZombieFactory
229
+ def initialize(zombies_results, world)
230
+ @zombies_results, @world = zombies_results, world
231
+ @current_zombie_number = 0
232
+ end
233
+
234
+ def create_zombie
235
+ mock_zombie = MockZombie.new_using_results(@zombies_results[@current_zombie_number], @world)
236
+ @current_zombie_number += 1
237
+ mock_zombie
238
+ end
239
+ end
240
+
241
+ class MockZombieList < ZombieList
242
+
243
+ def self.new_using_results(zombies_results, world)
244
+ mock_zombie_factory = MockZombieFactory.new(zombies_results, world)
245
+ new(mock_zombie_factory)
246
+ end
247
+
248
+ end
249
+
250
+ module ZombieInterface
251
+ def current_symbol
252
+ case @health
253
+ when :alive
254
+ "Z"
255
+ when :dying
256
+ "*"
257
+ when :dead
258
+ "+"
259
+ end
260
+ end
261
+
262
+ def actor_type
263
+ 'zombie'
264
+ end
265
+
266
+ def shuffle_in_one_place
267
+ shuffle_amount = 17
268
+ increase_angle_by(shuffle_amount)
269
+ sleep 0.1
270
+ end
271
+
272
+ def increase_angle_by(amount)
273
+ minimum_lurch_offset = -20
274
+ maximum_lurch_offset = 20
275
+ @lurch_offset = (@lurch_offset + amount - minimum_lurch_offset) % (maximum_lurch_offset - minimum_lurch_offset) + minimum_lurch_offset
276
+ end
277
+
278
+ def notify_finished
279
+ eat_human unless dead?
280
+ end
281
+
282
+ def eat_human
283
+ @status = :attacking #Even if the human's dead, look for leftovers
284
+ @world.notify_human_eaten
285
+ sleep 1
286
+ @status = nil
287
+ end
288
+
289
+ end
290
+
291
+ class Zombie < Human
292
+ include ZombieInterface
293
+
294
+ def initialize(*args)
295
+ @lurch_offset = 0
296
+ super
297
+ end
298
+
299
+ def actor_direction
300
+ (@angle * -360.0 / (2 * Math::PI) + @lurch_offset)
301
+ end
302
+ end
303
+
304
+ class MockZombie < MockHuman #Fixme provide a proper hierarchy
305
+ include ZombieInterface
306
+
307
+ def initialize(*args)
308
+ @lurch_offset = 0
309
+ super
310
+ end
311
+
312
+ end
File without changes
File without changes
@@ -0,0 +1,153 @@
1
+ require "monitor"
2
+
3
+ class Interface
4
+ attr_writer :human, :zombie_list
5
+
6
+ def sleep(duration)
7
+ super
8
+ end
9
+
10
+ def finish_if_neccessary
11
+ end
12
+
13
+ def interface_puts(*args)
14
+ puts(*args)
15
+ STDOUT.flush
16
+ end
17
+ end
18
+
19
+ class ConsoleInterface < Interface
20
+ @width = 79 #Having a value of 80 causes problems for Windows console sessions.
21
+
22
+ def self.width=(width); @width = width end
23
+ def self.width; @width end
24
+
25
+ def initialize
26
+ @representations = []
27
+ @zombie_list = nil
28
+ @progress_text_being_printed = false
29
+ @output_lock = Monitor.new
30
+ end
31
+
32
+ def current_representation
33
+ result = "." * human_position + @human.current_symbol
34
+ unless @zombie_list.nil?
35
+ @zombie_list.each_zombie do |zombie|
36
+ position = adjust_for_screen_width(zombie.successful_step_count)
37
+ result[position..position] = zombie.current_symbol
38
+ end
39
+ end
40
+ result
41
+ end
42
+ private :current_representation
43
+
44
+ def something_happened
45
+ @output_lock.synchronize do
46
+ @representations << current_representation
47
+ display_representation(@representations.last)
48
+ end
49
+ end
50
+
51
+ def display_representation(representation)
52
+ print "\r", representation
53
+ @progress_text_being_printed = true
54
+ STDOUT.flush
55
+ end
56
+ private :display_representation
57
+
58
+ def interface_puts(*args)
59
+ @output_lock.synchronize do
60
+ print_newline_if_neccessary
61
+ super
62
+ end
63
+ end
64
+
65
+ def human_position
66
+ adjust_for_screen_width(@human.successful_step_count)
67
+ end
68
+ private :human_position
69
+
70
+ def maximum_position
71
+ ConsoleInterface.width - 1 #Subtract one as position is zero-indexed
72
+ end
73
+ private :maximum_position
74
+
75
+ def adjust_for_screen_width(step_count)
76
+ (step_count * 1.0 * maximum_position / [@human.test_suite_size, maximum_position].max).round
77
+ end
78
+ private :adjust_for_screen_width
79
+
80
+ def no_living_zombies_apart_from_me?(desired_step_count, actor)
81
+ desired_position = adjust_for_screen_width(desired_step_count)
82
+ return true if desired_position == adjust_for_screen_width(0) #Hack to allow multiple zombies at the start
83
+ return true if desired_position == adjust_for_screen_width(@human.test_suite_size) #Always room for one more at the dinner table!
84
+ return true if desired_position == adjust_for_screen_width(actor.successful_step_count) #In case there's no advancement involved, and there's multiple zombies in a square even though they shouldn't be
85
+ @zombie_list.each_zombie do |zombie|
86
+ next if zombie.equal? actor #Only checking for collisions with other actors, not with itself
87
+ next if zombie.dead?
88
+ zombie_position = adjust_for_screen_width(zombie.successful_step_count)
89
+ #raise if adjust_for_screen_width(actor.successful_step_count) == zombie_position
90
+ return false if zombie_position == desired_position
91
+ end
92
+ true
93
+ end
94
+
95
+ def print_newline_if_neccessary
96
+ if @progress_text_being_printed
97
+ puts
98
+ @progress_text_being_printed = false
99
+ end
100
+ end
101
+ private :print_newline_if_neccessary
102
+
103
+ def finish_if_neccessary
104
+ @output_lock.synchronize do
105
+ print_newline_if_neccessary
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ class NoInterface < ConsoleInterface
112
+ attr_reader :representations
113
+
114
+ def display_representation(representation)
115
+ #Do nothing
116
+ end
117
+
118
+ #No need to sleep for a mock interface
119
+ def sleep(duration)
120
+ super(duration/1000.0) * 1000.0 #Not sure if it's needed, but just to be on the safe side
121
+ end
122
+ end
123
+
124
+ class GuiInterface < Interface
125
+
126
+ def initialize
127
+ @window = Window.new
128
+ #Actor.window = @window #Fixme why can't this do the trick?
129
+ Human.window = @window
130
+ Zombie.window = @window
131
+ @window_showing_thread = Thread.new {@window.show}
132
+ end
133
+
134
+ #Doesn't need to be used, as window updates 60 times a second anyway
135
+ def something_happened
136
+ end
137
+
138
+ def human=(human)
139
+ @window.human = human
140
+ end
141
+
142
+ def zombie_list=(zombie_list)
143
+ @window.zombie_list = zombie_list
144
+ end
145
+
146
+ def no_living_zombies_apart_from_me?(desired_step_count, actor)
147
+ @window.no_living_zombies_apart_from_me?(desired_step_count, actor)
148
+ end
149
+
150
+ def finish_if_neccessary
151
+ @window_showing_thread.join
152
+ end
153
+ end