forked from crystal-lang/crystal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
meteor.cr
206 lines (177 loc) · 4.04 KB
/
meteor.cr
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# Translated from: https://gitlab.redox-os.org/redox-os/rust/blob/a59de37e99060162a2674e3ff45409ac73595c0e/src/test/bench/shootout-meteor.rs
alias Masks = Array(Array(Array(UInt64)))
alias Point = Tuple(Int32, Int32)
class MyIterator(T)
include Enumerable(T)
def initialize(@data : T, &@block : T -> T)
end
def each(&)
while true
yield @data
@data = @block.call(@data)
end
end
end
def bo(offset) # bit offset
1_u64 << offset
end
def bm(mask, offset) # bit mask
mask & bo(offset)
end
def transform(piece, all)
i = MyIterator.new piece, &.map { |(y, x)| {x + y, -y} }
rots = i.first(all ? 6 : 3)
res = rots.flat_map do |cur_piece|
i2 = MyIterator.new cur_piece, &.map { |(y, x)| {x, y} }
i2.first(2)
end
res.map do |cur_piece|
dy, dx = cur_piece.min
cur_piece.map do |(y, x)|
{y - dy, x - dx}
end
end
end
def mask(dy, dx, id, p)
m = bo(50 + id)
p.each do |(y, x)|
x2 = x + dx + (y + (dy % 2)) // 2
return if x2 < 0 || x2 > 4
y2 = y + dy
return if y2 < 0 || y2 > 9
m |= bo(y2 * 5 + x2)
end
m
end
PIECES = [
[{0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 3}],
[{0, 0}, {0, 2}, {0, 3}, {1, 0}, {1, 1}],
[{0, 0}, {0, 1}, {0, 2}, {1, 2}, {2, 1}],
[{0, 0}, {0, 1}, {0, 2}, {1, 1}, {2, 1}],
[{0, 0}, {0, 2}, {1, 0}, {1, 1}, {2, 1}],
[{0, 0}, {0, 1}, {0, 2}, {1, 1}, {1, 2}],
[{0, 0}, {0, 1}, {1, 1}, {1, 2}, {2, 1}],
[{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 2}],
[{0, 0}, {0, 1}, {0, 2}, {1, 2}, {1, 3}],
[{0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 2}],
]
def make_masks
res = Masks.new
PIECES.each_with_index do |p, id|
trans = transform(p, id != 3)
cur_piece = [] of Array(UInt64)
10.times do |dy|
5.times do |dx|
cur_piece << trans.compact_map { |t| mask(dy, dx, id, t) }
end
end
res << cur_piece
end
res
end
def board_unfeasible?(board : UInt64, masks : Masks)
coverable = board
(0...50).select { |i| bm(board, i) == 0 }.each do |i|
masks.each_with_index do |pos_masks, cur_id|
next if bm(board, 50 + cur_id) != 0
pos_masks[i].each do |cur_m|
coverable |= cur_m if cur_m & board == 0
end
end
return true if bm(coverable, i) == 0
end
coverable != (bo(60) - 1)
end
def filter_masks(masks : Masks)
masks.map do |p|
p.map do |p2|
p2.select do |m|
!board_unfeasible?(m, masks)
end
end
end
end
def get_id(m : UInt64)
10.times do |id|
return id.to_u8 if bm(m, id + 50) != 0
end
raise "Does not have a valid identifier"
end
def to_utf8(raw_sol)
String.new(50) do |buf|
raw_sol.each do |m|
id = get_id(m)
50.times do |i|
buf[i] = '0'.ord.to_u8 + id if bm(m, i) != 0
end
end
{50, 50}
end
end
def print_sol(str)
i = 0
str.each_byte do |c|
puts if i % 5 == 0
print ' ' if (i + 5) % 10 == 0
print ' '
print c.chr
i += 1
end
puts
end
class SolutionNode
def initialize(@x : UInt64, @prev : SolutionNode?)
end
getter :x
getter :prev
def each(&)
yield @x
p = prev
while y = p
yield y.x
p = p.prev
end
end
end
class Meteor
def initialize(@masks : Masks, @stop_after : Int32)
@nb = 0
@min = "9" * 50
@max = "0" * 50
end
property min : String
property max : String
property :nb
def handle_sol(cur)
@nb += 2
sol1 = to_utf8(cur)
sol2 = sol1.reverse
@min = {sol1, sol2, @min}.min
@max = {sol1, sol2, @max}.max
nb < @stop_after
end
def search(board, i, cur = nil)
while bm(board, i) != 0 && (i < 50)
i += 1
end
return handle_sol(cur) if i >= 50 && cur
(0...10).each do |id|
if bm(board, id + 50) == 0
@masks[id][i].each do |m|
if board & m == 0
return false if !search(board | m, i + 1, SolutionNode.new(m, cur))
end
end
end
end
true
end
end
stop_after = (ARGV[0]? || 2098).to_i
masks = filter_masks(make_masks)
data = Meteor.new(masks, stop_after)
data.search(0_u64, 0)
puts "#{data.nb} solutions found"
print_sol(data.min)
print_sol(data.max)
puts