# Category Archives: Software

## Professor McBrainy’s Zany Vortex Optical Illusion Puzzle Solution

I came across this tile puzzle at a rental cottage and it frustrated most of those in attendance. Boasting “literally billions” of possible combinations I gave it a go and shortly decided swimming and canoeing was more rewarding.

The puzzle is comprised of 16 square tiles with one of 8 patterns on each of their 4 sides. To solve the puzzle one must place these tiles in a 4-by-4 grid with their edges aligned such that the patterns on each side match the patterns on the sides of the tiles adjacent, above and below.

I was curious, however, if the solution entailed some sort of offset layout, or if it was just a standard 4-by-4 edge-aligned grid as the box implied — it’s an optical illusion puzzle after all. So I took to searching online for the solution, with no luck, I again decided swimming and canoeing was more rewarding.

Upon returning home from the cottage the puzzle nagged me, so I set to writing a program to more-or-less brute force the solution, seen below, written in C#. I didn’t feel like programming routes for every possible combination so instead I wrote in some random elements to try while systematically eliminating other factors. I found an image of the tiles online and translated them to numeric representations of their patterns and rotations. After 142,594 attempts of the last iteration of my program it found a solution. I say “a solution” because I ran it again and it found a different solution, both valid. Multiple solutions runs contrary to the packaging, but oh well.

An image of the solved puzzle can be found by clicking here. Hopefully this helps others get on with their summer rentals.

You can find some of these puzzles on Amazon, though they are long out of production.

#### Successful Output

```Attempt #142594
[0, 0] Tile Success
[1, 0] Tile Success
[2, 0] Tile Success
[3, 0] Tile Success
[0, 1] Tile Success
[1, 1] Tile Success
[2, 1] Tile Success
[3, 1] Tile Success
[0, 2] Tile Success
[1, 2] Tile Success
[2, 2] Tile Success
[3, 2] Tile Success
[0, 3] Tile Success
Partial Success

4       9       12      10

2       0       7       14

3       6       8       15

5       _       _       _

[1, 3] Tile Success
Partial Success

4       9       12      10

2       0       7       14

3       6       8       15

5       11      _       _

[2, 3] Tile Success
Partial Success

4       9       12      10

2       0       7       14

3       6       8       15

5       11      1       _

[3, 3] Tile Success
Puzzle Success

4       9       12      10

2       0       7       14

3       6       8       15

5       11      1       13
```

#### C# Code

```using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Puzzle_Solver
{
class Program
{
static int[,] puzzle = new int[16,4] {
{5, 1, 4, 2},
{1, 2, 7, 2},
{4, 3, 2, 1},
{8, 4, 4, 7},
{2, 1, 4, 6},
{5, 6, 6, 8},
{7, 5, 3, 3},
{2, 1, 2, 3},
{2, 3, 3, 1},
{1, 4, 6, 2},
{3, 2, 1, 2},
{5, 3, 1, 2},
{2, 1, 1, 4},
{7, 3, 2, 5},
{3, 1, 2, 3},
{2, 3, 1, 1},
};

static void Main(string[] args)
{
List tile_set = new List();

for (int x = 0; x < 16; x++)
{
rotation[] rotations = new rotation[4]
{
new rotation(puzzle[x, 0], puzzle[x, 1], puzzle[x, 2], puzzle[x, 3]),
new rotation(puzzle[x, 3], puzzle[x, 0], puzzle[x, 1], puzzle[x, 2]),
new rotation(puzzle[x, 2], puzzle[x, 3], puzzle[x, 0], puzzle[x, 1]),
new rotation(puzzle[x, 1], puzzle[x, 2], puzzle[x, 3], puzzle[x, 0])
};
}

bool puzzle_success = false;

tile[,] solution;
int[,] starting_candidates = new int[4, 4];
int attempt = 0;
Random random = new Random();

while (!puzzle_success)
{
tile_set = tile_set.OrderBy(x => random.Next()).ToList();

int random_offsets = random.Next(16);
for(int ran = 0; ran < random_offsets; ran++)
{
starting_candidates = random.Next(16);
}

for (int tile_to_offset_start = 0; tile_to_offset_start < 16; tile_to_offset_start++)
{
for (int offset = 0; offset < 16; offset++)
{
attempt++;
List current_tile_set = tile_set.ToList();

// reset
foreach (tile t in current_tile_set)
{
t.match_rotation = null;
}

solution = new tile[4, 4];
Console.WriteLine("Attempt #" + attempt);

for (int y = 0; y < 4; y++)
{
bool tile_success = false;
for (int x = 0; x < 4; x++)
{
tile_success = false;
for (int t = 0; t < current_tile_set.Count(); t++)
{
int t_adjusted = starting_candidates[x, y] + t;
{
}

// used
if (current_tile.match_rotation != null)
{
continue;
}

int random_rotation = random.Next(4);
for (int r = 0; r < 4; r++)
{
int rotation = random_rotation + r;

if(rotation > 3)
{
rotation -= 4;
}

rotation current_rotation = current_tile.rotations[rotation];

// check left
if (x != 0
&& solution[x - 1, y].match_rotation.right != current_rotation.left)
{
continue;
}

// check top
if (y != 0
&& solution[x, y - 1].match_rotation.bottom != current_rotation.top)
{
continue;
}

// match
current_tile.match_rotation = current_rotation;
solution[x, y] = current_tile;
tile_success = true;
Console.WriteLine("[" + x + ", " + y + "] Tile Success");
break;
}

if (tile_success)
{
break;
}
}

if (tile_success && x == 3 && y == 3)
{
Console.WriteLine("Puzzle Success");
output_solution(solution);
puzzle_success = true;
break;
}
else if (tile_success && x >= 0 && y == 3)
{
Console.WriteLine("Partial Success");
output_solution(solution);
continue;
}
else if (!tile_success)
{
Console.WriteLine("[" + x + ", " + y + "] Puzzle Fail");
output_solution(solution);
break;
}
}

if (!tile_success)
{
break;
}
}

int offset_x = Convert.ToInt16(Math.Floor(Convert.ToDecimal(tile_to_offset_start) / 4));
int offset_y = tile_to_offset_start % 4;

starting_candidates[offset_x, offset_y] = offset;
}
}
}
}

public static void output_solution(tile[,] solution)
{
Console.WriteLine();
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
Console.Write(solution[x, y] == null ? "_" : Convert.ToString(solution[x, y].id));
Console.Write("\t");
}
Console.WriteLine();
Console.WriteLine();
}
}
}

class tile
{
public rotation[] rotations;
public rotation match_rotation;
public int id;

public tile(int id, rotation[] rotations)
{
this.id = id;
this.rotations = rotations;
}
}

class rotation
{
public int left;
public int top;
public int right;
public int bottom;

public rotation(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
}
}
```

## Arduino Hot Wheels Drag Strip Race Track

For my son’s second birthday I decided to introduce him to die-cast cars and what better way than building a drag strip race track with an electronic start gate, timing and race results?

While Hot Wheels does offer a 6-Lane Raceway and a number of other drag strip style tracks, they didn’t have the timing and electronic start gate that I knew an Arduino and some other bits could provide, so I set to work. The basic idea is was have a servo motor open the start gate by pulling down a hinged plate with dowel stoppers to release the cars and a photocell (photoresistor/LDR) pointed at an infrared LED on each track to detect each car crossing the finish line.

Some quick searching revealed a number of projects from which I could draw insight and inspiration, most useful were apachexmd’s Hot Wheels Track Timer and Robby C’s Ultimate Guide to Building a Hot Wheels Race Track. This demystified a number of points, I hadn’t really had any fun with die-cast cars since I was kid myself, turns out there are some oddities.

Tracks

One of the oddities is that Hot Wheels doesn’t offer any long track for sale, all of their tracks are sold in segments a foot or two long that must be connected — the only exception being a vintage 50-foot Track Pak Raceway sold at some point in the 70′s or 80′s which can be found for upwards of \$100 currently. Another track option is BluTrack which is sold in many lengths but only in a two-lane configuration for some reason. I decided to buy the readily available Hot Wheels segments in the form of 4 Hot Wheels Track Builder Straight Track kits.

I had some 1/2″ x 8″ pine left over from framing a door I thought I would use to mount the track on. There are a number of methods folks have employed to mount the Hot Wheels track on wood, I decided to use a method mentioned on Robby C’s page which involves using screws to secure a stack of two different sized washers to the wood at intervals.

A small washer below a larger one provides the elevation that the track needs to slide onto the larger washer. The exact washers and screws I used can be seen in the gallery below, be sure to drill pilot holes for the screws and try to keep them straight, the straighter the screws are, the better they will fit in the washer and, in turn, the smaller the bump in the track will be. This method (at least with the sizes I used) does leave ever so slight bumps in the track once it’s fitted, but I felt it was slight enough that it wouldn’t adversely affect the races. I used one of these washer guides for each section of track positioned to sit in the center of the track, except for the finish line track where I placed one at either end.

The track has 4 main pieces (though one I cut into two to make it more modular). These four pieces are a small section of track for the finish line which has the results display attached, the main straightaway, the start incline and a support for the start incline. Following some of the ideas on Robby C’s page again I decided to use hinges to connect the start incline track to its support piece as well as to the straightaway allowing for an adjustable incline. If you use hinges with removable pins it allows for easier disassembly.

I ended up cutting the straightaway into two sections, and I’ll probably do the same with the incline so that I can make the entire track shorter and thus more palatable in the living room.

For the joint between the incline and the straightaway I made sure that no track joints would run across the curve allowing for the longest pieces of track at this joint and thus a smooth transition for cars from the incline to the straightaway.

The flat joints I cut where the tracks connect, this makes disassembly a bit easier, but I’m not sure it was the right choice as some of the wood warped and having a track joint and a wood joint at the same spot may not result in the smoothest run. Experiment with the hinges and see which orientation works best for your setup.

Start Gate

The start gate on this track uses a high torque servo to actuate a hinged plate that has 4 wood dowels inserted into it. The dowels feed up through routed slots in the wood and tracks to hold the cars at the start gate.

The Hot Wheels tracks can be cut easily with a sharp exacto, though it’s difficult to get the corners of cuts perfectly smooth. The track can also be drilled, one method is to drill an appropriate diameter hole at either end of the route you wish to cut and then connect the two holes with a single exacto cut on either edge.

To start the race the servo pulls a stiff wire which is connected to the hinge plate with a small L-bracket, this pulls the dowels back down through the routed slots and releases the cars. The slots I routed in the wood were 1/2″, the track slots were a little smaller than that, somewhere between 1/4″ and 1/2″ as the dowels themselves were 1/4″ diameter and needed some clearance to move smoothly. The dowels are simply friction fit into drilled holes.

It may not be obvious from the video and photos, but I drilled a small hole in the L bracket which fit the wire much better than the large screw holes. This prevented any extra travel of the wire at the hinge plate connection when the servo actuates.

The wire I used to connect the servo to the hinge plate is a malleable steel of some sort, when my son decides to push or pull on the start gate hinge or dowels it will bend this wire rather than stress the servo, after which I inform him that is not how it’s suppose to function and bend the wire straight again. For my setup a run-of-the-mill servo wasn’t strong enough to push and pull the 1/4″ wooden hinged plate so I picked up a high torque servo. If you made a lighter, thinner hinge plate you may be alright with a weaker servo.

Robby C mentions something about this type of drop-out start gate being not as fair as a gate that lifts rather than drops, but I’m not sure why that would be — in any case, my goal was not to create a track fit for the world cup of die-cast racing. A lift gate would have required more fabrication, so I opted for the simpler drop-out.

Finish Line

The finish line employs 4 photocells, also know as photoresistors or LDR’s (Light Dependent Resistors) which sit in an enclosure above the track in holes drilled into a piece 1/2″ piece of wood. These photocells point down through the drilled holes, through larger drilled holes in the enclosure at 4 infrared LED’s embedded in the wood under the track and aligned with holes in the track above. Placing the photocells in holes in the 1/2″ piece of wood keeps them focused on their respective infrared LED underneath without picking up a lot of ambient light.

While the infrared LED’s don’t produce any light visible to the naked eye, they are detected by the photocells and when a car passes over the infrared LED, the reading on the respective photocell drops dramatically and thus the Arudino brain can determine when each car passes the finish line by waiting for the photocell reading to drop.

Embedding the photocells into a piece of wood also allowed me to align that wood with holes I drilled in the track wood for the infrared LED’s before placing the wood, along with the photocells in the enclosure. Once I’d aligned the wood, I drilled holes in it for the photocells, wired up the photocells and placed that entire piece of wood into the enclosure. The holes I drilled in the enclosure were much larger and thus I had room to align the wood with the photocells to the infrared LED’s without having to worry about aligning exactly with the holes in the enclosure.

I wrote a specific Arduino sketch in order to align the photocells which wrote the photocell values to the computer via a serial connection. Since then I have added a debug mode to the race track, pressing both the start race and track reset buttons simultaneously will engage the debug mode where the Arduino will write the photocell values out to the 7-segment race result displays so that I can check the photocell alignments.

One of the issues I ran into was that it seemed the power needed to actuate the start gate servo would draw too much from the rest of the circuit causing the infrared LED’s to dim and trigger the photocells. To get around this I added a 150ms delay to allow the circuit to recover after opening the start gate.

Circuit

None of the electronics used in the project are very complex. I collected various components mostly from RobotShop, SparkFun, Creatron and locally.

These components and their circuits are all connected to the Arduino which runs the sketch at the bottom of this post. If I get a chance to draw one up, I’ll post a circuit diagram.

Car Storage

After purchasing a good starting set of cars to go along with the drag track I needed some way to store the hoard. It seemed likely that a generic storage container would do the trick as long as the compartment sizing matched. I wasn’t all that pleased with the purpose-build die-cast storage containers. A little bit of searching revealed this Creative Options Thread Organizer which fits 48 cars almost perfectly. It’s only half-full in the photo below, with the same number of compartments on the reverse side still to be populated.

Next Steps

While it turned into a race to finish this project in time for my son’s birthday, I had initially intended to integrate a Raspberry Pi to record race statistics and another fun function, but I’ll leave that to everyone’s imagination until I get around to implementing it. The whole setup could also use some Hot Wheels stickers.

Update

I’ve since removed a section from the start incline and the straightaway to reduce the overall size of the track in order to move it to a more permanent location (rather than across our living room). Below is a short video of my son operating the shortened track, he doesn’t seem to mind the change and still enjoys the track quite a bit.

#### Arduino Sketch

```
/* Hot Wheels Drag Strip v1.2 */

#include <Servo.h>
#include <SPI.h> // Include the Arduino SPI library

// SPI SS pins
const int displayPinLane1 = 7;
const int displayPinLane2 = 4;
const int displayPinLane3 = 6;
const int displayPinLane4 = 5;

// Photocell pins
const int photocellPinLane1 = 2;
const int photocellPinLane2 = 3;
const int photocellPinLane3 = 4;
const int photocellPinLane4 = 5;

const int photocellThreshold = 4;

// Button pins
const int startButtonPin = 2;
const int resetButtonPin = 3;

const int gateServoPin = 14;

Servo gateServo;

// Servo positions
const int openGateServoPosition = 10;
const int closeGateServoPosition = 30;

int startButtonState = 0;
int resetButtonState = 0;

int raceStatus = 0;

int lane1Status = 0;
int lane2Status = 0;
int lane3Status = 0;
int lane4Status = 0;

int currentPlace = 0;

unsigned int counterLane1 = 0;  // These variables will count up to 65k
unsigned int counterLane2 = 0;
unsigned int counterLane3 = 0;
unsigned int counterLane4 = 0;

String displayString;
char tempString[10];  // Will be used with sprintf to create strings

int animationTimer = 0;
bool showPlace = true;

void setup()
{
gateServo.attach(gateServoPin);
gateServo.write(closeGateServoPosition);

pinMode(startButtonPin, INPUT);
pinMode(resetButtonPin, INPUT);

// -------- SPI initialization
pinMode(displayPinLane1, OUTPUT);
digitalWrite(displayPinLane1, HIGH);
pinMode(displayPinLane2, OUTPUT);
digitalWrite(displayPinLane2, HIGH);
pinMode(displayPinLane3, OUTPUT);
digitalWrite(displayPinLane3, HIGH);
pinMode(displayPinLane4, OUTPUT);
digitalWrite(displayPinLane4, HIGH);

// Begin SPI hardware
SPI.begin();
// Slow down SPI clock
SPI.setClockDivider(SPI_CLOCK_DIV64);

clearDisplays();

s7sSendStringSPI(displayPinLane1, "Ln 1");
s7sSendStringSPI(displayPinLane2, "Ln 2");
s7sSendStringSPI(displayPinLane3, "Ln 3");
s7sSendStringSPI(displayPinLane4, "Ln 4");

// High brightness
setBrightnessSPI(displayPinLane1, 255);
setBrightnessSPI(displayPinLane2, 255);
setBrightnessSPI(displayPinLane3, 255);
setBrightnessSPI(displayPinLane4, 255);
}

void loop()
{
animationTimer++;

if(animationTimer > 200) {
if(showPlace == true) {
showPlace = false;
} else {
showPlace = true;
}
animationTimer = 0;
}

if(resetButtonState == HIGH && startButtonState == HIGH) {
// Debug mode
clearDisplays();

delay(1000);
} else if(resetButtonState == HIGH && raceStatus > 0) {
raceStatus = 0;

clearDisplays();

s7sSendStringSPI(displayPinLane1, "Ln 1");
s7sSendStringSPI(displayPinLane2, "Ln 2");
s7sSendStringSPI(displayPinLane3, "Ln 3");
s7sSendStringSPI(displayPinLane4, "Ln 4");

lane1Status = 0;
lane2Status = 0;
lane3Status = 0;
lane4Status = 0;

counterLane1 = 0;
counterLane2 = 0;
counterLane3 = 0;
counterLane4 = 0;

currentPlace = 0;

gateServo.write(closeGateServoPosition);
} else {
if (raceStatus == 0 && startButtonState == HIGH) {

// start the race

gateServo.write(openGateServoPosition);

// wait for servo to drop
delay(150);
raceStatus = 1;
} else if (raceStatus == 1) {

// race is running

int carsAcross = 0;

if (photocellReadingLane1 < photocellThreshold && lane1Status == 0) {
showPlace = true;
currentPlace++;
carsAcross++;
lane1Status = currentPlace;
gateServo.write(closeGateServoPosition);
}

if (photocellReadingLane2 < photocellThreshold && lane2Status == 0) {
showPlace = true;
if(carsAcross == 0) {
currentPlace++;
}
carsAcross++;
lane2Status = currentPlace;
gateServo.write(closeGateServoPosition);
}

if (photocellReadingLane3 < photocellThreshold && lane3Status == 0) {
showPlace = true;
if(carsAcross == 0) {
currentPlace++;
}
carsAcross++;
lane3Status = currentPlace;
gateServo.write(closeGateServoPosition);
}

if (photocellReadingLane4 < photocellThreshold && lane4Status == 0) {
showPlace = true;
if(carsAcross == 0) {
currentPlace++;
}
carsAcross++;
lane4Status = currentPlace;
gateServo.write(closeGateServoPosition);
}

updateDisplay(displayPinLane1, lane1Status, counterLane1);
updateDisplay(displayPinLane2, lane2Status, counterLane2);
updateDisplay(displayPinLane3, lane3Status, counterLane3);
updateDisplay(displayPinLane4, lane4Status, counterLane4);

if (lane1Status == 0) {
counterLane1++;
}

if (lane2Status == 0) {
counterLane2++;
}

if (lane3Status == 0) {
counterLane3++;
}

if (lane4Status == 0) {
counterLane4++;
}
}
}

delay(10);  // This will make the display update at 100Hz.*/
}

void clearDisplays()
{
clearDisplaySPI(displayPinLane1);
clearDisplaySPI(displayPinLane2);
clearDisplaySPI(displayPinLane3);
clearDisplaySPI(displayPinLane4);
}

void updateDisplay(int displayPin, int laneStatus, unsigned int laneCounter) {

// Magical sprintf creates a string for us to send to the s7s.
//  The %4d option creates a 4-digit integer.
sprintf(tempString, "%4d", laneCounter);

// This will output the tempString to the S7S
if(laneStatus == 0 || showPlace == false) {
s7sSendStringSPI(displayPin, tempString);

// Print the decimal at the proper spot
if (laneCounter < 10000) {
setDecimalsSPI(displayPin, 0b00000010);  // Sets digit 3 decimal on
} else {
setDecimalsSPI(displayPin, 0b00000100);
}
} else {
setDecimalsSPI(displayPin, 0b000000000);

if(laneStatus == 1) {
s7sSendStringSPI(displayPin, "1st ");
} else if(laneStatus == 2) {
s7sSendStringSPI(displayPin, "2nd ");
} else if(laneStatus == 3) {
s7sSendStringSPI(displayPin, "3rd ");
} else {
s7sSendStringSPI(displayPin, "4th ");
}
}
}

// This custom function works somewhat like a serial.print.
//  You can send it an array of chars (string) and it'll print
//  the first 4 characters in the array.
void s7sSendStringSPI(int ssPin, String toSend)
{
digitalWrite(ssPin, LOW);
for (int i=0; i < 4; i++)
{
SPI.transfer(toSend[i]);
}
digitalWrite(ssPin, HIGH);
}

// Send the clear display command (0x76)
//  This will clear the display and reset the cursor
void clearDisplaySPI(int ssPin)
{
digitalWrite(ssPin, LOW);
SPI.transfer(0x76);  // Clear display command
digitalWrite(ssPin, HIGH);
}

// Set the displays brightness. Should receive byte with the value
//  to set the brightness to
//  dimmest------------->brightest
//     0--------127--------255
void setBrightnessSPI(int ssPin, byte value)
{
digitalWrite(ssPin, LOW);
SPI.transfer(0x7A);  // Set brightness command byte
SPI.transfer(value);  // brightness data byte
digitalWrite(ssPin, HIGH);
}

// Turn on any, none, or all of the decimals.
//  The six lowest bits in the decimals parameter sets a decimal
//  (or colon, or apostrophe) on or off. A 1 indicates on, 0 off.
//  [MSB] (X)(X)(Apos)(Colon)(Digit 4)(Digit 3)(Digit2)(Digit1)
void setDecimalsSPI(int ssPin, byte decimals)
{
digitalWrite(ssPin, LOW);
SPI.transfer(0x77);
SPI.transfer(decimals);
digitalWrite(ssPin, HIGH);
}```

## Maker Wedding: Animated HTML / jQuery wedding site

One of the projects I took on for our wedding was our official wedding website, I registered two domains, esterandphil.com as well as philandester.com thus avoiding any confusion. Both site pointed to the same page, for the general public a simple splash with a dynamic forest animation accomplished with the help of jQuery.

We wanted to create a page that connected with our venue which was in a barn on a conservation area. I’ve always enjoyed sine path algorithms so I created a scene where silhouetted trees scroll slowly by in a parallax-esc fashion and fuzzy Will-o’-the-wisp dots float lazily upward on sine waves. Randomly the wisps will be replaced with rain and visiting the site during the day versus during the night will result in different colours and songs.

The trees are selected randomly from one of six or seven images which then have a random amount of transparency and speed applied so that some seem further in the distance. Once they disappear off of one side they are recycled, animating in from the starting side again. Similarly the wisps have random factors applied to their size, speed, sine curve and alpha. The wisps are also interspersed on the z-axis to move in front of some trees while behind others adding to the illusion of depth. The wisps are also recycled once they’ve floated out of view.

On the live site I adjust the parameters for some popular clients, such as iOS for which I disabled animation and reduced the number of trees and wisps.

Inaccessible to the public is a guest site which used the splash page concept as a background with only the a couple of wisps animating with static trees (still randomly generated).

While this exact example is probably not portable verbatim you may find the method libraries I used as well as the technique useful. I have outlined some of the initializer functions below and linked to the libraries, some of which I have modified. The final library, animate.js, is almost completely custom code for this site and thus would have to be altered to suit your needs.

Will-o’-the-wisp Initialize

```window.numberOfDots = 50;

for(i=0;i<window.numberOfDots;i++)
window.dots.push(new dot(i));```

Forest Initialize

```window.numberOfTrees = 15;

for(i=0;i<window.numberOfTrees;i++)
window.trees.push(new tree(i,'http://philandester.com/'));```

Rain Initialize

```var raincolor = '#fff';

new Rain('canvas', {
speed: 500,
angle: 20,
intensity: 5,
size: 10,
color: raincolor
});```

Main Loop Start

`setInterval(function(){sine()}, 25);`

Raphael 1.5.2 – JavaScript Vector Library, MIT License (http://raphaeljs.com/)
http://philandester.com/rain.js

Andrew J. Peterson, NDP Software, MIT License (http://blog.ndpsoftware.com/)
http://philandester.com/colorfactory.js

Custom JavaScript Animation Library
http://philandester.com/animate.js

## Maker Wedding: Animated Arduino LED matrix lounge table top

Finally got around to making an LED table top, as it turns out — for my wedding reception. We decided to have a lounge area and an LED coffee table seemed like the perfect centerpiece for it. I decided instead of making a full table that I would make a table top that fit onto an existing ottoman. I affixed the LED strips to a plywood board which had a 2″ raised frame with aluminium duct tape, to help with brightness.

Arranging the LED’s in a proper matrix turned out to be quite a job as the strips I used came pre-wired and there isn’t all that much length between LED’s on the strip. I ended up having to cut and re-splice the connection leads for each row of the 7 x 7 matrix, after that the construction went quickly. You could get around this by using a more modular LED strip solution, I initially had ShiftBrites slated for this project, but I made something else with them and when I got around to this table top there were much less expensive options available.

I created an outer frame with a bevel to support a glass top. Initially I went with plexiglass but it would bow in the middle with anything of weight on the table, I didn’t want to add supports as this would disrupt the light diffusion, so I opted for a piece of tempered glass (actually intended for table tops to boot).

Adding adhesive obscuring film to the glass didn’t have the diffusion effect I’d hoped for so I sandwiched a sheet of white tracing paper between the tempered glass and a similarly sized piece of plexiglass and this gave the soft white diffused look I wanted.

The issue of programming the animations took a little longer. I wanted to use the disco(esc) animations available from the fine folks responsible for the 1E Disco Dance Floor — which I also used in my ohDisco! app for iPad. These animations are 32 x 16 and can have hundreds of frames — too much to load completely into the Arduino’s memory. But I didn’t want to have to deal with reducing the animations to 7 x 7, or reducing their total frame count, as this would affect the quality and overall impression. Instead I opted to add an SD card reader to the setup which stores the animations. The 7 x 7 section of each frame is loaded on-demand from each animation file and displayed on the table, with this setup the Arduino has no memory problems whatsoever and with a little more code it could index and play animations from the SD card without the need for code changes.

Worth noting is that the SdFat library used to interface with the Seeedstudio SD Card Shield wouldn’t run reliably (or at all sometimes) on an ATmega128 so be sure to use a more powerful Arduino running an ATmega328.

#### Arduino Sketch

```
const int chipSelect = 10;

#include <SdFat.h>
#include "SPI.h"

uint8_t dataPin  = 2;
uint8_t clockPin = 3;

SdFat sd;
SdFile myFile;

int rows = 32;
int cols = 16;

long framesize = rows*cols*3;
long rowsize = cols*3;

int ledrows = 6;
int ledcols = 6;

int rep = 0;
long reps = 5;

int brightness = 15;
int delaytime = 40;

char* files[]={
"pulsar.ddf",
"snake.ddf",
"inter3.ddf",
"inter4.ddf",
"inter5.ddf",
"rings.ddf",
"rings2.ddf",
"rings3.ddf",
"matrix.ddf"
};

int fileCount = 9;

char* file;
int frame = 0;
int frames;

// strip to matrix addressing array
1,2,3,4,5,6,7,
14,13,12,11,10,9,8,
15,16,17,18,19,20,21,
28,27,26,25,24,23,22,
29,30,31,32,33,34,35,
42,41,40,39,38,37,36,
43,44,45,46,47,48,49
};

void setup() {
Serial.begin(9600);
delay(400);  // catch Due reset problem
if (!sd.begin(chipSelect, SPI_FULL_SPEED))
sd.initErrorHalt();

file = files[ random(fileCount) ];

strip.begin();
strip.setPixelColor(0, 0, 0, 0);
strip.show();
}

void loop() {
sd.errorHalt("failed");
rep = reps + 1;
return;
}

// seek to next frame
if(myFile.fileSize() < ((frame*framesize)+1))
{
myFile.close();

frame = 0;
rep = rep + 1;

if(rep > reps)
{
rep = 0;
file = files[ random(fileCount) ];
Serial.println(file);
}

return;
}
else
{
myFile.seekSet(frame*framesize);
}

// adjust reps for number of frames
if(frame == 0)
{
frames = myFile.fileSize()/framesize;
reps = 750/frames;
Serial.println(frames,DEC);
Serial.println(reps,DEC);
}

int data;

int column = 0;
int row = 0;

while (row <= ledrows)
{
while (column <= ledcols)
{
int r = map(data,0,255,0,255);
int g = map(data,0,255,0,255);
int b = map(data,0,255,0,255);

// set pixel color

// next column
column = column + 1;
}

// reset column count
column = 0;

// increment row
row = row + 1;

// skip extra pixels
myFile.seekSet((frame*framesize)+(row*rowsize));
}

// turn off first pixel (7x7 matrix, 1 unused pixel)
strip.setPixelColor(0, 0, 0, 0);

// send current frame to strip
strip.show();

// close the file
myFile.close();

// increment frame
frame = frame + 1;

// rest
delay(delaytime);
}
```

I went and made a Mame cabinet. I had a whack of old PC stuff sitting around,
a P2-266 with a motherboard, CDRom and ATI All in Wonder Rage Pro. I managed to pan handled
an ISA SB16 off a friend and resurrect an old PCI NIC.

After cruising some sites of doods who’ve done this, I began thinking of my cabinet.
I decided that it’d be better for me to buy an old one rather than make one. My reasoning being
that my homemade one would probably not turn out as hoped, and for \$150 or so I can get an old
one with art and everything. So I checked ebay, ebay’s no good. Prices are usually too high and
shipping is craziness. Living in Toronto I found two distributers, Starburst, who holds auctions and New Way Sales. Starburst, although very friendly and helpful, did not have any stock of old cabinets on hand ( they later alerted me when they recieved some ). Starburst was going to charge me ~\$100 Canadian for a bottom of the line used cabinet ( probably without art ). So I found New Way Sales. After a little email back and forth, I showed up to find they had tons of old cabinets. I picked up an old 4 player cabinet with Ninja Turtle artwork on the side and X-Men on the Marqee. Complete with quarter slots. They offered to throw in the old controls, which in retrospect I should’ve taken, but I had already ordered mine ( see below ). Anyhow, I got a cabinet, with working quarter slots, screen bevel, marqee with light, speaker, and some leftover innards for ~\$200 Canadian.

For the controls, after surfing some sites with info, I decided to use a hacked up keyboard interface since I had an old keyboard laying around. The keyboard interface ( show below ) was a series of ~30 leads. Connecting different leads produced different keystrokes. So I plugged it into the normal PS2 keyboard port and spent an hour or so with my favourite girl mapping out which lead combinations produced which keystrokes, being careful not to touch any of the leads connected to the LEDs ( could royally mess your motherboard ). Ending up with something like this. Most if not all keyboards have their own unique matrix so you’ll have to do this for yourself. There is a program available from Arcade Controls Download Section which will display keystokes on the screen which came in handy. I then soldered wires to the leads I mapped out and attached them to a row of terminals ( little screws that you can put wires under ) to make control connections a little easier.

I then ordered my controls from Happs which ended up costing me way too much ( ~\$100 Canadian ) for 2 joysticks and 14 buttons. But I’m very happy with them so I guess it’s worth it. All of them are horizontal microswitches, although I noticed that Starburst had some leaf switches on display ( some say they’re not made anymore ). I then just attached said controls to the terminals which I had attached the keyboard interface, having the buttons and joystick directions produce the keystrokes I desired ( “O”, “K”, “ENTER”, etc. ). I also hooked up the quarter slot switches to the terminals so I’ve got to feed the monster quarters to play.

I went to a pawn shop and nabbed some no name 21″ TV for ~\$100 Canadian, took the back cover off for better fit ( being careful not to touch anything in the TV for fear of instant death by electrocution ) then braced it with a 2×4. I then tossed the PC together and threw it in the bottom of the cabinet, connected the SB16 to the speaker in the cabinet, plugged in my hacked keyboard interface and connected the TV to the AV OUT of the ATI All in Wonder.

I bought some plexi glass ( actually clear acrylic sheets ) and cut / drilled a sheet to fit the control panel ( which I had made from some extra ply wood and spray painted black ) I also cut a sheet to fit the screen and cut some presentation board to frame the screen perfectly. I ducked into a home stuff store and found some nice shiny black cabinet liner which I tossed under the plexi glass of the control panel. With all that done, I plugged it in and went to town!

Some further notes, 14 buttons weren’t enough. I had to buy some el cheapo buttons for the side so I could have “ESC”, “TAB” and “~” for Mame control. A TV with SVideo IN would’ve been nice ( but expensive ) for better clarity. All in all, cost me ~\$500 ( keep in mind I already had the CPU and PC components ). But I now have 1000+ games in my living room!