107 lines
3.8 KiB
Python
Executable file
107 lines
3.8 KiB
Python
Executable file
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
|