-
Notifications
You must be signed in to change notification settings - Fork 2
/
game_of_life.rb
120 lines (97 loc) · 2.75 KB
/
game_of_life.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
require "bundler/setup"
require "speculation"
require "speculation/gen"
require "speculation/test"
S = Speculation
STest = S::Test
Gen = S::Gen
S.def :"gol/coordinate", S.with_gen(Integer) { S.gen(S.int_in(-5..5)) }
S.def :"gol/cell", S.tuple(:"gol/coordinate", :"gol/coordinate")
S.def :"gol/world", S.coll_of(:"gol/cell", :kind => Set)
def self.neighbours(cell)
cell_x, cell_y = cell
(-1..1).repeated_permutation(2).map { |(x, y)| [cell_x - x, cell_y - y] }.to_set
block - Set[cell]
end
def self.cell_distance(cell_a, cell_b)
[
(cell_a[0] - cell_b[0]).abs,
(cell_a[1] - cell_b[1]).abs
]
end
S.fdef method(:neighbours),
:args => S.cat(:cell => :"gol/cell"),
:ret => S.coll_of(:"gol/cell", :count => 8, :kind => Set),
:fn => ->(fn) {
cell = fn[:args][:cell]
neighbours = fn[:ret]
neighbours.all? { |neighbour| [[1, 0], [0, 1], [1, 1]].include?(cell_distance(cell, neighbour)) }
}
S.exercise_fn(method(:neighbours))
STest.instrument(method(:neighbours))
# STest.summarize_results STest.check(method(:neighbours))
def self.alive_neighbours(world, cell)
neighbours(cell).intersection(world)
end
def self.alive?(world, cell)
world.include?(cell)
end
def self.tick(world)
world.
flat_map { |cell| neighbours(cell).to_a }.
select { |cell|
alive_neighbour_count = alive_neighbours(world, cell).count
if alive?(world, cell)
(2..3).cover?(alive_neighbour_count)
else
alive_neighbour_count == 3
end
}.
to_set
end
S.fdef method(:tick),
:args => S.cat(:world => :"gol/world"),
:ret => :"gol/world"
S.exercise_fn(method(:tick))
STest.instrument(method(:tick))
# STest.check(method(:tick))
def self.simulation(world)
Enumerator.new do |yielder|
loop do
yielder << world
world = tick(world)
end
end
end
def self.serialize_world(world)
return [[]] if world.empty?
min_y, max_y = world.minmax_by(&:last).map(&:last)
min_x, max_x = world.minmax_by(&:first).map(&:first)
(min_y.pred..max_y.next).map { |y| (min_x.pred..max_x.next).map { |x| world.include?([x, y]) } }
end
S.fdef method(:serialize_world),
:args => S.cat(:world => :"gol/world"),
:ret => S.coll_of(S.coll_of(:"Speculation/boolean")),
:fn => ->(fn) { fn[:ret].flatten.count(&:itself) == fn[:args][:world].count }
def self.print_world(world, out)
world.each do |line|
line.each do |cell|
if cell
out << "\u2588"
else
out << "\u2591"
end
end
out << "\n"
end
nil
end
init = Gen.generate(S.gen(:"gol/world"))
worlds = simulation(init).lazy.take_while { |world| !world.empty? }
worlds.first(10).each do |world|
print "\033[2J"
puts world.sort.to_s
print_world(serialize_world(world), STDOUT)
sleep 0.5
end
# STest.summarize_results STest.check