Author Archives: Phil Tucker

A Toronto based software developer, writer, musician, motorcyclist and hardware hacker, Phil Tucker is constantly putting technology to work in new and ingenious ways. He's also a contributing writer for the prominent Canadian tech blog, Sync.

If Phil's not programming, playing music, modifying a new gadget or sailing the Great Lakes then there's really no telling what he's up to. You can follow Phil on Twitter @unmaintained.

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.

View Solution

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;
using System.Threading.Tasks;

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])
                };
                tile_set.Add(new tile(x, rotations));
            }

            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;
                                    if (t_adjusted >= current_tile_set.Count())
                                    {
                                        t_adjusted -= current_tile_set.Count();
                                    }

                                    tile current_tile = current_tile_set[t_adjusted];

                                    // 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;
        }
    }
}

Building a Civil War Big Muff Pi on Stripboard

On a new YouTube channel, The Joy of Electronics, I build an Electro-Harmonix “Civil War” Big Muff Pi on a stripboard/veroboard PCB.

Building a $50 Klon Centaur Clone Kit

On a new YouTube channel, The Joy of Electronics, I build a Klon Centaur clone effects pedal from an inexpensive kit.

The real life battlefields that inspired Call of Duty: WWII

How close are Call of Duty: WWII‘s maps to the real life battlefields on which they were based? See for yourself in this gallery of screenshot-to-photograph comparisons.

These photos were taken in Normandy, France at Pointe du Hoc, Omaha Beach at Saint-Laurent-sur-Mer, at the Longues-sur-Mer battery (La Chaos) and one (the circular, overgrown bunker ceiling opening) at Blue Beach, Puys, Dieppe. The screenshots were taken from Call of Duty: WWII on an Xbox One.

 
Late last year I embarked on a tour of significant WWI and WWII battlefields in France, Belgium and the Netherlands. As it happens a number of the sites I had the honor to visit and photograph are also represented as maps in the new Activision title, including Omaha Beach and Pointe du Hoc. Activision has a blog post with some detail around the historical significance behind these real locations where fierce engagements took place during World War II.

My Grandfather landed on Juno Beach on D-Day with the No. 22 Canadian Field Ambulance Unit of the Royal Canadian Army Medical Corps. He spent the month of June 1944 in, and around, Beny-sur-Mer, in late June 1944 he was injured and evacuated. I set out on this battlefield tour with my father and one of my brothers so that we could consider the experience of my Grandfather.

Pages from my Grandfather's WWII Service Book. Note "Disembarked France 6 Jun 44," also known as D-Day, about 3/4 of the way down on right-hand page.

The tour began on November 4th, 2017, when I realized Call of Duty: WWII was to be released on November 3rd, just one day before our scheduled departure, I decided that I would bring my Xbox in the hope that my brother and I could visit historical battlefield sites during the days and play through the campaign in the evenings at the hotels. We completed the campaign on our last evening in Nijmegen, Belgium after visiting the John Frost Bridge (A Bridge Too Far), the Holten Canadian Military Cemetery and the Canadian Legion 005 earlier that day.

Over the course of the 10 day tour I took over 3,000 photos from which I was able to draw these comparison shots. Additional photos of these sites, and of the many others we visited, can be viewed here. Please contact me if you wish to inquire regarding usage rights.

The hero image is a screenshot from the Call of Duty: WWII Pointe du Hoc map split with a photograph taken at Pointe du Hoc in Normandy, France.

Special thanks to The Battlefield Tours for providing a top-notch tour experience.

All photographic images Copyright © 2017-2018 Phil Tucker. ACTIVISION, CALL OF DUTY, and CALL OF DUTY WWII are trademarks of Activision Publishing, Inc.

DIY toddler balance bike headlight

Light up your little one’s balance bike with this simple do-it-yourself headlight.

There’s not a lot to say here, glue something ferromagnetic (something magnets stick too) to the front of the balance bike and purchase a puck shaped utility light with a magnet on the back to stick to it. I used a washer and crazy glue, super simple, loads of fun. The light may slide off the washer in the event of a collision — but putting it back on is half the fun!

The balance bike my son has is the Kinderfeets TinyTot Wooden Balance Bike and Tricycle, which converts from a three-wheeled tricycle to a two-wheeled bike when they’re old enough.

Materials