zombie-chaser 0.0.3 → 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 (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