-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspace_invaders.rb
executable file
·175 lines (139 loc) · 4.13 KB
/
space_invaders.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
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
# frozen_string_literal: true
require 'mini-levenshtein'
module SpaceInvaders
DEF_PATTERN = '~~~~'
class Segment
SIMILARITY_THRESHOLD = ENV.fetch('SIMILARITY', 0.90).to_f
def initialize(source:, x: 0, y: 0)
self.x = x
self.y = y
self.source = source.gsub(DEF_PATTERN, '').strip
end
def to_s
source
end
def total_rows
lines.size
end
def total_columns
lines.first.size
end
def dimensions
{ rows: total_rows, columns: total_columns }
end
def coordinates
{ x:, y: }
end
def match?(other_segment:)
score = MiniLevenshtein.similarity(source, other_segment.to_s)
score > SIMILARITY_THRESHOLD
end
def sub_segments(rows:, columns:)
rows_count = lines.size
column_count = lines.first.size
segments = []
(rows - 1..rows_count - 1).each do |row_index| # Read rows (top -> bottom)
(columns - 1..column_count - 1).each do |column_index| # Read columns (left -> right)
x_range = row_index - rows + 1..row_index
y_range = column_index - columns + 1..column_index
segments << trim(rows: x_range, columns: y_range)
end
end
segments
end
def edges(rows:, columns:)
horizontal_edges(rows:) + vertical_edges(columns:)
end
def horizontal_edges(rows:)
[
{ rows: 0..rows - 1, columns: 0..total_columns }, # top edge
{ rows: total_rows - rows..total_rows, columns: 0..total_columns } # bottom edge
].map do |dimensions|
trim(**dimensions)
end
end
def vertical_edges(columns:)
[
{ rows: 0..total_rows, columns: 0..columns - 1 }, # left edge
{ rows: 0..total_rows, columns: total_columns - columns..total_columns } # right side
].map do |dimensions|
trim(**dimensions)
end
end
private
attr_accessor :x, :y, :source
def trim(rows:, columns:)
new_source = lines[rows].map { _1[columns] }.join("\n")
rows = rows.min + Array(x).min..rows.max + Array(x).min
columns = columns.min + Array(y).min..columns.max + Array(y).min
Segment.new(source: new_source, x: rows, y: columns)
end
def lines
source.split("\n")
end
end
class Invader < Segment
attr_accessor :name
def initialize(name:, source:)
super(source:)
self.name = name
end
def variants
sub_rows = dimensions[:rows] / 2
sub_columns = dimensions[:columns] / 2
horizontal_edges(rows: sub_rows).reverse + vertical_edges(columns: sub_columns).reverse
end
end
class Detector
attr_accessor :results
def initialize(segment:)
self.segment = segment
self.invaders = []
self.results = []
end
def add_invader(invader:)
invaders << invader
end
def scan
invaders.each do |invader|
# Main area scanning
segment.sub_segments(**invader.dimensions).each do |sub_segment|
next unless sub_segment.match?(other_segment: invader)
results << {
name: invader.name,
coordinates: sub_segment.coordinates
}
end
# Edge cases scanning
segment_edges = segment.edges(
rows: invader.dimensions[:rows] / 2,
columns: invader.dimensions[:columns] / 2
)
invader_variants = invader.variants
segment_edges.zip(invader_variants).each do |edge_segment, invader_variant|
edge_segment.sub_segments(**invader_variant.dimensions).each do |sub_segment|
next unless sub_segment.match?(other_segment: invader_variant)
results << {
name: invader.name,
coordinates: sub_segment.coordinates
}
end
end
end
end
def summary
segment.total_rows.times do |x|
segment.total_columns.times do |y|
if results.any? { _1[:coordinates][:x].include?(x) && _1[:coordinates][:y].include?(y) }
print 'x'
else
print '-'
end
end
puts "\n"
end
end
private
attr_accessor :segment, :invaders
end
end