<commit_before>/*jslint browser: true, unparam: true, nomen: true */

(function (tangelo, $, d3) {
    "use strict";

    if (!($ && $.widget && d3)) {
        $.fn.dendrogram = tangelo.unavailable({
            plugin: "dendrogram",
            required: ["JQuery", "JQuery UI", "d3"]
        });
        return;
    }

    $.widget("tangelo.dendrogram", {
        options: {
            label: null,
            distance: null,
            id: null,
            margin: {
                top: 20,
                right: 120,
                bottom: 20,
                left: 120
            },
            nodeLimit: null,
            duration: 750,
            root: null,
            data: null,
            mode: "hide",
            nodesize: 7.5,
            textsize: 10,
            orientation: "horizontal",
            initialize: null
        },

        _missing: {
            label: "",
            distance: 1,
            id: 0
        },

        orientation: {
            abscissa: "y",
            ordinate: "x",
            heightvar: "height",
            widthvar: "width",
            xfunc: function (_, v) {
                return v;
            }
        },

        _create: function () {
            var options,
                that = this;

            if (this.options.orientation === "vertical") {
                this.orientation = {
                    abscissa: "x",
                    ordinate: "y",
                    heightvar: "width",
                    widthvar: "height",
                    xfunc: function (that, v) {
                        return that.width - v;
                    }
                };
            } else if (this.options.orientation !== "horizontal") {
                throw "illegal option for 'orientation': " + this.options.orientation;
            }

            this.tree = d3.layout.partition()
                .value(function () { return 1; })
                .sort(d3.ascending);

            this.line = d3.svg.line()
                .interpolate("step-before")
                .x(function (d) {
                    return this.orientation.xfunc(that, d[that.orientation.abscissa]);
                })
                .y(function (d) {
                    return d[that.orientation.ordinate];
                });

            this.svg = d3.select(this.element.get(0))
                .append("svg")
                .append("g");

            options = $.extend(true, {}, this.options);
            delete options.disabled;
            delete options.create;
            this._setOptions(options);
        },

        _setOption: function (key, value) {
            if (key === "label" || key === "distance" || key === "id") {
                this._super(key, tangelo.accessor(value, this._missing[key]));
            } else {
                this._super(key, value);
            }
        },

        _setOptions: function (options) {
            var that = this;

            $.each(options, function (key, value) {
                that._setOption(key, value);
            });

            this._update();
        },

        _update: function () {
            this.width = 1200 - this.options.margin.right - this.options.margin.left;
            this.height = 800 - this.options.margin.top - this.options.margin.bottom;

            if (!this.options.root) {
                this.options.root = this.options.data;
            }

            if (!this.options.mode) {
                this.options.mode = "hide";
            }

            this.tree.size([this.height, this.width]);

            d3.select(this.element.get(0))
                .select("svg")
                .attr("width", this.width + this.options.margin.right + this.options.margin.left)
                .attr("height", this.height + this.options.margin.top + this.options.margin.bottom)
                .select("g")
                .attr("transform", "translate(" + this.options.margin.left + "," + this.options.margin.top + ")");

            //this.options.root.x0 = this.height / 2;
            this.options.root.x0 = this[this.orientation.heightvar] / 2;
            this.options.root.y0 = 0;

            // Compute the new tree layout.
            var nodes = this.tree.nodes(this.options.root).reverse(),
                links = this.tree.links(nodes),
                source = this.options.source || this.options.root,
                node,
                nodeEnter,
                nodeUpdate,
                nodeExit,
                link,
                maxY,
                visibleLeaves,
                filteredNodes,
                filteredLinks,
                that = this;

            visibleLeaves = 0;
            function setPosition(node, pos) {
                var xSum = 0;
                node.y = pos;
                node.x = node.x + node.dx / 2;
                if (!node.parent) {
                    node.parent = node;
                }
                if (node.children) {
                    node.children.forEach(function (d) {
                        d.parent = node;
                        setPosition(d, pos + 10 * that.options.distance(d));
                        xSum += d.x;
                    });
                    node.x = xSum / node.children.length;
                } else {
                    visibleLeaves += 1;
                }
            }
            setPosition(this.options.root, 0);

            // Normalize Y to fill space
            maxY = d3.extent(nodes, function (d) {
                return d.y;
            })[1];
            nodes.forEach(function (d) {
                d.y = d.y / maxY * (that.width - 150);
            });

            if (this.options.nodeLimit && nodes.length > this.options.nodeLimit) {
                // Filter out everything beyond parent y-position to keep things interactive
                nodes.sort(function (a, b) {
                    return d3.ascending(a.parent.y, b.parent.y);
                });
                nodes.forEach(function (d, i) {
                    d.index = i;
                });
                filteredNodes = nodes.slice(0, this.options.nodeLimit);
                maxY = filteredNodes[filteredNodes.length - 1].parent.y;
                filteredNodes.forEach(function (d) {
                    d.y = d.y > maxY ? maxY : d.y;
                });

                // Filter the links based on visible nodes
                filteredLinks = [];
                links.forEach(function (d) {
                    if (d.source.index < this.options.nodeLimit && d.target.index < this.options.nodeLimit) {
                        filteredLinks.push(d);
                    }
                });
                nodes = filteredNodes;
                links = filteredLinks;
            }

            // Toggle children on click.
            function click(d) {
                if (that.options.mode === "hide") {
                    if (d.children) {
                        d._children = d.children;
                        d.children = null;
                    } else {
                        d.children = d._children;
                        d._children = null;
                    }
                } else if (that.options.mode === "focus") {
                    that.options.root = d;
                } else if (that.options.mode === "label") {
                    d.showLabel = d.showLabel ? false : true;
                }
                //that._update({source: d});
                that._setOptions({source: d});
            }

            function firstChild(d) {
                if (d.children) {
                    return firstChild(d.children[0]);
                }
                if (d._children) {
                    return firstChild(d._children[0]);
                }
                return d;
            }

            function lastChild(d) {
                if (d.children) {
                    return lastChild(d.children[d.children.length - 1]);
                }
                if (d._children) {
                    return lastChild(d._children[d._children.length - 1]);
                }
                return d;
            }

            function leafCount(d) {
                var children = d.children,
                    sum = 0;
                if (!children) {
                    children = d._children;
                }

                // I am an internal node, so total the leaves of the children
                if (children) {
                    children.forEach(function (child) {
                        sum += leafCount(child);
                    });
                    return sum;
                }

                // I am a leaf
                return 1;
            }

            // Update the nodes…
            node = this.svg.selectAll("g.node")
                .data(nodes, function (d) {
                    return that.options.id(d);
                });

            // Enter any new nodes at the parent's previous position.
            nodeEnter = node.enter()
                .append("g")
                .classed("node", true)
                .attr("transform", function () {
                    return "translate(" + source.y0 + "," + source.x0 + ")";
                })
                .on("dblclick", click);

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .style("stroke", "none")
                .style("opacity", function (d) {
                    return d._children ? 1 : 0;
                })
                .style("fill", function (d) {
                    return d._children ? "lightsteelblue" : "#fff";
                })
                .on("mouseenter", function () {
                    var me = d3.select(this);
                    this.saved_opacity = me.style("opacity");
                    this.saved_fill = me.style("fill");
                    me.style("opacity", 1)
                        .style("fill", "black");
                })
                .on("mouseleave", function () {
                    if (this.saved_opacity) {
                        d3.select(this)
                            .style("opacity", this.saved_opacity);
                        this.saved_opacity = undefined;
                    }
                    if (this.saved_fill) {
                        d3.select(this)
                            .style("fill", this.saved_fill);
                        this.saved_fill = undefined;
                    }
                });

            nodeEnter.append("text")
                .attr("x", 10)
                .attr("dy", ".35em")
                .attr("text-anchor", "start")
                .style("font-size", this.options.textsize + "px")
                .text(this.options.label)
                .style("fill-opacity", 1e-6);

            // Transition nodes to their new position.
            nodeUpdate = node.transition()
                .duration(this.options.duration)
                .attr("transform", function (d) {
                    return "translate(" + that.orientation.xfunc(that, d[that.orientation.abscissa]) + "," + d[that.orientation.ordinate] + ")";
                });

            nodeUpdate.select("circle")
                .attr("r", this.options.nodesize)
                .style("opacity", function (d) {
                    return d._children ? 1 : 0;
                })
                .style("fill", function (d) {
                    return d._children ? "lightsteelblue" : "#fff";
                });

            nodeUpdate.select("text")
                .text(function (d) {
                    var label = that.options.label(d);
                    if (d._children || (d.children && d.showLabel)) {
                        if (label === "") {
                            label = that.options.label(firstChild(d)) + " ... " + that.options.label(lastChild(d));
                        }
                        return label + " (" + leafCount(d) + ")";
                    }
                    if (visibleLeaves < that.height / (0.8 * that.options.textsize)) {
                        return label;
                    }
                    return "";
                })
                .style("fill-opacity", 1);

            // Transition exiting nodes to the parent's new position.
            nodeExit = node
                .exit()
                .transition()
                .duration(this.options.duration)
                .attr("transform", function () {
                    return "translate(" + source.y + "," + source.x + ")";
                })
                .remove();

            nodeExit.select("circle")
                .attr("r", 1e-6);

            nodeExit.select("text")
                .style("fill-opacity", 1e-6);

            // Update the links…
            link = this.svg.selectAll("path.link")
                .data(links, function (d) {
                    return that.options.id(d.target);
                });

            // Enter any new links at the parent's previous position.
            link.enter()
                .insert("path", "g")
                .classed("link", true)
                .style("stroke", "black")
                .style("stroke-width", "1px")
                .style("fill", "none")
                .attr("d", function () {
                    var o = {x: source.x0, y: source.y0};
                    //return diagonal({source: o, target: o});
                    return that.line([o, o]);
                });

            // Transition links to their new position.
            link.transition()
                .duration(this.options.duration)
                .attr("d", function (d) {
                    return that.line([d.source, d.target]);
                });

            // Transition exiting nodes to the parent's new position.
            link.exit()
                .transition()
                .duration(this.options.duration)
                .attr("d", function () {
                    var o = {x: source.x, y: source.y};
                    return that.line([o, o]);
                })
                .remove();

            // Stash the old positions for transition.
            nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });

            if (this.options.initialize) {
                this.options.initialize(nodeEnter, node, nodeExit);
            }
        },

        download: function (format) {
            var node,
                s,
                d,
                str;

            if (format === "pdf") {
                node = this.svg
                    .selectAll("g.node")
                    .select("circle")
                    .attr("r", function (d) {
                        return d._children ? this.options.nodesize : 0;
                    });
                s = new window.XMLSerializer();
                d = d3.select("svg").node();
                str = s.serializeToString(d);

                // Change back to normal
                node.attr("r", this.options.nodesize);

                d3.json("/service/svg2pdf")
                    .send("POST", str, function (error, data) {
                        window.location = "/service/svg2pdf?id=" + data.result;
                    });
            } else {
                window.alert("Unsupported export format type: " + format);
            }
        },

        reset: function () {
            function unhideAll(d) {
                if (!d.children) {
                    d.children = d._children;
                    d._children = null;
                }
                if (d.children) {
                    d.children.forEach(unhideAll);
                }
            }
            unhideAll(this.options.data);
            this._setOptions({
                root: this.options.data
            });
        }
    });
}(window.tangelo, window.jQuery, window.d3));
<commit_msg>Abstracting styling out of inner mechanics of dendrogram.<commit_after>/*jslint browser: true, unparam: true, nomen: true */

(function (tangelo, $, d3) {
    "use strict";

    if (!($ && $.widget && d3)) {
        $.fn.dendrogram = tangelo.unavailable({
            plugin: "dendrogram",
            required: ["JQuery", "JQuery UI", "d3"]
        });
        return;
    }

    $.widget("tangelo.dendrogram", {
        options: {
            label: null,
            distance: null,
            id: null,
            margin: {
                top: 20,
                right: 120,
                bottom: 20,
                left: 120
            },
            nodeLimit: null,
            duration: 750,
            root: null,
            data: null,
            mode: "hide",
            nodesize: 7.5,
            textsize: 10,
            orientation: "horizontal",
            initialize: null
        },

        _missing: {
            label: "",
            distance: 1,
            id: 0
        },

        orientation: {
            abscissa: "y",
            ordinate: "x",
            heightvar: "height",
            widthvar: "width",
            xfunc: function (_, v) {
                return v;
            }
        },

        _create: function () {
            var options,
                that = this;

            if (this.options.orientation === "vertical") {
                this.orientation = {
                    abscissa: "x",
                    ordinate: "y",
                    heightvar: "width",
                    widthvar: "height",
                    xfunc: function (that, v) {
                        return that.width - v;
                    }
                };
            } else if (this.options.orientation !== "horizontal") {
                throw "illegal option for 'orientation': " + this.options.orientation;
            }

            this.tree = d3.layout.partition()
                .value(function () { return 1; })
                .sort(d3.ascending);

            this.line = d3.svg.diagonal()
                .projection(function (d) {
                    return [that.orientation.xfunc(that, d[that.orientation.abscissa]), d[that.orientation.ordinate]];
                });

            this.svg = d3.select(this.element.get(0))
                .append("svg")
                .append("g");

            options = $.extend(true, {}, this.options);
            delete options.disabled;
            delete options.create;
            this._setOptions(options);
        },

        _setOption: function (key, value) {
            if (key === "label" || key === "distance" || key === "id") {
                this._super(key, tangelo.accessor(value, this._missing[key]));
            } else {
                this._super(key, value);
            }
        },

        _setOptions: function (options) {
            var that = this;

            $.each(options, function (key, value) {
                that._setOption(key, value);
            });

            this._update();
        },

        _update: function () {
            this.width = 1200 - this.options.margin.right - this.options.margin.left;
            this.height = 800 - this.options.margin.top - this.options.margin.bottom;

            if (!this.options.root) {
                this.options.root = this.options.data;
            }

            if (!this.options.mode) {
                this.options.mode = "hide";
            }

            this.tree.size([this.height, this.width]);

            d3.select(this.element.get(0))
                .select("svg")
                .attr("width", this.width + this.options.margin.right + this.options.margin.left)
                .attr("height", this.height + this.options.margin.top + this.options.margin.bottom)
                .select("g")
                .attr("transform", "translate(" + this.options.margin.left + "," + this.options.margin.top + ")");

            //this.options.root.x0 = this.height / 2;
            this.options.root.x0 = this[this.orientation.heightvar] / 2;
            this.options.root.y0 = 0;

            // Compute the new tree layout.
            var nodes = this.tree.nodes(this.options.root).reverse(),
                links = this.tree.links(nodes),
                source = this.options.source || this.options.root,
                node,
                nodeEnter,
                nodeUpdate,
                nodeExit,
                link,
                maxY,
                visibleLeaves,
                filteredNodes,
                filteredLinks,
                that = this;

            visibleLeaves = 0;
            function setPosition(node, pos) {
                var xSum = 0;
                node.y = pos;
                node.x = node.x + node.dx / 2;
                if (!node.parent) {
                    node.parent = node;
                }
                if (node.children) {
                    node.children.forEach(function (d) {
                        d.parent = node;
                        setPosition(d, pos + 10 * that.options.distance(d));
                        xSum += d.x;
                    });
                    node.x = xSum / node.children.length;
                } else {
                    visibleLeaves += 1;
                }
            }
            setPosition(this.options.root, 0);

            // Normalize Y to fill space
            maxY = d3.extent(nodes, function (d) {
                return d.y;
            })[1];
            nodes.forEach(function (d) {
                d.y = d.y / maxY * (that.width - 150);
            });

            if (this.options.nodeLimit && nodes.length > this.options.nodeLimit) {
                // Filter out everything beyond parent y-position to keep things interactive
                nodes.sort(function (a, b) {
                    return d3.ascending(a.parent.y, b.parent.y);
                });
                nodes.forEach(function (d, i) {
                    d.index = i;
                });
                filteredNodes = nodes.slice(0, this.options.nodeLimit);
                maxY = filteredNodes[filteredNodes.length - 1].parent.y;
                filteredNodes.forEach(function (d) {
                    d.y = d.y > maxY ? maxY : d.y;
                });

                // Filter the links based on visible nodes
                filteredLinks = [];
                links.forEach(function (d) {
                    if (d.source.index < this.options.nodeLimit && d.target.index < this.options.nodeLimit) {
                        filteredLinks.push(d);
                    }
                });
                nodes = filteredNodes;
                links = filteredLinks;
            }

            // Toggle children on click.
            function click(d) {
                if (that.options.mode === "hide") {
                    if (d.children) {
                        d._children = d.children;
                        d.children = null;
                    } else {
                        d.children = d._children;
                        d._children = null;
                    }
                } else if (that.options.mode === "focus") {
                    that.options.root = d;
                } else if (that.options.mode === "label") {
                    d.showLabel = d.showLabel ? false : true;
                }
                //that._update({source: d});
                that._setOptions({source: d});
            }

            function firstChild(d) {
                if (d.children) {
                    return firstChild(d.children[0]);
                }
                if (d._children) {
                    return firstChild(d._children[0]);
                }
                return d;
            }

            function lastChild(d) {
                if (d.children) {
                    return lastChild(d.children[d.children.length - 1]);
                }
                if (d._children) {
                    return lastChild(d._children[d._children.length - 1]);
                }
                return d;
            }

            function leafCount(d) {
                var children = d.children,
                    sum = 0;
                if (!children) {
                    children = d._children;
                }

                // I am an internal node, so total the leaves of the children
                if (children) {
                    children.forEach(function (child) {
                        sum += leafCount(child);
                    });
                    return sum;
                }

                // I am a leaf
                return 1;
            }

            // Update the nodes…
            node = this.svg.selectAll("g.node")
                .data(nodes, function (d) {
                    return that.options.id(d);
                });

            // Enter any new nodes at the parent's previous position.
            nodeEnter = node.enter()
                .append("g")
                .classed("node", true)
                .attr("transform", function () {
                    return "translate(" + that.orientation.xfunc(that, source[that.orientation.abscissa + "0"]) + "," + source[that.orientation.ordinate + "0"] + ")";
                })
                .on("dblclick", click);

            nodeEnter.append("circle")
                .attr("r", 1e-6)
                .classed("node", true)
                .on("mouseenter", function () {
                    d3.select(this)
                        .classed("hovering", true);
                })
                .on("mouseleave", function () {
                    d3.select(this)
                        .classed("hovering", false);
                });

            nodeEnter.append("text")
                .attr("x", 10)
                .attr("dy", ".35em")
                .attr("text-anchor", "start")
                .style("font-size", this.options.textsize + "px")
                .text(this.options.label)
                .style("fill-opacity", 1e-6);

            // Transition nodes to their new position.
            nodeUpdate = node.transition()
                .duration(this.options.duration)
                .attr("transform", function (d) {
                    return "translate(" + that.orientation.xfunc(that, d[that.orientation.abscissa]) + "," + d[that.orientation.ordinate] + ")";
                });

            nodeUpdate.select("circle")
                .attr("r", this.options.nodesize)

            nodeUpdate.select("text")
                .text(function (d) {
                    var label = that.options.label(d);
                    if (d._children || (d.children && d.showLabel)) {
                        if (label === "") {
                            label = that.options.label(firstChild(d)) + " ... " + that.options.label(lastChild(d));
                        }
                        return label + " (" + leafCount(d) + ")";
                    }
                    if (visibleLeaves < that.height / (0.8 * that.options.textsize)) {
                        return label;
                    }
                    return "";
                })
                .style("fill-opacity", 1);

            // Transition exiting nodes to the parent's new position.
            nodeExit = node
                .exit()
                .transition()
                .duration(this.options.duration)
                .attr("transform", function () {
                    return "translate(" + that.orientation.xfunc(that, source[that.orientation.abscissa]) + "," + source[that.orientation.ordinate] + ")";
                })
                .remove();

            nodeExit.select("circle")
                .attr("r", 1e-6);

            nodeExit.select("text")
                .style("fill-opacity", 1e-6);

            // Update the links…
            link = this.svg.selectAll("path.link")
                .data(links, function (d) {
                    return that.options.id(d.target);
                });

            // Enter any new links at the parent's previous position.
            link.enter()
                .insert("path", "g")
                .classed("link", true)
                .style("stroke", "black")
                .style("stroke-width", "1px")
                .style("fill", "none")
                .attr("d", function () {
                    var o = {x: source.x0, y: source.y0};
                    return that.line({source: o, target: o});
                });

            // Transition links to their new position.
            link.transition()
                .duration(this.options.duration)
                .attr("d", function (d) {
                    return that.line({source: d.source, target: d.target});
                });

            // Transition exiting nodes to the parent's new position.
            link.exit()
                .transition()
                .duration(this.options.duration)
                .attr("d", function () {
                    var o = {x: source.x, y: source.y};
                    return that.line({source: o, target: o});
                })
                .remove();

            // Stash the old positions for transition.
            nodes.forEach(function (d) {
                d.x0 = d.x;
                d.y0 = d.y;
            });

            if (this.options.initialize) {
                this.options.initialize(nodeEnter, node, nodeExit);
            }
        },

        download: function (format) {
            var node,
                s,
                d,
                str;

            if (format === "pdf") {
                node = this.svg
                    .selectAll("g.node")
                    .select("circle")
                    .attr("r", function (d) {
                        return d._children ? this.options.nodesize : 0;
                    });
                s = new window.XMLSerializer();
                d = d3.select("svg").node();
                str = s.serializeToString(d);

                // Change back to normal
                node.attr("r", this.options.nodesize);

                d3.json("/service/svg2pdf")
                    .send("POST", str, function (error, data) {
                        window.location = "/service/svg2pdf?id=" + data.result;
                    });
            } else {
                window.alert("Unsupported export format type: " + format);
            }
        },

        reset: function () {
            function unhideAll(d) {
                if (!d.children) {
                    d.children = d._children;
                    d._children = null;
                }
                if (d.children) {
                    d.children.forEach(unhideAll);
                }
            }
            unhideAll(this.options.data);
            this._setOptions({
                root: this.options.data
            });
        }
    });
}(window.tangelo, window.jQuery, window.d3));
<|endoftext|>


<commit_before>define("ace/snippets/diff",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# DEP-3 (http://dep.debian.net/deps/dep3/) style patch header\nsnippet header DEP-3 style header\n	Description: ${1}\n	Origin: ${2:vendor|upstream|other}, ${3:url of the original patch}\n	Bug: ${4:url in upstream bugtracker}\n	Forwarded: ${5:no|not-needed|url}\n	Author: ${6:`g:snips_author`}\n	Reviewed-by: ${7:name and email}\n	Last-Update: ${8:`strftime("%Y-%m-%d")`}\n	Applied-Upstream: ${9:upstream version|url|commit}\n\n',t.scope="diff"})<commit_msg>Update tmp/bower_components/ace-builds/src-min/snippets/diff.js<commit_after><|endoftext|>


<commit_before>import * as Timespan from './timespan.js';
import { assert, clone, forKeys, hasOwnProperty, keys, removeAt, splitOn, spread } from './utils.js';

/*
{
  dayLength: xx,
  dayCount: xx,
  activities: {
    pattern: {
      slots: {
        a: {
          activity: 'activity 1',
          preferredTime: xx,
          minimumTime: xx,
          maximumTime: xx
        },
        ...
      },
      requires: [
        // if one of a, b, or c is included, the others must be as well.
        ['a', 'b', 'c']
      ],
      excludes: [
        // if one of x, y, or z is included, the others must not be.
        ['x', 'y', 'z']
      ],
      nonoptional: [
        // c will always be included. since it requires a & b, they will, too.
        'c'
      ],
      once: {
        // range [ss,ee) relative to the day's start will always be activity 1.
        'ss:ee': 'activity 1'
      }
    },
    // or more than one pattern. `patterns: [...],`
    once: {
      // range [ss,ee) will be activity 1. patterns will work around it.
      'ss:ee': 'activity 1'
    },
    // if specified, overrides the array of patterns.
    // this would map `patterns: [a, b, c]` to `[a, b, b, a]`.
    week: [0, 1, 1, 0]
  }
}
*/

export function schedule({ dayLength, dayCount, activities }) {
  dayLength = dayLength|0;
  dayCount = dayCount|0;

  assert(dayLength >= 0, "dayLength of ",dayLength," is less than zero!");
  assert(dayCount >= 0, "dayCount of ",dayCount," is less than zero!");

  const length = dayLength * dayCount, timespan = Timespan.create(length);
  let patterns = activities.patterns || [activities.pattern];
  const patternCount = Math.min(patterns.length, dayCount);
  let requiresExcludes = Array(patternCount);
  for(let patternIndex = 0; patternIndex < patternCount; ++patternIndex) {
    const pattern = patterns[patternIndex];
    if(!pattern) {
      requiresExcludes[patternIndex] = undefined;
      continue;
    }
    if(pattern.requires) {
      pattern.requires.forEach(x => x.forEach(y => assert(pattern.slots[y],
          "Can't require nonexistent slot \"",y,"\"!")));
    }
    if(pattern.excludes) {
      pattern.excludes.forEach(x => x.forEach(y => assert(pattern.slots[y],
          "Can't exclude nonexistent slot \"",y,"\"!")));
    }
    requiresExcludes[patternIndex] = {
      requires: spread(pattern.requires),
      excludes: spread(pattern.excludes)
    };
  }
  const week = activities.week;
  if(week) {
    patterns = week.map(x => patterns[x]);
    requiresExcludes = week.map(x => requiresExcludes[x]);
  }
  const timeSpentSoFar = clone(activities.timeSpentSoFar);
  const allotments = {};
  let greatestAllotment = 1;
  for(let patternIndex = 0; patternIndex < patternCount; ++patternIndex) {
    forKeys(patterns[patternIndex].slots, (key, { activity }) => {
      if(hasOwnProperty.call(timeSpentSoFar, activity)) {
        const tssf = timeSpentSoFar[activity];
        assert(tssf === (tssf|0), "Time spent so far of ",tssf," isn't an integer!");
      } else {
        timeSpentSoFar[activity] = 0;
      }
      if(!allotments[activity]) {
        if(activities.allotments) {
          const allotment = activities.allotments[activity];
          assert(allotment === (allotment|0), "Allotment of ",allotment," isn't an integer!");
          assert(allotment > 0, "Allotment of ",allotment," isn't greater than zero!");
          allotments[activity] = allotment;
          if(allotment > greatestAllotment) {
            greatestAllotment = allotment;
          }
        } else {
          allotments[activity] = 1;
        }
      }
    });
  }

  function adjustTimeSpentSoFar() {
    let minimum = Infinity;
    forKeys(timeSpentSoFar, (key, value) => {
      value = value|0;
      minimum = Math.min(minimum, value / allotments[key]);
    });
    minimum = minimum|0;
    if(minimum > 0) {
      forKeys(timeSpentSoFar, (key, value) => {
        value = value|0;
        timeSpentSoFar[key] = value - minimum * allotments[key];
      });
    }
  }

  function createDay(from, to) {
    from = Math.max(from, 0);
    to = Math.min(to, length);

    const blocks = [];
    let next = from;
    while(true) {
      const start = timespan.findStartOf({ next, to });
      if(start >= to) {
        break;
      }
      next = timespan.findEnd({ from: start, to });
      blocks.push({ from: start, to: next });
    }

    blocks.reverse();

    return (function buildDay(blocks) {
      return {
        blocks,
        allocate({ length, activity }) {
          // Returns the amount of length that couldn't fit.
          while(true) {
            if(!blocks.length) {
              return length;
            }
            const block = blocks[blocks.length - 1];
            const blockLength = block.to - block.from;
            if(blockLength > length) {
              timespan.overwrite({
                from: block.from,
                to: block.from += length,
                activity: activity
              });
              timeSpentSoFar[activity] = (timeSpentSoFar[activity]|0) + length;
              return 0;
            } else {
              timespan.overwrite({
                from: block.from,
                to: block.to,
                activity: activity
              });
              length -= blockLength;
              timeSpentSoFar[activity] = (timeSpentSoFar[activity]|0) + blockLength;
              blocks.pop();
            }
          }
        },
        clone() {
          return buildDay(blocks.map(block => ({
            from: block.from,
            to: block.to
          })));
        },
        wipe() {
          blocks.forEach(timespan.overwrite);
        }
      };
    })(blocks);
  }

  /*
  forKeys(activities.daily, (name, activity) => {
    const { start, end } = splitOn(name, ':');
    const from = +start, to = +end;
    const shift = Math.floor(to / dayLength) * dayLength;
    timespan.overwritePeriodically({
      from: from - shift,
      to: to - shift,
      period: dayLength,
      cycles: dayCount + 1,
      activity
    });
  });
  */

  for(let dayIndex = 0; dayIndex < dayCount; ++dayIndex) {
    const pattern = patterns[dayIndex % patternCount];
    if(!pattern) {
      continue;
    }
    forKeys(pattern.once, (name, activity) => {
      const { start, end } = splitOn(name, ':');
      timespan.overwrite({
        from: Math.max(+start, 0) + dayIndex * dayLength,
        to: Math.min(+end, dayLength) + dayIndex * dayLength,
        activity
      });
    });
  }

  forKeys(activities.once, (name, activity) => {
    const { start, end } = splitOn(name, ':');
    timespan.overwrite({
      from: +start,
      to: +end,
      activity
    });
  });

  for(let dayIndex = 0; dayIndex < dayCount; ++dayIndex) {
    const pattern = patterns[dayIndex % patternCount];
    if(!pattern) {
      continue;
    }

    const { requires, excludes } = requiresExcludes[dayIndex % patternCount];
    const { slots, nonoptional } = pattern;

    adjustTimeSpentSoFar();
    const probabilities = {};
    let greatestProbability = 0;
    forKeys(allotments, (key, allotment) => {
      const probability = Math.sqrt(allotment) * Math.pow(0.85, timeSpentSoFar[key] * greatestAllotment / allotment);
      probabilities[key] = probability;
      if(probability > greatestProbability) {
        greatestProbability = probability;
      }
    });
    forKeys(probabilities, (key, probability) => {
      probabilities[key] = probability / greatestProbability;
    });

    const emptyDay = createDay(dayIndex * dayLength, dayIndex * dayLength + dayLength);
    const dayLength = emptyDay.blocks.reduce((l, b) => l + b.to - b.from, 0);

    // This is in a while(true) loop to allow failing and retrying.
    // That isn't currently taken advantage of, so the loop only runs once.
    while(true) {
      let includedSlots = [], pendingSlots = [], numberOfSlotsForActivity = {};
      forKeys(slots, (key, value) => {
        pendingSlots.push(key);
        numberOfSlotsForActivity[key] = (numberOfSlotsForActivity[key]|0) + 1;
      });

      let minimumTime = 0, preferredTime = 0;

      const exclude = function exclude(slot) {
        const index = pendingSlots.indexOf(slot);
        if(index) {
          removeAt(pendingSlots, index);
          numberOfSlotsForActivity[slot] -= 1;
          const requirements = requires[slot];
          requirements && requirements.forEach(exclude);
        }
      };

      const add = function add(slot) {
        const index = pendingSlots.indexOf(slot);
        assert(index !== -1, "Slot \"",slot,"\" was not pending!");

        removeAt(pendingSlots, index);
        numberOfSlotsForActivity[slot] -= 1;

        includedSlots.push(slot);
        const slotObj = slots[slot];
        preferredTime += slotObj.preferredTime;
        minimumTime += slotObj.minimumTime;

        const requirements = requires[slot], exclusions = excludes[slot];
        exclusions && exclusions.forEach(exclude);
        requirements && requirements.forEach(add);
      };

      if(nonoptional) {
        nonoptional.forEach(add);
      }
      assert(minimumTime <= dayLength,
             "Non-optional slots add up to at least ", minimumTime,
             "; day length is only ", dayLength, "!");

      while(preferredTime < dayLength && pendingSlots.length) {
        const previous = {
          minimumTime,
          preferredTime,
          includedSlots: includedSlots.slice(),
          pendingSlots: pendingSlots.slice(),
          numberOfSlotsForActivity: clone(numberOfSlotsForActivity)
        };

        let newSlot;
        do {
          newSlot = pendingSlots[(Math.random() * pendingSlots.length)|0];
        } while(Math.random() >=
                probabilities[newSlot.activity] /
                numberOfSlotsForActivity[newSlot.activity]);

        add(newSlot);
        if(minimumTime > dayLength) {
          ({
            minimumTime,
            preferredTime,
            includedSlots,
            pendingSlots,
            numberOfSlotsForActivity
          } = previous);
          exclude(newSlot);
        }
      }

      // Clone emptyDay instead of just moving it if fail/retry is implemented.
      const day = emptyDay;

      const allocatedSlots = [];
      const unallocatedSlots = includedSlots.map(slot => ({
        slotName: slot,
        slotObj: slots[slot],
        time: slot.preferredTime
      }));
      let bleed = 0;
      while(unallocatedSlots.length) {
        const currentTime = unallocatedSlots.reduce((t, s) => t + s.time, 0) +
                              allocatedSlots.reduce((t, s) => t + s.time, 0);
        if(currentTime === dayLength) {
          break;
        }
        const makeup = (dayLength - currentTime) / unallocatedSlots.length;
        for(let slotIndex = 0; slotIndex < unallocatedSlots.length;) {
          const slot = unallocatedSlots[slotIndex];
          const goalTime = slot.time + makeup + bleed;
          const actualTime = (slot.time = (goalTime + 0.5)|0);
          bleed = goalTime - actualTime;
          const { slotObj } = slot;
          if(actualTime > slotObj.maximumTime) {
            slot.time = slotObj.maximumTime;
            removeAt(unallocatedSlots, slotIndex);
            allocatedSlots.push(slot);
          } else if(actualTime < slotObj.minimumTime) {
            slot.time = slotObj.minimumTime;
            removeAt(unallocatedSlots, slotIndex);
            allocatedSlots.push(slot);
          } else {
            ++slotIndex;
          }
        }
      }
      allocatedSlots.push.apply(allocatedSlots, unallocatedSlots);
      const slotTimes = {};
      allocatedSlots.forEach(({ slotName, time }) => {
        slotTimes[slotName] = time;
      });

      forKeys(slots, key => {
        const time = slotTimes[key];
        if(time) {
          day.allocate({ length: time, activity: key });
        }
      });

      break;
      // Wipe emptyDay before retrying if fail/retry is implemented.
    }
  }

  return timespan;
}
<commit_msg>Fixed algorithm.js a bit<commit_after>import * as Timespan from './timespan.js';
import { assert, clone, forKeys, hasOwnProperty, keys, removeAt, splitOn, spread } from './utils.js';

/*
{
  dayLength: xx,
  dayCount: xx,
  activities: {
    pattern: {
      slots: {
        a: {
          activity: 'activity 1',
          preferredTime: xx,
          minimumTime: xx,
          maximumTime: xx
        },
        ...
      },
      requires: [
        // if one of a, b, or c is included, the others must be as well.
        ['a', 'b', 'c']
      ],
      excludes: [
        // if one of x, y, or z is included, the others must not be.
        ['x', 'y', 'z']
      ],
      nonoptional: [
        // c will always be included. since it requires a & b, they will, too.
        'c'
      ],
      once: {
        // range [ss,ee) relative to the day's start will always be activity 1.
        'ss:ee': 'activity 1'
      }
    },
    // or more than one pattern. `patterns: [...],`
    once: {
      // range [ss,ee) will be activity 1. patterns will work around it.
      'ss:ee': 'activity 1'
    },
    // if specified, overrides the array of patterns.
    // this would map `patterns: [a, b, c]` to `[a, b, b, a]`.
    week: [0, 1, 1, 0],
    timeSpentSoFar: {
      'activity 1': 12,
      'activity 2': 17,
      ...
    },
    allotments: {
      // the algorithm will attempt to allocate about two-thirds as much time
      // to activity 1 as to activity 2.
      'activity 1': 2,
      'activity 2': 3,
      ...
    }
  }
}
*/

export function schedule({ dayLength, dayCount, activities }) {
  dayLength = dayLength|0;
  dayCount = dayCount|0;

  assert(dayLength >= 0, "dayLength of ",dayLength," is less than zero!");
  assert(dayCount >= 0, "dayCount of ",dayCount," is less than zero!");

  const length = dayLength * dayCount, timespan = Timespan.create(length);
  let patterns = activities.patterns || [activities.pattern];
  let patternCount = Math.min(patterns.length, dayCount);
  let requiresExcludes = Array(patternCount);
  for(let patternIndex = 0; patternIndex < patternCount; ++patternIndex) {
    const pattern = patterns[patternIndex];
    if(!pattern) {
      requiresExcludes[patternIndex] = undefined;
      continue;
    }
    if(pattern.requires) {
      pattern.requires.forEach(x => x.forEach(y => assert(pattern.slots[y],
          "Can't require nonexistent slot \"",y,"\"!")));
    }
    if(pattern.excludes) {
      pattern.excludes.forEach(x => x.forEach(y => assert(pattern.slots[y],
          "Can't exclude nonexistent slot \"",y,"\"!")));
    }
    requiresExcludes[patternIndex] = {
      requires: spread(pattern.requires),
      excludes: spread(pattern.excludes)
    };
  }
  const week = activities.week;
  if(week) {
    patterns = week.map(x => patterns[x]);
    requiresExcludes = week.map(x => requiresExcludes[x]);
    patternCount = Math.min(patterns.length, dayCount);
  }
  const timeSpentSoFar = clone(activities.timeSpentSoFar);
  const allotments = {};
  let greatestAllotment = 1;
  for(let patternIndex = 0; patternIndex < patternCount; ++patternIndex) {
    forKeys(patterns[patternIndex].slots, (key, { activity }) => {
      if(hasOwnProperty.call(timeSpentSoFar, activity)) {
        const tssf = timeSpentSoFar[activity];
        assert(tssf === (tssf|0), "Time spent so far of ",tssf," isn't an integer!");
      } else {
        timeSpentSoFar[activity] = 0;
      }
      if(!allotments[activity]) {
        if(activities.allotments) {
          const allotment = activities.allotments[activity];
          assert(allotment === (allotment|0), "Allotment of ",allotment," isn't an integer!");
          assert(allotment > 0, "Allotment of ",allotment," isn't greater than zero!");
          allotments[activity] = allotment;
          if(allotment > greatestAllotment) {
            greatestAllotment = allotment;
          }
        } else {
          allotments[activity] = 1;
        }
      }
    });
  }

  function adjustTimeSpentSoFar() {
    let minimum = Infinity;
    forKeys(timeSpentSoFar, (key, value) => {
      value = value|0;
      minimum = Math.min(minimum, value / allotments[key]);
    });
    minimum = minimum|0;
    if(minimum > 0) {
      forKeys(timeSpentSoFar, (key, value) => {
        value = value|0;
        timeSpentSoFar[key] = value - minimum * allotments[key];
      });
    }
  }

  function createDay(from, to) {
    from = Math.max(from, 0);
    to = Math.min(to, length);

    const blocks = [];
    let next = from;
    while(true) {
      const start = timespan.findStartOf({ next, to });
      if(start >= to) {
        break;
      }
      next = timespan.findEnd({ from: start, to });
      blocks.push({ from: start, to: next });
    }

    blocks.reverse();

    return (function buildDay(blocks) {
      return {
        blocks,
        allocate({ length, activity }) {
          // Returns the amount of length that couldn't fit.
          while(true) {
            if(!blocks.length) {
              return length;
            }
            const block = blocks[blocks.length - 1];
            const blockLength = block.to - block.from;
            if(blockLength > length) {
              timespan.overwrite({
                from: block.from,
                to: block.from += length,
                activity: activity
              });
              timeSpentSoFar[activity] = (timeSpentSoFar[activity]|0) + length;
              return 0;
            } else {
              timespan.overwrite({
                from: block.from,
                to: block.to,
                activity: activity
              });
              length -= blockLength;
              timeSpentSoFar[activity] = (timeSpentSoFar[activity]|0) + blockLength;
              blocks.pop();
            }
          }
        },
        clone() {
          return buildDay(blocks.map(block => ({
            from: block.from,
            to: block.to
          })));
        },
        wipe() {
          blocks.forEach(timespan.overwrite);
        }
      };
    })(blocks);
  }

  /*
  forKeys(activities.daily, (name, activity) => {
    const { start, end } = splitOn(name, ':');
    const from = +start, to = +end;
    const shift = Math.floor(to / dayLength) * dayLength;
    timespan.overwritePeriodically({
      from: from - shift,
      to: to - shift,
      period: dayLength,
      cycles: dayCount + 1,
      activity
    });
  });
  */

  for(let dayIndex = 0; dayIndex < dayCount; ++dayIndex) {
    const pattern = patterns[dayIndex % patternCount];
    if(!pattern) {
      continue;
    }
    forKeys(pattern.once, (name, activity) => {
      const { start, end } = splitOn(name, ':');
      timespan.overwrite({
        from: Math.max(+start, 0) + dayIndex * dayLength,
        to: Math.min(+end, dayLength) + dayIndex * dayLength,
        activity
      });
    });
  }

  forKeys(activities.once, (name, activity) => {
    const { start, end } = splitOn(name, ':');
    timespan.overwrite({
      from: +start,
      to: +end,
      activity
    });
  });

  for(let dayIndex = 0; dayIndex < dayCount; ++dayIndex) {
    const pattern = patterns[dayIndex % patternCount];
    if(!pattern) {
      continue;
    }

    const { requires, excludes } = requiresExcludes[dayIndex % patternCount];
    const { slots, nonoptional } = pattern;

    adjustTimeSpentSoFar();
    const probabilities = {};
    let greatestProbability = 0;
    forKeys(allotments, (key, allotment) => {
      const probability = Math.sqrt(allotment) * Math.pow(0.85, timeSpentSoFar[key] * greatestAllotment / allotment);
      probabilities[key] = probability;
      if(probability > greatestProbability) {
        greatestProbability = probability;
      }
    });
    forKeys(probabilities, (key, probability) => {
      probabilities[key] = probability / greatestProbability;
    });

    const emptyDay = createDay(dayIndex * dayLength, dayIndex * dayLength + dayLength);
    const dayLength = emptyDay.blocks.reduce((l, b) => l + b.to - b.from, 0);

    // This is in a while(true) loop to allow failing and retrying.
    // That isn't currently taken advantage of, so the loop only runs once.
    while(true) {
      let includedSlots = [], pendingSlots = [], numberOfSlotsForActivity = {};
      forKeys(slots, (key, value) => {
        pendingSlots.push(key);
        numberOfSlotsForActivity[key] = (numberOfSlotsForActivity[key]|0) + 1;
      });

      let minimumTime = 0, preferredTime = 0;

      const exclude = function exclude(slot) {
        const index = pendingSlots.indexOf(slot);
        if(index) {
          removeAt(pendingSlots, index);
          numberOfSlotsForActivity[slot] -= 1;
          const requirements = requires[slot];
          requirements && requirements.forEach(exclude);
        }
      };

      const add = function add(slot) {
        const index = pendingSlots.indexOf(slot);
        assert(index !== -1, "Slot \"",slot,"\" was not pending!");

        removeAt(pendingSlots, index);
        numberOfSlotsForActivity[slot] -= 1;

        includedSlots.push(slot);
        const slotObj = slots[slot];
        preferredTime += slotObj.preferredTime;
        minimumTime += slotObj.minimumTime;

        const requirements = requires[slot], exclusions = excludes[slot];
        exclusions && exclusions.forEach(exclude);
        requirements && requirements.forEach(add);
      };

      if(nonoptional) {
        nonoptional.forEach(add);
      }
      assert(minimumTime <= dayLength,
             "Non-optional slots add up to at least ", minimumTime,
             "; day length is only ", dayLength, "!");

      while(preferredTime < dayLength && pendingSlots.length) {
        const previous = {
          minimumTime,
          preferredTime,
          includedSlots: includedSlots.slice(),
          pendingSlots: pendingSlots.slice(),
          numberOfSlotsForActivity: clone(numberOfSlotsForActivity)
        };

        let newSlot;
        do {
          newSlot = pendingSlots[(Math.random() * pendingSlots.length)|0];
        } while(Math.random() >=
                probabilities[newSlot.activity] /
                numberOfSlotsForActivity[newSlot.activity]);

        add(newSlot);
        if(minimumTime > dayLength) {
          ({
            minimumTime,
            preferredTime,
            includedSlots,
            pendingSlots,
            numberOfSlotsForActivity
          } = previous);
          exclude(newSlot);
        }
      }

      // Clone emptyDay instead of just moving it if fail/retry is implemented.
      const day = emptyDay;

      const allocatedSlots = [];
      const unallocatedSlots = includedSlots.map(slot => ({
        slotName: slot,
        slotObj: slots[slot],
        time: slot.preferredTime
      }));
      let bleed = 0;
      while(unallocatedSlots.length) {
        const currentTime = unallocatedSlots.reduce((t, s) => t + s.time, 0) +
                              allocatedSlots.reduce((t, s) => t + s.time, 0);
        if(currentTime === dayLength) {
          break;
        }
        const makeup = (dayLength - currentTime) / unallocatedSlots.length;
        for(let slotIndex = 0; slotIndex < unallocatedSlots.length;) {
          const slot = unallocatedSlots[slotIndex];
          const goalTime = slot.time + makeup + bleed;
          const actualTime = (slot.time = (goalTime + 0.5)|0);
          bleed = goalTime - actualTime;
          const { slotObj } = slot;
          if(actualTime > slotObj.maximumTime) {
            slot.time = slotObj.maximumTime;
            removeAt(unallocatedSlots, slotIndex);
            allocatedSlots.push(slot);
          } else if(actualTime < slotObj.minimumTime) {
            slot.time = slotObj.minimumTime;
            removeAt(unallocatedSlots, slotIndex);
            allocatedSlots.push(slot);
          } else {
            ++slotIndex;
          }
        }
      }
      allocatedSlots.push.apply(allocatedSlots, unallocatedSlots);
      const slotTimes = {};
      allocatedSlots.forEach(({ slotName, time }) => {
        slotTimes[slotName] = time;
      });

      forKeys(slots, key => {
        const time = slotTimes[key];
        if(time) {
          day.allocate({ length: time, activity: key });
        }
      });

      break;
      // Wipe emptyDay before retrying if fail/retry is implemented.
      // Also restore timeSpentSoFar.
    }
  }

  return timespan;
}
<|endoftext|>


<commit_before>/**
 * A superclass for games where you need to guess the correct number.
 * This class will set the .doStartFunction and .doReturnFunction.
 * They will map to the functions that you should setup (see _setupFunctions).
 * See BeeFlightGame for inspiration of how you can use this class.
 *
 * SETUP THESE IN THE SUBCLASS:
 * "this" object should have a property used when setting agent start position:
 *    this.pos: { agent: { start: { x, y }, scale: z } } }.

 * startStop:  When round start without automatic "guessing". Used in count and incremental-steps method.
 * startBelow: When round start by guessing lower than target. Used in addition method.
 * startAbove: When round start by guessing higher than target. Used in subtraction method.
 * startThink: When round start by guessing something. Used in add/subt method.

 * runNumber:  The function to run when a number has been chosen.

 * returnToStart:            When returning to the start position on incorrect answer.
 * returnNone:               When the game stays at the incorrect answer position.
 * returnToPreviousIfHigher: When returning to previous value if the incorrect answer was too high.
 * returnToPreviousIfLower:  When returning to previous value if the incorrect answer was too low.
 *
 * VARIABLES THE SUBCLASS CAN USE:
 * Number amount:        this.amount
 * Representation:       this.representation
 * The method:           this.method
 * The number to answer: this.currentNumber (updates automatically)
 *
 * FUNCTIONS THE SUBCLASS CAN USE:
 * Try a number: this.tryNumber(number) - when testing a guess against this.currentNumber
 *
 *
 * Typical game flow:
 * this.startGame();    // the first mode, this.modeIntro, will be called
 * this.nextRound();    // start next round (will automatically start next mode)
 * this.disable(false); // make it possible to interact with anything
 * this.tryNumber(x);   // try a number against the current one
 * this.nextRound();    // do this regardless if right or wrong,
 *                      // it takes care of mode switching and function calls for you
 * // Repeat last two functions until game is over.
 */
NumberGame.prototype = Object.create(Subgame.prototype);
NumberGame.prototype.constructor = NumberGame;
function NumberGame () {
	Subgame.call(this); // Call parent constructor.
}

/*
 * Phaser state function.
 * Publishes subgameStarted event.
 */
NumberGame.prototype.init = function (options) {
	Subgame.prototype.init.call(this, options);
	GeneralButton.prototype.buttonColor = this.buttonColor || GLOBAL.BUTTON_COLOR;

	/* Public variables */
	this.method = parseInt(options.method || GLOBAL.METHOD.count);
	this.representation = options.representation;
	this.amount = GLOBAL.NUMBER_RANGE[options.range];
	/* The current number to answer */
	this.currentNumber = null;
	/* Stores the offset of the last try, can be used to judge last try */
	/* Ex: -1 means that last try was one less than currentNumber */
	this.lastTry = 0;
	/* This should be used to save the current position. */
	this.atValue = 0;
	/* This is used to add to the number of the button pushes. */
	/* This should be modified only when isRelative is set to true. */
	this.addToNumber = 0;

	// Setup gameplay differently depending on situation.
	this.isRelative = false;
	this._setupFunctions();

	/* Numbers for randomisation. */
	this._weighted = this.amount > 4 && this.method === GLOBAL.METHOD.count;
	this._numberMin = 1;
	this._numberMax = this.amount;
	if (this.method === GLOBAL.METHOD.addition) {
		this._numberMin++;
	}
	if (this.method === GLOBAL.METHOD.subtraction) {
		this._numberMax--;
	}

	// Agent is added to the game in the superclass, but we set it up here.
	this.agent.x = this.pos.agent.start.x;
	this.agent.y = this.pos.agent.start.y;
	this.agent.scale.set(this.pos.agent.scale);
	this.agent.visible = true;
	this.agent.addThought(this.representation[0], this.pos.agent.mirror || false);

	var _this = this;
	this.agent.moveTo = {
		start: function () {
			if (_this.agent.x === _this.pos.agent.stop.x &&
				_this.agent.y === _this.pos.agent.stop.y) {
				return new TweenMax(_this.agent);
			}
			return _this.agent.move({ x: _this.pos.agent.stop.x, y: _this.pos.agent.stop.y }, 3);
		}
	};
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Private functions                              */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Setup start and return functions.
 * Will map to the functions the subclass should overshadow (see bottom of this class).
 * The .doStartFunction is an easy way to call the appropriate start function.
 * The .doReturnFunction is an wasy way to call the appropriate return function when answer is incorrect.
 */
NumberGame.prototype._setupFunctions = function () {
	if (this.method === GLOBAL.METHOD.count) {
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnToStart;
	} else if (this.method === GLOBAL.METHOD.incrementalSteps) {
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnNone;
	} else if (this.method === GLOBAL.METHOD.addition) {
		this.doStartFunction = this.startBelow;
		this.doReturnFunction = this.returnToPreviousIfHigher;
		this.isRelative = true;
	} else if (this.method === GLOBAL.METHOD.subtraction) {
		this.doStartFunction = this.startAbove;
		this.doReturnFunction = this.returnToPreviousIfLower;
		this.isRelative = true;
	} else {
		this.doStartFunction = this.startThink;
		this.doReturnFunction = this.returnNone;
		this.isRelative = true;
	}
};

/** Change this.currentNumber to a new one (resets the tries). */
NumberGame.prototype._nextNumber = function () {
	// Should we allow the same number again?
	this._totalTries += this._currentTries;
	this._currentTries = 0;

	// Weighted randomisation if applicable
	if (this._weighted && this.rnd.frac() < 0.2) {
		this.currentNumber = this.rnd.integerInRange(5, this._numberMax);
	} else {
		this.currentNumber = this.rnd.integerInRange(this._numberMin, this._numberMax);
	}
};

NumberGame.prototype._getRange = function () {
	if (this.method === GLOBAL.METHOD.addition) {
		return { min: 1, max: this.amount - this.addToNumber };
	} else if (this.method === GLOBAL.METHOD.subtraction) {
		return { min: 1 - this.addToNumber, max: -1 };
	} else if (this.method === GLOBAL.METHOD.additionSubtraction) {
		return { min: 1 - this.addToNumber, max: this.amount - this.addToNumber };
	} else {
		return { min: 1, max: this.amount };
	}
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Public functions                               */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Try a number against this.currentNumber.
 * The offset of the last try is stored in this.lastTry.
 * Publishes tryNumber event.
 * @param {number} The number to try.
 * @param {number} The offset to the number (example if you start at 2).
 * @return {boolean} The offset of the last try (0 is correct, -x is too low, +x is too high).
 */
NumberGame.prototype.tryNumber = function (number, offset) {
	var sum = number + (offset || 0);
	EventSystem.publish(GLOBAL.EVENT.tryNumber, [sum, this.currentNumber, number, offset]);
	this._currentTries++;
	this.lastTry = sum - this.currentNumber;

	if (!this.lastTry) {
		this._counter.value++; // This will trigger next mode if we loop.
		this._nextNumber();
	}
	return this.lastTry;
};

/** Have the agent guess a number. */
NumberGame.prototype.agentGuess = function () {
	var t = new TimelineMax();
	t.addCallback(function () {
		var range = this._getRange();
		this.agent.guessNumber(this.currentNumber - (this.isRelative ? this.addToNumber : 0), range.min, range.max);
	}, 0, null, this);
	t.add(this.agent.think());
	return t;
};

/**
 * Setup HUD buttons.
 * @param {Object} options - The options for the buttons
 *                           Supply like this: { buttons: {...}, yesnos: {...} }.
 *                           NOTE: .method and .onClick will be set in this function.
 */
NumberGame.prototype.setupButtons = function (options) {
	var _this = this;
	if (options.buttons) {
		options.buttons.method = this.method;
		options.buttons.onClick = function (number) { _this.pushNumber(number); };
		this.buttons = new ButtonPanel(this.amount, this.representation, options.buttons);
		this.buttons.visible = false;
		this.hudGroup.add(this.buttons);
	}

	if (options.yesnos) {
		options.yesnos.onClick = function (value) { _this.pushYesNo(value); };
		this.yesnos = new ButtonPanel(2, GLOBAL.NUMBER_REPRESENTATION.yesno, options.yesnos);
		this.yesnos.visible = false;
		this.hudGroup.add(this.yesnos);
	}
};

/* Function to trigger when a button in the number panel is pushed. */
NumberGame.prototype.pushNumber = function (number) {
	return this.runNumber(number)
		.addCallback(this.nextRound, null, null, this);
};

/* Function to trigger when a button in the yes-no panel is pushed. */
NumberGame.prototype.pushYesNo = function (value) {
	if (!value) {
		if (this.speech) {
			this.agent.say(this.speech).play('agentCorrected');
		}
		this.showNumbers();
	} else {
		this.pushNumber(this.agent.lastGuess);
	}
};

/* Show the number panel, hide the yes/no panel and enable input. */
NumberGame.prototype.showNumbers = function () {
	this.disable(true);
	if (this.buttons) {
		this.buttons.reset();
		if (this.isRelative) {
			this.updateButtons();
		}
		fade(this.buttons, true).eventCallback('onComplete', this.disable, false, this);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) {
		fade(this.agent.thought, false);
		this.agent.eyesFollowPointer();
	}
};

/* Show the yes/no panel, hide the number panel and enable input. */
NumberGame.prototype.showYesnos = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		this.yesnos.reset();
		fade(this.yesnos, true).eventCallback('onComplete', this.disable, false, this);
	}

	if (this.agent.visible) { this.agent.eyesFollowPointer(); }
};

/* Hide the number and yes/no panel. */
NumberGame.prototype.hideButtons = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) { this.agent.eyesStopFollow(); }
};

/* Update the values of the button panel. */
NumberGame.prototype.updateButtons = function () {
	if (this.buttons) {
		var range = this._getRange();
		this.buttons.setRange(range.min, range.max);
	}
};

/* Update the relative value. */
NumberGame.prototype.updateRelative = function () {
	if (this.isRelative) {
		this.addToNumber = this.atValue;
	}
};

/* Start the game. */
NumberGame.prototype.startGame = function () {
	this._nextNumber();
	Subgame.prototype.startGame.call(this);
};

/* The following functions should be overshadowed in the game object. */
NumberGame.prototype.pos = { agent: { start: { x: 0, y: 0 }, scale: 1 } };
NumberGame.prototype.startStop = function () {};
NumberGame.prototype.startBelow = function () {};
NumberGame.prototype.startAbove = function () {};
NumberGame.prototype.startThink = function () {};
NumberGame.prototype.returnToStart = function () {};
NumberGame.prototype.returnNone = function () {};
NumberGame.prototype.returnToPreviousIfHigher = function () {};
NumberGame.prototype.returnToPreviousIfLower = function () {};<commit_msg>NumberGame: Instruction functions<commit_after>/**
 * A superclass for games where you need to guess the correct number.
 * This class will set the .doInstructions, .doStartFunction and .doReturnFunction.
 * They will map to the functions that you should setup (see _setupFunctions).
 * See BeeFlightGame for inspiration of how you can use this class.
 *
 *
 * SETUP THESE IN THE SUBCLASS:
 * "this" object should have a property used when setting agent start position:
 *    this.pos: { agent: { start: { x, y }, scale: z } } }.
 *
 * instructionCount:       Method count.
 * instructionSteps:       Method incremental-steps.
 * instructionAdd:         Method addition.
 * instructionSubtract:    Method subtraction.
 * instructionAddSubtract: Method addition subtraction.
 *
 * startStop:  When round start without automatic "guessing". Used in count and incremental-steps method.
 * startBelow: When round start by guessing lower than target. Used in addition method.
 * startAbove: When round start by guessing higher than target. Used in subtraction method.
 * startThink: When round start by guessing something. Used in add/subt method.
 *
 * runNumber:  The function to run when a number has been chosen.
 *
 * returnToStart:            When returning to the start position on incorrect answer.
 * returnNone:               When the game stays at the incorrect answer position.
 * returnToPreviousIfHigher: When returning to previous value if the incorrect answer was too high.
 * returnToPreviousIfLower:  When returning to previous value if the incorrect answer was too low.
 *
 *
 * VARIABLES THE SUBCLASS CAN USE:
 * Number amount:        this.amount
 * Representation:       this.representation
 * The method:           this.method
 * The number to answer: this.currentNumber (updates automatically)
 *
 * FUNCTIONS THE SUBCLASS CAN USE:
 * Try a number: this.tryNumber(number) - when testing a guess against this.currentNumber
 *
 *
 * Typical game flow:
 * this.startGame();    // the first mode, this.modeIntro, will be called
 * this.nextRound();    // start next round (will automatically start next mode)
 * this.disable(false); // make it possible to interact with anything
 * this.tryNumber(x);   // try a number against the current one
 * this.nextRound();    // do this regardless if right or wrong,
 *                      // it takes care of mode switching and function calls for you
 * // Repeat last two functions until game is over.
 */
NumberGame.prototype = Object.create(Subgame.prototype);
NumberGame.prototype.constructor = NumberGame;
function NumberGame () {
	Subgame.call(this); // Call parent constructor.
}

/*
 * Phaser state function.
 * Publishes subgameStarted event.
 */
NumberGame.prototype.init = function (options) {
	Subgame.prototype.init.call(this, options);
	GeneralButton.prototype.buttonColor = this.buttonColor || GLOBAL.BUTTON_COLOR;

	/* Public variables */
	this.method = parseInt(options.method || GLOBAL.METHOD.count);
	this.representation = options.representation;
	this.amount = GLOBAL.NUMBER_RANGE[options.range];
	/* The current number to answer */
	this.currentNumber = null;
	/* Stores the offset of the last try, can be used to judge last try */
	/* Ex: -1 means that last try was one less than currentNumber */
	this.lastTry = 0;
	/* This should be used to save the current position. */
	this.atValue = 0;
	/* This is used to add to the number of the button pushes. */
	/* This should be modified only when isRelative is set to true. */
	this.addToNumber = 0;

	// Setup gameplay differently depending on situation.
	this.isRelative = false;
	this._setupFunctions();

	/* Numbers for randomisation. */
	this._weighted = this.amount > 4 && this.method === GLOBAL.METHOD.count;
	this._numberMin = 1;
	this._numberMax = this.amount;
	if (this.method === GLOBAL.METHOD.addition) {
		this._numberMin++;
	}
	if (this.method === GLOBAL.METHOD.subtraction) {
		this._numberMax--;
	}

	// Agent is added to the game in the superclass, but we set it up here.
	this.agent.x = this.pos.agent.start.x;
	this.agent.y = this.pos.agent.start.y;
	this.agent.scale.set(this.pos.agent.scale);
	this.agent.visible = true;
	this.agent.addThought(this.representation[0], this.pos.agent.mirror || false);

	var _this = this;
	this.agent.moveTo = {
		start: function () {
			if (_this.agent.x === _this.pos.agent.stop.x &&
				_this.agent.y === _this.pos.agent.stop.y) {
				return new TweenMax(_this.agent);
			}
			return _this.agent.move({ x: _this.pos.agent.stop.x, y: _this.pos.agent.stop.y }, 3);
		}
	};
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Private functions                              */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Setup start and return functions.
 * Will map to the functions the subclass should overshadow (see bottom of this class).
 * The .doStartFunction is an easy way to call the appropriate start function.
 * The .doReturnFunction is an wasy way to call the appropriate return function when answer is incorrect.
 */
NumberGame.prototype._setupFunctions = function () {
	if (this.method === GLOBAL.METHOD.count) {
		this.doInstructions = this.instructionCount;
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnToStart;
	} else if (this.method === GLOBAL.METHOD.incrementalSteps) {
		this.doInstructions = this.instructionSteps;
		this.doStartFunction = this.startStop;
		this.doReturnFunction = this.returnNone;
	} else if (this.method === GLOBAL.METHOD.addition) {
		this.doInstructions = this.instructionAdd;
		this.doStartFunction = this.startBelow;
		this.doReturnFunction = this.returnToPreviousIfHigher;
		this.isRelative = true;
	} else if (this.method === GLOBAL.METHOD.subtraction) {
		this.doInstructions = this.instructionSubtract;
		this.doStartFunction = this.startAbove;
		this.doReturnFunction = this.returnToPreviousIfLower;
		this.isRelative = true;
	} else {
		this.doInstructions = this.instructionAddSubtract;
		this.doStartFunction = this.startThink;
		this.doReturnFunction = this.returnNone;
		this.isRelative = true;
	}
};

/** Change this.currentNumber to a new one (resets the tries). */
NumberGame.prototype._nextNumber = function () {
	// Should we allow the same number again?
	this._totalTries += this._currentTries;
	this._currentTries = 0;

	// Weighted randomisation if applicable
	if (this._weighted && this.rnd.frac() < 0.2) {
		this.currentNumber = this.rnd.integerInRange(5, this._numberMax);
	} else {
		this.currentNumber = this.rnd.integerInRange(this._numberMin, this._numberMax);
	}
};

NumberGame.prototype._getRange = function () {
	if (this.method === GLOBAL.METHOD.addition) {
		return { min: 1, max: this.amount - this.addToNumber };
	} else if (this.method === GLOBAL.METHOD.subtraction) {
		return { min: 1 - this.addToNumber, max: -1 };
	} else if (this.method === GLOBAL.METHOD.additionSubtraction) {
		return { min: 1 - this.addToNumber, max: this.amount - this.addToNumber };
	} else {
		return { min: 1, max: this.amount };
	}
};

/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM*/
/*                            Public functions                               */
/*WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW*/

/**
 * Try a number against this.currentNumber.
 * The offset of the last try is stored in this.lastTry.
 * Publishes tryNumber event.
 * @param {number} The number to try.
 * @param {number} The offset to the number (example if you start at 2).
 * @return {boolean} The offset of the last try (0 is correct, -x is too low, +x is too high).
 */
NumberGame.prototype.tryNumber = function (number, offset) {
	var sum = number + (offset || 0);
	EventSystem.publish(GLOBAL.EVENT.tryNumber, [sum, this.currentNumber, number, offset]);
	this._currentTries++;
	this.lastTry = sum - this.currentNumber;

	if (!this.lastTry) {
		this._counter.value++; // This will trigger next mode if we loop.
		this._nextNumber();
	}
	return this.lastTry;
};

/** Have the agent guess a number. */
NumberGame.prototype.agentGuess = function () {
	var t = new TimelineMax();
	t.addCallback(function () {
		var range = this._getRange();
		this.agent.guessNumber(this.currentNumber - (this.isRelative ? this.addToNumber : 0), range.min, range.max);
	}, 0, null, this);
	t.add(this.agent.think());
	return t;
};

/**
 * Setup HUD buttons.
 * @param {Object} options - The options for the buttons
 *                           Supply like this: { buttons: {...}, yesnos: {...} }.
 *                           NOTE: .method and .onClick will be set in this function.
 */
NumberGame.prototype.setupButtons = function (options) {
	var _this = this;
	if (options.buttons) {
		options.buttons.method = this.method;
		options.buttons.onClick = function (number) { _this.pushNumber(number); };
		this.buttons = new ButtonPanel(this.amount, this.representation, options.buttons);
		this.buttons.visible = false;
		this.hudGroup.add(this.buttons);
	}

	if (options.yesnos) {
		options.yesnos.onClick = function (value) { _this.pushYesNo(value); };
		this.yesnos = new ButtonPanel(2, GLOBAL.NUMBER_REPRESENTATION.yesno, options.yesnos);
		this.yesnos.visible = false;
		this.hudGroup.add(this.yesnos);
	}
};

/* Function to trigger when a button in the number panel is pushed. */
NumberGame.prototype.pushNumber = function (number) {
	return this.runNumber(number)
		.addCallback(this.nextRound, null, null, this);
};

/* Function to trigger when a button in the yes-no panel is pushed. */
NumberGame.prototype.pushYesNo = function (value) {
	if (!value) {
		if (this.speech) {
			this.agent.say(this.speech).play('agentCorrected');
		}
		this.showNumbers();
	} else {
		this.pushNumber(this.agent.lastGuess);
	}
};

/* Show the number panel, hide the yes/no panel and enable input. */
NumberGame.prototype.showNumbers = function () {
	this.disable(true);
	if (this.buttons) {
		this.buttons.reset();
		if (this.isRelative) {
			this.updateButtons();
		}
		fade(this.buttons, true).eventCallback('onComplete', this.disable, false, this);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) {
		fade(this.agent.thought, false);
		this.agent.eyesFollowPointer();
	}
};

/* Show the yes/no panel, hide the number panel and enable input. */
NumberGame.prototype.showYesnos = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		this.yesnos.reset();
		fade(this.yesnos, true).eventCallback('onComplete', this.disable, false, this);
	}

	if (this.agent.visible) { this.agent.eyesFollowPointer(); }
};

/* Hide the number and yes/no panel. */
NumberGame.prototype.hideButtons = function () {
	this.disable(true);
	if (this.buttons) {
		fade(this.buttons, false);
	}
	if (this.yesnos) {
		fade(this.yesnos, false);
	}

	if (this.agent.visible) { this.agent.eyesStopFollow(); }
};

/* Update the values of the button panel. */
NumberGame.prototype.updateButtons = function () {
	if (this.buttons) {
		var range = this._getRange();
		this.buttons.setRange(range.min, range.max);
	}
};

/* Update the relative value. */
NumberGame.prototype.updateRelative = function () {
	if (this.isRelative) {
		this.addToNumber = this.atValue;
	}
};

/* Start the game. */
NumberGame.prototype.startGame = function () {
	this._nextNumber();
	Subgame.prototype.startGame.call(this);
};

/* The following functions should be overshadowed in the game object. */
NumberGame.prototype.pos = { agent: { start: { x: 0, y: 0 }, scale: 1 } };
NumberGame.prototype.instructionCount = function () {};
NumberGame.prototype.instructionSteps = function () {};
NumberGame.prototype.instructionAdd = function () {};
NumberGame.prototype.instructionSubtract = function () {};
NumberGame.prototype.instructionAddSubtract = function () {};
NumberGame.prototype.startStop = function () {};
NumberGame.prototype.startBelow = function () {};
NumberGame.prototype.startAbove = function () {};
NumberGame.prototype.startThink = function () {};
NumberGame.prototype.returnToStart = function () {};
NumberGame.prototype.returnNone = function () {};
NumberGame.prototype.returnToPreviousIfHigher = function () {};
NumberGame.prototype.returnToPreviousIfLower = function () {};<|endoftext|>


<commit_before>'use strict';
define(
    [
        'app/config',
        'three',
        'stats',
        'app/InputHandler',
        'app/classes/Debug',
        'app/classes/Camera',
        'app/classes/GameObjectManager',
        'app/classes/Planet',
        'app/classes/Star',
        'app/classes/SpaceShip',
        'app/classes/Grid'
    ],
    function(config, THREE, Stats, InputHandler, Debug, Camera, GameObjectManager, Planet, Star, SpaceShip, Grid) {
        let stats = new Stats();
        stats.showPanel(0);
        document.body.appendChild(stats.dom);

        let renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(config.canvasWidth, config.canvasHeight);
        document.getElementById('game').appendChild(renderer.domElement);

        config.maxAnisotropy = renderer.getMaxAnisotropy();

        let scene = new THREE.Scene();

        // Create camera
        let camera = new Camera(
            scene,
            renderer,
            config.camera.fov,
            config.canvasWidth / config.canvasHeight,
            0.1,
            1000,
            new THREE.Vector3(25, 50, 50)
        );

        scene.add(new THREE.AmbientLight( 0x101010 ));

        // Add objects
        GameObjectManager.add([
            new Grid(scene, 'grid', new THREE.Vector3(0, 0, 0), 750, 750, 60, true),

            new Planet(scene, 'redPlanet',     3.301,   20, new THREE.Vector3( 0, 18, 0), true, 0xff3333),
            new Planet(scene, 'homePlanet',   48.690,  100, new THREE.Vector3(25, 0,  0), true, 0x33ff33),

            new Star(  scene, 'sol',        1.9984E8, 1400, new THREE.Vector3( 0,  0, 0),       0xffff00),

            new SpaceShip(scene, 'player', 40.0, new THREE.Vector3(0, 0, -50))
        ]);

        // Give objects a speed so they won't
        // just plunge back into the sun
        GameObjectManager.get('redPlanet').velocity.setX(0.2);
        GameObjectManager.get('redPlanet').velocity.setZ(0.3);

        GameObjectManager.get('homePlanet').velocity.setY(0.03);
        GameObjectManager.get('homePlanet').velocity.setZ(0.3);

        GameObjectManager.get('player').velocity.setX(.2);
        GameObjectManager.get('player').velocity.setZ(0);

        // Inititiate
        let lastFrameTime    = 0;
        let lastDeltaTValues = [];
        let smoothDeltaT     = 0;

        function update(currentFrameTime) {
            stats.begin();
            Debug.clear();

            let deltaT = currentFrameTime - lastFrameTime;
            lastFrameTime = currentFrameTime;

            // Ugly hack to prevent "jumps" when the tab lost focus
            if(deltaT > 32) deltaT = 1000/60;

            // Smooth deltaT
            while(lastDeltaTValues.length >= config.framesToSmoothDeltaT) {
                lastDeltaTValues.splice(0, 1);
            }
            lastDeltaTValues.push(deltaT);
            smoothDeltaT = lastDeltaTValues.reduce(function(prev, cur) { return prev + cur;}) / lastDeltaTValues.length;

            // Update controls
            camera.controls.update();

            // Handle user input
            InputHandler.checkInput(camera);

            // Update all the objects' positions
            GameObjectManager.updatePositions(deltaT, smoothDeltaT);

            // Update all the objects
            GameObjectManager.update(deltaT, smoothDeltaT);

            // Render the scene
            renderer.render(scene, camera.camera);

            stats.end();
            requestAnimationFrame(update);
        }

        requestAnimationFrame(update);
    }
);<commit_msg>Added fog (fixes #7)<commit_after>'use strict';
define(
    [
        'app/config',
        'three',
        'stats',
        'app/InputHandler',
        'app/classes/Debug',
        'app/classes/Camera',
        'app/classes/GameObjectManager',
        'app/classes/Planet',
        'app/classes/Star',
        'app/classes/SpaceShip',
        'app/classes/Grid'
    ],
    function(config, THREE, Stats, InputHandler, Debug, Camera, GameObjectManager, Planet, Star, SpaceShip, Grid) {
        let stats = new Stats();
        stats.showPanel(0);
        document.body.appendChild(stats.dom);

        let renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(config.canvasWidth, config.canvasHeight);
        document.getElementById('game').appendChild(renderer.domElement);

        config.maxAnisotropy = renderer.getMaxAnisotropy();

        let scene = new THREE.Scene();
        scene.fog = new THREE.Fog(0x000000, 100, 300);

        // Create camera
        let camera = new Camera(
            scene,
            renderer,
            config.camera.fov,
            config.canvasWidth / config.canvasHeight,
            0.1,
            1000,
            new THREE.Vector3(25, 50, 50)
        );

        scene.add(new THREE.AmbientLight( 0x101010 ));

        // Add objects
        GameObjectManager.add([
            new Grid(scene, 'grid', new THREE.Vector3(0, 0, 0), 750, 750, 60, true),

            new Planet(scene, 'redPlanet',     3.301,   20, new THREE.Vector3( 0, 18, 0), true, 0xff3333),
            new Planet(scene, 'homePlanet',   48.690,  100, new THREE.Vector3(25, 0,  0), true, 0x33ff33),

            new Star(  scene, 'sol',        1.9984E8, 1400, new THREE.Vector3( 0,  0, 0),       0xffff00),

            new SpaceShip(scene, 'player', 40.0, new THREE.Vector3(0, 0, -50))
        ]);

        // Give objects a speed so they won't
        // just plunge back into the sun
        GameObjectManager.get('redPlanet').velocity.setX(0.2);
        GameObjectManager.get('redPlanet').velocity.setZ(0.3);

        GameObjectManager.get('homePlanet').velocity.setY(0.03);
        GameObjectManager.get('homePlanet').velocity.setZ(0.3);

        GameObjectManager.get('player').velocity.setX(.2);
        GameObjectManager.get('player').velocity.setZ(0);

        // Inititiate
        let lastFrameTime    = 0;
        let lastDeltaTValues = [];
        let smoothDeltaT     = 0;

        function update(currentFrameTime) {
            stats.begin();
            Debug.clear();

            let deltaT = currentFrameTime - lastFrameTime;
            lastFrameTime = currentFrameTime;

            // Ugly hack to prevent "jumps" when the tab lost focus
            if(deltaT > 32) deltaT = 1000/60;

            // Smooth deltaT
            while(lastDeltaTValues.length >= config.framesToSmoothDeltaT) {
                lastDeltaTValues.splice(0, 1);
            }
            lastDeltaTValues.push(deltaT);
            smoothDeltaT = lastDeltaTValues.reduce(function(prev, cur) { return prev + cur;}) / lastDeltaTValues.length;

            // Update controls
            camera.controls.update();

            // Handle user input
            InputHandler.checkInput(camera);

            // Update all the objects' positions
            GameObjectManager.updatePositions(deltaT, smoothDeltaT);

            // Update all the objects
            GameObjectManager.update(deltaT, smoothDeltaT);

            // Render the scene
            renderer.render(scene, camera.camera);

            stats.end();
            requestAnimationFrame(update);
        }

        requestAnimationFrame(update);
    }
);<|endoftext|>