<commit_before>/******************\
|   Fourier Image  |
| @author Anthony  |
| @version 0.1     |
| @date 2014/06/14 |
| @edit 2014/06/14 |
\******************/

/**********
 * config */
var dims = [256, 256];
var imageLoc = 'image.png'; //must have 'dims' dimensions
var cc = 9e-3; //contrast constant

/*************
 * constants */

/*********************
 * working variables */
var canvases;
var ctxs;
var h;
var $h; //h hat
var h_; //h prime, the reconstructed h values

/******************
 * work functions */
function initFourierImage() {
    //event listeners
    $s('#draw-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //draw the initial image
            var img = new Image();
            img.addEventListener('load', function() {
                ctxs[0].drawImage(img, 0, 0, img.width, img.height);

                //grab the pixels
                var imageData = ctxs[0].getImageData(0, 0, dims[0], dims[1]);
                var h_es = []; //the h values
                for (var ai = 0; ai < imageData.data.length; ai+=4) {
                    //greyscale, so you only need every 4th value
                    h_es.push(imageData.data[ai]);
                }

                //initialize the h values
                h = function(n, m) {
                    if (arguments.length === 0) return h_es;

                    var idx = n*dims[0] + m;
                    return h_es[idx];
                }; //create it in function form to make the code match the math

                enableButtons();

                var duration = +new Date() - start;
                console.log('It took '+duration+'ms to draw the image.');
            });

            //THE IMAGE DIMENSIONS MUST MATCH dims FOR THIS TO WORK!!!
            img.src = imageLoc;
        });
    });

    $s('#transform-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //compute the h hat values
            var h_hats = [];
            FFT(h_hats, h());
            h_hats = shiftFFT(h_hats);

            //get the largest magnitude
            var maxMagnitude = 0;
            for (var ai = 0; ai < h_hats.length; ai++) {
                var mag = h_hats[ai].magnitude();
                if (mag > maxMagnitude) {
                    maxMagnitude = mag;
                }
            }

            //apply a low or high pass filter
            var lpr = parseInt($s('#low-freq-radius').value); //low pass radius
            var hpr = parseInt($s('#high-freq-radius').value); //low pass radius
            var N = dims[1], M = dims[0];
            for (var k = 0; k < N; k++) {
                for (var l = 0; l < M; l++) {
                    var idx = k*M + l;
                    var dist = Math.pow(k-M/2, 2) + Math.pow(l-N/2, 2);
                    if (dist > lpr*lpr && isNaN(hpr) ||
                        dist < hpr*hpr && isNaN(lpr) ||
                        dist < lpr*lpr && !isNaN(lpr) && !isNaN(hpr) ||
                        dist > hpr*hpr && !isNaN(hpr) && !isNaN(hpr)) {
                        h_hats[idx] = new Complex(0, 0);
                    }
                }
            }

            //store them in a nice function to match the math
            $h = function(k, l) {
                if (arguments.length === 0) return h_hats;

                var idx = k*dims[0] + l;
                return h_hats[idx];
            };

            //draw the pixels
            var currImageData = ctxs[1].getImageData(0, 0, dims[0], dims[1]);
            var logOfMaxMag = Math.log(cc*maxMagnitude+1);
            for (var k = 0; k < dims[1]; k++) {
                for (var l = 0; l < dims[0]; l++) {
                    var idxInPixels = 4*(dims[0]*k + l);
                    currImageData.data[idxInPixels+3] = 255; //full alpha
                    var color = Math.log(cc*$h(k, l).magnitude()+1);
                        color = Math.round(255*(color/logOfMaxMag));
                    for (var c = 0; c < 3; c++) { //RGB are the same, lol c++
                        currImageData.data[idxInPixels+c] = color;
                    }
                }
            }
            ctxs[1].putImageData(currImageData, 0, 0);

            enableButtons();

            var duration = +new Date() - start;
            console.log('It took '+duration+'ms to compute the FT.');
        });
    });

    $s('#reconstruct-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //compute the h prime values
            var h_primes = [];
            var h_hats = $h();
            h_hats = unshiftFFT(h_hats);
            invFFT(h_primes, h_hats);

            //store them in a nice function to match the math
            h_ = function(n, m) {
                if (arguments.length === 0) return h_primes;

                var idx = n*dims[0] + m;
                return h_primes[idx];
            };

            //draw the pixels
            var currImageData = ctxs[2].getImageData(0, 0, dims[0], dims[1]);
            for (var n = 0; n < dims[1]; n++) {
                for (var m = 0; m < dims[0]; m++) {
                    var idxInPixels = 4*(dims[0]*n + m);
                    currImageData.data[idxInPixels+3] = 255; //full alpha
                    for (var c = 0; c < 3; c++) { //RGB are the same, lol c++
                        currImageData.data[idxInPixels+c] = h_(n, m);
                    }
                }
            }
            ctxs[2].putImageData(currImageData, 0, 0);

            enableButtons();

            var duration = +new Date() - start;
            console.log('It took '+duration+'ms to reconstruct the image.');
        });
    });

    $s('#difference-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //draw the pixels
            var currImageData = ctxs[3].getImageData(0, 0, dims[0], dims[1]);
            for (var n = 0; n < dims[1]; n++) {
                for (var m = 0; m < dims[0]; m++) {
                    var idxInPixels = 4*(dims[0]*n + m); //idx in the pixels array
                    for (var c = 0; c < 3; c++) {
                        currImageData.data[idxInPixels+c] = 0;
                    }
                    var error = Math.ceil(Math.abs(h_(n, m) - h(n, m)));
                    currImageData.data[idxInPixels+3] = error; //alpha
                }
            }
            ctxs[3].putImageData(currImageData, 0, 0);

            enableButtons();

            var duration = +new Date() - start;
            console.log('It took '+duration+'ms to compute the difference.');
        });
    });

    //initialize the working variables
    canvases = [], ctxs = [];
    for (var ai = 0; ai < 4; ai++) {
        canvases[ai] = $s('#canvas'+ai);
        canvases[ai].width = dims[0], canvases[ai].height = dims[1];
        ctxs[ai] = canvases[ai].getContext('2d');
    }
    h = $h = h_ = function() { return 0; };
}

function FFT(out, sig) {
    rec_FFT(out, 0, sig, 0, sig.length, 1);
}
function rec_FFT(out, start, sig, offset, N, s) {
    if (N === 1) {
        out[start] = new Complex(sig[offset], 0); //array
    } else {
        rec_FFT(out, start, sig, offset, N/2, 2*s);
        rec_FFT(out, start+N/2, sig, offset+s, N/2, 2*s);
        for (var k = 0; k < N/2; k++) {
            var twiddle = cisExp(-2*Math.PI*k/N);
            var t = out[start+k];
            out[start+k] = t.plus(twiddle.times(out[start+k+N/2]));
            out[start+k+N/2] = t.minus(twiddle.times(out[start+k+N/2]));
        }
    }
}

function invFFT(sig, transform) {
    rec_invFFT(sig, 0, transform, 0, transform.length, 1);
    for (var ai = 0; ai < sig.length; ai++) {
        sig[ai] = sig[ai].real/sig.length;
    }
}
function rec_invFFT(sig, start, transform, offset, N, s) {
    if (N === 1) {
        sig[start] = transform[offset];
    } else {
        rec_invFFT(sig, start, transform, offset, N/2, 2*s);
        rec_invFFT(sig, start+N/2, transform, offset+s, N/2, 2*s);
        for (var k = 0; k < N/2; k++) {
            var twiddle = cisExp(2*Math.PI*k/N);
            var t = sig[start+k];
            sig[start+k] = t.plus(twiddle.times(sig[start+k+N/2]));
            sig[start+k+N/2] = t.minus(twiddle.times(sig[start+k+N/2]));
        }
    }
}

function shiftFFT(transform) {
    return flipRightHalf(
        halfShiftFFT(
            halfShiftFFT(transform)
        )
    );
}
function unshiftFFT(transform) {
    return halfShiftFFT(
        halfShiftFFT(
            flipRightHalf(transform)
        )
    );
}
function halfShiftFFT(transform) {
    var ret = [];
    var N = dims[1];
    var M = dims[0];
    for (var n = 0, vOff = N/2; n < N; n++) {
        for (var m = 0; m < M/2; m++) {
            var idx = vOff*dims[0] + m;
            ret.push(transform[idx]);
        }
        vOff += vOff >= N/2 ? -N/2 : (N/2)+1;
    }
    for (var n = 0, vOff = N/2; n < N; n++) {
        for (var m = M/2; m < M; m++) {
            var idx = vOff*dims[0] + m;
            ret.push(transform[idx]);
        }
        vOff += vOff >= N/2 ? -N/2 : (N/2)+1;
    }
    return ret;
}
function flipRightHalf(transform) {
    var ret = [];

    //flip the right half of the image across the x axis
    var N = dims[1];
    var M = dims[0];
    for (var n = 0; n < N; n++) {
        for (var m = 0; m < M; m++) {
            var $n = m < M/2 ? n : (N-1)-n;
            var idx = $n*dims[0] + m;
            ret.push(transform[idx]);
        }
    }

    return ret;
}

/********************
 * helper functions */
function cisExp(x) { //e^ix = cos x + i*sin x
    return new Complex(Math.cos(x), Math.sin(x));
}

//returns array of pixel colors in the image
function getPixelsFromImage(location, callback) {
	var startedGettingPixels = new Date().getTime();
	var img = new Image(); //make a new image
	img.onload = function() { //when it is finished loading
		var canvas = document.createElement('canvas'); //make a canvas element
		canvas.width = img.width; //with this width
        //and this height (keep it the same as the image)
		canvas.height = img.height;
		canvas.style.display = 'none'; //hide it from the user
		document.body.appendChild(canvas); //then add it to the body
		var ctx = canvas.getContext('2d'); //now get the context
        //so that you can draw the image
		ctx.drawImage(img, 0, 0, img.width, img.height);
        //and grab its pixels
		var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
		document.body.removeChild(canvas); //all done, so get rid of it

		//...all so you can send the pixels, width, and the time taken to get
        //   them back through the callback
		var ret = [];
		for (var ai = 0; ai < imageData.data.length; ai++) {
            //annoying copy so the array can be edited
            ret.push(imageData.data[ai]);
        }
		callback(ret, img.width, new Date().getTime() - startedGettingPixels);
	};

	img.src = location; //load the image
}

function disableButtons(callback) {
    $s('#draw-btn').disabled = true;
    $s('#transform-btn').disabled = true;
    $s('#reconstruct-btn').disabled = true;
    $s('#difference-btn').disabled = true;

    setTimeout(callback, 6); //6ms for the UI to update
}

function enableButtons() {
    $s('#draw-btn').disabled = false;
    $s('#transform-btn').disabled = false;
    $s('#reconstruct-btn').disabled = false;
    $s('#difference-btn').disabled = false;
}

function $s(id) { //for convenience
    if (id.charAt(0) !== '#') return false;
    return document.getElementById(id.substring(1));
}

function getRandInt(low, high) { //output is in [low, high)
    return Math.floor(low + Math.random()*(high-low));
}

function round(n, places) {
    var mult = Math.pow(10, places);
    return Math.round(mult*n)/mult;
}

/***********
 * objects */
function Complex(re, im) {
    this.real = re;
    this.imag = im;
}
Complex.prototype.magnitude2 = function() {
    return this.real*this.real + this.imag*this.imag;
};
Complex.prototype.magnitude = function() {
    return Math.sqrt(this.magnitude2());
};
Complex.prototype.plus = function(z) {
    return new Complex(this.real+z.real, this.imag+z.imag);
};
Complex.prototype.minus = function(z) {
    return new Complex(this.real-z.real, this.imag-z.imag);
};
Complex.prototype.times = function(z) {
    if (typeof z === 'object') { //complex multiplication
        var rePart = this.real*z.real - this.imag*z.imag;
        var imPart = this.real*z.imag + this.imag*z.real;
        return new Complex(rePart, imPart);
    } else { //scalar multiplication
        return new Complex(z*this.real, z*this.imag);
    }
};

window.addEventListener('load', initFourierImage);

<commit_msg>Fixes typo that introduced a bug in the previous commit.<commit_after>/******************\
|   Fourier Image  |
| @author Anthony  |
| @version 0.1     |
| @date 2014/06/14 |
| @edit 2014/06/14 |
\******************/

/**********
 * config */
var dims = [256, 256];
var imageLoc = 'image.png'; //must have 'dims' dimensions
var cc = 9e-3; //contrast constant

/*************
 * constants */

/*********************
 * working variables */
var canvases;
var ctxs;
var h;
var $h; //h hat
var h_; //h prime, the reconstructed h values

/******************
 * work functions */
function initFourierImage() {
    //event listeners
    $s('#draw-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //draw the initial image
            var img = new Image();
            img.addEventListener('load', function() {
                ctxs[0].drawImage(img, 0, 0, img.width, img.height);

                //grab the pixels
                var imageData = ctxs[0].getImageData(0, 0, dims[0], dims[1]);
                var h_es = []; //the h values
                for (var ai = 0; ai < imageData.data.length; ai+=4) {
                    //greyscale, so you only need every 4th value
                    h_es.push(imageData.data[ai]);
                }

                //initialize the h values
                h = function(n, m) {
                    if (arguments.length === 0) return h_es;

                    var idx = n*dims[0] + m;
                    return h_es[idx];
                }; //create it in function form to make the code match the math

                enableButtons();

                var duration = +new Date() - start;
                console.log('It took '+duration+'ms to draw the image.');
            });

            //THE IMAGE DIMENSIONS MUST MATCH dims FOR THIS TO WORK!!!
            img.src = imageLoc;
        });
    });

    $s('#transform-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //compute the h hat values
            var h_hats = [];
            FFT(h_hats, h());
            h_hats = shiftFFT(h_hats);

            //get the largest magnitude
            var maxMagnitude = 0;
            for (var ai = 0; ai < h_hats.length; ai++) {
                var mag = h_hats[ai].magnitude();
                if (mag > maxMagnitude) {
                    maxMagnitude = mag;
                }
            }

            //apply a low or high pass filter
            var lpr = parseInt($s('#low-freq-radius').value); //low pass radius
            var hpr = parseInt($s('#high-freq-radius').value); //low pass radius
            var N = dims[1], M = dims[0];
            for (var k = 0; k < N; k++) {
                for (var l = 0; l < M; l++) {
                    var idx = k*M + l;
                    var dist = Math.pow(k-M/2, 2) + Math.pow(l-N/2, 2);
                    if (dist > lpr*lpr && isNaN(hpr) ||
                        dist < hpr*hpr && isNaN(lpr) ||
                        dist < lpr*lpr && !isNaN(lpr) && !isNaN(hpr) ||
                        dist > hpr*hpr && !isNaN(lpr) && !isNaN(hpr)) {
                        h_hats[idx] = new Complex(0, 0);
                    }
                }
            }

            //store them in a nice function to match the math
            $h = function(k, l) {
                if (arguments.length === 0) return h_hats;

                var idx = k*dims[0] + l;
                return h_hats[idx];
            };

            //draw the pixels
            var currImageData = ctxs[1].getImageData(0, 0, dims[0], dims[1]);
            var logOfMaxMag = Math.log(cc*maxMagnitude+1);
            for (var k = 0; k < dims[1]; k++) {
                for (var l = 0; l < dims[0]; l++) {
                    var idxInPixels = 4*(dims[0]*k + l);
                    currImageData.data[idxInPixels+3] = 255; //full alpha
                    var color = Math.log(cc*$h(k, l).magnitude()+1);
                        color = Math.round(255*(color/logOfMaxMag));
                    for (var c = 0; c < 3; c++) { //RGB are the same, lol c++
                        currImageData.data[idxInPixels+c] = color;
                    }
                }
            }
            ctxs[1].putImageData(currImageData, 0, 0);

            enableButtons();

            var duration = +new Date() - start;
            console.log('It took '+duration+'ms to compute the FT.');
        });
    });

    $s('#reconstruct-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //compute the h prime values
            var h_primes = [];
            var h_hats = $h();
            h_hats = unshiftFFT(h_hats);
            invFFT(h_primes, h_hats);

            //store them in a nice function to match the math
            h_ = function(n, m) {
                if (arguments.length === 0) return h_primes;

                var idx = n*dims[0] + m;
                return h_primes[idx];
            };

            //draw the pixels
            var currImageData = ctxs[2].getImageData(0, 0, dims[0], dims[1]);
            for (var n = 0; n < dims[1]; n++) {
                for (var m = 0; m < dims[0]; m++) {
                    var idxInPixels = 4*(dims[0]*n + m);
                    currImageData.data[idxInPixels+3] = 255; //full alpha
                    for (var c = 0; c < 3; c++) { //RGB are the same, lol c++
                        currImageData.data[idxInPixels+c] = h_(n, m);
                    }
                }
            }
            ctxs[2].putImageData(currImageData, 0, 0);

            enableButtons();

            var duration = +new Date() - start;
            console.log('It took '+duration+'ms to reconstruct the image.');
        });
    });

    $s('#difference-btn').addEventListener('click', function() {
        var start = +new Date();

        //placed in a callback so the UI has a chance to update
        disableButtons(function() {
            //draw the pixels
            var currImageData = ctxs[3].getImageData(0, 0, dims[0], dims[1]);
            for (var n = 0; n < dims[1]; n++) {
                for (var m = 0; m < dims[0]; m++) {
                    var idxInPixels = 4*(dims[0]*n + m); //idx in the pixels array
                    for (var c = 0; c < 3; c++) {
                        currImageData.data[idxInPixels+c] = 0;
                    }
                    var error = Math.ceil(Math.abs(h_(n, m) - h(n, m)));
                    currImageData.data[idxInPixels+3] = error; //alpha
                }
            }
            ctxs[3].putImageData(currImageData, 0, 0);

            enableButtons();

            var duration = +new Date() - start;
            console.log('It took '+duration+'ms to compute the difference.');
        });
    });

    //initialize the working variables
    canvases = [], ctxs = [];
    for (var ai = 0; ai < 4; ai++) {
        canvases[ai] = $s('#canvas'+ai);
        canvases[ai].width = dims[0], canvases[ai].height = dims[1];
        ctxs[ai] = canvases[ai].getContext('2d');
    }
    h = $h = h_ = function() { return 0; };
}

function FFT(out, sig) {
    rec_FFT(out, 0, sig, 0, sig.length, 1);
}
function rec_FFT(out, start, sig, offset, N, s) {
    if (N === 1) {
        out[start] = new Complex(sig[offset], 0); //array
    } else {
        rec_FFT(out, start, sig, offset, N/2, 2*s);
        rec_FFT(out, start+N/2, sig, offset+s, N/2, 2*s);
        for (var k = 0; k < N/2; k++) {
            var twiddle = cisExp(-2*Math.PI*k/N);
            var t = out[start+k];
            out[start+k] = t.plus(twiddle.times(out[start+k+N/2]));
            out[start+k+N/2] = t.minus(twiddle.times(out[start+k+N/2]));
        }
    }
}

function invFFT(sig, transform) {
    rec_invFFT(sig, 0, transform, 0, transform.length, 1);
    for (var ai = 0; ai < sig.length; ai++) {
        sig[ai] = sig[ai].real/sig.length;
    }
}
function rec_invFFT(sig, start, transform, offset, N, s) {
    if (N === 1) {
        sig[start] = transform[offset];
    } else {
        rec_invFFT(sig, start, transform, offset, N/2, 2*s);
        rec_invFFT(sig, start+N/2, transform, offset+s, N/2, 2*s);
        for (var k = 0; k < N/2; k++) {
            var twiddle = cisExp(2*Math.PI*k/N);
            var t = sig[start+k];
            sig[start+k] = t.plus(twiddle.times(sig[start+k+N/2]));
            sig[start+k+N/2] = t.minus(twiddle.times(sig[start+k+N/2]));
        }
    }
}

function shiftFFT(transform) {
    return flipRightHalf(
        halfShiftFFT(
            halfShiftFFT(transform)
        )
    );
}
function unshiftFFT(transform) {
    return halfShiftFFT(
        halfShiftFFT(
            flipRightHalf(transform)
        )
    );
}
function halfShiftFFT(transform) {
    var ret = [];
    var N = dims[1];
    var M = dims[0];
    for (var n = 0, vOff = N/2; n < N; n++) {
        for (var m = 0; m < M/2; m++) {
            var idx = vOff*dims[0] + m;
            ret.push(transform[idx]);
        }
        vOff += vOff >= N/2 ? -N/2 : (N/2)+1;
    }
    for (var n = 0, vOff = N/2; n < N; n++) {
        for (var m = M/2; m < M; m++) {
            var idx = vOff*dims[0] + m;
            ret.push(transform[idx]);
        }
        vOff += vOff >= N/2 ? -N/2 : (N/2)+1;
    }
    return ret;
}
function flipRightHalf(transform) {
    var ret = [];

    //flip the right half of the image across the x axis
    var N = dims[1];
    var M = dims[0];
    for (var n = 0; n < N; n++) {
        for (var m = 0; m < M; m++) {
            var $n = m < M/2 ? n : (N-1)-n;
            var idx = $n*dims[0] + m;
            ret.push(transform[idx]);
        }
    }

    return ret;
}

/********************
 * helper functions */
function cisExp(x) { //e^ix = cos x + i*sin x
    return new Complex(Math.cos(x), Math.sin(x));
}

//returns array of pixel colors in the image
function getPixelsFromImage(location, callback) {
	var startedGettingPixels = new Date().getTime();
	var img = new Image(); //make a new image
	img.onload = function() { //when it is finished loading
		var canvas = document.createElement('canvas'); //make a canvas element
		canvas.width = img.width; //with this width
        //and this height (keep it the same as the image)
		canvas.height = img.height;
		canvas.style.display = 'none'; //hide it from the user
		document.body.appendChild(canvas); //then add it to the body
		var ctx = canvas.getContext('2d'); //now get the context
        //so that you can draw the image
		ctx.drawImage(img, 0, 0, img.width, img.height);
        //and grab its pixels
		var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
		document.body.removeChild(canvas); //all done, so get rid of it

		//...all so you can send the pixels, width, and the time taken to get
        //   them back through the callback
		var ret = [];
		for (var ai = 0; ai < imageData.data.length; ai++) {
            //annoying copy so the array can be edited
            ret.push(imageData.data[ai]);
        }
		callback(ret, img.width, new Date().getTime() - startedGettingPixels);
	};

	img.src = location; //load the image
}

function disableButtons(callback) {
    $s('#draw-btn').disabled = true;
    $s('#transform-btn').disabled = true;
    $s('#reconstruct-btn').disabled = true;
    $s('#difference-btn').disabled = true;

    setTimeout(callback, 6); //6ms for the UI to update
}

function enableButtons() {
    $s('#draw-btn').disabled = false;
    $s('#transform-btn').disabled = false;
    $s('#reconstruct-btn').disabled = false;
    $s('#difference-btn').disabled = false;
}

function $s(id) { //for convenience
    if (id.charAt(0) !== '#') return false;
    return document.getElementById(id.substring(1));
}

function getRandInt(low, high) { //output is in [low, high)
    return Math.floor(low + Math.random()*(high-low));
}

function round(n, places) {
    var mult = Math.pow(10, places);
    return Math.round(mult*n)/mult;
}

/***********
 * objects */
function Complex(re, im) {
    this.real = re;
    this.imag = im;
}
Complex.prototype.magnitude2 = function() {
    return this.real*this.real + this.imag*this.imag;
};
Complex.prototype.magnitude = function() {
    return Math.sqrt(this.magnitude2());
};
Complex.prototype.plus = function(z) {
    return new Complex(this.real+z.real, this.imag+z.imag);
};
Complex.prototype.minus = function(z) {
    return new Complex(this.real-z.real, this.imag-z.imag);
};
Complex.prototype.times = function(z) {
    if (typeof z === 'object') { //complex multiplication
        var rePart = this.real*z.real - this.imag*z.imag;
        var imPart = this.real*z.imag + this.imag*z.real;
        return new Complex(rePart, imPart);
    } else { //scalar multiplication
        return new Complex(z*this.real, z*this.imag);
    }
};

window.addEventListener('load', initFourierImage);

<|endoftext|>


<commit_before>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.version = 0.3;

  this.storageManager.clearIfOutdated(this.version);

  this.startTiles     = 2;

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  this.setup();
	return this;
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var value = Math.random() < 0.9 ? "Hydrogen" : "Deuteron";
    var tile = new Tile(this.grid.randomAvailableCell(), value, this.labels[value]);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        var shouldMove = true;
        if (next && !next.mergedFrom) {
          //if(next.value === tile.value) {
          if( self.canFuse(next.value,tile.value) ) {
            shouldMove = false;
            var fusionValue = self.fusion(next.value,tile.value);
            var merged = new Tile(positions.next, fusionValue, self.labels[fusionValue]);
            merged.mergedFrom = [tile, next];

            var decay = self.decay[fusionValue] || false;

            if(decay !== false) {
              merged.movesLeft = Math.floor(Math.random() * (10*decay['multipler'] - 4*decay['multipler'] + 1)) + 4*decay['multipler'];
            }

            //merged.movesLeft = 5;

            self.grid.insertTile(merged);
            self.grid.removeTile(tile);

            // Converge the two tiles' positions
            tile.updatePosition(positions.next);

            // Update the score
            self.score += self.pointValues[merged.value];

            // TODO win state
            // The mighty 2048 tile
            if (merged.value === "56Iron") self.won = true;
          }
        }
        if (shouldMove) {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    this.grid.eachCell(function(x, y, tile) {
      if(tile !== null && self.decay[tile.value] && tile.decay()) {
        var decayValue = self.decay[tile.value]['to'];
        var decayed = new Tile({
          x: tile.x,
          y: tile.y
        }, decayValue, self.labels[decayValue]);
        self.grid.removeTile(tile);
        self.grid.insertTile(decayed);
      }
    });

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && self.canFuse(other.value, tile.value)) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};

GameManager.prototype.canFuse = function (first, second) {
  return (this.fusionRules[first]  && this.fusionRules[first][second]) ||
         (this.fusionRules[second] && this.fusionRules[second][first]);
};

GameManager.prototype.fusion = function (first, second) {
  var forward = this.fusionRules[first];
  if (forward && forward[second]) {
    return forward[second];
  } else {
    var backward = this.fusionRules[second][first];
    return backward;
  }
};

// a:{b:c}
// a + b = c
GameManager.prototype.fusionRules = {
  "Hydrogen":{"Hydrogen":"Deuteron",
							"Deuteron":"3Helium"
						 },
  "3Helium":{"3Helium":"4Helium",
							"4Helium":"7Beryllium"
						},
  "4Helium":{"4Helium":"8Beryllium", // unstable decays into 2 4heliums
						 "8Beryllium":"12Carbon",
						 "12Carbon":"16Oxygen",
						 "16Oxygen":"20Neon",
						 "20Neon":"24Magnesium", // this is a killer!
						 "28Silicon":"32Sulfur",
						 "32Sulfur":"36Argon",
						 "36Argon":"40Calcium",
						 "40Calcium":"44Titanium",
						 "44Titanium":"48Chromium",
						 "48Chromium":"52Iron",
						 "52Iron":"56Nickel"
						},
  "16Oxygen":{"16Oxygen":"28Silicon", // + 4Helium
             },
  "12Carbon":{"12Carbon":"20Neon", // + 4Helium (randomness)
						 }
};

GameManager.prototype.labels = {
  "Hydrogen": "Hydrogen",
  "Deuteron": "Deuteron",
  "3Helium": "3Helium",
  "4Helium": "4Helium",
  "7Beryllium": "7Beryllium",
  "8Beryllium": "8Beryllium",
  "12Carbon": "12Carbon",
  "16Oxygen": "16Oxygen",
  "20Neon": "20Neon",
  "24Magnesium": "24Magnesium",
  "28Silicon": "28Silicon",
  "32Sulfur": "32Sulfur",
  "36Argon": "36Argon",
  "40Calcium": "40Calcium",
  "44Titanium": "44Titanium",
  "48Chromium": "48Chromium",
  "52Iron": "52Iron",
  "56Nickel": "56Nickel",
  "56Iron": "56Iron"
}

GameManager.prototype.decay = {
  "7Beryllium": {
    "multipler": "3",
    "to": "4Helium"
  },
  "8Beryllium": {
    "multipler": "0.5",
    "to": "4Helium"
  },
  "52Iron": {
    "multipler": "1",
    "to": "4Helium"
  },
  "56Nickel": {
    "multipler": "2",
    "to": "56Iron"
  }
}

GameManager.prototype.pointValues = {
  "Deuteron":1,
  "3Helium":1.5,
  "4Helium":2,
  "7Beryllium":3,
  "8Beryllium":4,
  "12Carbon":6,
  "16Oxygen":8,
  "20Neon":10,
  "24Magnesium":12,
  "28Silicon":14,
  "32Sulfur":16,
  "36Argon":18,
  "40Calcium":20,
  "44Titanium":22,
  "48Chromium":24,
  "52Iron":26,
  "56Nickel":28,
  "56Iron":56
};
<commit_msg>Make a win possible after a decay<commit_after>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.version = 0.4;

  this.storageManager.clearIfOutdated(this.version);

  this.startTiles     = 2;
  this.winningValue = "56Iron";

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  this.setup();
	return this;
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var value = Math.random() < 0.9 ? "Hydrogen" : "Deuteron";
    var tile = new Tile(this.grid.randomAvailableCell(), value, this.labels[value]);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        var shouldMove = true;
        if (next && !next.mergedFrom) {
          //if(next.value === tile.value) {
          if( self.canFuse(next.value,tile.value) ) {
            shouldMove = false;
            var fusionValue = self.fusion(next.value,tile.value);
            var merged = new Tile(positions.next, fusionValue, self.labels[fusionValue]);
            merged.mergedFrom = [tile, next];

            var decay = self.decay[fusionValue] || false;

            if(decay !== false) {
              merged.movesLeft = Math.floor(Math.random() * (10*decay['multipler'] - 4*decay['multipler'] + 1)) + 4*decay['multipler'];
            }

            //merged.movesLeft = 5;

            self.grid.insertTile(merged);
            self.grid.removeTile(tile);

            // Converge the two tiles' positions
            tile.updatePosition(positions.next);

            // Update the score
            self.score += self.pointValues[merged.value];

            // TODO win state
            // The mighty 2048 tile
            if (merged.value === self.winningValue) self.won = true;
          }
        }
        if (shouldMove) {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    this.grid.eachCell(function(x, y, tile) {
      if(tile !== null && self.decay[tile.value] && tile.decay()) {
        var decayValue = self.decay[tile.value]['to'];
        var decayed = new Tile({
          x: tile.x,
          y: tile.y
        }, decayValue, self.labels[decayValue]);
        self.grid.removeTile(tile);
        self.grid.insertTile(decayed);

        if (decayed.value === self.winningValue) self.won = true;
      }
    });

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && self.canFuse(other.value, tile.value)) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};

GameManager.prototype.canFuse = function (first, second) {
  return (this.fusionRules[first]  && this.fusionRules[first][second]) ||
         (this.fusionRules[second] && this.fusionRules[second][first]);
};

GameManager.prototype.fusion = function (first, second) {
  var forward = this.fusionRules[first];
  if (forward && forward[second]) {
    return forward[second];
  } else {
    var backward = this.fusionRules[second][first];
    return backward;
  }
};

// a:{b:c}
// a + b = c
GameManager.prototype.fusionRules = {
  "Hydrogen":{"Hydrogen":"Deuteron",
							"Deuteron":"3Helium"
						 },
  "3Helium":{"3Helium":"4Helium",
							"4Helium":"7Beryllium"
						},
  "4Helium":{"4Helium":"8Beryllium", // unstable decays into 2 4heliums
						 "8Beryllium":"12Carbon",
						 "12Carbon":"16Oxygen",
						 "16Oxygen":"20Neon",
						 "20Neon":"24Magnesium", // this is a killer!
						 "28Silicon":"32Sulfur",
						 "32Sulfur":"36Argon",
						 "36Argon":"40Calcium",
						 "40Calcium":"44Titanium",
						 "44Titanium":"48Chromium",
						 "48Chromium":"52Iron",
						 "52Iron":"56Nickel"
						},
  "16Oxygen":{"16Oxygen":"28Silicon", // + 4Helium
             },
  "12Carbon":{"12Carbon":"20Neon", // + 4Helium (randomness)
						 }
};

GameManager.prototype.labels = {
  "Hydrogen": "Hydrogen",
  "Deuteron": "Deuteron",
  "3Helium": "3Helium",
  "4Helium": "4Helium",
  "7Beryllium": "7Beryllium",
  "8Beryllium": "8Beryllium",
  "12Carbon": "12Carbon",
  "16Oxygen": "16Oxygen",
  "20Neon": "20Neon",
  "24Magnesium": "24Magnesium",
  "28Silicon": "28Silicon",
  "32Sulfur": "32Sulfur",
  "36Argon": "36Argon",
  "40Calcium": "40Calcium",
  "44Titanium": "44Titanium",
  "48Chromium": "48Chromium",
  "52Iron": "52Iron",
  "56Nickel": "56Nickel",
  "56Iron": "56Iron"
}

GameManager.prototype.decay = {
  "7Beryllium": {
    "multipler": "3",
    "to": "4Helium"
  },
  "8Beryllium": {
    "multipler": "0.5",
    "to": "4Helium"
  },
  "52Iron": {
    "multipler": "1",
    "to": "4Helium"
  },
  "56Nickel": {
    "multipler": "2",
    "to": "56Iron"
  }
}

GameManager.prototype.pointValues = {
  "Deuteron":1,
  "3Helium":1.5,
  "4Helium":2,
  "7Beryllium":3,
  "8Beryllium":4,
  "12Carbon":6,
  "16Oxygen":8,
  "20Neon":10,
  "24Magnesium":12,
  "28Silicon":14,
  "32Sulfur":16,
  "36Argon":18,
  "40Calcium":20,
  "44Titanium":22,
  "48Chromium":24,
  "52Iron":26,
  "56Nickel":28,
  "56Iron":56
};
<|endoftext|>


<commit_before>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.startTiles     = 2;

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
  this.inputManager.on("clearAll", this.clearAll.bind(this))

  this.setup();
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

GameManager.prototype.clearAll = function () {
  this.storageManager.clearHistory();
}

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells,
                                previousState.grid.value_jpg_refs); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

GameManager.prototype.canPlayMP3 = function () {
  var a = document.createElement('audio');
  return !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
};

GameManager.prototype.canPlayOGG = function () {
  var a = document.createElement('audio');
  return !!(a.canPlayType && a.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''));
};

GameManager.prototype.canPlayAAC = function () {
  var a = document.createElement('audio');
  return !!(a.canPlayType && a.canPlayType('audio/mp4; codecs="mp4a.40.2"').replace(/no/, ''));
};

GameManager.prototype.playSound = function(sound_source) {
    if (this.canPlayOGG()) sound_source = sound_source + '.ogg';
    else if (this.canPlayMP3()) sound_source = sound_source + '.mp3';
    else if (this.canPlayAAC()) sound_source = sound_source + '.mp4';
    var sound = new Audio(sound_source);
    sound.load();
    sound.play();
};

GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var r = Math.random()
    var value = r < 0.09 ? 2 : r < 0.0999 ? 4 : r < 0.49995 ? -1 : -2;
    var tile = new Tile(this.grid.randomAvailableCell(), value);
    if(value === -1 && this.grid.isMaravillas) this.playSound('tile-sets/maravillas/lasalle');
    if(value === -2 && this.grid.isMaravillas) this.playSound('tile-sets/maravillas/maravillas');
    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && (next.value === tile.value || next.value < 0 || tile.value < 0) && !next.mergedFrom) {
              if (tile.value > 0) {
                var merged = new Tile(positions.next, tile.value * 2);
                }
              else if (next.value > 0) {
                var merged = new Tile(positions.next, next.value * 2);
                }
              else {
                var merged = new Tile(positions.next, 2);
                }
          // Eliminamos el halo de los comodines cambiándolos de clase
          if (tile.value < 0 && tile.value > -10) tile.value = tile.value - 10;
          if (next.value < 0 && next.value > -10) next.value = next.value - 10;
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score += merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
<commit_msg>pause<commit_after>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.startTiles     = 2;

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));
  this.inputManager.on("clearAll", this.clearAll.bind(this))

  this.setup();
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

GameManager.prototype.clearAll = function () {
  this.storageManager.clearHistory();
}

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells,
                                previousState.grid.value_jpg_refs); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

GameManager.prototype.canPlayMP3 = function () {
  var a = document.createElement('audio');
  return !!(a.canPlayType && a.canPlayType('audio/mpeg;').replace(/no/, ''));
};

GameManager.prototype.canPlayOGG = function () {
  var a = document.createElement('audio');
  return !!(a.canPlayType && a.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''));
};

GameManager.prototype.canPlayAAC = function () {
  var a = document.createElement('audio');
  return !!(a.canPlayType && a.canPlayType('audio/mp4; codecs="mp4a.40.2"').replace(/no/, ''));
};

GameManager.prototype.playSound = function(sound_source) {
    if (this.canPlayOGG()) sound_source = sound_source + '.ogg';
    else if (this.canPlayMP3()) sound_source = sound_source + '.mp3';
    else if (this.canPlayAAC()) sound_source = sound_source + '.mp4';
    var sound = new Audio(sound_source);
    sound.load();
    sound.play();
    sound.pause();
};

GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var r = Math.random()
    var value = r < 0.09 ? 2 : r < 0.0999 ? 4 : r < 0.49995 ? -1 : -2;
    var tile = new Tile(this.grid.randomAvailableCell(), value);
    if(value === -1 && this.grid.isMaravillas) this.playSound('tile-sets/maravillas/lasalle');
    if(value === -2 && this.grid.isMaravillas) this.playSound('tile-sets/maravillas/maravillas');
    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && (next.value === tile.value || next.value < 0 || tile.value < 0) && !next.mergedFrom) {
              if (tile.value > 0) {
                var merged = new Tile(positions.next, tile.value * 2);
                }
              else if (next.value > 0) {
                var merged = new Tile(positions.next, next.value * 2);
                }
              else {
                var merged = new Tile(positions.next, 2);
                }
          // Eliminamos el halo de los comodines cambiándolos de clase
          if (tile.value < 0 && tile.value > -10) tile.value = tile.value - 10;
          if (next.value < 0 && next.value > -10) next.value = next.value - 10;
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score += merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
<|endoftext|>


<commit_before>function GameManager(size, InputManager, Actuator, StorageManager, AutoPlayStrategy) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;
  this.AutoPlayStrategy = new AutoPlayStrategy(this);
  this.autoplaySpeeds  =  [{ name: "Slow"   , delay: 500 },
                           { name: "Normal" , delay: 100 },
                           { name: "Fast"   , delay: 50 },
                           { name: "Fastest", delay: 0 }];

  this.autoplaySpeed    = 1;

  this.startTiles     = 2;

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  this.setup();
  this.startAutoPlay();
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  return this.over || (this.won && !this.keepPlaying);
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var value = Math.random() < 0.9 ? 2 : 4;
    var tile = new Tile(this.grid.randomAvailableCell(), value);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && next.value === tile.value && !next.mergedFrom) {
          var merged = new Tile(positions.next, tile.value * 2);
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score += merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
<commit_msg>1) código para resetar la estrategia y ejecutar la funcion auto play que es la realiza los movimientos 2) código para ejecutar el movimiento y se setea la velocidad del movimiento<commit_after>function GameManager(size, InputManager, Actuator, StorageManager, AutoPlayStrategy) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;
  this.AutoPlayStrategy = new AutoPlayStrategy(this);
  this.autoplaySpeeds  =  [{ name: "Slow"   , delay: 500 },
                           { name: "Normal" , delay: 100 },
                           { name: "Fast"   , delay: 50 },
                           { name: "Fastest", delay: 0 }];

  this.autoplaySpeed    = 1;

  this.startTiles     = 2;

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  this.setup();
  this.startAutoPlay();
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  return this.over || (this.won && !this.keepPlaying);
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var value = Math.random() < 0.9 ? 2 : 4;
    var tile = new Tile(this.grid.randomAvailableCell(), value);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && next.value === tile.value && !next.mergedFrom) {
          var merged = new Tile(positions.next, tile.value * 2);
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score += merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
GameManager.prototype.startAutoPlay = function() {
  this.AutoPlayStrategy.reset();
  this.autoplay();
};

GameManager.prototype.autoplay = function() {
  if(!this.isGameTerminated() && this.movesAvailable()) {
    this.move(this.AutoPlayStrategy.nextMove());
    setTimeout(this.autoplay.bind(this), this.autoplaySpeeds[this.autoplaySpeed].delay);
  }
};
<|endoftext|>


<commit_before>function GraphManager(canvasOverlay){
  $("#" + canvasOverlay).show();
  this.canvasOverlay = document.getElementById(canvasOverlay);
  this.canvas = document.getElementById(canvasOverlay.substr(0, canvasOverlay.search('-')));
  $("#" + canvasOverlay.substr(0, canvasOverlay.search('-'))).show();
  this.canvas.width = 300;
  this.canvas.height = $(document).height();
  this.context = this.canvas.getContext("2d");
  this.graphs = {};
}

GraphManager.prototype.addGraph = function(name){
  var graph = new Graph(name, this.context, this.canvas, this.canvasOverlay, 10, Object.keys(this.graphs).length * 140 + 10);
  for(var i in this.graphs){
    if(this.graphs[i] === graph){
      return;
    } else {
      this.graphs[name] = graph;
      return;
    }
  }
  this.graphs[name] = graph;
};

GraphManager.prototype.drawOnGraph = function(graphName, x, y){
  this.graphs[graphName].drawDot(x,y);
};<commit_msg>Have to push some debug :(<commit_after>function GraphManager(canvasOverlay){
  $("#" + canvasOverlay).show();
  this.canvasOverlay = document.getElementById(canvasOverlay);
  this.canvas = document.getElementById(canvasOverlay.substr(0, canvasOverlay.search('-')));
  console.log(canvasOverlay.substr(0, canvasOverlay.search('-')));
  $("#" + canvasOverlay.substr(0, canvasOverlay.search('-'))).show();
  this.canvas.width = 300;
  this.canvas.height = $(document).height();
  this.context = this.canvas.getContext("2d");
  this.graphs = {};
}

GraphManager.prototype.addGraph = function(name){
  var graph = new Graph(name, this.context, this.canvas, this.canvasOverlay, 10, Object.keys(this.graphs).length * 140 + 10);
  for(var i in this.graphs){
    if(this.graphs[i] === graph){
      return;
    } else {
      this.graphs[name] = graph;
      return;
    }
  }
  this.graphs[name] = graph;
};

GraphManager.prototype.drawOnGraph = function(graphName, x, y){
  this.graphs[graphName].drawDot(x,y);
};<|endoftext|>


<commit_before>/**
 * jQuery Slidy - A Transition Plugin - http://wbotelhos.com/slidy
 * ---------------------------------------------------------------------------------
 *
 * jQuery Slidy is a plugin that generates a customizable transitions automatically.
 *
 * Licensed under The MIT License
 *
 * @version         0.2.0
 * @since           11.16.2010
 * @author          Washington Botelho dos Santos
 * @documentation   wbotelhos.com/slidy
 * @twitter         twitter.com/wbotelhos
 * @license         opensource.org/licenses/mit-license.php MIT
 * @package         jQuery Plugins
 *
 * Usage with default values:
 * ---------------------------------------------------------------------------------
 * $('#default').slidy();
 *
 *

 *
 *
 *
 *

 *
 */

;(function($) {

	$.fn.slidy = function(settings) {
		if (this.length == 0) {
			debug('Selector invalid or missing!');
			return;
		} else if (this.length > 1) {
			return this.each(function() {
				$.fn.slidy.apply($(this), [settings]);
			});
		}

		var opt			= $.extend({}, $.fn.slidy.defaults, settings),
			$this		= $(this),
			id			= this.attr('id'),
			elements	= $this.children(opt.children),
			timer		= 0,
			isAnimate	= false;

		$this
		.data('options', opt)
		.css({
			'cursor':	opt.cursor,
			'height':	opt.height + 'px',
			'overflow':	'hidden',
			'position':	'relative',
			'width':	opt.width + 'px'
		});

		elements.each(function(i) {
			$(this)
			.css({ 'position': 'absolute', 'z-index': elements.length - i })
			.attr('id', $this.attr('id') + '-' + (i + 1))
			.hide();
		});

		$this
			.find('img')
			.attr({ height: opt.height, width: opt.width });

		if (opt.children == 'a' && opt.target != '') {
			elements.attr('target',	opt.target);
		}

		elements
			.first().show()
		.end()
			.find('img').css('border', '0');

		go(elements, opt, 0);

		var stop = function() {
			var target = $(this),
				index,
				last;

			clearTimeout(timer);

			if (target.is('li')) {
				index	= target.index();
				last	= target.parent().children('.slidy-link-selected').index();
			} else {
				index	= $this.next('ul').children('.slidy-link-selected').index();
				last	= index;
			}

			if (index != last) {
				target.addClass('slidy-link-selected')
					.parent()
						.children()
							.eq(last).removeClass('slidy-link-selected');

				change(elements, opt, index, last);
			}
		}, start = function() {
			var index	= $this.next('ul').children('.slidy-link-selected').index(),
				isBanner; // Avoid hover the same li again.

			// Is not used element.index() because the delay effect can be select another is not the same of cursor over.
			if (!$(this).is('li')) {
				isBanner = true;
			}

			go(elements, opt, index, isBanner);
		};

		if (opt.menu) {
			var imgs	= elements.find('img'),
				menu	= '',
				target	= (opt.target != '') ? 'target="' + opt.target + '"' : '',
				parent,
				img;

			imgs.each(function() {
				img		= $(this);
				parent	= img.parent(opt.children);

				menu += '
' + img.attr('title') + '
';
			});

			$this.after('
' + menu + '
');

			var space	= parseInt((opt.width / imgs.length) + (imgs.length - 1)),
				diff	= opt.width - (space * imgs.length),
				links	= $('ul.slidy-menu').children('li');

			links
			.css('width', space)
			.hover(stop, start)
			.mousemove(stop) // To fix the delay of the effect and moviment of cursor. Avoid mouseout and mouseover again to change. Overhead?
				.first().addClass('slidy-link-selected')
			.end()
				.last().css({ 'border-right': '0', 'width': (space + diff) - (imgs.length - 1) });
		}

		if (opt.pause) {
			$this.hover(stop, start);
		}

		function go(elements, opt, index, isBanner) {
			change(elements, opt, index, index - 1);

			if (isBanner == undefined) {
				selectMenu(index);
			}

			index = (index < elements.length - 1) ? index + 1 : 0;

			timer =	setTimeout(function() {
						go(elements, opt, index);
					}, opt.time);
		};

		function change(elements, opt, index, last) {
			if (!isAnimate) {
				isAnimate = true;

				if (opt.animation == 'fade') {
					elements.eq(last).fadeOut(opt.speed);
					elements.eq(index).fadeIn(opt.speed, function() {
						selectMenu(index);
						isAnimate = false;
					});
				} else if (opt.animation == 'slide') {
					elements
					.css('z-index', 0)
						.eq(index)
						.css('z-index', elements.length)
						.slideDown(opt.speed, function() {
							elements.eq(last).hide();
							selectMenu(index);
							isAnimate = false;
						});
				} else {
					elements.eq(last).hide();
					elements.eq(index).show();
					isAnimate = false;
				}
			}
		};

		function selectMenu(index) {
			$this
				.next('ul')
					.children().removeClass('slidy-link-selected')
						.eq(index).addClass('slidy-link-selected');
		};

		return $this;
	};

	function debug(message) {
		if (window.console && window.console.log) {
			window.console.log(message);
		}
	};

	$.fn.slidy.defaults = {
		animation:	'normal',
		children:	'img',
		cursor:		'default',
		height:		200,
		menu:		false,
		pause:		false,
		speed:		600,
		target:		'',
		time:		3600,
		width:		500
	};

})(jQuery);<commit_msg>Slidy always will has an ID.<commit_after>/**
 * jQuery Slidy - A Transition Plugin - http://wbotelhos.com/slidy
 * ---------------------------------------------------------------------------------
 *
 * jQuery Slidy is a plugin that generates a customizable transitions automatically.
 *
 * Licensed under The MIT License
 *
 * @version         0.2.0
 * @since           11.16.2010
 * @author          Washington Botelho dos Santos
 * @documentation   wbotelhos.com/slidy
 * @twitter         twitter.com/wbotelhos
 * @license         opensource.org/licenses/mit-license.php MIT
 * @package         jQuery Plugins
 *
 * Usage with default values:
 * ---------------------------------------------------------------------------------
 * $('#default').slidy();
 *
 *

 *
 *
 *
 *

 *
 */

;(function($) {

	$.fn.slidy = function(settings) {
		if (this.length == 0) {
			debug('Selector invalid or missing!');
			return;
		} else if (this.length > 1) {
			return this.each(function() {
				$.fn.slidy.apply($(this), [settings]);
			});
		}

		var opt			= $.extend({}, $.fn.slidy.defaults, settings),
			$this		= $(this),
			id			= this.attr('id'),
			elements	= $this.children(opt.children),
			timer		= 0,
			isAnimate	= false;

		if (id === undefined) {
			id = 'slidy-' + $this.index();
			$this.attr('id', id);
		}

		$this
		.data('options', opt)
		.css({
			'cursor':	opt.cursor,
			'height':	opt.height + 'px',
			'overflow':	'hidden',
			'position':	'relative',
			'width':	opt.width + 'px'
		});

		elements.each(function(i) {
			$(this)
			.css({ 'position': 'absolute', 'z-index': elements.length - i })
			.attr('id', $this.attr('id') + '-' + (i + 1))
			.hide();
		});

		$this
			.find('img')
			.attr({ height: opt.height, width: opt.width });

		if (opt.children == 'a' && opt.target != '') {
			elements.attr('target',	opt.target);
		}

		elements
			.first().show()
		.end()
			.find('img').css('border', '0');

		go(elements, opt, 0);

		var stop = function() {
			var target = $(this),
				index,
				last;

			clearTimeout(timer);

			if (target.is('li')) {
				index	= target.index();
				last	= target.parent().children('.slidy-link-selected').index();
			} else {
				index	= $this.next('ul').children('.slidy-link-selected').index();
				last	= index;
			}

			if (index != last) {
				target.addClass('slidy-link-selected')
					.parent()
						.children()
							.eq(last).removeClass('slidy-link-selected');

				change(elements, opt, index, last);
			}
		}, start = function() {
			var index	= $this.next('ul').children('.slidy-link-selected').index(),
				isBanner; // Avoid hover the same li again.

			// Is not used element.index() because the delay effect can be select another is not the same of cursor over.
			if (!$(this).is('li')) {
				isBanner = true;
			}

			go(elements, opt, index, isBanner);
		};

		if (opt.menu) {
			var imgs	= elements.find('img'),
				menu	= '',
				target	= (opt.target != '') ? 'target="' + opt.target + '"' : '',
				parent,
				img;

			imgs.each(function() {
				img		= $(this);
				parent	= img.parent(opt.children);

				menu += '
' + img.attr('title') + '
';
			});

			$this.after('
' + menu + '
');

			var space	= parseInt((opt.width / imgs.length) + (imgs.length - 1)),
				diff	= opt.width - (space * imgs.length),
				links	= $('ul.slidy-menu').children('li');

			links
			.css('width', space)
			.hover(stop, start)
			.mousemove(stop) // To fix the delay of the effect and moviment of cursor. Avoid mouseout and mouseover again to change. Overhead?
				.first().addClass('slidy-link-selected')
			.end()
				.last().css({ 'border-right': '0', 'width': (space + diff) - (imgs.length - 1) });
		}

		if (opt.pause) {
			$this.hover(stop, start);
		}

		function go(elements, opt, index, isBanner) {
			change(elements, opt, index, index - 1);

			if (isBanner == undefined) {
				selectMenu(index);
			}

			index = (index < elements.length - 1) ? index + 1 : 0;

			timer =	setTimeout(function() {
						go(elements, opt, index);
					}, opt.time);
		};

		function change(elements, opt, index, last) {
			if (!isAnimate) {
				isAnimate = true;

				if (opt.animation == 'fade') {
					elements.eq(last).fadeOut(opt.speed);
					elements.eq(index).fadeIn(opt.speed, function() {
						selectMenu(index);
						isAnimate = false;
					});
				} else if (opt.animation == 'slide') {
					elements
					.css('z-index', 0)
						.eq(index)
						.css('z-index', elements.length)
						.slideDown(opt.speed, function() {
							elements.eq(last).hide();
							selectMenu(index);
							isAnimate = false;
						});
				} else {
					elements.eq(last).hide();
					elements.eq(index).show();
					isAnimate = false;
				}
			}
		};

		function selectMenu(index) {
			$this
				.next('ul')
					.children().removeClass('slidy-link-selected')
						.eq(index).addClass('slidy-link-selected');
		};

		return $this;
	};

	function debug(message) {
		if (window.console && window.console.log) {
			window.console.log(message);
		}
	};

	$.fn.slidy.defaults = {
		animation:	'normal',
		children:	'img',
		cursor:		'default',
		height:		200,
		menu:		false,
		pause:		false,
		speed:		600,
		target:		'',
		time:		3600,
		width:		500
	};

})(jQuery);<|endoftext|>


<commit_before>// numeral.js language configuration
// language : Finnish
// author : Sami Saada : https://github.com/samitheberber
(function(){var e={delimiters:{thousands:" ",decimal:","},abbreviations:{thousand:"k",million:"M",billion:"G",trillion:"T"},ordinal:function(e){return"."},currency:{symbol:"€"}};typeof module!="undefined"&&module.exports&&(module.exports=e),typeof window!="undefined"&&this.numeral&&this.numeral.language&&this.numeral.language("fi",e)})();<commit_msg>minify fi<commit_after>// numeral.js language configuration
// language : Finnish
// author : Sami Saada : https://github.com/samitheberber
(function(){var e={delimiters:{thousands:" ",decimal:","},abbreviations:{thousand:"k",million:"M",billion:"G",trillion:"T"},ordinal:function(e){return"."},currency:{symbol:"€"}};typeof module!="undefined"&&module.exports&&(module.exports=e);typeof window!="undefined"&&this.numeral&&this.numeral.language&&this.numeral.language("fi",e)})();<|endoftext|>