• Very cool...

    Use Neopixels - see WS2811/WS2812/WS2812B/APA104/APA106/SK68­12 LED String/Strip. The cool thing about them is that they are daisy chained and you feed them a string of on and offs - like light and dark beads on a string. As long as the feeding goes on, pixel n passes the information onto the next pixel n + 1, but keeps and acts on the information that it ends up on: lit or not lit. Furthermore, neopixels consist actually of three LEDs - red, green, blue (RGB) - which requires to feed 3 bytes per pixel defining the intensity of each of the base colors resulting as many different color as you want - out of 256 x 256 x 256. Since you want a balanced / equal brightness, it is a bit less, but still a great number.

    Esprunio has everything you need to drive a Neopixel string. Lay it in a zig-zag pattern onto the board. To make it simpler, use 25 pixles per lane, and light the first in each lane at start time (horse in start position).

    1. In 1st row, positions are pixels 1..25 w/ pixel 1 on at start
    2. in 2nd row, positions are pixels 50..26 w/ pixel 25 on at start
    3. in 3rd row, positions are pixels 51..75 w/ pixel 49 on at start
    4. in 4th row, positions are pixels 100..76 w/ pixel 76 on at start

    Let's go to some coding. Note that it is not optimized to point out the data structure.
    Also note that in code we start to count from 0, such as 1st lane is lane 0, etc.:

    // ---- some declarations to make it flexible (we will start with much smaller values)
    var maxPos = 25; // ...if a horse reaches - or overshoots - this position, it has won
    var maxLanes = 19; // ...took this from your board in the picture
    // --- initialization of data buffers
    var lanePositions = new Uint8ClampedArray(maxLanes); // knows each lane's position
    var npx = new Uint8ClampedArray(maxLanes*maxPos*3); // NeoPixeL buffer 3 bytes / neopixel
    // --- initialize board
    function boardInit() {
      for (var lane=0;lane<maxLanes;lane++) lanePositions[lane]=0; // init w/ start positions
      for (var pdx=0;pdx<maxLanes*maxPos*3;pdx++) npx[pdx]=0; // make all pixels dark
      for (lane=0;lane<maxLanes;lane++) { var basePos = lane*maxPos;
        if (lane%2 === 0) { // positions incrementing
          pdx =  basePos*3; // 0|0|0, 2|50|150,... lane|basePos|pixel index
        } else { // positions decrementing from adjusted basePos
          pdx = (basePos + maxPos + maxPos - 1)*3; // 1|49|147, 3|99|297,...
        // set pixel w/ random color - each line will have its own random color
        npx[pdx  ] = Math.random()*255;
        npx[pdx+1] = Math.random()*255;
        npx[pdx+3] = Math.random()*255;

    When you now send the npx buffer to the string as described in WS2811/WS2812/WS2812B/APA104/APA106/SK68­12 LED String/Strip, all horses show in start position - first LED in each lane is on.

    Nineteen (19) lanes w/ twentyfive (25) positions is a lot of hardware... you may start out with much smaller number of lanes and positions to validate the concepts and the software implementation: take three (3) lanes with five (5) positions only... which requires 15 neopixel LEDs or a string with at least 15 neopixels.

    For moving a horse in a lane by 1...3 positions and detecting passing the finish line, we can go like this in the code:

    // advances horse in lane by positions
    // and returns true when at/passed finish line, else false
    function advance(lane,positions) {
      var oldPos = lanePos[lane];
      var oldBasePos = lane*maxPos + oldPos;
      var newPos = oldPos += positions; 
      if (newPos >= maxPos) newPos = maxPos - 1;
      lanePos[lane] = newPos;
      var newBasePos = lane*maxPos + newPos;
      var oldPdx, newPdx;
      if (lane%2 === 0) {
        oldPdx = oldBasePos*3;
        newPdx = newBasePos*3;
      } else {
        oldPdx = (oldBasePos + maxPos - 2*oldPos - 1)*3;
        newPdx = (newBasePos + maxPos - 2*newPos - 1)*3;
      npx[newPdx  ] = npx[oldPdx  ]; npx[oldPdx  ] = 0;
      npx[newPdx+1] = npx[oldPdx+1]; npx[oldPdx+1] = 0;
      npx[newPdx+2] = npx[oldPdx+2]; npx[oldPdx+2] = 0;
      return (newPos >= maxPos - 1);

    After every advancement, the buffer is again sent to the Neopixl string, and when advancement returned true, player of lane has won and game is over...

    After you have this working, you may go over the top and also light the fields before the lanes and above the lanes... it just adds/inserts corrective terms into the buffer... Using a second, independent string for those things though keeps it as simple as above.

  • Yes, it would be a lot of hardware. I was thinking of building a couple then if it works I can add more. Right now it would be fun to race my grandkids (13 total) on like 6 tracks at home. I'm assuming each player/horse is a complete circuit independent if another, right?


Avatar for allObjects @allObjects started