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.
- data/History.txt +22 -0
- data/README.txt +57 -47
- data/Rakefile +26 -24
- data/bin/zombie-chaser +77 -79
- data/lib/{chaser.rb → zombie-chaser/chaser.rb} +392 -373
- data/lib/zombie-chaser/human.rb +312 -0
- data/{ui → lib/zombie-chaser}/icons/death.png +0 -0
- data/{ui → lib/zombie-chaser}/icons/robot.png +0 -0
- data/lib/zombie-chaser/interface.rb +153 -0
- data/{ui → lib/zombie-chaser}/sprites/robot-attacking.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/robot-dead.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/robot-dying.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/robot-idle.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/robot-moving.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/robot-turning.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/tank-attacking.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/tank-dead.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/tank-idle.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/tank-moving.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/tank-turning.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/witch-attacking.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/witch-dead.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/witch-idle.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/witch-moving.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/witch-turning.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/zombie-attacking.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/zombie-dead.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/zombie-dying.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/zombie-idle.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/zombie-moving.png +0 -0
- data/{ui → lib/zombie-chaser}/sprites/zombie-turning.png +0 -0
- data/lib/zombie-chaser/test_unit_handler.rb +78 -0
- data/{ui → lib/zombie-chaser}/tiles/grass.png +0 -0
- data/{ui → lib/zombie-chaser}/tiles/shrubbery.png +0 -0
- data/{ui → lib/zombie-chaser}/ui.rb +165 -127
- data/lib/{world.rb → zombie-chaser/world.rb} +105 -98
- data/lib/zombie-chaser/zombie_test_chaser.rb +139 -0
- data/test/fixtures/chased.rb +56 -56
- data/test/integration.rb +58 -0
- data/test/test_chaser.rb +150 -144
- data/test/test_unit.rb +2 -2
- data/test/test_zombie.rb +302 -108
- data/zombie-chaser.gemspec +88 -88
- metadata +40 -46
- data/lib/human.rb +0 -189
- data/lib/interface.rb +0 -86
- data/lib/test_unit_handler.rb +0 -43
- 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
|