|
| 1 | +class MatrixSand: |
| 2 | + """Class to simulate simplified sand physics.""" |
| 3 | + |
| 4 | + def __init__(self, width, height): |
| 5 | + self._width = width |
| 6 | + self._height = height |
| 7 | + self._grains = [False] * width * height |
| 8 | + |
| 9 | + def __getitem__(self, value): |
| 10 | + if isinstance(value, tuple): |
| 11 | + value = value[0] + self._width * value[1] |
| 12 | + return self._grains[value] |
| 13 | + |
| 14 | + def __setitem__(self, value, key): |
| 15 | + if isinstance(value, tuple): |
| 16 | + value = value[0] + self._width * value[1] |
| 17 | + self._grains[value] = key |
| 18 | + |
| 19 | + def _side_count(self, upside_down=False): |
| 20 | + left = right = 0 |
| 21 | + for x in range(self._width): |
| 22 | + for y in range(self._height): |
| 23 | + if x != y and self[x, y]: |
| 24 | + if x > y: |
| 25 | + right += 1 |
| 26 | + else: |
| 27 | + left += 1 |
| 28 | + if upside_down: |
| 29 | + return right, left |
| 30 | + else: |
| 31 | + return left, right |
| 32 | + |
| 33 | + def iterate(self, acceleration): |
| 34 | + """Update sand based on supplied acceleration tuple. Returns True if |
| 35 | + any motion occurred, otherwise False.""" |
| 36 | + #pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches |
| 37 | + |
| 38 | + ax, ay, az = acceleration |
| 39 | + |
| 40 | + # if z dominates, don't do anything |
| 41 | + if abs(az) > abs(ax) and abs(az) > abs(ay): |
| 42 | + return False |
| 43 | + |
| 44 | + # unit vectors for accelo |
| 45 | + ix = iy = 0 |
| 46 | + if abs(ax) > 0.01: |
| 47 | + ratio = abs(ay / ax) |
| 48 | + if ratio < 2.414: # tan(67.5deg) |
| 49 | + ix = 1 if ax > 0 else -1 |
| 50 | + if ratio > 0.414: # tan(22.5deg) |
| 51 | + iy = 1 if ay > 0 else -1 |
| 52 | + else: |
| 53 | + iy = 1 if ay > 0 else -1 |
| 54 | + |
| 55 | + # buffer |
| 56 | + new_grains = self._grains[:] |
| 57 | + |
| 58 | + # flag to indicate change |
| 59 | + updated = False |
| 60 | + |
| 61 | + # loop through the grains |
| 62 | + for x in range(self._width): |
| 63 | + for y in range(self._height): |
| 64 | + # is there a grain here? |
| 65 | + if self[x, y]: |
| 66 | + moved = False |
| 67 | + # compute new location |
| 68 | + newx = x + ix |
| 69 | + newy = y + iy |
| 70 | + # bounds check |
| 71 | + newx = max(min(self._width-1, newx), 0) |
| 72 | + newy = max(min(self._height-1, newy), 0) |
| 73 | + # wants to move? |
| 74 | + if x != newx or y != newy: |
| 75 | + moved = True |
| 76 | + # is it blocked? |
| 77 | + if new_grains[newx + self._width * newy]: |
| 78 | + # can we move diagonally? |
| 79 | + if not new_grains[x + self._width * newy] and \ |
| 80 | + not new_grains[newx + self._width * y]: |
| 81 | + # can move either way |
| 82 | + # move away from fuller side |
| 83 | + left, right = self._side_count(ax < 0 and ay < 0) |
| 84 | + if left >= right: |
| 85 | + newy = y |
| 86 | + elif right > left: |
| 87 | + newx = x |
| 88 | + elif not new_grains[x + self._width * newy]: |
| 89 | + # move in y only |
| 90 | + newx = x |
| 91 | + elif not new_grains[newx + self._width * y]: |
| 92 | + # move in x only |
| 93 | + newy = y |
| 94 | + else: |
| 95 | + # nope, totally blocked |
| 96 | + moved = False |
| 97 | + # did it move? |
| 98 | + if moved: |
| 99 | + new_grains[x + self._width * y] = False |
| 100 | + new_grains[newx + self._width * newy] = True |
| 101 | + updated = True |
| 102 | + |
| 103 | + # did things change? |
| 104 | + if updated: |
| 105 | + self._grains = new_grains |
| 106 | + |
| 107 | + return updated |
0 commit comments