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.
- 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
|