/* * Board: Seeeduino XAIO * Target: 12 x 12 NeoPixel-powered ping pong ball array * Author: Clive "Max" Maxfield (max@clivemaxfield.com) * License: The MIT License (See full license at the bottom of this file) * * Notes: Worm demo program to run on the NeoPixel Simulator * Uses a NeoPixel_Simulator library that works as a drop-in replacement * for the Adafruit_NeoPixel library and outputs a 12x12 grid to the * Serial Monitor window at 115200 baud. */ // Decide which library to use #define SIMULATE_NEOS // Comment this line out to use the Adafruit library #ifdef SIMULATE_NEOS // Remember to set the Serial Monitor to 115200 baud #include // if you are using the simulator #define Adafruit_NeoPixel NeoPixel_Simulator #else #include #endif #define NUM_NEOS 145 // 144 in array plus "sacrificial" pixel as voltage level shifter #define NUM_ROWS 12 #define NUM_COLS 12 #define MIN_X 0 #define MAX_X (NUM_COLS - 1) #define MIN_Y 0 #define MAX_Y (NUM_ROWS - 1) #define NUM_WHEEL_COLORS 12 // Number of primary, secondary, and tertiary colors we're using #define COLOR_WHITE 0xFFFFFFU #define COLOR_BLACK 0x000000U #define COLOR_RED 0xFF0000U #define COLOR_GREEN 0x00FF00U #define COLOR_BLUE 0x0000FFU #define COLOR_YELLOW 0xFFFF00U #define COLOR_CYAN 0x00FFFFU #define COLOR_MAGENTA 0xFF00FFU #define COLOR_FLUSH_ORANGE 0xFF8000U #define COLOR_ROSE 0xFF0080U #define COLOR_CHARTREUSE 0x80FF00U #define COLOR_SPRING_GREEN 0x00FF80U #define COLOR_AZURE 0x0080FFU #define COLOR_ELECTRIC_INDIGO 0x8000FFU #define COLOR_WORM_HEAD COLOR_MAGENTA #define COLOR_WORM_BODY COLOR_BLUE #define WORM_LENGTH 4 #define HEAD 0 #define TAIL (WORM_LENGTH - 1) #define NUM_DIRECTIONS 4 #define NORTH 0 // Binary #define SOUTH 1 #define EAST 2 #define WEST 3 #define ANY_DIRECTION 0b1111 #define NOT_NORTH 0b1110 // One hot #define NOT_SOUTH 0b1101 #define NOT_EAST 0b1011 #define NOT_WEST 0b0111 #define INTER_MOVE_DELAY 100 #define BANG_HEAD_DELAY 500 // Assign any input/output (I/O) pins const int PinNeos = 10; // Instantiate NeoPixel(s) // Parameter 1 = number of pixels in strip // Parameter 2 = pin number (most are valid) // Parameter 3 = pixel type flags, add together as needed: // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) Adafruit_NeoPixel Neos(NUM_NEOS, PinNeos, NEO_GRB + NEO_KHZ800); // Define any global variables uint32_t ColorWheel[NUM_WHEEL_COLORS] = { COLOR_RED, COLOR_FLUSH_ORANGE, COLOR_YELLOW, COLOR_CHARTREUSE, COLOR_GREEN, COLOR_SPRING_GREEN, COLOR_CYAN, COLOR_AZURE, COLOR_BLUE, COLOR_ELECTRIC_INDIGO, COLOR_MAGENTA, COLOR_ROSE }; typedef struct { int x; int y; } XYPair; XYPair Worm[WORM_LENGTH]; // Element 0 is the worm's head XYPair DeltaValues[WORM_LENGTH] = { { 0, 1}, // If going NORTH { 0, -1}, // If going SOUTH { 1, 0}, // If going EAST {-1, 0} // If going WEST }; int WormDirection; int WormDistance; void setup () { delay(3000); // Give the serial port time to open Neos.begin(); Neos.show(); InitializeWorm(); DrawWorm(); } void loop () { GetWormDirection(); GetWormDistance(); while (WormDistance > 0) { MoveWorm(); DrawWorm(); } } void InitializeWorm() { // Specify initial values; WormDirection = EAST; WormDistance = 0; // Specify initial location of the worm Worm[HEAD].x = 6; Worm[HEAD].y = 6; Worm[HEAD + 1].x = 5; Worm[HEAD + 1].y = 6; Worm[HEAD + 2].x = 4; Worm[HEAD + 2].y = 6; Worm[TAIL].x = 3; Worm[TAIL].y = 6; } void DrawWorm () { int iNeo; iNeo = GetNeoNum(Worm[HEAD].x, Worm[HEAD].y); Neos.setPixelColor(iNeo, COLOR_WORM_HEAD); for (int iWrm = 1; iWrm < WORM_LENGTH; iWrm++) { iNeo = GetNeoNum(Worm[iWrm].x, Worm[iWrm].y); Neos.setPixelColor(iNeo, COLOR_WORM_BODY); } Neos.show(); delay(INTER_MOVE_DELAY); } void MoveWorm() { int iNeo; // Check to see if the worm is about to hit a wall if ( ( (WormDirection == NORTH) && (Worm[HEAD].y == MAX_Y) ) || ( (WormDirection == SOUTH) && (Worm[HEAD].y == MIN_Y) ) || ( (WormDirection == EAST) && (Worm[HEAD].x == MAX_X) ) || ( (WormDirection == WEST) && (Worm[HEAD].x == MIN_X) ) ) { // Yes, its about to hit a wall iNeo = GetNeoNum(Worm[HEAD].x, Worm[HEAD].y); Neos.setPixelColor(iNeo, COLOR_WHITE); Neos.show(); delay(BANG_HEAD_DELAY); Neos.setPixelColor(iNeo, COLOR_WORM_HEAD); Neos.show(); WormDistance = 0; } else { // No wall, we're good to go // Clear the tail iNeo = GetNeoNum(Worm[TAIL].x, Worm[TAIL].y); Neos.setPixelColor(iNeo, COLOR_BLACK); // Move the body of the snake forward one pixel for (int iWrm = TAIL; iWrm > HEAD; iWrm--) { Worm[iWrm].x = Worm[iWrm - 1].x; Worm[iWrm].y = Worm[iWrm - 1].y; } // Calculate new head location Worm[HEAD].x += DeltaValues[WormDirection].x; Worm[HEAD].y += DeltaValues[WormDirection].y; WormDistance = WormDistance - 1; } } int GetWormDirection() { int mask; int oneHot; // N=0001 S=0010 E=0100 W=1000 // At first, all things are possible... mask = ANY_DIRECTION; // ...but the worm can't reverse direction if (WormDirection == NORTH) mask &= NOT_SOUTH; else if (WormDirection == SOUTH) mask &= NOT_NORTH; else if (WormDirection == EAST) mask &= NOT_WEST; else if (WormDirection == WEST) mask &= NOT_EAST; // Also, the worm can't go outside the boundaries of the array if (Worm[0].y == MAX_X) mask &= NOT_NORTH; if (Worm[0].y == MIN_Y) mask &= NOT_SOUTH; if (Worm[0].x == MAX_X) mask &= NOT_EAST; if (Worm[0].x == MIN_X) mask &= NOT_WEST; // Based on the above restrictions, generate a new direction do { WormDirection = random(0, NUM_DIRECTIONS); oneHot = 1 << WormDirection; } while ( (mask | oneHot) != mask ); } int GetWormDistance() { // Based on its current direction, genereate a random distance // between 1 pixel and 1 pixel outside the array if (WormDirection == NORTH) WormDistance = random(1, ((MAX_Y - Worm[HEAD].y) + 2) ); else if (WormDirection == SOUTH) WormDistance = random(1, ((Worm[HEAD].y - MIN_Y) + 2) ); else if (WormDirection == EAST) WormDistance = random(1, ((MAX_X - Worm[HEAD].x) + 2) ); else if (WormDirection == WEST) WormDistance = random(1, ((Worm[HEAD].x - MIN_X) + 2) ); } // Accepts an x/y (column/row) pair and returns the corresponding pixel number int GetNeoNum (int xInd, int yInd) { int iNeo; iNeo = yInd * NUM_COLS; if ( (yInd % 2) == 0) { // Even row iNeo = iNeo + (12 - xInd); } else { // Odd row iNeo = iNeo + (xInd + 1); } return iNeo; } /* * Copyright (c) 2020 Clive "Max" Maxfield * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and any associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHOR(S) OR COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */