diff --git a/CircuitPython_LED_Sand_Hourglass/matrixsand.py b/CircuitPython_LED_Sand_Hourglass/double_matrix/matrixsand.py similarity index 100% rename from CircuitPython_LED_Sand_Hourglass/matrixsand.py rename to CircuitPython_LED_Sand_Hourglass/double_matrix/matrixsand.py diff --git a/CircuitPython_LED_Sand_Hourglass/hourglass/matrixsand.py b/CircuitPython_LED_Sand_Hourglass/hourglass/matrixsand.py new file mode 100755 index 000000000..f4adf4d69 --- /dev/null +++ b/CircuitPython_LED_Sand_Hourglass/hourglass/matrixsand.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +class MatrixSand: + """Class to simulate simplified sand physics.""" + + def __init__(self, width, height): + self._width = width + self._height = height + self._grains = [False] * width * height + + def __getitem__(self, value): + if isinstance(value, tuple): + value = value[0] + self._width * value[1] + return self._grains[value] + + def __setitem__(self, value, key): + if isinstance(value, tuple): + value = value[0] + self._width * value[1] + self._grains[value] = key + + def _side_count(self, upside_down=False): + left = right = 0 + for x in range(self._width): + for y in range(self._height): + if x != y and self[x, y]: + if x > y: + right += 1 + else: + left += 1 + if upside_down: + return right, left + else: + return left, right + + def iterate(self, acceleration): + """Update sand based on supplied acceleration tuple. Returns True if + any motion occurred, otherwise False.""" + #pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches + + ax, ay, az = acceleration + + # if z dominates, don't do anything + if abs(az) > abs(ax) and abs(az) > abs(ay): + return False + + # unit vectors for accelo + ix = iy = 0 + if abs(ax) > 0.01: + ratio = abs(ay / ax) + if ratio < 2.414: # tan(67.5deg) + ix = 1 if ax > 0 else -1 + if ratio > 0.414: # tan(22.5deg) + iy = 1 if ay > 0 else -1 + else: + iy = 1 if ay > 0 else -1 + + # buffer + new_grains = self._grains[:] + + # flag to indicate change + updated = False + + # loop through the grains + for x in range(self._width): + for y in range(self._height): + # is there a grain here? + if self[x, y]: + moved = False + # compute new location + newx = x + ix + newy = y + iy + # bounds check + newx = max(min(self._width-1, newx), 0) + newy = max(min(self._height-1, newy), 0) + # wants to move? + if x != newx or y != newy: + moved = True + # is it blocked? + if new_grains[newx + self._width * newy]: + # can we move diagonally? + if not new_grains[x + self._width * newy] and \ + not new_grains[newx + self._width * y]: + # can move either way + # move away from fuller side + left, right = self._side_count(ax < 0 and ay < 0) + if left >= right: + newy = y + elif right > left: + newx = x + elif not new_grains[x + self._width * newy]: + # move in y only + newx = x + elif not new_grains[newx + self._width * y]: + # move in x only + newy = y + else: + # nope, totally blocked + moved = False + # did it move? + if moved: + new_grains[x + self._width * y] = False + new_grains[newx + self._width * newy] = True + updated = True + + # did things change? + if updated: + self._grains = new_grains + + return updated diff --git a/CircuitPython_LED_Sand_Hourglass/single_matrix/matrixsand.py b/CircuitPython_LED_Sand_Hourglass/single_matrix/matrixsand.py new file mode 100755 index 000000000..f4adf4d69 --- /dev/null +++ b/CircuitPython_LED_Sand_Hourglass/single_matrix/matrixsand.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +class MatrixSand: + """Class to simulate simplified sand physics.""" + + def __init__(self, width, height): + self._width = width + self._height = height + self._grains = [False] * width * height + + def __getitem__(self, value): + if isinstance(value, tuple): + value = value[0] + self._width * value[1] + return self._grains[value] + + def __setitem__(self, value, key): + if isinstance(value, tuple): + value = value[0] + self._width * value[1] + self._grains[value] = key + + def _side_count(self, upside_down=False): + left = right = 0 + for x in range(self._width): + for y in range(self._height): + if x != y and self[x, y]: + if x > y: + right += 1 + else: + left += 1 + if upside_down: + return right, left + else: + return left, right + + def iterate(self, acceleration): + """Update sand based on supplied acceleration tuple. Returns True if + any motion occurred, otherwise False.""" + #pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches + + ax, ay, az = acceleration + + # if z dominates, don't do anything + if abs(az) > abs(ax) and abs(az) > abs(ay): + return False + + # unit vectors for accelo + ix = iy = 0 + if abs(ax) > 0.01: + ratio = abs(ay / ax) + if ratio < 2.414: # tan(67.5deg) + ix = 1 if ax > 0 else -1 + if ratio > 0.414: # tan(22.5deg) + iy = 1 if ay > 0 else -1 + else: + iy = 1 if ay > 0 else -1 + + # buffer + new_grains = self._grains[:] + + # flag to indicate change + updated = False + + # loop through the grains + for x in range(self._width): + for y in range(self._height): + # is there a grain here? + if self[x, y]: + moved = False + # compute new location + newx = x + ix + newy = y + iy + # bounds check + newx = max(min(self._width-1, newx), 0) + newy = max(min(self._height-1, newy), 0) + # wants to move? + if x != newx or y != newy: + moved = True + # is it blocked? + if new_grains[newx + self._width * newy]: + # can we move diagonally? + if not new_grains[x + self._width * newy] and \ + not new_grains[newx + self._width * y]: + # can move either way + # move away from fuller side + left, right = self._side_count(ax < 0 and ay < 0) + if left >= right: + newy = y + elif right > left: + newx = x + elif not new_grains[x + self._width * newy]: + # move in y only + newx = x + elif not new_grains[newx + self._width * y]: + # move in x only + newy = y + else: + # nope, totally blocked + moved = False + # did it move? + if moved: + new_grains[x + self._width * y] = False + new_grains[newx + self._width * newy] = True + updated = True + + # did things change? + if updated: + self._grains = new_grains + + return updated