Skip to content

Commit 8fe1d49

Browse files
committed
2 parents 3802df1 + 41c8cfe commit 8fe1d49

File tree

3 files changed

+222
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)