graphics 1.0.0b1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ srand 42
4
+
5
+ require "graphics"
6
+
7
+ class Array
8
+ def sorted_include? o
9
+ a, b = o
10
+ !!bsearch { |(x, y)|
11
+ c = a - x
12
+ c.zero? ? b - y : c
13
+ }
14
+ end
15
+ end
16
+
17
+ class ZenspiderGol
18
+ delta = [-1, 0, 1]
19
+ same = [0, 0]
20
+
21
+ DELTAS = (delta.product(delta) - [same]).sort
22
+ MIN = { true => 2, false => 3 }
23
+
24
+ @@neighbors = Hash.new { |h, k| h[k] = {} }
25
+
26
+ attr_accessor :cells
27
+ attr_accessor :cache
28
+
29
+ def initialize
30
+ self.cells = []
31
+ end
32
+
33
+ def randomize n, pct
34
+ m = ((n*n) * pct).to_i
35
+ dimensions = n.times.to_a
36
+ cells.replace dimensions.product(dimensions).sample(m).sort
37
+ end
38
+
39
+ def update
40
+ cells.replace considered.select { |(x, y)| alive? x, y }.sort
41
+ end
42
+
43
+ def considered
44
+ cells.map { |(x, y)| neighbors_for(x, y) }.flatten(1).uniq
45
+ end
46
+
47
+ def alive? x, y
48
+ count = (neighbors_for(x, y) & cells).size
49
+ min = MIN[cells.sorted_include? [x, y]]
50
+ count.between? min, 3
51
+ end
52
+
53
+ def neighbors_for x, y
54
+ @@neighbors[x][y] ||=
55
+ DELTAS.map { |(dx, dy)| [x+dx, y+dy] }.reject { |(m, n)| m < 0 || n < 0 }
56
+ end
57
+ end
58
+
59
+ class ZenspiderGolSimulation < Graphics::Simulation
60
+ attr_accessor :gol
61
+
62
+ SIZE, WIDTH = 10, 64
63
+
64
+ def initialize
65
+ super 640, 640, 16, "Conway's Game of Life"
66
+
67
+ self.gol = ZenspiderGol.new
68
+ gol.randomize WIDTH, 0.15
69
+ end
70
+
71
+ def draw n
72
+ clear
73
+
74
+ gol.cells.each do |(x, y)|
75
+ ellipse x*SIZE, y*SIZE, (SIZE-1)/2, (SIZE-1)/2, :white, :filled
76
+ end
77
+
78
+ fps n
79
+ end
80
+
81
+ def update n
82
+ gol.update.reject! { |(x, y)| x >= WIDTH || y >= WIDTH }
83
+ end
84
+ end
85
+
86
+ if ARGV.first == "prof" then
87
+ ZenspiderGolSimulation.new.run 5
88
+ else
89
+ ZenspiderGolSimulation.new.run
90
+ end
@@ -0,0 +1,385 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ srand 42
4
+
5
+ require "graphics"
6
+
7
+ class Entity
8
+ attr_accessor :x, :y
9
+ attr_accessor :speed
10
+ attr_accessor :sim
11
+
12
+ @@colors = false
13
+
14
+ def initialize sim
15
+ self.sim = sim
16
+ self.speed = 5
17
+
18
+ self.x = rand(sim.w / sim.scale)
19
+ self.y = rand(sim.h / sim.scale)
20
+ end
21
+
22
+ def distance_from_squared p
23
+ dx = p.x - x
24
+ dy = p.y - y
25
+ dx * dx + dy * dy
26
+ end
27
+
28
+ VISIBILITY = 16
29
+ VIS_SQ = VISIBILITY * VISIBILITY
30
+
31
+ def near? p
32
+ distance_from_squared(p) < VIS_SQ
33
+ end
34
+
35
+ def touching? p
36
+ distance_from_squared(p) < 4 # 2 * 2
37
+ end
38
+
39
+ def partition
40
+ (x / sim.width_of_partition) + sim.side * (y / sim.width_of_partition)
41
+ end
42
+
43
+ def random_walk
44
+ self.x += rand(speed)-speed/2
45
+ self.y += rand(speed)-speed/2
46
+ end
47
+
48
+ def max
49
+ @@max ||= sim.max
50
+ end
51
+
52
+ def limit_bounds
53
+ self.x = 0 if x < 0
54
+ self.x = max - 1 if x >= max
55
+ self.y = 0 if y < 0
56
+ self.y = max - 1 if y >= max
57
+ end
58
+
59
+ def move_towards entity
60
+ self.x += (entity.x - x) <=> 0
61
+ self.y += (entity.y - y) <=> 0
62
+ limit_bounds
63
+ end
64
+
65
+ def draw
66
+ raise "subclass responsibility"
67
+ end
68
+ end
69
+
70
+ class Person < Entity
71
+ COUNT = 750
72
+
73
+ NORMAL = 1
74
+ FREAKD = 2
75
+ INFECT = 3
76
+
77
+ INFECT_STEPS = 50.0 # must be a float
78
+
79
+ NORMAL_COLOR = :blue
80
+ FREAKD_COLOR = :yellow
81
+
82
+ attr_accessor :state, :infect, :speed
83
+
84
+ def initialize sim, state = NORMAL
85
+ super(sim)
86
+
87
+ self.state = state
88
+ self.speed = 5
89
+ self.infect = nil
90
+
91
+ initialize_colors unless @@colors
92
+ end
93
+
94
+ def initialize_colors
95
+ @@colors = true
96
+
97
+ INFECT_STEPS.to_i.times do |n|
98
+ r = (255 * ((INFECT_STEPS - n) / INFECT_STEPS)).to_i
99
+ g = (192 * (n / INFECT_STEPS)).to_i
100
+ b = 0
101
+ sim.register_color "infect#{n}", r, g, b
102
+ end
103
+ end
104
+
105
+ def infected?
106
+ state == INFECT
107
+ end
108
+
109
+ def freaked?
110
+ state == FREAKD
111
+ end
112
+
113
+ def color
114
+ case state
115
+ when NORMAL
116
+ NORMAL_COLOR
117
+ when FREAKD
118
+ FREAKD_COLOR
119
+ when INFECT
120
+ "infect#{INFECT_STEPS.to_i - infect}"
121
+ end
122
+ end
123
+
124
+ def draw
125
+ sim.fast_rect x*2, y*2, 2, 2, color
126
+ end
127
+
128
+ def update_infection
129
+ if @infect then
130
+ @infect -= 1
131
+ sim.zombie << Zombie.from_person(sim.person.delete(self), sim) if @infect <= 0
132
+ true
133
+ end
134
+ end
135
+
136
+ def visible
137
+ sim.part_z[partition].find_all { |p| self.near? p }
138
+ end
139
+
140
+ def nearest
141
+ visible.sort_by { |p| self.distance_from_squared p }.first
142
+ end
143
+
144
+ def update i
145
+ return if update_infection
146
+ random_walk
147
+ limit_bounds
148
+
149
+ nearest = self.nearest
150
+
151
+ unless nearest then
152
+ if state == FREAKD then
153
+ self.state = NORMAL
154
+ self.speed = 5
155
+ end
156
+ else
157
+ unless touching? nearest then
158
+ if state == NORMAL then
159
+ self.state = FREAKD
160
+ self.speed = 9
161
+ end
162
+ else
163
+ self.state = INFECT
164
+ self.infect = INFECT_STEPS.to_i
165
+ end
166
+ end
167
+ end
168
+
169
+ def kill
170
+ sim.person.delete self
171
+ end
172
+ end
173
+
174
+ class Hunter < Person
175
+ COUNT = 6
176
+ COLOR = :white
177
+
178
+ def color
179
+ if @infect then
180
+ "infect#{INFECT_STEPS.to_i - infect}"
181
+ else
182
+ COLOR
183
+ end
184
+ end
185
+
186
+ def update i
187
+ return if update_infection
188
+ random_walk
189
+ limit_bounds
190
+
191
+ baddies = sim.zombie + sim.person.select(&:infect)
192
+ nearest = baddies.sort_by { |z| self.distance_from_squared z }.first
193
+ if nearest then
194
+ if self.touching? nearest then
195
+ if Person === nearest then
196
+ nearest.kill
197
+ else
198
+ if rand(10) != 0 then
199
+ nearest.kill
200
+ else
201
+ self.state = INFECT
202
+ self.infect = INFECT_STEPS.to_i
203
+ end
204
+ end
205
+ elsif near? nearest then
206
+ move_towards nearest
207
+ else
208
+ move_towards nearest
209
+ end
210
+ end
211
+ end
212
+
213
+ def draw
214
+ sim.circle x*2, y*2, VISIBILITY, color
215
+ super
216
+ end
217
+ end
218
+
219
+ class Zombie < Entity
220
+ COUNT = 5
221
+ ZOMBIE_COLOR = :red
222
+
223
+ def self.from_person p, sim
224
+ z = new sim
225
+ z.x = p.x
226
+ z.y = p.y
227
+ z
228
+ end
229
+
230
+ def initialize sim
231
+ super
232
+ self.speed = 3
233
+ end
234
+
235
+ def draw
236
+ sim.fast_rect x*2, y*2, 2, 2, ZOMBIE_COLOR
237
+ end
238
+
239
+ def visible
240
+ sim.part_p[partition].find_all { |p| Hunter === p || p.freaked? }
241
+ end
242
+
243
+ def nearest
244
+ visible.sort_by { |p| self.distance_from_squared p }.first
245
+ end
246
+
247
+ def update i
248
+ nearest = self.nearest
249
+
250
+ if nearest then
251
+ move_towards nearest
252
+ else
253
+ random_walk
254
+ end
255
+
256
+ limit_bounds
257
+ end
258
+
259
+ def kill
260
+ sim.zombie.delete self
261
+ end
262
+ end
263
+
264
+ class ZombieGame < Graphics::Simulation
265
+ attr_accessor :person, :zombie
266
+ attr_accessor :part_p, :part_z
267
+ attr_accessor :scale, :partitions
268
+ attr_accessor :start
269
+
270
+ def initialize
271
+ super 512, 512, 16, "Zombie Epidemic Simulator"
272
+ self.scale = 2
273
+ self.partitions = 64
274
+
275
+ self.part_p = Array.new(partitions) do [] end
276
+ self.part_z = Array.new(partitions) do [] end
277
+
278
+ self.person = []
279
+ self.zombie = []
280
+
281
+ populate Person, person
282
+ populate Zombie, zombie
283
+ populate Hunter, person
284
+
285
+ self.start = Time.now
286
+ end
287
+
288
+ def draw tick
289
+ clear
290
+
291
+ person.each do |p|
292
+ p.draw
293
+ end
294
+
295
+ zombie.each do |p|
296
+ p.draw
297
+ end
298
+
299
+ fps tick
300
+ end
301
+
302
+ def update i
303
+ partition_into person, part_p, partitions, side
304
+ partition_into zombie, part_z, partitions, side
305
+
306
+ person.each do |p|
307
+ p.update i
308
+ end
309
+
310
+ zombie.each do |p|
311
+ p.update i
312
+ end
313
+
314
+ if zombie.empty? or person.all?(&:infected?) then
315
+ t = Time.now - start
316
+ if zombie.empty? then
317
+ print "Infestation stopped "
318
+ else
319
+ print "All people infected "
320
+ end
321
+ puts "in #{i} iterations, #{t} sec"
322
+ puts " #{i / t} frames / sec"
323
+
324
+ exit
325
+ end
326
+ end
327
+
328
+ def populate klass, coll
329
+ klass::COUNT.times do
330
+ coll << klass.new(self)
331
+ end
332
+ end
333
+
334
+ # TODO: rename
335
+ def side
336
+ @side ||= Math.sqrt(partitions).to_i
337
+ end
338
+
339
+ def partition_into from, to, size, side
340
+ to.each(&:clear)
341
+
342
+ from.each do |p|
343
+ part = p.partition
344
+
345
+ # -3 or less - do nothing
346
+ # -2 - add 0
347
+ # -1 - add 0, 1
348
+ # 0 or more - add idx..idx+2
349
+
350
+ idx = part - side - 1
351
+ if idx >= 0 then
352
+ to[idx] << p
353
+ to[idx+1] << p
354
+ to[idx+2] << p
355
+ else
356
+ to[0] << p if idx >= -2
357
+ to[1] << p if idx >= -1
358
+ end
359
+
360
+ idx = part - 1
361
+ to[idx] << p if idx >= 0
362
+ idx += 1
363
+ to[idx] << p
364
+ idx += 1
365
+ to[idx] << p if idx < size
366
+
367
+ idx = part + side - 1
368
+ to[idx] << p if idx < size
369
+ idx += 1
370
+ to[idx] << p if idx < size
371
+ idx += 1
372
+ to[idx] << p if idx < size
373
+ end
374
+ end
375
+
376
+ def width_of_partition
377
+ @width_of_partition ||= (w / scale) / side
378
+ end
379
+
380
+ def max
381
+ @max ||= w / scale
382
+ end
383
+ end
384
+
385
+ ZombieGame.new.run