13
\$\begingroup\$

Inspired by the Lego gear ratios challenge by Keith Randall.

I, too, plan on building a giant lego robot that will eventually be able to destroy the other robots in the never-before-mentioned competition.* In the process of constructing the robot, I will be using a lot of gear trains to connect different parts of the robot. I want you to write me the shortest program that will help me construct the complex gear trains that are required for such a complex task. I will, of course, only be using gears with radii 1, 2, 3, and 5 arbitrary-lego-units.

Each gear in the gear train has a specific integer coordinate on a 2D grid. The first gear is located at (0,0) and the final gear will be located at non-negative coordinates. The location and size of the first and last gears will be provided as input, your program must tell what gears go where to fill in the gaps.

Additionally, your program must use the minimum possible number of gears in the gear train. Fewer gears / train = more trains** = bigger and better robot of destruction.

Input will consist of one line:

X,Y,B,A

X and Y are the coordinates of the final gear. The first gear is always located at (0,0). B and A are the radii of the final and initial gears, respectively. To add some difficulty, you need to make sure that the output gear rotates in the correct direction. If A and B have the same sign, then the output gear needs to rotate in the same direction, and an odd number of gears must be used. If they have opposite signs, then an even number of gears need to be used.

Output should be a list of the X location, Y location, and radii of each additional gear, one gear per line. If there are multiple minimal-gear solutions, print only one of your choice. The order of gears in the output does not matter.

Examples (more equivalent solutions may be possible):

in
4,0,1,1
out
2,0,1

in
7,7,-2,-2
out
4,3,3
OR
0,7,5
OR
the above reflected over y=x line

in
7,8,-1,2
out
7,0,5
7,6,1
OR
7,0,5
1,8,5

in
7,7,2,-2
out
4,-3,3
7,1,2
12,1,3
12,7,3
OR
any permutation of the above, or reflected over y=x line
Now you're thinking with gear trains!

Here's the solutions to the above examples, visualized:

enter image description here

As far as I know, no problem is impossible unless the two input gears overlap or directly connect. You won't have to deal with this.

This is code golf, shortest answer wins.


*A future KOTH, anyone?

**CHOO CHOO!!

\$\endgroup\$
5
  • \$\begingroup\$ I would have it so that both the initial and final radii can be negative. \$\endgroup\$
    – wizzwizz4
    Jan 6, 2016 at 16:45
  • 9
    \$\begingroup\$ Welcome to Phi's Lego Gear Train Challenge. After 4 years in the Sandbox, hopefully it will have been worth the weight. \$\endgroup\$ Jan 6, 2016 at 16:46
  • \$\begingroup\$ @wizzwizz4 Made the change. \$\endgroup\$
    – PhiNotPi
    Jan 6, 2016 at 16:53
  • \$\begingroup\$ Was this really in the sandbox for 4 years? \$\endgroup\$
    – Riker
    Jan 6, 2016 at 17:12
  • \$\begingroup\$ @RikerW More like 3 1/3. \$\endgroup\$
    – PhiNotPi
    Jan 6, 2016 at 17:15

1 Answer 1

2
\$\begingroup\$

C#, 660 bytes

using System.Linq;using System;class P{int p=1,x,y,r;P l;static void Main(){var Q="$&.$'7$(@$*R$'/$(8$)A'(A('A$+S$(0$)9'(9('9$*B$,T$*2$+;$,D$.V*,V,*V";var I=Console.ReadLine().Split(',').Select(int.Parse).ToList();int i=0,t,s=7,u,v,w,p=I[3]*I[2];for(var D=new[]{new P{r=Math.Abs(I[3]),l=new P{r=Math.Abs(I[2]),x=I[0],y=I[1],p=3}}}.ToList();i>=0;){P c=D[i++],l=c.l;for(;(l=l?.l)!=null&&(s=(t=c.x-l.x)*t+(t=c.y-l.y)*t-(t=c.r+l.r)*t)>0;);if(s==0&&l.p>2&p*c.p<0)for(i=-1;c.l.p<3;c=c.l)Console.WriteLine(c.x+","+c.y+","+c.r);for(t=0;s>0&t<66;t++)for(u=Q[t++]-36,v=Q[t++]-36,s=1;s++<5&Q[t]%9==c.r;w=u,u=v,v=-w,D.Add(new P{l=c,r=Q[t]/9-4,x=c.x+u,y=c.y+v,p=-c.p}));}}}

Try It Online

This was a lot of fun!! Complete program, accepts input from STDIN, output to STDOUT. Output is the gears in order from the end to the start. Usage:

Performs a simple Breadth First Search, which solves a 4-gear problem in less than a second. Branching factor isn't actually that large, so it's it should be good for considerably more (not really tested it). Sadly it uses Linq.

The Q string is a table of all allowed gear connections (i.e an r=3 and connect to an r=1 if dx=4 and dy=0) in one quadrant, which is then rotated to find the others. Every set of 3 bytes is the dx, dy, and radius information for a legal connection. The choice of ( as an offset was very deliberate: it was fun for once to be choosing an ASCII character for nice properties, rather than desperately trying to find nice properties for imposed ASCII characters.

I can probably do a better job of reading the input, but I've had no luck yet, not least because the Linq is paid for by the need for a list. I'm also very disappointed by the rotate code, I feel like that could be done in considerably fewer bytes.

Formatted and Commented Code with Q generator:

using System.Linq; // seems to pay today
using System;

class P
{
    static void GenQ()
    {
        int t, k = 0, m = 0;
        Func<P, P, int> C = (P c, P l) => (t = c.x - l.x) * t + (t = c.y - l.y) * t - (t = c.r + l.r) * t; // ==0 -> touching, <0 -> not touching, >0 -> overlap

        string str = "";

        string T(int i) => "" + (char)('$' + i); // $ is zero, '$' == 36, so we can mod and div by 9, and greater than " so we don't have to escape it

        foreach (int r in new[] { 1, 2, 3, 5 }) // at 0,0 (current gear)
            foreach (int s in new[] { 1, 2, 3, 5 }) // gear to place
                for (int i = 0; i <= r + s; i++) // x
                    for (int j = 1; j <= r + s; j++, m++) // y
                        if (C(new P { r = r }, new P { r = s, x = i, y = j }) == 0) // 
                        {
                            str += T(i) + T(j) + T(r + s * 9);
                            k++;
                        }

        System.Console.WriteLine("K : " + k);
        System.Console.WriteLine("M : " + m);
        System.Console.WriteLine(str);
        System.Console.ReadKey(true);
        return;
    }

    int p=1, // parity
        x, // x
        y, // y
        r; // radias (TODO: store radias^2 ?)
    P l; // previous in search list

    static void Main()
    {
        //GenQ();

        // '$' == 36 (4*9)
        // 3char blocks: x,y,r+9*s
        var Q="$&.$'7$(@$*R$'/$(8$)A'(A('A$+S$(0$)9'(9('9$*B$,T$*2$+;$,D$.V*,V,*V"; // quarter table

        // primative read ints
        var I=Console.ReadLine().Split(',').Select(int.Parse).ToList();

        int i=0, // position in Due
            t, // check differential cache, position in Q
            s=7, // check cache, rotation counter (>0)
            u, // rotation x
            v, // rotation y
            w, // rotation x cache
            p=I[3]*I[2]; // parity (>0 -> same, even ; <0 -> different, odd)

        // due (not point using a queue, the search space grows exponentially)
        for(var D=new[]{
                new P{r=Math.Abs(I[3]), // start (p==1)
                    l=new P{r=Math.Abs(I[2]),x=I[0],y=I[1],p=3} // terminal (detect with p==3)
                }}.ToList();
            i>=0;) // infinite number of configurations, no bounds, i is escape term
        {
            P c=D[i++], // current
                l=c.l; // check, initially the one before the previous (we know we are touching last already)

            // 'checks' c against l
            //Func<int>C=()=>(t=c.x-l.x)*t+(t=c.y-l.y)*t-(t=c.r+l.r)*t; // ==0 -> touching, >0 -> not touching, <0 -> overlap

            // check we arn't touching any before us (last thing we check is terminal)
            for(;(l=l?.l)!=null&& // for each before us (skipping the first one)
                (s=(t=c.x-l.x)*t+(t=c.y-l.y)*t-(t=c.r+l.r)*t)>0;); // check c against l and cache in s, ==0 -> touching, >0 -> not touching, <0 -> overlap

            if(s==0&& // touching last checked?
                l.p>2& // stopped on terminal?
                p*c.p<0) // correct parity? -> win
                for(i=-1; // escape
                    c.l.p<3;c=c.l) // for each that wasn't the first
                    Console.WriteLine(c.x+","+c.y+","+c.r);

            // enumerate possible additions, and queue them in due
            for(t=0;
                s>0& // not touching or overlapping anything (including terminal)
                t<66;t++) // 66 = Q.Length
                for(
                    u=Q[t++]-36, // '$'
                    v=Q[t++]-36,
                    s=1;s++<5& // rotate 4 times
                    Q[t]%9==c.r; // our raidus matches
                        w=u, // chache y value
                        u=v, // rotate
                        v=-w,
                        D.Add(new P // add
                        {
                            l=c,
                            r=Q[t]/9-4, // radius
                            x=c.x+u,
                            y=c.y+v,
                            p=-c.p // flip parity
                        }));
        }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.