27
\$\begingroup\$

Many different types of train set exist, ranging from wooden tracks like Brio, to fully digital control perfect tiny metal replicas of real trains, but they all require a track to be designed, ideally using as many of your pieces as possible.

So, your task is to determine whether, given input of the available pieces, it is possible to build a complete closed circuit using all of the elements, and if not, how many pieces will be left from the maximum possible circuit.

Since this is a simplified train set, there are only 3 elements: big curve, little curve, and straight. These are all based on a square grid:

Square grid showing big curve and little curve

  • "Big Curve" is a 90 degree corner, covering 2 units in each dimension
  • "Little Curve" is a 90 degree corner, covering one unit in each direction
  • "Straight" is a straight element, 1 unit long

This means that the minimum circuit possible is formed of 4 little curves - it's a circle, of radius 1 unit. This can be extended by adding pairs of straight elements to form various ovals. There are other circuits possible by adding more curves, or by mixing the types of curve.

This train set doesn't include any junctions, or methods for tracks to cross, so it's not valid for two elements to connect to the same end of an other element (no Y formations) or to cross over one another (no X formations). Additionally, it's a train set, so any formation which doesn't allow a train to pass isn't valid: examples include straights meeting at 90 degree angles (there must always be a curve between perpendicular straights) and curves meeting at 90 degree angles (curves must flow).

You also want to use as many pieces as possible, ignoring what type they are, so you'll always opt for a circuit which has more bits in. Finally, you only have one train, so any solution which results in multiple circuits is unacceptable.

Input

Either an array of three integers, all greater than or equal to 0, corresponding to the number of big curves, little curves, and straights available, or parameters passed to your program, in the same order.

Output

A number corresponding to the number of pieces left over when the maximum possible circuit for the elements provided is constructed.

Test data

Minimal circuit using big curves
Input: [4,0,0]
Output: 0

Slightly more complicated circuit
Input: [3,1,2]
Output: 0

Incomplete circuit - can't join
Input: [3,0,0]
Output: 3

Incomplete circuit - can't join
Input: [3,1,1]
Output: 5

Circuit where big curves share a centre
Input: [2,2,0]
Output: 0

Bigger circuit
Input: [2,6,4]
Output: 0

Circuit where both concave and convex curves required
Input: [8,0,0] or [0,8,0]
Output: 0

Circuit with left over bit
Input: [5,0,0] or [0,5,0]
Output: 1

Notes

  • 2 straights and a little curve are equivalent to a big curve, but use more pieces, so are preferred - should never be a situation where this combination is left, if there are any big curves in the circuit
  • 4 little curves can usually be swapped for 4 straights, but not if this would cause the circuit to intersect itself
  • The train set is also idealised - the track elements take up the widths shown, so it is valid for curves to pass through a single grid square without intersecting, in some cases. The grid just defines the element dimensions. In particular, two big curves can be placed so that the grid square at the top left of the example diagram would also be the bottom right square of another big curve running from left to top (with the diagram showing one running from right to bottom)
  • A small curve can fit in the empty space under a big curve (bottom right grid square above). A second big curve could also use that space, shifted one across and one down from the first
  • A small curve cannot fit on the same grid space as the outside of a big curve - mostly because there is no way to connect to it which doesn't intersect illegally
\$\endgroup\$
6
  • \$\begingroup\$ So the output for [5,0,0] or [0,5,0] would be 1. Is that correct? Could you add such a test case? \$\endgroup\$
    – Arnauld
    Apr 15, 2017 at 10:31
  • \$\begingroup\$ @arnauld Yes, that's correct. Should always be the remaining number of elements after building the longest possible circuit. \$\endgroup\$
    – Matthew
    Apr 15, 2017 at 10:33
  • \$\begingroup\$ Could you please confirm that this is a solution for [8,0,0], with two 2x2 elements overlapping in the center of the grid? \$\endgroup\$
    – Arnauld
    Apr 15, 2017 at 11:19
  • \$\begingroup\$ Yes, that's the expected solution for that test case. \$\endgroup\$
    – Matthew
    Apr 15, 2017 at 11:36
  • \$\begingroup\$ I am not clear on how the self-intersection works. Could you be more explicit in defining what is allowed and what is forbidden? \$\endgroup\$
    – Wheat Wizard
    Apr 15, 2017 at 14:09

1 Answer 1

9
\$\begingroup\$

[JavaScript (Node.js)], 1220 bytes

f=r=>{var a=[{n:0,d:[[0,-1,"0000000101011"],[1,-1,"0011111111111"],[0,0,"0111101111111"],[1,0,"1100010000000"]],e:[2,-1,1]},{n:0,d:[[-1,-1,"1001111111111"],[0,-1,"0000010010110"],[-1,0,"0110000100000"],[0,0,"1101111011111"]],e:[-2,-1,3]},{n:1,d:[[0,0,"0011101111111"]],e:[1,0,1]},{n:1,d:[[0,0,"1001111011111"]],e:[-1,0,3]},{n:2,d:[[0,0,"1111101011111"]],e:[0,-1,0]}],e=r=>{var a=r.d,e=r.e,n=[];return a.forEach(r=>{var a=r[2];n.push([-r[1],r[0],""+a[10]+a[5]+a[0]+a[8]+a[3]+a[11]+a[6]+a[1]+a[9]+a[4]+a[12]+a[7]+a[2]])}),{d:n,e:[-e[1],e[0],e[2]]}};i=((r,a)=>{for(var n=0;n<r.d;n++,a=e(a));var p=!1;return a.d.forEach(a=>{var e=r[`${r.p.x+a[0]},${r.p.y+a[1]}`];void 0===e&&(e="00000000000000");for(var n="",d=0;d<13;d++)"1"===e[d]&&"1"===a[2][d]&&(p=!0),n+=e[d]===a[2][d]?e[d]:"1";r[`${r.p.x+a[0]},${r.p.y+a[1]}`]=n}),r.p.x+=a.e[0],r.p.y+=a.e[1],r.d=(r.d+a.e[2])%4,!p});var n=[],p=(r,e)=>{a.forEach(a=>{var d=Object.assign({},r);if(d.p=Object.assign({},r.p),!(e[a.n]<=0)&&i(d,a)){if(d.ps+=a.n,0==d.p.x&&0==d.p.y&&0==d.d)return void n.push(d);var s=Object.assign([],e);s[a.n]-=1,p(d,s)}})};p({p:{x:0,y:0},d:0,ps:""},Object.assign([],r));var d=0;n.forEach(r=>{r.ps.length>d&&(d=r.ps.length)}),console.log(r[0]+r[1]+r[2]-d)};

Try it online!

Note: The input is actually the variable q at the start. [2,6,4] will also take quite a bit of time as this is a brute force solution without optimizations.

I actually did this because it hasn't been answered in over a year and I was just kinda curious if it was possible.


Original Code:

var q = [4, 2, 4];
var t = [
    {
        n: 0,
        d: [
            [0, -1, "0000000101011"],
            [1, -1, "0011111111111"],
            [0, 0, "0111101111111"],
            [1, 0, "1100010000000"]
        ],
        e: [2, -1, 1],

    },
    {
        n: 0,
        d: [
            [-1, -1, "1001111111111"],
            [0, -1, "0000010010110"],
            [-1, 0, "0110000100000"],
            [0, 0, "1101111011111"]
        ],
        e: [-2, -1, 3]
    },
    {
        n: 1,
        d: [
            [0, 0, "0011101111111"]
        ],
        e: [1, 0, 1]
    },
    {
        n: 1,
        d: [
            [0, 0, "1001111011111"]
        ],
        e: [-1, 0, 3]
    },
    {
        n: 2,
        d: [
            [0, 0, "1111101011111"]
        ],
        e: [0, -1, 0]
    },
];

r = (p) => {
    var d = p.d; var e = p.e; var o = [];
    d.forEach(i => {
        var d = i[2];
        o.push([-i[1], i[0], "" + d[10] + d[5] + d[0] + d[8] + d[3] + d[11] + d[6] + d[1] + d[9] + d[4] + d[12] + d[7] + d[2]])
    });
    return { d: o, e: [-e[1], e[0], e[2]] };
};

i = (g, p) => {
    //console.log(g.p, g.d);
    for (var i = 0; i < g.d; i++ , p = r(p));
    var c = false;
    p.d.forEach(d => {
        var v = g[`${g.p.x + d[0]},${g.p.y + d[1]}`];
        if (v === undefined) v = "00000000000000";
        var o = "";
        for (var i = 0; i < 13; i++) {
            if (v[i] === '1' && d[2][i] === '1')
                c = true;
            o += (v[i] === d[2][i]) ? v[i] : '1';
        }
        //console.log(o);
        g[`${g.p.x + d[0]},${g.p.y + d[1]}`] = o;
    });
    g.p.x += p.e[0];
    g.p.y += p.e[1];
    g.d = (g.d + p.e[2]) % 4;
    return !c;
};

var l = [];
var re = (g, p) => {
    t.forEach(piece => {
        var gr = Object.assign({}, g);
        gr.p = Object.assign({}, g.p);
        if (p[piece.n] <= 0)
            return;
        if (i(gr, piece)) {
            gr.ps += piece.n;
            if (gr.p.x == 0 && gr.p.y == 0 && gr.d == 0) {
                l.push(gr);
                return;
            }
            var ti = Object.assign([], p);
            ti[piece.n] -= 1;
            re(gr, ti);
        }
    });
};
var gr = { p: { x: 0, y: 0 }, d: 0, ps: "" };
re(gr, Object.assign([], q));

var c = 0;
var lo = 0;
l.forEach(g => {
    if (g.ps.length > lo) {
        require("./draw.js")(g, `outs/out${c++}.png`)
        lo = g.ps.length;
    }
});

console.log(q[0] + q[1] + q[2] - lo);

first I should include a graphic of the tiles I used.

tiles used

The sections of this tile were given a number and
used for comparison and overlap handling later.

So there first thing is the array t at the start. 
This is a collection of track pieces that contain
    n[ame]: the index of the input array.
    d[ata]: the offset from the current tile and the Tile bit values.
    e[nd]: the relative offset and rotation that the piece provides.

function r[otate] ( p[iece] )
    this outputs a piece that is rotated by 90 degrees
    by rearranging the tile bits and the end offset

function i[nsert] ( g[rid], p[iece] )
    this modifies the passed in grid trying to place down each tile of the piece.
    if it hits a point where 2 tiles intersect it sets a flag c[ollision]
    it then adjusts the current p[osition] and and d[irection] stored in the grid.
    then it returns !c[ollision]

function re[peat] ( g[rid], p[eices] )
    this iterates across all nodes which
        creates a copy of the g[rid] as gr[id].
        checks if the piece is available if not continue
        if the peice is added without a collision
            add piece name to gr[id].ps[piece string];
            it checks if its back at the start
                add gr[id] to l[ist]
                return as no more pieces can be added without a collision.
            clone peices remove the used peice ti[nput]
            call re[peate] (gr[id], ti[nput])

call re[peate] with empty grid

search l[ist] for longest piece string
and output input added together minus the length of the longest string.

Sorry if the write up is hard to read I'm not used to explaining how my code works.

P.S. I actually also made a few functions for drawing the maps to a png but of course those were remove to save at least some space.

\$\endgroup\$
14
  • \$\begingroup\$ I'm impressed - I'd kind of given up hope on this one! Would be interested in a write up \$\endgroup\$
    – Matthew
    Oct 18, 2018 at 20:03
  • \$\begingroup\$ @Matthew I'll see when I get time to write one up. It actually might take a little while. But yeah, normally these are my favorite kinds of puzzles to do. Even if it's not short it's fun to prove it's possible. \$\endgroup\$
    – Cieric
    Oct 18, 2018 at 21:15
  • \$\begingroup\$ @Matthew added the write up. \$\endgroup\$
    – Cieric
    Oct 19, 2018 at 14:01
  • \$\begingroup\$ Is there a reason why you chose to use p[a.n]-=1 instead of p[a.n]--? \$\endgroup\$ Oct 19, 2018 at 14:04
  • \$\begingroup\$ Initializing q like that isn't an allowed input method. Most commonly, either make it a function argument or read it from stdin. \$\endgroup\$ Oct 19, 2018 at 14:48

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.