Enable Javascript in your browser and then refresh this page, for a much enhanced experience.
First solution in Clear category for Unruly by mortonfox
import itertools
def unruly(grid):
# Check if we can add an opposing color to a horizontal or vertical triple.
def check3(c1, c2, c3):
w = 0
b = 0
e = 0
emptyr = None
emptyc = None
for r, c in (c1, c2, c3):
cell = grid[r][c]
if cell == 'W':
w += 1
elif cell == 'B':
b += 1
else:
emptyr = r
emptyc = c
e += 1
if e == 1 and w == 2:
grid[emptyr][emptyc] = 'B'
return True
if e == 1 and b == 2:
grid[emptyr][emptyc] = 'W'
return True
# Check an entire row or column.
def checkgrp(coords):
w = 0
b = 0
e = 0
empties = []
for r, c in coords:
cell = grid[r][c]
if cell == 'W':
w += 1
elif cell == 'B':
b += 1
else:
empties.append((r, c))
e += 1
if e == 0:
return False
# If setting all the empty cells to a certain color will make W and B
# cells balance, then do that.
if w + e == b:
for r, c in empties:
grid[r][c] = 'W'
return True
if b + e == w:
for r, c in empties:
grid[r][c] = 'B'
return True
if e > 9:
return
# If there are a small number of empty cells remaining, try all the
# possibilities.
half = len(coords) // 2
valid_poss = []
for poss in set(itertools.permutations(['W'] * (half - w) + ['B'] * (half - b))):
s = ''
poss_indx = 0
for r, c in coords:
if grid[r][c] == '.':
s += poss[poss_indx]
poss_indx += 1
else:
s += grid[r][c]
if 'WWW' not in s and 'BBB' not in s:
valid_poss.append(poss)
# In all the valid possibilities, if a certain cell is always B or
# always W, then set it that way.
changed = False
for i in range(e):
if all(poss[i] == 'W' for poss in valid_poss):
r, c = empties[i]
grid[r][c] = 'W'
changed = True
continue
if all(poss[i] == 'B' for poss in valid_poss):
r, c = empties[i]
grid[r][c] = 'B'
changed = True
continue
return changed
grid = [list(row) for row in grid]
progress = True
while progress:
progress = False
# Check all 3-cell triples.
for r in range(len(grid)):
for c in range(len(grid[r]) - 2):
if check3((r, c), (r, c+1), (r, c+2)):
progress = True
for c in range(len(grid[0])):
for r in range(len(grid) - 2):
if check3((r, c), (r+1, c), (r+2, c)):
progress = True
# Check all rows and all columns.
for r in range(len(grid)):
if checkgrp([(r, c) for c in range(len(grid[r]))]):
progress = True
for c in range(len(grid[0])):
if checkgrp([(r, c) for r in range(len(grid))]):
progress = True
return [''.join(row) for row in grid]
if __name__ == '__main__':
def grid2spec(grid):
"""To get the game id."""
import re
nb_rows, nb_cols = len(grid), len(grid[0])
spec, length = [f'{nb_cols}x{nb_rows}:'], nb_rows * nb_cols
for points, item in re.findall(r'(\.*)(B|W)', ''.join(grid)):
length -= len(points) + 1
char = chr(ord('a') + len(points))
spec.append(char.upper() if item == 'B' else char)
spec.append(chr(ord('a') + length))
return ''.join(spec)
def checker(grid, result):
try:
result = list(result)
except TypeError:
raise AssertionError('You must return an iterable/list/tuple.')
assert all(isinstance(row, str) for row in result), \
'Must return an iterable of strings.'
nb_rows, nb_cols = len(grid), len(grid[0])
assert len(result) == nb_rows and \
all(len(row) == nb_cols for row in result), 'Wrong size.'
forbidden_chars = ''.join(set.union(*map(set, result)) - set('WB.'))
assert not forbidden_chars, \
f'Only the chars "WB." are allowed, not {forbidden_chars!r}.'
forbidden_changes = sum(c1 != c2 for r1, r2 in zip(grid, result)
for c1, c2 in zip(r1, r2) if c1 != '.')
assert not forbidden_changes, \
f'You changed {forbidden_changes} cells given at the start.'
miss = sum(row.count('.') for row in result)
if miss:
print('You can look what is missing here:')
print('https://www.chiark.greenend.org.uk/~sgtatham/puzzles/js/'
f'unruly.html#{grid2spec(result)}')
print('You just need to open a new webpage with that url.')
assert not miss, f'{miss} cells are still empty.'
columns = map(''.join, zip(*result))
for _type, lines in (('row', result), ('column', columns)):
for n, line in enumerate(lines):
Ws, Bs = map(line.count, 'WB')
assert Ws == Bs, f'{Ws} W & {Bs} B in {_type} {n} {line!r}.'
for item in 'WB':
assert item * 3 not in line, \
f'{item * 3} found in {_type} {n} {line!r}.'
TESTS = (
('......',
'..B...',
'W.B.W.',
'......',
'W...W.',
'WW..W.'),
('....WW.B',
'W..B....',
'.B...W..',
'BW....W.',
'........',
'.WW.....',
'W...BB.B',
'W....B..'),
('B..........B',
'..BB.B.W..B.',
'B........B..',
'....BW.W...W',
'.W........W.',
'.W...B.....B',
'..B..BB...W.',
'BW....B.....'),
)
for test_nb, grid in enumerate(TESTS, 1):
result = unruly(grid)
try:
checker(grid, result)
except AssertionError as error:
print(f'You failed the test #{test_nb}:', error.args[0])
print('Your result:', *result, sep='\n')
break
else:
print('Well done! Click on "Check" for bigger tests.')
Jan. 7, 2020