1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12: def print *args
13: end
14:
15:
16: class Rotation
17:
18:
19: attr_reader :start_masks
20:
21:
22:
23: @@rotation_even_adder = { :west => -1, :east => 1, :nw => -7, :ne => -6, :sw => 5, :se => 6 }
24: @@rotation_odd_adder = { :west => -1, :east => 1, :nw => -6, :ne => -5, :sw => 6, :se => 7 }
25:
26: def initialize( directions )
27: @even_offsets, @odd_offsets = normalize_offsets( get_values( directions ))
28:
29: @even_mask = mask_for_offsets( @even_offsets)
30: @odd_mask = mask_for_offsets( @odd_offsets)
31:
32: @start_masks = Array.new(60)
33:
34:
35:
36:
37: 0.upto(59) do | offset |
38: mask = is_even(offset) ? (@even_mask << offset) : (@odd_mask << offset)
39: if (blank_board & mask == 0 && !prunable(blank_board | mask, 0, true)) then
40: imask = compute_required( mask, offset)
41: @start_masks[offset] = [ mask, imask, imask | mask ]
42: else
43: @start_masks[offset] = false
44: end
45: end
46: end
47:
48: def compute_required( mask, offset )
49: board = blank_board
50: 0.upto(offset) { | i | board |= 1 << i }
51: board |= mask
52: return 0 if (!prunable(board | mask, offset))
53: board = flood_fill(board,58)
54: count = 0
55: imask = 0
56: 0.upto(59) do | i |
57: if (board[i] == 0) then
58: imask |= (1 << i)
59: count += 1
60: end
61: end
62: (count > 0 && count < 5) ? imask : 0
63: end
64:
65: def flood_fill( board, location)
66: return board if (board[location] == 1)
67: board |= 1 << location
68: row, col = location.divmod(6)
69: board = flood_fill( board, location - 1) if (col > 0)
70: board = flood_fill( board, location + 1) if (col < 4)
71: if (row % 2 == 0) then
72: board = flood_fill( board, location - 7) if (col > 0 && row > 0)
73: board = flood_fill( board, location - 6) if (row > 0)
74: board = flood_fill( board, location + 6) if (row < 9)
75: board = flood_fill( board, location + 5) if (col > 0 && row < 9)
76: else
77: board = flood_fill( board, location - 5) if (col < 4 && row > 0)
78: board = flood_fill( board, location - 6) if (row > 0)
79: board = flood_fill( board, location + 6) if (row < 9)
80: board = flood_fill( board, location + 7) if (col < 4 && row < 9)
81: end
82: board
83: end
84:
85:
86: def offsets( location)
87: if is_even( location) then
88: @even_offsets.collect { | value | value + location }
89: else
90: @odd_offsets.collect { | value | value + location }
91: end
92: end
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: def normalize_offsets( values)
118: min = values.min
119: even_min = is_even(min)
120: other_min = even_min ? min + 6 : min + 7
121: other_values = values.collect do | value |
122: if is_even(value) then
123: value + 6 - other_min
124: else
125: value + 7 - other_min
126: end
127: end
128: values.collect! { | value | value - min }
129:
130: if even_min then
131: [values, other_values]
132: else
133: [other_values, values]
134: end
135: end
136:
137:
138: def mask_for_offsets( offsets )
139: mask = 0
140: offsets.each { | value | mask = mask + ( 1 << value ) }
141: mask
142: end
143:
144:
145:
146:
147: def start_adjust( directions )
148: south = east = 0;
149: directions.each do | direction |
150: east += 1 if ( direction == :sw || direction == :nw || direction == :west )
151: south += 1 if ( direction == :nw || direction == :ne )
152: end
153: south * 6 + east
154: end
155:
156:
157:
158: def get_values ( directions )
159: start = start_adjust(directions)
160: values = [ start ]
161: directions.each do | direction |
162: if (start % 12 >= 6) then
163: start += @@rotation_odd_adder[direction]
164: else
165: start += @@rotation_even_adder[direction]
166: end
167: values += [ start ]
168: end
169:
170:
171: values.uniq
172: end
173: end
174:
175:
176:
177:
178:
179:
180:
181: class Piece
182: attr_reader :rotations, :type, :masks
183: attr_accessor :placed
184:
185:
186: @@flip_converter = { :west => :west, :east => :east, :nw => :sw, :ne => :se, :sw => :nw, :se => :ne }
187: @@rotate_converter = { :west => :nw, :east => :se, :nw => :ne, :ne => :east, :sw => :west, :se => :sw }
188:
189: def initialize( directions, type )
190: @type = type
191: @rotations = Array.new();
192: @map = {}
193:
194: generate_rotations( directions )
195: directions.collect! { | value | @@flip_converter[value] }
196: generate_rotations( directions )
197:
198:
199:
200: @masks = Array.new();
201: 0.upto(59) do | i |
202: even = true
203: @masks[i] = @rotations.collect do | rotation |
204: mask = rotation.start_masks[i]
205: @map[mask[0]] = [ i, rotation ] if (mask)
206: mask || nil
207: end
208: @masks[i].compact!
209: end
210: end
211:
212:
213: def generate_rotations( directions )
214: 6.times do
215: rotations.push( Rotation.new(directions))
216: directions.collect! { | value | @@rotate_converter[value] }
217: end
218: end
219:
220:
221:
222: def fill_string( board_string)
223: location, rotation = @map[@placed]
224: rotation.offsets(location).each do | offset |
225: row, col = offset.divmod(6)
226: board_string[ row*5 + col, 1 ] = @type.to_s
227: end
228: end
229: end
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248: def blank_board
249: 0b111111100000100000100000100000100000100000100000100000100000100000
250: end
251:
252: def full_board
253: 0b111111111111111111111111111111111111111111111111111111111111111111
254: end
255:
256:
257: def is_even( location)
258: (location % 12) < 6
259: end
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272: def create_collector_support
273: odd_map = [0b11, 0b110, 0b1100, 0b11000, 0b10000]
274: even_map = [0b1, 0b11, 0b110, 0b1100, 0b11000]
275:
276: all_odds = Array.new(0b100000)
277: all_evens = Array.new(0b100000)
278: bit_counts = Array.new(0b100000)
279: new_regions = Array.new(0b100000)
280: 0.upto(0b11111) do | i |
281: bit_count = odd = even = 0
282: 0.upto(4) do | bit |
283: if (i[bit] == 1) then
284: bit_count += 1
285: odd |= odd_map[bit]
286: even |= even_map[bit]
287: end
288: end
289: all_odds[i] = odd
290: all_evens[i] = even
291: bit_counts[i] = bit_count
292: new_regions[i] = create_regions( i)
293: end
294:
295: $converter = []
296: 10.times { | row | $converter.push((row % 2 == 0) ? all_evens : all_odds) }
297: $bit_counts = bit_counts
298: $regions = new_regions.collect { | set | set.collect { | value | [ value, bit_counts[value], value] } }
299: end
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327: def prunable( board, location, slotting = false)
328: collectors = []
329:
330: (location / 6).to_i.upto(9) do | row_on |
331:
332: regions = $regions[(board >> (row_on * 6)) & 0b11111]
333: converter = $converter[row_on]
334:
335:
336:
337: initial_collector_count = collectors.length
338:
339:
340:
341:
342:
343:
344:
345:
346: regions.each do | region |
347: collector_found = nil
348: region_mask = region[2]
349: initial_collector_count.times do | collector_num |
350: collector = collectors[collector_num]
351: if (collector) then
352: collector_mask = collector[0]
353: if (collector_mask & region_mask != 0) then
354: if (collector_found) then
355: collector_found[0] |= collector_mask
356: collector_found[1] += collector[1]
357: collector_found[2] |= collector[2]
358: collectors[collector_num] = nil
359: else
360: collector_found = collector
361: collector[1] += region[1]
362: collector[2] |= region_mask
363: end
364: end
365: end
366: end
367: if (collector_found == nil) then
368: collectors.push(Array.new(region))
369: end
370: end
371:
372:
373:
374:
375:
376:
377: collectors.length.times do | collector_num |
378: collector = collectors[collector_num]
379: if (collector) then
380: if (collector[2] == 0) then
381: return true if (collector[1] % 5 != 0)
382: collectors[collector_num] = nil
383: else
384:
385:
386:
387:
388:
389:
390:
391:
392: return false if (collector[2] == 0b11111 && !slotting)
393: collector[0] = converter[collector[2]]
394: collector[2] = 0
395: end
396: end
397: end
398:
399:
400: collectors.compact!
401: end
402: return false if (collectors.length <= 1)
403: collectors.any? { | collector | (collector[1] % 5) != 0 }
404: end
405:
406:
407: def create_regions( value )
408: regions = []
409: cur_region = 0
410: 5.times do | bit |
411: if (value[bit] == 0) then
412: cur_region |= 1 << bit
413: else
414: if (cur_region != 0 ) then
415: regions.push( cur_region)
416: cur_region = 0;
417: end
418: end
419: end
420: regions.push(cur_region) if (cur_region != 0)
421: regions
422: end
423:
424:
425: def find_all
426: find_top( 1)
427: find_top( 0)
428: print_results
429: end
430:
431:
432: def print_results
433: print "#{@boards_found} solutions found\n\n"
434: print_full_board( @min_board)
435: print "\n"
436: print_full_board( @max_board)
437: print "\n"
438: end
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453: def find_top( rotation_skip)
454: board = blank_board
455: (@pieces.length-1).times do
456: piece = @pieces.shift
457: piece.masks[0].each do | mask, imask, cmask |
458: if ((rotation_skip += 1) % 2 == 0) then
459: piece.placed = mask
460: find( 1, 1, board | mask)
461: end
462: end
463: @pieces.push(piece)
464: end
465: piece = @pieces.shift
466: @pieces.push(piece)
467: end
468:
469:
470:
471:
472:
473:
474:
475:
476:
477:
478: def find( start_location, placed, board)
479:
480: while board[start_location] == 1
481: start_location += 1
482: end
483:
484: @pieces.length.times do
485: piece = @pieces.shift
486: piece.masks[start_location].each do | mask, imask, cmask |
487: if ( board & cmask == imask) then
488: piece.placed = mask
489: if (placed == 9) then
490: add_board
491: else
492: find( start_location + 1, placed + 1, board | mask)
493: end
494: end
495: end
496: @pieces.push(piece)
497: end
498: end
499:
500:
501: def print_full_board( board_string)
502: 10.times do | row |
503: print " " if (row % 2 == 1)
504: 5.times do | col |
505: print "#{board_string[row*5 + col,1]} "
506: end
507: print "\n"
508: end
509: end
510:
511: