a-robots-conundrum/arobotsconundrum/logic/colourboxes.py

142 lines
4.3 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# This file is part of A Robot's Conundrum.
#
# A Robot's Conundrum is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# A Robot's Conundrum is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# A Robot's Conundrum. If not, see <http://www.gnu.org/licenses/>.
#
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
#
# colourboxes.py
# --------------------
# date created : Wed Aug 8 2012
# copyright : (C) 2012 Niels G. W. Serup
# maintained by : Niels G. W. Serup <ngws@metanohi.name>
"""
Colour boxes.
"""
from __future__ import print_function
import random
import itertools
def generate_colour_boxes(nwells, nboxes):
"""
Generate colour boxes that can be used to make all wells white. None of the
generated colour boxes are white.
Arguments:
nwells -- number of wells
nboxes -- maximum number of boxes needed to make all wells white.
Return [[(r, g, b)]]
where r : 0|1, g : 0|1, b : 0|1
"""
nbits = nwells * 3
data = [[0 for _ in range(nboxes)] for _ in range(nbits)]
def insert_1(x):
t = random.randrange(0, nboxes)
x0, x1 = _get_oxs(x)
for y in range(t, nboxes) + range(0, t):
if data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1):
data[x][y] = 1
break
else:
raise Exception("Cannot maintain no 111s invariant.")
def insert_two_1(x):
t = random.randrange(0, nboxes)
x0, x1 = _get_oxs(x)
if len(list(filter(lambda y: data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1),
range(nboxes)))) < 2:
return
for _ in range(2):
for y in range(t, nboxes) + range(0, t):
if data[x][y] == 0 and not (data[x0][y] == 1 and data[x1][y] == 1):
data[x][y] = 1
break
for x in range(len(data)):
insert_1(x)
for x in range(len(data)):
for _ in range(random.randrange(0, (nboxes + 1) / 2)):
insert_two_1(x)
boxes = []
for y in range(nboxes):
box = []
boxes.append(box)
for x in range(0, nbits, 3):
r = data[x][y]
g = data[x + 1][y]
b = data[x + 2][y]
box.append((r, g, b))
return boxes
def _get_oxs(x):
return (x + 1, x + 2) if x % 3 == 0 \
else (x - 1, x + 1) if x % 3 == 1 \
else (x - 2, x - 1)
def generate_random_box(nwells, min_nonblacks=0):
"""
Generate a box that triggers nwells wells, with random colors except white
(111).
Arguments:
min_nonblacks -- minimum number of well colours in a box required not to be
black.
"""
def gen_wc():
wc = [random.choice((0, 1)) for i in range(3)]
if all(b == 1 for b in wc):
wc[random.randrange(3)] = 0
return wc
def gen_wc_nonblack():
wc = gen_wc()
if all(b == 0 for b in wc):
wc[random.randrange(3)] = 1
return wc
colours = [tuple(gen_wc()) for _ in range(nwells)]
nonblack = lambda t: any(n == 1 for n in t)
missing_nonblacks = min_nonblacks - len(list(filter(nonblack, colours)))
i = 0
while missing_nonblacks > 0:
if not nonblack(colours[i]):
colours[i] = gen_wc_nonblack()
missing_nonblacks -= 1
i += 1
return colours
def get_colours(boxes):
colours = []
for i in range(len(boxes[0])):
r, g, b = boxes[0][i]
for j in range(1, len(boxes)):
r1, g1, b1 = boxes[j][i]
r ^= r1
g ^= g1
b ^= b1
colours.append((r, g, b))
return colours
def makes_all_wells_white(boxes):
"""
Determine if the boxes make all wells white when XOR'ed together.
"""
return all(c == 1 for c in itertools.chain(*get_colours(boxes)))