7
\$\begingroup\$

The goal is to calculate the output speed of a series of cogs. The winner will be chosen by popular vote.

The arrangement of cogs is defined in an ASCII-art string:

O large cog
o medium cog
* small cog
- axle (one or more in a row)
= chain (one or more in a row)

An example input is:

o*-O*==oO

o medium cog, meshed with * small cog, attached by - axle to O large cog, meshed with * small cog, connected by == chain to medium cog, meshed with O large cog.

The code takes five inputs by command line argument, standard in, or function parameter:

Arrangement
Number of teeth on large cogs
Number of teeth on medium cogs
Number of teeth on small cogs
Left-side speed in teeth per second (+ve is clockwise, -ve is anti-clockwise)

And outputs:

Right-side speed in teeth per second

Interactions between cogs and other components occur as follows:

SL = Speed of left cog
SR = Speed of right cog
TL = Teeth of left cog
TR = Teeth of right cog

Two meshed cogs

O*
SR = -SL

Two cogs connected by a chain of any length

O===*
SR = SL

Two cogs connected by an axle of any length

O---*
SR = SL * TR / TL

Any invalid arrangement can throw an error or return 0.

Test Cases

arrangement     O   o   *  speed  =>  speed

*-Oo-O         50  30  10    3.0  =>  -25.0
o*-O*==oO      50  30  10   -3.0  =>   15.0
O---*oO=O*-oo  15  12   8   10.0  =>    8.0
=o*            50  30  10   10.0  =>  error
o*-            50  30  10   10.0  =>  error
o-=*           50  30  10   10.0  =>  error
o *            50  30  10   10.0  =>  error
\$\endgroup\$
6
\$\begingroup\$

Javascript – animated version

Playing on with canvas again. Adding a sample going far out of the scope of the task. But, anyhow, here it is. It is an animated / paint version.

Issues

Can be a resource hog. In some quick tests it runs best in Chrome, worse in Opera and worst in Firefox. There is also an issue with movement as on relatively high speeds as the turn on each frame gets too big.

If one use small value for Teeth Width, it performs OK in various cases. Decor also has an impact.

By choice the use of Animation Frame or interval can be selected, and while using interval frames per second (FPS) can be tweaked. FPS can easily be lower then set if painting one frame takes longer.

If it only hogs, one can also use it to simply paint still images. Press Stop button and enter string to be parsed. With full decor it can be for a nice picture ;)

Try it out in this Fiddle.

enter image description here

Javascript code:

It could definitively have been tidied up some more and refactored.

requestAnimationFrame polyfill removed in this post due to limit of body length on SO.

var PI  = Math.PI,
    P2  = PI*2,
    cos = Math.cos,
    sin = Math.sin;

function isNumber(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
}

function isNumbers(a) {
    var i;
    for (i = 0; i < a.length; ++i)
        if (!isNumber(a[i]))
            return 0;
    return 1;
}

function CogWheels() {
    this.can    = document.getElementById('can');
    this.ctx    = this.can.getContext("2d");
    this.cm = {
        main:{can: document.createElement('CANVAS'),ok:0}
        //gear:{can: document.createElement('CANVAS'),ok:0}
    };
    this.cm.main.ctx = this.cm.main.can.getContext("2d");

    this.ofs    = {x:10, y:50};
    this.grect  = null;

    this.parts  = [];
    this.pix    = {wheel:[],axle:[],chain:[]};

    this.sz     = {rb:100, rm:75, rs:50};
    this.dir    = 1;
    this.last   = null;
    this.tw     = 4;
    this.tl     = this.tw + 1;
    this.navcr  = 7.5;

    this.tsp    = null; // Timestamp previus frame
    this.raf    = 0;    // Use requestAnimationFrame
    this.mode   = 0;    // 1=Auto start
    this.decor  = 3;    // Decoration level
    this.shade  = 1;    // Use shading
    this.pin    = 1;    // Paint "pin"
    this.clr = {
        wheel: '#321',
        axle : '#852',
        chain: '#842',
        axis : '#a50',
        teeth: '#1a1108',
        pin  : '#f00',
        dec1 : '#321'
    };
}

var CW = CogWheels.prototype;

CW.getShadow = function() {
    return {
        x:this.ctx.shadowOffsetX,
        y:this.ctx.shadowOffsetY,
        b:this.ctx.shadowBlur,
        c:this.ctx.shadowColor.match(/\((.*)\)/)[1]
    };
};

CW.setShadow = function(p) {
    this.ctx.shadowOffsetX  = p.x || 0;
    this.ctx.shadowOffsetY  = p.y || 0;
    this.ctx.shadowBlur     = p.b || 5;
    this.ctx.shadowColor    = p.c ? "rgba("+p.c+")" : "rgba(0,0,0,.8)";
};

CW.noShadow = function() {
    this.setShadow({x:0, y:0, b:0, c:"0,0,0,0"});
};

/*
 * Drawings for static buffer.
 *
 * * */
CW.drawBulb = function(x, y, r) {
    var gradient = this.ctx.createRadialGradient(x, y, 0, x, y, r);
    gradient.addColorStop(1.00, 'rgba(0, 0, 0, 0)');
    gradient.addColorStop(0.95, 'rgba(40, 40, 40, 1)');
    gradient.addColorStop(0.85, 'rgba(85, 85, 85, 1)');
    gradient.addColorStop(0.70, 'rgba(170, 170, 170, 1)');
    gradient.addColorStop(0.40, 'rgba(250, 250, 250, 1)');
    gradient.addColorStop(0.00, 'rgba(255, 255, 255, 1)');
    this.ctx.fillStyle = gradient;
    this.ctx.globalCompositeOperation = 'source-over';
    this.ctx.fillRect(x - r, y - r, x + r, y + r);
    this.ctx.fillStyle = "#000";
};

CW.drawWheel = function(p) {
    this.ctx.save();
    this.ctx.globalCompositeOperation = 'source-over';
    this.ctx.lineWidth      = this.tw;
    this.ctx.strokeStyle    = this.clr.wheel;
    this.ctx.moveTo(p.a[0], p.a[1]);
    this.ctx.beginPath();
    this.ctx.arc(p.a[0], p.a[1], p.r, 0, P2, p.d);
    this.ctx.stroke();
    if (this.decor > 0 && p.r > 10) {
        this.ctx.beginPath();
        this.ctx.strokeStyle = '#444';
        this.ctx.lineWidth = 1;//0.8;
        this.ctx.arc(p.a[0], p.a[1], p.r / 1.15, 0, P2, p.d === 1);
        this.ctx.stroke();
    }

    // Axis
    this.ctx.beginPath();
    this.ctx.globalCompositeOperation = 'xor';
    this.ctx.lineWidth = 1.5;
    this.ctx.strokeStyle = this.clr.axis;
    this.ctx.arc(p.a[0], p.a[1], this.navcr, 0, P2);
    this.ctx.stroke();
    this.ctx.restore();
    //this.ctx.globalCompositeOperation = 'source-over';
};

CW.drawLinep = function(a) {
    this.ctx.beginPath();
    this.ctx.moveTo(a[0][0], a[0][1]);
    this.ctx.lineTo(a[0][2], a[0][3]);
    this.ctx.moveTo(a[1][0], a[1][1]);
    this.ctx.lineTo(a[1][2], a[1][3]);
    this.ctx.stroke();
};

CW.drawAxle = function(p) {
    this.ctx.save();
    this.ctx.shadowOffsetX  = -this.tw/2;
    this.ctx.shadowOffsetY  = this.tw/2;
    this.ctx.globalCompositeOperation = 'source-over';
    this.ctx.lineWidth = 2;
    this.ctx.strokeStyle = this.clr.axle;

    this.drawLinep(p.a);

    this.noShadow();
    /* Nav left wheel. */
    this.ctx.beginPath();
    this.ctx.lineWidth = 3;
    this.ctx.globalCompositeOperation = 'xor';
    this.ctx.arc(p.a[0][0], p.pp[1], this.navcr, 0, P2);
    this.ctx.stroke();

    /* Nav right wheel. */
    this.ctx.beginPath();
    this.ctx.arc(p.a[0][2], p.pp[1], this.navcr, 0, P2);
    this.ctx.stroke();

    this.ctx.restore();
};

CW.drawChain = function(p) {
    this.ctx.save();
    this.ctx.lineWidth = 2;
    this.ctx.strokeStyle = this.clr.chain;
    this.ctx.globalCompositeOperation = 'destination-over';
    this.ctx.shadowOffsetX  = -this.tw/2;
    this.ctx.shadowOffsetY  = this.tw/2;

    this.drawLinep(p.a);

    this.ctx.restore();
};

CW.circleTangents = function(x1, y1, r1, x2, y2, r2) {
    var d = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)),
        vx = (x2 - x1) / d,
        vy = (y2 - y1) / d,
        s1, s2, c, h,
        nx, ny, a, re = [], i = 0;
    for (s1 = 1; s1 >= -1; s1 -= 2) {
        c = (r1 - s1 * r2) / d;
        if (c*c > 1.0)
            continue;
        h = Math.sqrt(Math.max(0.0, 1.0 - c * c));
        for (s2 = 1; s2 >= -1; s2 -= 2) {
            nx = vx * c - s2 * h * vy;
            ny = vy * c + s2 * h * vx;
            a = [];
            a[0] = x1 + r1 * nx;
            a[1] = y1 + r1 * ny;
            a[2] = x2 + s1 * r2 * nx;
            a[3] = y2 + s1 * r2 * ny;
            re[i++] = a;
        }
    }
    return re;
};

/*
 * Paint gear base to static buffer.
 *
 * */
CW.drawStatic = function() {
    var sh;

    if (this.cm.main.ok) {
        sh = this.getShadow();
        this.noShadow();
        this.ctx.drawImage(
            this.cm.main.can,
            this.cm.main.x,
            this.cm.main.y
        );
        this.setShadow(sh);
        return;
    }
};

CW.genStatic = function() {
    var i, e,
        p   = this.parts,
        ix  = this.pix;

    this.ctx.save();
    if (this.shwheel) {
        for (i = 0; i < ix.wheel.length; ++i) {
            e = p[ix.wheel[i]];
            this.drawBulb(e.a[0], e.a[1], e.r);
        }
    }
    for (i = 0; i < ix.wheel.length; ++i) {
        this.drawWheel(p[ix.wheel[i]]);
        //setTimeout(this.drawWheel.bind(this, p[ix.wheel[i]]), (tt+=500));
    }
    for (i = 0; i < ix.axle.length; ++i) {
        this.drawAxle(p[ix.axle[i]]);
        //setTimeout(this.drawAxle.bind(this, p[ix.axle[i]]), (tt+=500));
    }
    for (i = 0; i < ix.chain.length; ++i) {
        this.drawChain(p[ix.chain[i]]);
        //setTimeout(this.drawChain.bind(this, p[ix.chain[i]]), (tt+=500));
    }
    for (i = 0; i < p.length; ++i) {
        this.ctx.globalCompositeOperation = 'source-over';
        this.ctx.strokeSyle = "#000";
        this.ctx.font="10px sans-serif";
        if (this.navcr > 10)
            this.ctx.fillText(i, p[i].pp[0], p[i].pp[1]);
        else if (p[i].w === 'wheel')
            this.ctx.fillText('x', p[i].pp[0], p[i].pp[1]);
    }
    this.cm.main.can.width  = this.grect.w - this.ofs.x + 20;
    this.cm.main.can.height = this.br * 2 + 40;
    // Could have painted directly on memory canvas, but due to coordinates
    // and post-implementation of memory canvas, it is here for now.
    this.cm.main.ctx.drawImage(
        this.can,                       // image
        0,                              // dx
        0,                              // dy
        this.grect.w - this.ofs.x,      // dw
        this.br * 2 + 60,               // dh WHY 60 HERE ????
        -this.ofs.x + 20,               // sx
        -this.ofs.y + 20,               // sy
        this.grect.w - this.ofs.x ,     // sw
        this.br * 2 + 60                // sh WHY 60 HERE ????
    );
    this.cm.main.x = this.ofs.x - 20;
    this.cm.main.y = this.ofs.y - 20;
    this.cm.main.ok = 1;
    this.ctx.restore();
};

/*
 * Drawings for moving parts.
 *
 * * */
CW.spikes = function(x1, y1, spikes, r0, r1, skew_0, skew_1, con) {
    var t = PI / 2 * 3,
        x = x1,
        y = y1,
        step = PI / spikes,
        i, r2 = con ? r1 - 10 : r1;

    this.ctx.beginPath();
    this.ctx.moveTo(x1, y1 - r0);
    for (i = 0; i < spikes; ++i) {
        x = x1 + cos(t)*r0;
        y = y1 + sin(t)*r0;
        this.ctx.lineTo(x, y);
        if (skew_0) {
            t += step / 2;
            x = x1 + cos(t)*r0;
            y = y1 + sin(t)*r0;
            this.ctx.lineTo(x, y);
            t += (step - step/2);
        } else {
            t += step;
        }

        if (skew_1) {
            x = x1 + cos(t - step/18) * r2;
            y = y1 + sin(t - step/18) * r2;
            this.ctx.lineTo(x, y);
            if (con) {
                x = x1 + cos(t - step/8) * r1;
                y = y1 + sin(t - step/8) * r1;
                this.ctx.lineTo(x, y);
                x = x1 + cos(t + step/8) * r1;
                y = y1 + sin(t + step/8) * r1;
                this.ctx.lineTo(x, y);
            }
            x = x1 + cos(t + step/18) * r2;
            y = y1 + sin(t + step/18) * r2;
            this.ctx.lineTo(x, y);
        } else {
            x = x1 + cos(t) * r1;
            y = y1 + sin(t) * r1;
            this.ctx.lineTo(x, y);
        }

        if (skew_0) {
            t += step / 2;
            x = x1 + cos(t)*r0;
            y = y1 + sin(t)*r0;
            this.ctx.lineTo(x, y);
            t += step - step/2;
        } else {
            t += step;
        }
    }
    this.ctx.lineTo(x1, y1 - r0);
    this.ctx.stroke();
    this.ctx.closePath();
};

CW.drawTeeth = function(p) {
    var i, th = p.h, x, y;
    for (i = 0; i < p.t; ++i) {
        x = p.pp[0] + p.r * cos(th);
        y = p.pp[1] + p.r * sin(th);
        this.ctx.moveTo(x, y);
        this.ctx.lineTo(x + cos(th) * this.tl, y + sin(th) * this.tl);
        th += p.thi;
    }
    return th;
};

CW.drawPin = function(p) {
    var x = p.pp[0] + p.r * cos(p.h),
        y = p.pp[1] + p.r * sin(p.h);
    this.ctx.moveTo(
        p.pp[0] + (this.navcr) * cos(p.h),
        p.pp[1] + (this.navcr) * sin(p.h)
    );
    this.ctx.lineTo(x + cos(p.h) * this.tl, y + sin(p.h) * this.tl);
};

CW.drawDecor1 = function(p) {
    var i, x, y,
        th = p.h,
        dd = p.r / 1.15 * 0.63,
        rr = (p.r / this.spk) * 1.125;

    this.ctx.strokeStyle = this.clr.dec1;
    this.ctx.lineWidth = 2;
    this.ctx.globalCompositeOperation = 'source-over';

    for (i = 0; i < this.spk; ++i) {
        x = p.pp[0] + dd * cos(th);
        y = p.pp[1] + dd * sin(th);
        this.ctx.moveTo(x, y);
        this.ctx.beginPath();
        this.ctx.arc(x, y, rr, 0, P2);
        this.ctx.stroke();
        th += PI/this.spk*2;
    }
    //return th;
};

CW.drawDecor2 = function(p) {
    var theta = p.h;
    this.ctx.save();
    this.ctx.translate(p.pp[0], p.pp[1]);
    this.ctx.rotate(theta - (PI / 2 * 3));
    if (this.shade)
        this.setShadow({c:"12,17,23,1",b:17, x:-2, y:2});
    this.ctx.lineWidth = 2;
    if (this.star)
        this.spikes(0, 0, this.spk, p.r / 6 /*this.navcr*/, p.r / 1.19);
    else if (p.r > 50)
        this.spikes(0, 0, this.spk, p.r / 6 /*this.navcr*/, p.r / 1.15, 1, 1, 1);
    else
        this.spikes(0, 0, this.spk, p.r / 6 /*this.navcr*/, p.r / 1.19, 1, 1);
    this.ctx.restore();
};

CW.drawAnim = function(tt) {
    var i, j,
        p = this.parts,
        ix = this.pix,
        sh = this.getShadow();

    this.ctx.save();

    if (this.decor > 1) {
        for (i = 0; i < ix.wheel.length; ++i) {
            if (p[ix.wheel[i]].r < this.navcr * 2)
                continue;
            if ((this.decor === 2 || this.decor > 3))
                this.drawDecor1(p[ix.wheel[i]]);
            if ((this.decor > 2))
                this.drawDecor2(p[ix.wheel[i]]);
        }
    }

    this.ctx.lineWidth = this.tw;

    this.noShadow();
    if (this.pin) {
        this.ctx.beginPath();
        this.ctx.strokeStyle = this.clr.pin;
        this.ctx.globalCompositeOperation = 'darker';
        for (i = 0; i < ix.wheel.length; ++i)
            this.drawPin(p[ix.wheel[i]]);
        this.ctx.stroke();
    }

    this.ctx.globalCompositeOperation = 'destination-over';
    this.ctx.strokeStyle = this.clr.teeth;
    this.ctx.beginPath();
    for (i = 0; i < ix.wheel.length; ++i) {
        j = ix.wheel[i];
        this.drawTeeth(p[j]);
        p[j].h += p[j].s * tt * p[j].d;
    }
    this.ctx.stroke();
    this.setShadow(sh);

    this.ctx.restore();
};

/*
 * Build functions.
 *
 * * */
CW.add = function(a) {
    var e = {
            w: a.type,
            r: a.rad,
            s: a.speed,
            d: this.dir,
            h: a.theta,
        };
    if (a.type === 'wheel') {
        if (this.last === 'wheel')
            this.dir = -this.dir;
        e.t     = a.teeth;
        e.d     = this.dir;
        // Theta increment
        e.thi   = PI / a.teeth * 2;
    } else if (a.type === 'axle' || a.type === 'chain') {
        e.t     = 30;
    } else {
        // Throw error
        return null;
    }
    e.hs = e.h; // Starting angle
    this.last = a.type;
    this.parts.push(e);
    this.grect.w += e.r * 2 + this.tl * 2;
    return this;
};

/*
 * Various calculations that is the same for each frame.
 *
 * */
CW.postBuild = function() {
    var i,
        x1 = this.ofs.x, x2,
        y1 = this.ofs.y + this.br, y2,
        p  = this.parts,
        c  = this.navcr,
        a, d, txl = this.navcr > 10 ? 5 : 15,
        tf = this.tw < 2 ? 2 : 3;

    this.ctx.textBaseline = "middle";
    this.ctx.fillText("tps:", 2, this.grect.h + txl);
    txl += 20;
    this.ctx.fillText("Hz:", 2, this.grect.h + txl);
    txl -= 10;
    this.ctx.textAlign = "center";

    for (i = 0; i < p.length; ++i) {
        if (p[i].w === 'wheel') {
            x1 += p[i].r;
            p[i].a = [x1, y1];
            p[i].tps = (1 / ((P2 / p[i].t) / p[i].s) * p[i].d).toFixed(1);
            if (this.navcr <= 10)
                this.ctx.fillText(i, x1, this.grect.h + 5);
            this.ctx.fillText(p[i].tps, x1, this.grect.h + txl);
            this.ctx.fillText((Math.abs(p[i].tps)/p[i].t).toFixed(tf) ,
                              x1, this.grect.h + txl + 20);
            this.pix.wheel.push(i);
        } else if (p[i].w === 'axle') { // AXLE
            x1 -= p[i-1].r + c;
            x2 = x1 + p[i-1].r + c + p[i].r*2 + c + p[i + 1].r;
            a = [];
            // Left - right nav, top
            a[0] = [x1, y1 - c, x2, y1 - c];
            // Left - right nav, bottom
            a[1] = [x1, y1 + c, x2, y1 + c];
            p[i].a = a;
            // Real center + radius
            d = x2 - x1;
            p[i].cc = [(x1+x2)/2, y1, d / 2];
            p[i].t  = d * 2 / this.tw;
            x1 += p[i - 1].r + c + p[i].r;
            this.pix.axle.push(i);
        } else if (p[i].w === 'chain') {  // CHAIN
            x1 -= p[i-1].r + c;
            x2 = x1 + p[i-1].r + c + p[i].r*2 + c + p[i + 1].r;
            y2 = y1 - p[i+1].r - c ;
            p[i].a = this.circleTangents(x1, y1, p[i-1].r, x2, y1, p[i+1].r);
            x1 += p[i - 1].r + c + p[i].r;
            this.pix.chain.push(i);
        }
        p[i].pp = [x1, y1];
        x1 += p[i].r + c;
    }
    document.getElementById('tps_e').value = p[--i].tps;
};

/*
 *
 * @a   Arrangement
 * @tb  Teeth O wheel
 * @tm  Teeth o wheel
 * @ts  Teeth * wheel
 * @tps Teeth per second leftmost wheel
 * */
CW.build = function(a, tb, tm, ts, tps) {
    var i, w, c,
        rb, rm, rs, // Radius
        ob, om, os, // Odd/even flag
        sp = null,  // Speed
        pr = '',    // Previous
        th,         // theta
        hor = 0,    // If last wheel has right most tooth horizontal.
        xcw;        // Axel / Chain base width.

    if (a.match(/^[=-]|[=-]$|-=|=-/))
        return 1;
    clearInterval(this.iv);
    this.tsp    = null;
    this.parts  = [];
    this.pix    = {wheel:[],axle:[],chain:[]};
    this.imgm   = 0;
    //this.ctx.clearRect(0, 0, this.can.width, this.can.height);
    this.can.width = this.can.width;

    this.spk    = parseInt(document.getElementById('spikes').value);
    this.tw     = parseFloat(document.getElementById('teeth_w').value);

    if (!isNumbers([tb, tm, ts, tps, this.tw, this.spk]))
        return 1;
    if (tb < 1 || tm < 1 || ts < 1 /*|| tps === 0*/ || this.tw <= 0)
        return 1;

    this.tl     = this.tw + 2;
    this.navcr  = this.tl * 1.5;
    xcw         = this.tl * 2;

    this.sz.rb = rb = this.tw * tb / PI;
    this.sz.rm = rm = this.tw * tm / PI;
    this.sz.rs = rs = this.tw * ts / PI;

    // Biggest radius
    this.br = rb > rm ? rb : rm;
    if (rs > this.br)
        this.br = rs;

    ob = tb % 2;
    om = tm % 2;
    os = ts % 2;

    // Offset x:left, y:center first wheel
    this.ofs = {
        x: this.tl * 3,
        y: /*this.br +*/ 50
    };
    // Repaint rectangle
    this.grect = {
        w: this.ofs.x,
        h: this.br * 2 + 100
    };

    this.dir  = tps > 0 ? 1 : -1;
    tps = Math.abs(tps);
    this.last = null;

    for (i = 0; i < a.length; ++i) {
        th = 0;
        switch ((c = a.charAt(i))) {
        case 'O':
            if (sp === null) {
                sp = (P2 / tb) * tps;
            } else if (pr === 'o') {
                sp = sp / rb * rm;
            } else if (pr === '*') {
                sp = sp / rb * rs;
            }
            // ODD + HOR = no skew
            // If odd result is skew
            if ((hor && ob) || (!hor && !ob)) {
                hor = 1;
            } else {
                th = this.tw / rb;
                hor = 0;
            }
            this.add({type:'wheel', rad:rb, teeth:tb, speed:sp, theta:th});
            break;
        case 'o':
            if (sp === null) {
                sp = (P2 / tm) * tps;
            } if (pr === 'O') {
                sp = sp / rm * rb;
            } else if (pr === '*') {
                sp = sp / rm * rs;
            }
            if ((hor && om) || (!hor && !om)) {
                hor = 1;
            } else {
                th = this.tw / rm;
                hor = 0;
            }
            this.add({type:'wheel', rad:rm, teeth:tm, speed:sp, theta:th});
            break;
        case '*':
            if (sp === null) {
                sp = (P2 / ts) * tps;
            } if (pr === 'O') {
                sp = sp / rs * rb;
            } else if (pr === 'o') {
                sp = sp / rs * rm;
            }
            if ((hor && os) || (!hor && !os)) {
                hor = 1;
            } else {
                th = this.tw / rs;
                hor = 0;
            }
            this.add({type:'wheel', rad:rs, teeth:ts, speed:sp, theta:th});
            break;
        case '-':
            for (w = 1; a.charAt(i + 1) === '-'; ++i, ++w)
                ;
            this.add({type:'axle', rad:w * xcw, speed:sp, theta:th});
            hor = 1;
            break;
        case '=':
            for (w = 1; a.charAt(i + 1) === '='; ++i, ++w)
                ;
            this.add({type:'chain', rad: w * xcw, speed:sp, theta:th});
            hor = 1;
            break;
        default:
            this.parts = [];
            return 1;
        }
        if (c !== '=')
            pr = a.charAt(i);
    }
    if (!this.parts.length)
        return 1;
    // Hack to prevent stray borders in page.
    this.can.style.display = 'none';
    this.can.width = this.can.width + 50;

    this.can.width  = this.grect.w;
    this.can.height = this.grect.h + 50;
    this.can.style.display = 'block';

    this.postBuild();
    if (this.shade) {
        //this.setShadow({x:-1,y:1,b:13});
        this.setShadow({c:"0,0,0,.6", x:-4, y:4, b:13});
        this.setShadow({c:"0,0,0,.8", x:0, y:0, b:this.tw*2});
        //this.ctx.shadowBlur = 17;
    } else {
        this.ctx.shadowBlur = 0;
    }
    this.genStatic();
    this.step();
};

/*
 * Runtime functions.
 *
 * * */
CW.tick = function(tt) {
    this.ctx.clearRect(0, 0, this.grect.w, this.grect.h);
    this.noShadow();
    this.ctx.textAlign = "left";
    this.ctx.fillText("F: " + tt, 15, 15);
    this.ctx.textAlign = "center";
    if (this.shade) {
        this.setShadow({c:"0,0,0,.8",x:0,y:0,b:this.tw*2});
    }
    this.drawStatic();
    this.drawAnim(tt);
};

CW.step = function () {
    if (this.parts.length)
        this.tick((1/this.fps).toFixed(3));
};

CW.run_raf = function(ts) {
    if (this.tsp === null) {
        this.tsp = ts;
    }
    this.tsp = ts - this.tsp;
    this.tick(this.tsp/1000);
    requestAnimationFrame(this.run.bind(this));
    this.tsp = ts;
};

CW.run_iv = function() {
    var ts, that = this, ic = this.fps;
    this.tsp = +Date.now();
    this.iv = setInterval(function() {
        ts  = +Date.now() - that.tsp;
        that.tsp = +Date.now();
        that.tick(ts/1000);
        if (ic !== that.fps) {
            clearInterval(that.iv);
            that.run_iv();
        }
    }, 1000 / this.fps);
};

CW.stop = function () {
    clearInterval(this.iv);
    this.mode = 0;
};

CW.run = function () {
    this.stop();
    if (!this.parts.length)
        return this.ui_build_run;
    this.mode = 1;
    this.raf = !document.getElementById('run_iv').checked;
    if (this.raf)
        requestAnimationFrame(this.run_raf.bind(this));
    else
        this.run_iv();
};

/*
 * UI functions
 *
 * * */
CW.ui_build = function() {
    if (this.build(
        document.getElementById('arr').value,
        +document.getElementById('teeth_b').value,
        +document.getElementById('teeth_m').value,
        +document.getElementById('teeth_s').value,
        +document.getElementById('tps_1').value
    ))
        return 1;
};

CW.ui_build_run = function() {
    if (this.ui_build())
        return 1;
    if (this.mode > 0)
        this.run();
};

/*
 * This should have been refactored.
 * * */
CW.ui_init = function() {
    var i, cw = this,
    e   = document.getElementById('fps'),
    fv  = document.getElementById('fpsv'),
    dv  = document.getElementById('decorv');

    this.fps = +e.value;
    fv.innerHTML = this.fps.toFixed(1);
    e.addEventListener('change', function(e) {
        cw.fps = parseFloat(e.target.value);
        fv.innerHTML = cw.fps.toFixed(1);
    });

    e  = document.getElementById('decor');
    this.decor = +e.value;
    dv.innerHTML = this.decor;
    e.addEventListener('change', function(e) {
        cw.decor = parseInt(e.target.value);
        dv.innerHTML = cw.decor;
        cw.ui_build_run();
    });

    e  = document.getElementById('pin');
    this.pin = !!e.checked;
    e.addEventListener('change', function(e) {
        cw.pin = e.target.checked;
        cw.ui_build_run();
    });
    e  = document.getElementById('shadow');
    this.shade = !!e.checked;
    e.addEventListener('change', function(e) {
        cw.shade = e.target.checked;
        cw.ui_build_run();
    });

    e  = document.getElementById('shwheel');
    this.shwheel = !!e.checked;
    e.addEventListener('change', function(e) {
        cw.shwheel = e.target.checked;
        cw.ui_build_run();
    });

    e  = document.getElementById('star');
    this.star = !!e.checked;
    e.addEventListener('change', function(e) {
        cw.star = e.target.checked;
        cw.ui_build_run();
    });

    e = document.getElementById('run_iv');
    cw.raf = !e.checked;

    document.getElementById('build').addEventListener(
        'click', cw.ui_build.bind(cw));
    document.getElementById('stop').addEventListener(
        'click', cw.stop.bind(cw));
    document.getElementById('step').addEventListener(
        'click', cw.step.bind(cw));
    document.getElementById('run').addEventListener(
        'click', cw.run.bind(cw));

    e = document.getElementsByTagName('INPUT');
    for (i = 0; i < e.length; ++i) {
        if (e[i].type === 'text') {
            e[i].addEventListener('keyup', cw.ui_build_run.bind(cw));
        } else if (e[i].type === 'radio') {
            e[i].addEventListener('change', cw.ui_build_run.bind(cw));
        }
    }
};

document.addEventListener("DOMContentLoaded", function () {
    var cw = new CogWheels();
    cw.ui_init();
    cw.ui_build_run();
});
\$\endgroup\$
3
\$\begingroup\$

Julia

chain(s) = all((c)->c=='=',s)
axle(s) = all((c)->c=='-',s)
direct(s) = s==""

function mechanical(arrangment,O,o,x,speed_l)
    teeth = {"O"=>O, "o"=>o, "*"=>x}
    process(c) = direct(c[2]) ? -1 : chain(c[2]) ? 1 : axle(c[2]) ? teeth[c[3]]/teeth[c[1]] : 0
    matches = collect(eachmatch(r"([Oo*])([-=]*)([Oo*])",arrangment,true))
    length(matches) == 0 ? 0 : speed_l*prod([process(m.captures) for m in matches])
end

# test cases
println(mechanical("*-Oo-O",50,30,10,3))
println(mechanical("o*-O*= =oO",50,30,10,-3))
println(mechanical("O---*oO=O*-oo",15,12,8,10))
println(mechanical("O *",50,30,10,10))

Gives correct output. r"stuff" is a regex.

\$\endgroup\$
2
\$\begingroup\$

Java

import java.util.Scanner;

/**
 *
 * @author Quincunx
 */
public class MechanicalCoding {
    final static int[] teeth = new int[3];

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.println("Input the arrangement:");
        String layout = scan.nextLine();

        System.out.println("Input the:");
        System.out.print("\tNumber of teeth on large gear:\n\t");
        teeth[2] = scan.nextInt();
        scan.nextLine();
        System.out.print("\tNumber of teeth on medium gear:\n\t");
        teeth[1] = scan.nextInt();
        scan.nextLine();
        System.out.print("\tNumber of teeth on small gear:\n\t");
        teeth[0] = scan.nextInt();
        scan.nextLine();

        System.out.println("Input the speed of the left hand side:");
        double speed = scan.nextDouble();

        layout = layout.replaceAll("([-=])+", "$1");

        char lastC = ' ';
        boolean mustBeNext = false;
        for (char c : layout.toCharArray()){
            switch (c){
                case 'O':
                case 'o':
                case '*':
                    if (lastC == '=' || lastC == ' '){
                        break;
                    }
                    if (lastC == '-'){
                        speed *= 1.*teeth(c)/teeth(lastC);
                        break;
                    }
                    speed *= -1;
                    break;
                case '-':
                case '=':
                default:
                    if (lastC == ' '){
                        System.exit(1);
                    }
            }
            lastC = c;
        }
        if (lastC == '-' || lastC == '='){
            System.exit(1);
        }
        System.out.println(speed);
    }
    public static int teeth(char c){
        return teeth[c=='O'?2:c=='o'?1:0];
    }
}

What a relief to be able to use Java! Sample run:

Input the arrangement:
o*-O*==oO
Input the:
    Number of teeth on large gear:
    50
    Number of teeth on medium gear:
    30
    Number of teeth on small gear:
    10
Input the speed of the left hand side:
-3.0
15.0
\$\endgroup\$
1
\$\begingroup\$

C#, 396 bytes

Golfed for fun with added indentation. I'm sure the same code would be smaller in a loosely-typed language.

namespace System.Text.RegularExpressions{
    class P{
        static void Main(string[]a){
            var s=Regex.IsMatch(a[0],"^[-=]|[-=]$|-=|=-|[^-=Oo*]")?0:double.Parse(a[4]);
            foreach(Match m in Regex.Matches(a[0],"[Oo*]{2,}"))
                s*=m.Length%2*2-1;
            foreach(Match m in Regex.Matches(a[0],"[Oo*]-+[Oo*]"))
                s*=double.Parse(a[" Oo*".IndexOf(m.Value[m.Length-1])])/double.Parse(a[" Oo*".IndexOf(m.Value[0])]);
            Console.Write(s);
        }
    }
}
\$\endgroup\$
1
\$\begingroup\$

159 Bytes - Javascript

This uses ECMAScript 6 Features so will only work in browsers that support those - Firefox is one.

(g,l,m,s,v,n)=>{for(a={'O':l,'o':m,'*':s};n!=g&&(n=g);g=g.replace(/[Oo*](-*|=*)([Oo*])/,(w,y,z)=>((v*=y?(y[0]=='='?1:a[z]/a[w[0]]):-1)-v)||z));return a[g]?v:0}

Defines an anonymous function that takes the parameters arrangement O o * speed and returns the speed of the last cog for a valid arrangement, and zero otherwise.

Can test all test cases in firefox with:

[((g,l,m,s,v,n)=>{for(a={'O':l,'o':m,'*':s};n!=g&&(n=g);g=g.replace(/[Oo*](-*|=*)([Oo*])/,(w,y,z)=>((v*=y?(y[0]=='='?1:a[z]/a[w[0]]):-1)-v)||z));return a[g]?v:0}).apply(0,a) for each (a in [['*-Oo-O',50,30,10,3],['o*-O*==oO',50,30,10,-3],['O---*oO=O*-oo',15,12,8,10],['=o*',50,30,10,-3],['o*-',50,30,10,-3],['o-=*',50,30,10,-3],['o *',50,30,10,-3]])]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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