Each code example is broken into two general parts:

• matrixsand.py - This contains a class which has the physics engine. You can't use it stand alone, you need to use it your application code. Also, you generally don't change anything in here.
• examplecode.py - This is your application code. This is what you write.

The name examplecode.py is notional. Replace that with whatever you want. And remember that naming it code.py will allow it to run automatically when powered.

## Simplified Matrix Sand Code

Here is the code for the simplified matrix sand physics. Save this as matrixsand.py into your CIRCUITPY folder. After that, just leave it alone. It will be used by application code examples which we we'll cover next.

```# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
#

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 + self._width * value
return self._grains[value]

def __setitem__(self, value, key):
if isinstance(value, tuple):
value = value + self._width * value
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
```

## Single Matrix Example

Let's start simple and just use a single 8x8 LED matrix. Wire up a matrix with address set to the default 0x70 as shown:

And then save the following code as code.py in your CIRCUITPY folder.

```# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
#

import time
import board
from adafruit_ht16k33 import matrix
import matrixsand

DELAY = 0.00 # add some delay if you want

# the accelo

# the matrix
matrix = matrix.Matrix8x8(board.I2C(), 0x70)

# the sand
sand = matrixsand.MatrixSand(8, 8)

# simple helper
def update_matrix():
for x in range(8):
for y in range(8):
matrix[x,y] = sand[x,y]

# add some initial sand
for sx in range(4):
for sy in range(4):
sand[sx, sy] = 1
update_matrix()

# loop forever
while True:
ax, ay, az = accelo.acceleration

# rotate coord sys
xx = ay
yy = ax
zz = az

# iterate the sand
updated = sand.iterate((xx, yy, zz))

# update matrix if needed
if updated:
update_matrix()

# sleep
time.sleep(DELAY)
```

Now move the breadboard around and watch the grains of sand go!

## Double Matrix Example

OK, now let's try two 8x8 matrices to create a 16x8 area. Since the class that takes care of the physics is general purpose, we can set it up for different sizes. So adapting the code for different sizes is easy.

Wire up the two matrices as shown below, with address 0x71 on the left and address 0x70 on the right.

And then save the following code as code.py in your CIRCUITPY folder.

```# SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
#

import time
import board
from adafruit_ht16k33 import matrix
import matrixsand

DELAY = 0.00 # add some delay if you want

# the accelo

# the matrix
matrix1 = matrix.Matrix8x8(board.I2C(), 0x70)
matrix2 = matrix.Matrix8x8(board.I2C(), 0x71)

# the sand
sand = matrixsand.MatrixSand(8, 16)

# simple helper
def update_matrix():
for x in range(8):
for y in range(16):
if y < 8:
matrix1[x, y] = sand[x, y]
else:
matrix2[x, y-8] = sand[x, y]

# add some initial sand
for sx in range(4):
for sy in range(4):
sand[sx, sy] = 1

update_matrix()

# loop forever
while True:
ax, ay, az = accelo.acceleration

# rotate coord sys
xx = ay
yy = ax
zz = az

# iterate the sand
updated = sand.iterate((xx, yy, zz))

# update matrix if needed
if updated:
update_matrix()

# sleep
time.sleep(DELAY)
```

Now move the breadboard around and watch the grains of sand go!

## Does This Work?

So...does this approach work? Well, as you can see from running the examples above, it's not perfect. Things move in sort of a klunky fashion and they can stack up in odd ways. Also, the lack of true kinematics seems to start to become apparent with 16 pixels to move.

But it does provide a general sense of the actual physics. It's reasonably fun to move the examples above around and watch the "sand" move about. So, sure, it works. Just not perfectly. But it's a good option for smaller displays where the full physics are not necessarily warranted.

Let's now use it to make the Time Triangle Thing.

This guide was first published on Jun 06, 2020. It was last updated on Jun 06, 2020.

This page (Examples) was last updated on Sep 03, 2022.