# Task #8: Serpentine Square!

**URL:** <https://forum.electromage.com/t/task-8-serpentine-square/1088>\
**Category:** Pixelblaze Academy of Wizardry and Enchantment\
**Created:** [March 20, 2021, 3:20am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088 "2021-03-20T03:20:58Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Scruffynerf](https://forum.electromage.com/letter_avatar_proxy/v4/letter/s/34f0e0/32.png) [@Scruffynerf](https://forum.electromage.com/u/Scruffynerf)\
**Post date:** [March 20, 2021, 3:20am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/1 "2021-03-20T03:20:58Z")

</div>

This one is free form, and partly related to St. Paddy’s day (just passed):

Make a snake with your matrix… Could be a line that moves, could be a wriggle, could be a full-on Snake game (self playing).

You decide how to make it Snake-y.

I would be remiss to not point at and recommend the [3D printed 16x16 matrix frame](https://forum.electromage.com/t/3d-printed-16x16-matrix-frame/1086)

Very slick, and I might print one myself soon. (Need to get my printer working again)

---

<div class="post-metadata">

**Author:** ![jeff](https://forum.electromage.com/user_avatar/forum.electromage.com/jeff/32/3473_2.png) [@jeff](https://forum.electromage.com/u/jeff)\
**Post date:** [March 23, 2021, 6:25am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/2 "2021-03-23T06:25:43Z")

</div>

This was really fun. Here’s what I came up with.

[![](https://img.youtube.com/vi/RfDidPGp3Vg/maxresdefault.jpg "Pixelblaze Snake") ](https://www.youtube.com/watch?v=RfDidPGp3Vg)

I’ll post the code in the library at the end of the week.

---

<div class="post-metadata">

**Author:** ![JustPete](https://forum.electromage.com/user_avatar/forum.electromage.com/justpete/32/1832_2.png) [@JustPete](https://forum.electromage.com/u/JustPete)\
**Post date:** [March 23, 2021, 5:14pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/3 "2021-03-23T17:14:56Z")

</div>

Fantastic! Brilliant! Organic motion. Love it!

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [March 23, 2021, 6:59pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/4 "2021-03-23T18:59:35Z")

</div>

As someone who lives around a lot of 'em, I heartily approve that snake! 🐍

 ![sheilasnake2](https://forum.electromage.com/uploads/default/original/1X/22feffce1efde389c76daf652d836d28a5b1760e.jpeg)

(Dog is very well trained concerning snakes. She stays well back, but she is **not** letting that guy get up on the porch. I used a broom to sweep him into a bucket, popped the lid on and released him well away from the house. They eat the incredibly destructive packrats, so they’re good to have around. Just another normal day in the desert… )

---

<div class="post-metadata">

**Author:** ![JustPete](https://forum.electromage.com/user_avatar/forum.electromage.com/justpete/32/1832_2.png) [@JustPete](https://forum.electromage.com/u/JustPete)\
**Post date:** [March 24, 2021, 3:04pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/5 "2021-03-24T15:04:07Z")

</div>

> [@zranger1](#):
>
> Just another normal day in the desert…

Yup. That’s one big 'ol snake! And a very good dog. Glad you don’t kill 'em, those snakes.

---

<div class="post-metadata">

**Author:** ![wizard](https://forum.electromage.com/user_avatar/forum.electromage.com/wizard/32/4_2.png) [@wizard](https://forum.electromage.com/u/wizard)\
**Post date:** [March 24, 2021, 3:45pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/6 "2021-03-24T15:45:58Z")

</div>

@jeff,  
That is so cool! I couldn’t help but stare and wonder, and mentally try to reverse engineer it 🙂 I can’t wait to play with it when you post the code. The way it jumps in speed is pretty cool too! And the softness/antialiasing of the “head” 💯

---

<div class="post-metadata">

**Author:** ![jeff](https://forum.electromage.com/user_avatar/forum.electromage.com/jeff/32/3473_2.png) [@jeff](https://forum.electromage.com/u/jeff)\
**Post date:** [March 25, 2021, 1:28am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/7 "2021-03-25T01:28:51Z")

</div>

Cool, I just uploaded. I know we don’t want spoilers, so I’ve included it here using the forum’s handy “Hide details” thing.

> **Click to expand code for Snake 2D**
>
> ```javascript
> /* 
> Snake - Generate a serpentine path around a matrix
>   
> This pattern relies on functions present in Pixelblaze v3 (hypot(), 
> mod(), arrayMutate()) but can be adapted to v2.
>   
> Demo on 16x16: https://youtu.be/RfDidPGp3Vg
> 
> Jeff Vyduna 2021 / MIT License
> */
> 
> // User-adjustable parameters
> var width = 16 // Width of matrix, in pixels
> var edgePct = .15 // Edge width, in percentage of the lesser of width or height. Snake tries to avoid edges. 
> var radius = sqrt(pixelCount) * 3/16 // Radius of the snake head in pixels. 3 on a 16x16
> var nominalDecay = .98 // Fade each pixel per frame. Adjusted for speed and delta.
> var maxDeflection = .2 // Radians per frame when escaping a boundary (edge)
> var nominalDeflection = .1 // Radians per frame when slithering
> 
> var nominalSpeed = 0.02, speed = nominalSpeed
> export function sliderSpeed (_v) { 
> speed = 2 * nominalSpeed * _v
> }
> 
> var height = pixelCount / width // Height of matrix, in pixels
> var posX = 0, posY = 0 // Positon of snakehead, in pixels. Set initial position.
> var bearing = PI / 4 // Direction of travel, in radians
> var deflection = nominalDeflection // Turn by deflecting the bearing each frame (radians)
> var adjSpeed // User-commanded speed modified by the wandering fn and proximity to edges
> var adjDeflection // Deflection angle modified for the adjusted speed
> var thisTurn // Accumulated radians in the current slither / turn
> var nominalDelta = 15 // A reference delta used to adapt turns for running speed (e.g. number of pixels)
> var cx = width / 2, cy = height / 2 // Centers in x and y, in pixels
> var cornerDist = hypot(cx, cy) // Distance from center to a corner
> var edge = min(width, height) * edgePct // Edge border thickness, in pixels
> var escapeDeflection = 0 // The current corrective deflection to escape edges or corners
> var matrix = array(width) // Array of pixels in matrix, [width][height]. Stores "heat".
> matrix.mutate((v) => array(height))
> 
> export function beforeRender(delta) {
> t1 = time(6 / 65.535) // Hue modifier
> t2 = time(30 / 65.535) // Speed wandering period
>   
> changeDirRandomly(delta)
> avoidWalls()
>   
> // Speed wanders around the user-specified speed, and gets faster when near the edges
> adjSpeed = speed * wander(t2) * (.5 + hypot(posX - cx, posY - cy)/cornerDist) 
> // Adjust the bearing deflection (curve) for speed input and delta
> adjDeflection = deflection * adjSpeed / nominalSpeed * delta / nominalDelta
> bearing = mod(bearing + adjDeflection, PI2) // Make the turn, keep bearing in 0..PI2
>   
> slither() // Reverse the turn so it doesn't normally eat its own tail
>   
> posX += adjSpeed * delta * cos(bearing)
> posY += adjSpeed * delta * sin(bearing)
>   
> bounceOffWalls()
> prerender()
> }
> 
> // Moody snake doesn't always slither predictibly
> function changeDirRandomly(_delta) {
> // At 100FPS, this is roughly reversing once within 3 seconds
> if(random(30 * _delta) < 1) deflection *= -1
> }
> 
> function avoidWalls() {
> var towardsVecX = 0, towardsVecY = 0
> if (width - posX < edge) towardsVecX = 1
> if (height - posY < edge) towardsVecY = 1
> if (posX < edge) towardsVecX = -1
> if (posY < edge) towardsVecY = -1
> // If set, (towardsVecX, towardsVecY) is now the vector to the closest edge or corner
> 
> if (towardsVecX == 0 && towardsVecY == 0) { // Not near an edge
> if (escapeDeflection) { // Was just in an edge (but now not)
> deflection = nominalDeflection * ((random(2) > 1) ? -1 : 1)
> thisTurn = escapeDeflection = 0
> }
> return
> }
>   
> // If we get here, we're in range of an edge or corner.
>   
> // Angle towards nearest wall or corner, 0..PI2
> towardsAngle = (atan2(towardsVecY, towardsVecX) + PI2) % PI2
>   
> if (towardsVecX * towardsVecY != 0) {
> // In a corner; keep an existing turn going or set one up
> escapeDeflection = escapeDeflection || maxDeflection
> } else {
> // Near an edge. Deflect away from it.
> deviation = angleDiff(bearing, towardsAngle)
> escapeDeflection = deviation > 0 ? maxDeflection : -maxDeflection
> }
> 
> // We will escape if our bearing is within 45 degrees of the vector away from edge of corner
> var willEscape = abs(angleDiff(towardsAngle + PI, bearing)) < PI / 8
> 
> deflection = willEscape ? 0 : escapeDeflection // End turns if headed away from the edge
> }
> 
> // Pseudo gradient noise
> // https://www.desmos.com/calculator/rvtrkskoqa
> function wander(t) {
> t *= 49.261 // Define period for t in 0..1; see graph
> return .6 + 3 * wave(t/2)*wave(t/3)*wave(t/5)*wave(t/7)
> }
> 
> // Turn the other direction if it's been a half turn
> function slither() {
> thisTurn += adjDeflection
> if (abs(thisTurn) > PI) {
> deflection *= -1
> thisTurn = 0
> }
> }
> 
> // Return (angle - targetAngle) within -PI..PI
> function angleDiff(angle, target) {
> return mod(angle - target + PI, PI2) - PI
> }
> 
> // Bounce like a billiard ball
> function bounceOffWalls() {
> if (posX <= 0 || posX >= width) {
> bearing = -bearing + PI
> posX = clamp(posX, 0, width)
> }
> if (posY <= 0 || posY >= height) {
> bearing *= -1
> posY = clamp(posY, 0, height)
> }
> }
> 
> // A 2D array holds each pixel's heat. Cool each pixel, and heat pixels 
> // within `radius` of head (posX, posY). Since the inner loop runs as 
> // much as render(), there are modifications for speed over readability.
> function prerender() {
> // Adjust the exponential decay (tail cooling) for the speed of travel
> var decay = pow(2, log2(nominalDecay) / (nominalSpeed / adjSpeed))
> var speedFactor = adjSpeed / nominalSpeed
> 
> // Heat up pixels near the snake's head
> for(x = 0; x < width; x++) {
> for(y = 0; y < height; y++) {
> matrix[x][y] *= decay
> var distanceToHead = hypot(posX - x, posY - y)
> if (distanceToHead > radius) continue // Boosts FPS
> proximity = 1 - distanceToHead / radius
> matrix[x][y] = min(1, matrix[x][y] + pow(proximity, 6) * speedFactor)
> }
> }
> }
> 
> export function render2D(index, x, y) {
> v = matrix[x * width][y * height]
> s = 1 - pow(v, 8)
> h = v / 4 + t1
> hsv(h, s, v * v)
>   
> // Uncomment to see a line showing the current commanded bearing.
> // Helpful when debugging edge avoidance. Costs half your FPS though!
> // plotAngle(bearing, x * width, y * height)
> }
> 
> // Utility to plot an angle from the center of the matrix to the 
> // boundary of the border `edge`. Can be called from within prerender() or render() 
> // Usage: plotAngle(bearing, x * width, y * height)
> function plotAngle(a, x, y) {
> x -= cx; y -= cy
> var pixelAng = atan2(y, x)
> var ad = abs(angleDiff(a, pixelAng)) // Angle difference between a and this pixel
> var hc = (width - 2 * edge) / 2 // Horizontal center box height
> var vc = (height - 2 * edge) / 2
> var bx = cos(a), by = sin(a)
> var dist = abs(x * by - y * bx) / hypot(bx, by) // Distance from pixel to the line to be plotted
> 
> if (abs(x) < hc && abs(y) < vc && ad < PI/2) hsv(0, 0, 1 - dist * 2)
> }
> 
> ```

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [March 25, 2021, 1:52am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/8 "2021-03-25T01:52:04Z")

</div>

@jeff, this is simply beautiful code! And @scruffynerf, thanks again for managing the tasks. These challenges are helping everyone up their game, as well as being a whole lot of fun.

---

<div class="post-metadata">

**Author:** ![wizard](https://forum.electromage.com/user_avatar/forum.electromage.com/wizard/32/4_2.png) [@wizard](https://forum.electromage.com/u/wizard)\
**Post date:** [March 25, 2021, 4:30pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/9 "2021-03-25T16:30:13Z")

</div>

@jeff, you have created my new favorite desk pet!

---

<div class="post-metadata">

**Author:** ![Patchlead](https://forum.electromage.com/letter_avatar_proxy/v4/letter/p/c68b51/32.png) [@Patchlead](https://forum.electromage.com/u/Patchlead)\
**Post date:** [March 27, 2021, 5:33am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/10 "2021-03-27T05:33:56Z")

</div>

My Pixelbaze2 does not like

return mod(angle - target + PI, PI2) - PI

tells me “Undefined symbol mod” is this a pizelblaze V3 code?

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [March 29, 2021, 2:20am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/11 "2021-03-29T02:20:58Z")

</div>

mod() is a PB3 thing – as I understand it, it’s actually a _remainder_ operation as opposed to the usual %. From the PB3 docs:

#### mod(`x`,`y`)

The floored remainder of the division `x/y`. The result uses the same sign as `y`. For example `mod(-3.5, 3)==2.5` whereas `-3.5 % 3==-.5`. This provides the same “wrapping” behavior used in the animation functions like `triangle` when `y`==1.

---

<div class="post-metadata">

**Author:** ![wizard](https://forum.electromage.com/user_avatar/forum.electromage.com/wizard/32/4_2.png) [@wizard](https://forum.electromage.com/u/wizard)\
**Post date:** [March 29, 2021, 6:34pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/12 "2021-03-29T18:34:58Z")

</div>

The `%` and `mod` functions very quite a bit from language to language. The new `mod` function is a floored remainder operation, which is what Python would use, but JavaScript would not. It is very handy and works well with the way Pixelblaze treads cyclical values between 0-1.

From Wikipedia:

> “[…] the remainder would have the _same sign as the divisor_ . Due to the floor function, the quotient is always rounded downwards, even if it is already negative.”

> **[Modulo](https://en.wikipedia.org/wiki/Modulo_operation)**
>
> In computing and mathematics, the modulo operation returns the remainder or signed remainder of a division, after one number is divided by another, the latter being called the modulus of the operation.
> Given two positive numbers a and n, a modulo n (often abbreviated as a mod n) is the remainder of the Euclidean division of a by n, where a is the dividend and n is the divisor.
> For example, the expression "5 mod 2" evaluates to 1, because 5 divided by 2 has a quotient of 2 and a remainder of 1, ...

The implementation is pretty simple. Using `%`, if the result has a sign that doesn’t equal the sign of the divisor, add the divisor to it.

```auto
function flooredMod(dividend, divisor) {
  var res = dividend % divisor
  if ((res != 0) && (res > 0) != (divisor > 0)) { //this compares the sign of both
    res += divisor
  }
  return res
}

```

As you can see, they will only differ from `%` when there are negative numbers mixed with positive.  
As a quick example, using the `%` operator if you take `-1.2 % 1` you get `-0.2`. This makes sense if you think `-1.2/1 == -1.2 == -1 + -0.2` so taking away the whole integer part, leaving `-0.2` is the remainder.

Using `mod` on V3, or the equivalent `flooredMod` above gets you a different result. `mod(-1.2, 1)` results in `0.8` a positive number. This is very useful if you are trying to “wrap” a number between 0 and 1 (or some other range), and might end up with a negative number. This makes sense if you think about modulus math as a number line that wraps around in a circle with a circumference equal to the divisor. Imagine a circle with 0-360 degrees. Where is -45 degrees? 315 degrees! This is where the floored division modulus comes in handy: `mod(-45, 360) == 315`.

---

<div class="post-metadata">

**Author:** ![Scruffynerf](https://forum.electromage.com/letter_avatar_proxy/v4/letter/s/34f0e0/32.png) [@Scruffynerf](https://forum.electromage.com/u/Scruffynerf)\
**Post date:** [April 27, 2021, 3:45pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/13 "2021-04-27T15:45:36Z")

</div>

@jeff, on a fresh epe install from the pattern collection, v3.16 and ensuring it’s got the right number of pixels (16x16 map, 256 pixels in settings)  
getting this error:

`line 162: v = matrix[x * width][y * height]`  
Array index out of bounds

---

<div class="post-metadata">

**Author:** ![jeff](https://forum.electromage.com/user_avatar/forum.electromage.com/jeff/32/3473_2.png) [@jeff](https://forum.electromage.com/u/jeff)\
**Post date:** [April 28, 2021, 12:57am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/14 "2021-04-28T00:57:51Z")

</div>

Hey @Scruffynerf! This actually might be related to a bug @wizard was chasing down today, having to do with how I’ve been allocating my 2D arrays lately.

Can you try replacing line 37:

`matrix.mutate((v) => array(height))`

With this instead?

`for (i = 0; i < width; i++) matrix[i] = array(height)`

---

<div class="post-metadata">

**Author:** ![Scruffynerf](https://forum.electromage.com/letter_avatar_proxy/v4/letter/s/34f0e0/32.png) [@Scruffynerf](https://forum.electromage.com/u/Scruffynerf)\
**Post date:** [April 28, 2021, 1:43pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/15 "2021-04-28T13:43:28Z")

</div>

weirdly, when I reloaded it today, from the .epe, it just worked. Clearly, a bug somewhere.

---

<div class="post-metadata">

**Author:** ![pixie](https://forum.electromage.com/letter_avatar_proxy/v4/letter/p/7bcc69/32.png) [@pixie](https://forum.electromage.com/u/pixie)\
**Post date:** [May 19, 2021, 9:43am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/16 "2021-05-19T09:43:16Z")

</div>

I’m trying to backport this to V2 (I’m running v2.25) by adding stubs for the V3 functions:

```
// V2 stubs
function hypot(x, y) {
  return sqrt(x*x + y*y)
}
function mod(dividend,divisor) {
  var res = dividend % divisor
  if ((res > 0) != (divisor > 0)) { //this compares the sign of both
    res += divisor
  }
  return res
}

```

but I’m getting an error message “lastSrc is not defined” which seems to be referring to the editor/parser code itself rather than my pattern. Bug?

Also, I love all the cool patterns people are generating but – please don’t take this as a criticism – I notice that a lot of them contain hardcoded “magic numbers” that negate the benefits of PB’s clever 0…1 world coordinate system. If these numbers were derived from pixelCount (or sqrt(pixelCount) for the width of square matrixes) then the patterns would work the same regardless of the number of LEDs.

---

<div class="post-metadata">

**Author:** ![Scruffynerf](https://forum.electromage.com/letter_avatar_proxy/v4/letter/s/34f0e0/32.png) [@Scruffynerf](https://forum.electromage.com/u/Scruffynerf)\
**Post date:** [May 19, 2021, 9:56am UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/17 "2021-05-19T09:56:48Z")

</div>

Ooh, I like that idea, using sqrt of pixelcount as a easy default (with a commented out option to define directly for the non square Matrixes)

I agree, hard coded assumptions suck. Making patterns that don’t need any is better. I still plan on going thru patterns and doing some tweaks like this. On my to-do list (for way too long)

No idea where the lastSrc comes from. @wizard ?

---

<div class="post-metadata">

**Author:** ![pixie](https://forum.electromage.com/letter_avatar_proxy/v4/letter/p/7bcc69/32.png) [@pixie](https://forum.electromage.com/u/pixie)\
**Post date:** [May 19, 2021, 1:48pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/18 "2021-05-19T13:48:06Z")

</div>

Found the cause: the line `matrix.mutate((v) => array(height))` uses the V3 feature `array.mutate`, but I still think the parser should give a better error message.

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [May 19, 2021, 3:06pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/19 "2021-05-19T15:06:59Z")

</div>

If by “magic numbers”, you mean frame buffer dimensions, well… if your algorithm needs a frame buffer, you kind of need to know how large it’s going to be so you can allocate memory for it. You can determine this at runtime by deriving it from x,y and index on the first render pass, and I’ve done it this way in a few patterns, but it has always struck me as way too clever for supposedly tuturial code.

So in the interest of simplicity, yes, I declare height and width variables and use them for array allocation. It’s easier to understand than swapping render functions, and easy to change in the code if necessary. And at the incoming x and y are simply scaled to the array size to select a frame buffer value, effectively scaling the frame buffer to the actual size of the array.

---

<div class="post-metadata">

**Author:** ![pixie](https://forum.electromage.com/letter_avatar_proxy/v4/letter/p/7bcc69/32.png) [@pixie](https://forum.electromage.com/u/pixie)\
**Post date:** [May 19, 2021, 7:57pm UTC](https://forum.electromage.com/t/task-8-serpentine-square/1088/20 "2021-05-19T19:57:42Z")

</div>

Not just frame buffers, but everything. For example, I’m trying to get @scruffynerf’s cool port of tixy to run on 8x8 and 32x32 matrixes, but the the original tixy.land patterns assume a 16x16 grid so many of the pattern functions (which he copied directly from the original) have hardcoded numbers like 15, 7.5, 16, 8, 4, … which are really just the matrix width (sometimes zero-based, sometimes one-based) divided by 1, 2 and 4.

@wizard went to all the trouble of designing a scalable world coordinate system; I just think it would be a good thing to use it whenever possible.

[Next page](https://forum.electromage.com/t/task-8-serpentine-square/1088.md?page=2)
