Last night I found a numpy-less solution.
The only libraries you need is math and random, and they both have micropython ports.
from random import random, seed, choice, uniform
from math import floor, ceil
To replace the functionality of the numpy array I made this Pixamon object. It’s really just a 3d array with some getters, setters, validators, and helper methods.
class Pixamon:
def __init__(self, size):
self._size = size
self.data = self.get_blank_data()
def get_blank_data(self):
return [[[0, 0, 0] for _ in range(self._size)] for _ in range(self._size)]
def set(self, x, y, rgb):
if not self.valid_colour(rgb):
raise NotImplementedError("RGB data must be 3*collection of uInt8")
if not self.valid_coords(x, y):
raise ArgumentException("Invalid Co-ordinates")
self.data[x][y] = list(rgb)
def get_rgb(self, x, y):
if not self.valid_coords(x, y):
raise ArgumentException("Invalid Co-ordinates")
return self.data[x][y]
def is_colour_at(self, x, y):
return any(self.data[x][y])
def get_rotated90(self):
rotated_data = self.get_blank_data()
for i in range(self._size):
for j in range(self._size):
rotated_data[j][self._size - 1 - i] = self.data[i][j]
return rotated_data
def valid_coords(self, x, y):
return not (0 < x < self._size & 0 < y < self._size)
def valid_colour(self, rgb):
is_valid = (len(rgb) == 3)
print(len(rgb) == 3)
for c in rgb:
print(c)
print(isinstance(c, int))
print(c in range(0, 256))
is_valid &= isinstance(c, int)
is_valid &= c in range(0, 256)
return is_valid
def draw_to_console(self):
rota = self.get_rotated90()
for i in range(len(rota)):
for j in range(len(rota[i])):
if any(rota[i][j]):
print("*", end="")
else:
print(" ", end="")
print()
We need to modify the make_monster()
function to use our new data-type.
class Vec2:
#A 2d Vector
def __init__(self, x, y):
self.x = x
self.y = y
def make_random_colour():
r = floor(random() * 255)
g = floor(random() * 255)
b = floor(random() * 255)
return [r,g,b]
def fmap(a_rge, b_rge, c):
#maps C from range A0-A1 to range B0-B1
return b_rge.x + (c - a_rge.x) * (b_rge.y - b_rge.x) / (a_rge.y - a_rge.x)
def make_monster(mon_seed, size, loc):
'''
Construct an pixmon
args ->
mon_seed : the seed for the random number generator
size : width & height of the numpy array
loc : Vector of top center point of the pixmon
returns ->
A 3d numpy array of u8s with shape [width][height][rgb]
'''
#initialise the rng
seed(mon_seed)
#Blank Datastructure for our monster
monster = Pixamon(size)
#Chance to flip axis for a bit more variety
flip_axis = choice([True, False])
#width and height of draw space
w = size - 3 if flip_axis else size/2 - 1
h = size - 3 if not flip_axis else size/2 - 1
#Generate sensible biases... just the right amount of chaos
normal = Vec2(0.0, 1.0)
density = fmap(normal, Vec2(0.8, 0.9), random())
y_bias = fmap(normal, Vec2(0.2, -0.2), random())
colour_rand = fmap(normal, Vec2(0.75, 0.95), random())
for k in range(0, int(w*h)):
#We need our globals, depending on if we flipped
i = k/w if flip_axis else k % w
j = k/w if not flip_axis else k % w
#Let's put our biases to work
is_hole = random() > density
a_scalar = floor(uniform(0, size/2)) * floor(uniform(0, size/2))
y_scalar = floor(1.0 - 2.0 * y_bias)
xy_scalar = pow(i, 2) + pow(j - y_scalar * (h/2), 2)
bias = a_scalar > xy_scalar
#If we're in the sweet spot
if bias and not is_hole:
# Indices
pxl = floor(loc.x - int(i)) % size #Prime X axis
pxr = floor(loc.x + int(i)) % size #Mirror X axis
py = floor(loc.y + int(j)) % size #Row
#Get the colour based on colour_rand (which is based on mon_seed)
colour = make_random_colour()
#Paint it symetrically
monster.set(pxl, py, colour)
monster.set(pxr, py, colour)
return monster
And now make_monster()
returns a Pixamon, not a numpy array.
#Make a Monster
my_pixamon = make_monster(seed_val, size, location)
my_pixamon.draw_to_console()
Should be able to run this in micropython for LEDS