<commit_before>function reformat() {
    try {
        var pos = editor.getCursorPosition();
        var json = JSON.parse(editor.getSession());
        editor.getSession().setValue(JSON.stringify(json, null, 2));
        editor.navigateTo(pos.row, pos.column);
    } catch(err) {
        // ignore
    }
}

function logReq(text) {
    log('
'+text+'
');
}

function logOk(text) {
    log('
'+text+'

');
}

function logError(text) {
    log('
'+$('
').text(text).html()+'

');
}

function log(html) {
    $("#log").append(html);
    $("#log").scrollTop($("#log")[0].scrollHeight);
}

function get() {
    logReq("GET "+uri);
    var p = $.ajax(uri, { dataType:"text", headers: getHeaders() });
    p.done( function(data, status, xhr)
        {
            $("#title").css({ "font-style": "normal" });
            logOk(xhr.status+" "+xhr.statusText);
            var type=xhr.getResponseHeader("Content-Type");
            if(type && type.indexOf("application/json") != -1) {
				editor.getSession().setMode("ace/mode/json");
				editor.getSession().setValue(data);
				reformat();
            } else {
				editor.getSession().setMode("ace/mode/text");
				$("#reformat").hide();
				editor.getSession().setValue(data);
            }
        });
    p.fail( function(xhr, status, err)
        {
            $("#title").css({ "font-style": "italic" });
            logError(xhr.status+" "+xhr.statusText+"\n");
            initEmpty();
        });
}

function put() {
    logReq("PUT "+uri);
    var p = $.ajax(uri, {
        method: "PUT",
        data: editor.getSession().getValue(),
        contentType: "application/json; ; charset=UTF-8",
        processData: false,
        headers: getHeaders()
    });
    p.done( function(data, status, xhr)
        {
            $("#title").css({ "font-style": "normal" });
            logOk(xhr.status+" "+xhr.statusText);
        });
    p.fail( function(xhr, status, err)
        {
            logError(xhr.status+" "+xhr.statusText+"\n"+ ((status==400 || status >= 500) ? xhr.responseText : ""));
        });
}

function post() {
    logReq("POST "+uri);
    var p = $.ajax(uri, {
        method: "POST",
        data: editor.getSession().getValue(),
        contentType: "application/json; ; charset=UTF-8",
        processData: false,
        headers: getHeaders()
    });
    p.done( function(data, status, xhr)
        {
            logOk(xhr.status+" "+xhr.statusText);
        });
    p.fail( function(xhr, status, err)
        {
            logError(xhr.status+" "+xhr.statusText+"\n"+ ((status==400 || status >= 500) ? xhr.responseText : ""));
        });
}

function del() {
    $.confirm("Are you sure you want to delete this resource?", "Confirm Delete",
        function() {
            logReq("DELETE "+uri);
            var p = $.ajax(uri, {
                method: "DELETE",
                headers: getHeaders()
            });
            p.done( function(data, status, xhr)
                {
                    $("#title").css({ "font-style": "italic" });
                    logOk(xhr.status+" "+xhr.statusText);
                });
            p.fail( function(xhr, status, err)
                {
                    logError(xhr.status+" "+xhr.statusText+"\n");
                });
        });
}

function openHeader() {
	$("#header-button").hide();
	$("#new-header-container").show();
	$("#new-header").focus();
}

function closeHeader() {
	$("#header-button").show();
	$("#new-header-container").hide();
	return false;
}

function addHeader(header, star) {
	$("#new-header-form").submit();
	$("#headers").append('
'+header+'
');
	$(".del-header").unbind("click");
	$(".del-header").click( function() {
		$(this).parent().remove();
		updateHeaderBar();
	});
	$(".header-star").unbind("click");
	$(".header-star").click( function() {
		toggleStar($(this).parent());
	});
	$(".header-value").click( function() {
		$("#new-header").val($(this).text());
		removeStar($(this).text());
		$(this).parent().remove();
		openHeader();
		updateHeaderBar();
	});
	$("#editor").css("top", "80px");
}

function toggleStar(headerElement) {
	var header = headerElement.children(".header-value").text();
	var star = headerElement.children(".header-star");
	star.toggleClass("icon-star");
	star.toggleClass("icon-star-empty");
	var headers = $.cookie("headers") || [];
	if(star.hasClass("icon-star")) {
		removeStar(header);
	} else {
		var pos = $.inArray(header, headers);
		if(pos != -1) {
			headers.splice(pos, 1);
			$.cookie("headers", headers);
			console.log("done");
		}
	}
}

function removeStar(header) {
	var headers = $.cookie("headers") || [];
	if($.inArray(header, headers)==-1) {
		headers.push(header);
		$.cookie("headers", headers);
	}
}

function updateHeaderBar() {
	if($("#headers").children().length === 0) {
		$("#editor").css("top", "48px");
	}
}

function getHeaders() {
	var result = {};
	$("#headers").find(".header-value").each(function() {
		var splitted = $(this).text().split(":");
		result[splitted[0]]=splitted[1];
	});
	return result;
}

function switchMode() {
	window.onhashchange = function() {};
	if(!postMode) {
		window.location.assign(window.location.href.replace(/\??.[^#]*#/, "?mode=post#"));
	} else {
		window.location.assign(window.location.href.replace(/mode=post/, ""));
	}
}

function initEmpty() {
	editor.getSession().setMode("ace/mode/json");
	$("#title").css({ "font-style": "italic" });
	editor.getSession().setValue("{\n\t\n}");
	editor.navigateTo(1, 1);
}

function init() {
	$.cookie.json = true;
	uri = window.location.hash;
	if(uri) {
	    uri = uri.substring(1,uri.length);
	    var lastSlash;
	    if(uri.lastIndexOf("/") == uri.length-1) {
	        lastSlash = uri.substring(0,uri.length-1).lastIndexOf("/");
	    } else {
	        lastSlash = uri.lastIndexOf("/");
	    }
	    if(lastSlash == -1) {
	        lastSlash=0;
	    }
	    var docName = uri.substring(lastSlash+1);
	    document.title = docName;
		var create = window.location.search.indexOf("new=true") != -1;
	    postMode = window.location.search.indexOf("mode=post") != -1;

		var logSize = $.cookie("log-size");
		if(logSize) {
			if(logSize > $(window).height() -100) {
				logSize = $(window).height() -100;
			}
			$("#log-container").css("height", logSize);
			$("#editor").css("bottom", logSize);
		}

		editor = ace.edit("editor");
		editor.setTheme("ace/theme/tomorrow");
		editor.getSession().setTabSize(2);
		editor.getSession().setUseSoftTabs(true);

		editor.commands.addCommand({
		    name: "reformat",
		    bindKey: {win: "Ctrl-Shift-F", mac: "Command-Shift-F"},
		    exec: reformat
		});

		editor.commands.addCommand({
			name: "save",
			bindKey: {win: "Ctrl-S", mac: "Command-S"},
			exec: postMode ? function() { logError("Use Ctrl-Enter to Post"); } : put
		});

	    $("#reformat").click(reformat);
	    $("#reload").click(get);
	    $("#save").click(put);
	    $("#post").click(post);
	    $("#delete").click(del);
	    $("#mode-switch").click(switchMode);
	    $("#header-button").click(openHeader);
	    $("#header-button-active").click(closeHeader);
	    $("#new-header").keypress(function(ev) {
			var keycode = (ev.keyCode ? ev.keyCode : ev.which);
		    if((keycode == 13) && $("#new-header").val()) {
				ev.preventDefault();
		        addHeader($("#new-header").val());
		        $("#new-header").val("");
		    }
		});
	    $("#title").attr("href", uri);
	    $("#title").html(docName.split("?")[0]);
	    $("#splitter").draggable({
			axis: "y",
			containment: "window",
			drag: function( event, ui ) {
				logSize = $("#log-container").height()-ui.position.top;
				if(logSize>=$(window).height()) {
					return false;
				}
				$("#log").css("top", ui.position.top);
				$("#editor").css("bottom", logSize);
				editor.resize();
			},
			stop: function() {
				var size=$("#editor").position().top;
				$("log-container").height(logSize);
				$.cookie("log-size", logSize);
			}
		});
	    if(!create && !postMode) {
			editor.getSession().setValue("");
			get();
		}
		if(postMode) {
			$(".mode-normal").hide();
			$(".mode-post").show();
			$("#mode-switch").addClass("active");
			editor.commands.addCommand({
			    name: "post",
			    bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
			    exec: post
			});
			initEmpty();
	    }
		if(create) {
	        var p = $.ajax(uri, { dataType:"text" });
	        p.done( function(data, status, xhr)
	            {
	                logError("Resource already exists");
	                editor.getSession().setValue(data);
	                reformat();
	            });
	        p.fail( function(xhr, status, err)
	            {
	               initEmpty();
	            });
	    }
	    editor.focus();
	    $("#headers").text("");
	    $.each($.cookie("headers") || [], function(pos, header) {
			addHeader(header, true);
	    });
	    updateHeaderBar();
	} else {
	    logError("Please specify the resource URI after a # in the address bar. Example: "+window.location+"#/mypath/myresource");
	}
}

$.extend({
    confirm: function(message, title, okAction) {
        $("

").dialog({
            open: function(event, ui) { $(".ui-dialog-titlebar-close").hide(); },
            buttons: {
                "Ok": function() {
                    $(this).dialog("close");
                    okAction();
                },
                "Cancel": function() {
                    $(this).dialog("close");
                }
            },
            close: function(event, ui) { $(this).remove(); },
            resizable: false,
            title: title,
            modal: true
        }).text(message);
    }
});

$().ready(init);
window.onhashchange = init;

var editor;
var postMode;
var uri;

<commit_msg>Fixed splitter pos after hash change<commit_after>function reformat() {
    try {
        var pos = editor.getCursorPosition();
        var json = JSON.parse(editor.getSession());
        editor.getSession().setValue(JSON.stringify(json, null, 2));
        editor.navigateTo(pos.row, pos.column);
    } catch(err) {
        // ignore
    }
}

function logReq(text) {
    log('
'+text+'
');
}

function logOk(text) {
    log('
'+text+'

');
}

function logError(text) {
    log('
'+$('
').text(text).html()+'

');
}

function log(html) {
    $("#log").append(html);
    $("#log").scrollTop($("#log")[0].scrollHeight);
}

function get() {
    logReq("GET "+uri);
    var p = $.ajax(uri, { dataType:"text", headers: getHeaders() });
    p.done( function(data, status, xhr)
        {
            $("#title").css({ "font-style": "normal" });
            logOk(xhr.status+" "+xhr.statusText);
            var type=xhr.getResponseHeader("Content-Type");
            if(type && type.indexOf("application/json") != -1) {
				editor.getSession().setMode("ace/mode/json");
				editor.getSession().setValue(data);
				reformat();
            } else {
				editor.getSession().setMode("ace/mode/text");
				$("#reformat").hide();
				editor.getSession().setValue(data);
            }
        });
    p.fail( function(xhr, status, err)
        {
            $("#title").css({ "font-style": "italic" });
            logError(xhr.status+" "+xhr.statusText+"\n");
            initEmpty();
        });
}

function put() {
    logReq("PUT "+uri);
    var p = $.ajax(uri, {
        method: "PUT",
        data: editor.getSession().getValue(),
        contentType: "application/json; ; charset=UTF-8",
        processData: false,
        headers: getHeaders()
    });
    p.done( function(data, status, xhr)
        {
            $("#title").css({ "font-style": "normal" });
            logOk(xhr.status+" "+xhr.statusText);
        });
    p.fail( function(xhr, status, err)
        {
            logError(xhr.status+" "+xhr.statusText+"\n"+ ((status==400 || status >= 500) ? xhr.responseText : ""));
        });
}

function post() {
    logReq("POST "+uri);
    var p = $.ajax(uri, {
        method: "POST",
        data: editor.getSession().getValue(),
        contentType: "application/json; ; charset=UTF-8",
        processData: false,
        headers: getHeaders()
    });
    p.done( function(data, status, xhr)
        {
            logOk(xhr.status+" "+xhr.statusText);
        });
    p.fail( function(xhr, status, err)
        {
            logError(xhr.status+" "+xhr.statusText+"\n"+ ((status==400 || status >= 500) ? xhr.responseText : ""));
        });
}

function del() {
    $.confirm("Are you sure you want to delete this resource?", "Confirm Delete",
        function() {
            logReq("DELETE "+uri);
            var p = $.ajax(uri, {
                method: "DELETE",
                headers: getHeaders()
            });
            p.done( function(data, status, xhr)
                {
                    $("#title").css({ "font-style": "italic" });
                    logOk(xhr.status+" "+xhr.statusText);
                });
            p.fail( function(xhr, status, err)
                {
                    logError(xhr.status+" "+xhr.statusText+"\n");
                });
        });
}

function openHeader() {
	$("#header-button").hide();
	$("#new-header-container").show();
	$("#new-header").focus();
}

function closeHeader() {
	$("#header-button").show();
	$("#new-header-container").hide();
	return false;
}

function addHeader(header, star) {
	$("#new-header-form").submit();
	$("#headers").append('
'+header+'
');
	$(".del-header").unbind("click");
	$(".del-header").click( function() {
		$(this).parent().remove();
		updateHeaderBar();
	});
	$(".header-star").unbind("click");
	$(".header-star").click( function() {
		toggleStar($(this).parent());
	});
	$(".header-value").click( function() {
		$("#new-header").val($(this).text());
		removeStar($(this).text());
		$(this).parent().remove();
		openHeader();
		updateHeaderBar();
	});
	$("#editor").css("top", "80px");
}

function toggleStar(headerElement) {
	var header = headerElement.children(".header-value").text();
	var star = headerElement.children(".header-star");
	star.toggleClass("icon-star");
	star.toggleClass("icon-star-empty");
	var headers = $.cookie("headers") || [];
	if(star.hasClass("icon-star")) {
		removeStar(header);
	} else {
		var pos = $.inArray(header, headers);
		if(pos != -1) {
			headers.splice(pos, 1);
			$.cookie("headers", headers);
			console.log("done");
		}
	}
}

function removeStar(header) {
	var headers = $.cookie("headers") || [];
	if($.inArray(header, headers)==-1) {
		headers.push(header);
		$.cookie("headers", headers);
	}
}

function updateHeaderBar() {
	if($("#headers").children().length === 0) {
		$("#editor").css("top", "48px");
	}
}

function getHeaders() {
	var result = {};
	$("#headers").find(".header-value").each(function() {
		var splitted = $(this).text().split(":");
		result[splitted[0]]=splitted[1];
	});
	return result;
}

function switchMode() {
	window.onhashchange = function() {};
	if(!postMode) {
		window.location.assign(window.location.href.replace(/\??.[^#]*#/, "?mode=post#"));
	} else {
		window.location.assign(window.location.href.replace(/mode=post/, ""));
	}
}

function initEmpty() {
	editor.getSession().setMode("ace/mode/json");
	$("#title").css({ "font-style": "italic" });
	editor.getSession().setValue("{\n\t\n}");
	editor.navigateTo(1, 1);
}

function init() {
	$.cookie.json = true;
	uri = window.location.hash;
	if(uri) {
	    uri = uri.substring(1,uri.length);
	    var lastSlash;
	    if(uri.lastIndexOf("/") == uri.length-1) {
	        lastSlash = uri.substring(0,uri.length-1).lastIndexOf("/");
	    } else {
	        lastSlash = uri.lastIndexOf("/");
	    }
	    if(lastSlash == -1) {
	        lastSlash=0;
	    }
	    var docName = uri.substring(lastSlash+1);
	    document.title = docName;
		var create = window.location.search.indexOf("new=true") != -1;
	    postMode = window.location.search.indexOf("mode=post") != -1;

		var logSize = $.cookie("log-size");
		if(logSize) {
			if(logSize > $(window).height() -100) {
				logSize = $(window).height() -100;
			}
			$("#log-container").css("height", logSize);
			$("#splitter").css("top", 0);
			$("#log").css("top", $("#splitter").height());
			$("#editor").css("bottom", logSize);
		}

		editor = ace.edit("editor");
		editor.setTheme("ace/theme/tomorrow");
		editor.getSession().setTabSize(2);
		editor.getSession().setUseSoftTabs(true);

		if(logSize) {
			editor.resize();
		}

	    $("#reformat").click(reformat);
	    $("#reload").click(get);
	    $("#save").click(put);
	    $("#post").click(post);
	    $("#delete").click(del);
	    $("#mode-switch").click(switchMode);
	    $("#header-button").click(openHeader);
	    $("#header-button-active").click(closeHeader);
	    $("#new-header").keypress(function(ev) {
			var keycode = (ev.keyCode ? ev.keyCode : ev.which);
		    if((keycode == 13) && $("#new-header").val()) {
				ev.preventDefault();
		        addHeader($("#new-header").val());
		        $("#new-header").val("");
		    }
		});
	    $("#title").attr("href", uri);
	    $("#title").html(docName.split("?")[0]);
	    $("#splitter").draggable({
			axis: "y",
			containment: "window",
			drag: function( event, ui ) {
				logSize = $("#log-container").height()-ui.position.top;
				if(logSize>=$(window).height()) {
					return false;
				}
				$("#log").css("top", ui.position.top);
				$("#editor").css("bottom", logSize);
				editor.resize();
			},
			stop: function() {
				var size=$("#editor").position().top;
				$("log-container").height(logSize);
				$.cookie("log-size", logSize);
			}
		});
	    if(!create && !postMode) {
			editor.getSession().setValue("");
			get();
		}
		if(postMode) {
			$(".mode-normal").hide();
			$(".mode-post").show();
			$("#mode-switch").addClass("active");
			editor.commands.addCommand({
			    name: "post",
			    bindKey: {win: "Ctrl-Enter", mac: "Command-Enter"},
			    exec: post
			});
			initEmpty();
	    } else {
			editor.commands.addCommand({
				name: "save",
				bindKey: {win: "Ctrl-S", mac: "Command-S"},
				exec: put
			});
			editor.commands.addCommand({
				name: "refresh",
				bindKey: {win: "Ctrl-R", mac: "Command-R"},
				exec: get
			});
	    }
		editor.commands.addCommand({
			name: "reformat",
			bindKey: {win: "Ctrl-Shift-F", mac: "Command-Shift-F"},
			exec: reformat
		});
		if(create) {
	        var p = $.ajax(uri, { dataType:"text" });
	        p.done( function(data, status, xhr)
	            {
	                logError("Resource already exists");
	                editor.getSession().setValue(data);
	                reformat();
	            });
	        p.fail( function(xhr, status, err)
	            {
	               initEmpty();
	            });
	    }
	    editor.focus();
	    $("#headers").text("");
	    $.each($.cookie("headers") || [], function(pos, header) {
			addHeader(header, true);
	    });
	    updateHeaderBar();
	} else {
	    logError("Please specify the resource URI after a # in the address bar. Example: "+window.location+"#/mypath/myresource");
	}
}

$.extend({
    confirm: function(message, title, okAction) {
        $("

").dialog({
            open: function(event, ui) { $(".ui-dialog-titlebar-close").hide(); },
            buttons: {
                "Ok": function() {
                    $(this).dialog("close");
                    okAction();
                },
                "Cancel": function() {
                    $(this).dialog("close");
                }
            },
            close: function(event, ui) { $(this).remove(); },
            resizable: false,
            title: title,
            modal: true
        }).text(message);
    }
});

$().ready(init);
window.onhashchange = init;

var editor;
var postMode;
var uri;

<|endoftext|>


<commit_before>(function() {

"use strict";

var requestAnimationFrame =
   window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.ieRequestAnimationFrame;

// Utility function
var square = function square(x) {
  return x * x;
};

/**
 * @constructor
 */
var Engine = function Engine() {
  // A map from the various kinds of events to their observers
  this._listeners = {
    ":levelComplete": [],
    ":textShown": [],
    ":textHidden": []
  };

  // The balls
  this._balls = [];
  this._areas = [];
  this._previousFrameStamp = null;
  this._pad = new Pad();
  this._border = new Border();
  this._step = null;
  this._complete = false;
  this._health = 100;
};

Engine.prototype = {
  /**
   * Call this to notify that the level is complete
   *
   * @param {boolean} victory If true, the player won, otherwise, the player lost
   */
  levelComplete: function levelComplete(victory) {
    console.log("Informing that level is complete", victory);
    if (this._complete) {
      return;
    }
    this._step = function() { }; // Level is complete, nothing left to do
    this._complete = true;
    this._fireEvent(":levelComplete", {kind: "levelComplete", victory: victory});
  },

  // Handling events
  _fireEvent: function _fireEvent(key, event) {
    console.log("Informing observers of event", key);
    var set = this._listeners[key];
    if (!set) {
      console.error("Event key", key, "does not exist");
    }
    set.forEach(function(listener) {
      listener(event);
    });
  },
  addEventListener: function addEventListener(kind, listener) {
    var set = this._listeners[":" + kind];
    if (set == null) {
      throw new Error("Event kind " + kind + " does not exist");
    }
    if (set.indexOf(listener) == -1) {
      set.push(listener);
    }
  },
  removeEventListener: function removeEventListener(kind, listener) {
    var set = this._listeners[":" + kind];
    if (set == null) {
      throw new Error("Event kind " + kind + " does not exist");
    }
    var index = set.indexOf(listener);
    if (index != -1) {
      delete set[index];
    }
  },

  // Displaying text
  showText: function showText(text) {
    var eltText;
    var self = this;
    if (!this._eltText) {
      var eltBackground = document.getElementById("background");
      this._eltText = eltText = document.createElement("div");
      eltBackground.appendChild(eltText);
    } else {
      eltText = this._eltText;
    }
    eltText.textContent = text;
    eltText.classList.add("mayappear");
    eltText.classList.add("hidden");
    window.setTimeout(function() {
      eltText.classList.remove("hidden");
      eltText.classList.add("shown");
      eltText.addEventListener("transitionend", onshown);
      eltText.addEventListener("webkitTransitionEnd", onshown);
    });
    var onshown = function onshown() {
      eltText.removeEventListener("transitionend", onshown);
      eltText.removeEventListener("webkitTransitionEnd", onshown);
      self._fireEvent(":textShown", null);
    };
  },
  hideText: function hideText() {
    var eltText = this._eltText;
    if (!eltText) {
      return;
    }
    var self = this;
    eltText.classList.remove("shown");
    eltText.classList.add("hidden");
    var onhidden = function onhidden() {
      eltText.removeEventListener("transitionend", onhidden);
      eltText.removeEventListener("webkitTransitionEnd", onhidden);
      self._fireEvent(":textHidden", null);
    };
    eltText.addEventListener("transitionend", onhidden);
    eltText.addEventListener("webkitTransitionEnd", onhidden);
  },

  // Sprites
  /**
   * @return Ball
   */
  addBall: function addBall() {
    var ball = new Ball();
    this._balls.push(ball);
    return ball;
  },
  addArea: function addArea() {
    var area = new Area();
    this._areas.push(area);
    return area;
  },

  // Running the game (should be called by the level)
  run: function run(level) {
    var self = this;
    this._step = function() {
      Statistics.fps.begin();
      level.step(self);
      Statistics.fps.end();
      requestAnimationFrame(self._step);
    };
    var config = function config() {
      if (self._complete) {
        Config.removeEventListener("screenChanged", config);
        return;
      }
      console.log("Updating radius of just about everything");
      var radius = Math.min(Config.width, Config.height) / 2;
      self._pad.updateRadius(radius);
      self._balls.forEach(function (ball) {
        ball.updateRadius(radius);
      });
      self._areas.forEach(function (area) {
        area.updateRadius(radius);
      });
      self._border.updateRadius(radius);
    };
    Config.addEventListener("screenChanged", config);
    this._previousFrameStamp = Date.now();
    requestAnimationFrame(this._step);
  },

  // One step of the game (should be called by the level)
  step: function() {
    var now = Date.now();

    var midX = Config.width / 2;
    var midY = Config.height / 2;
    var radius = Math.min(Config.width, Config.height) / 2;

    var delta = now - this._previousFrameStamp;

    var pad = this._pad;

    ////// Handle inputs
    if (Input.changed) {
      if (Input.mouseX == midX) {
        if (Input.mouseY >= midY) {
          pad.destRad = Math.PI / 2;
        } else {
          pad.destRad = - Math.PI / 2;
        }
      } else {
        var div = (Input.mouseY - midY) / (Input.mouseX - midX);
        if (Input.mouseX >= midX) {
          pad.destRad = Math.atan(div);
        } else {
          pad.destRad = Math.PI + Math.atan(div);
        }
      }
      Input.changed = false;
    }

    ////// Handle movements

    if (pad.destRad - pad.posRad < pad.posRad - pad.destRad) {
      pad.posRad = Math.min(pad.destRad, pad.posRad + 0.01 * delta);
    } else {
      pad.posRad = Math.max(pad.destRad, pad.posRad - 0.01 * delta);
    }
    pad.x = radius * Math.cos(this._pad.posRad);
    pad.y = radius * Math.sin(this._pad.posRad);

    for (var i = 0; i < this._balls.length; ++i) {
      var ball = this._balls[i];
      ball.x += ball.dx * ( ball.velocity * delta) ;
      ball.y += ball.dy * ( ball.velocity * delta) ;
    }

    ////// Handle display
    var ctx = canvasContext;
    // Clear and display game zone
    ctx.clearRect(0, 0, Config.width, Config.height);

    ctx.save();
    ctx.translate(midX, midY);

    // Display center
    for (i = 0; i < this._areas.length; ++i) {
      this._areas[i].show(ctx);
    }

    // Display border
    this._border.show(ctx);

    // Display paddle
    this._pad.show(ctx);

    // Display ball
    for (i = 0; i < this._balls.length; ++i) {
      this._balls[i].show(ctx);
    }

    ////// Handle collisions
    var destructions = false;
    for (i = 0; i < this._balls.length; ++i) {
      var collisions = 0;
      ball = this._balls[i];
      if (this._pad.handleCollision(ball)) collisions++;
      if (this._border.handleCollision(ball)) {
        ball.velocity = ball.velocity * 0.9;
        this._health -= 10;
        console.log("Health reduced to", this._health, Math.floor(this._health * 255 / 100));
        this._border.strokeStyle = "rgb(0, 0, " + Math.floor(this._health * 255 / 100) + ")";
        this._border.lineWidth = Math.ceil(this._health / 10);
        if (this._health <= 0) {
          this.levelComplete(false);
        }
      }
      for (var j = 0; j < this._areas.length; ++j) {
        var area = this._areas[j];
        if (area.handleCollision(ball)) collisions++;
        if (area.isDestroyed) {
          destructions = true;
        }
      }
      // FIXME: Collision with other balls

      // Increase speed progressively
      if (collisions > 0) {
        ball.velocity *= (1 + collisions / 20);
      }
    }

    // FIXME: This won't scale if we have too many areas
    if (destructions) {
      var newAreas = [];
      for (j = 0; j < this._areas.length; ++j) {
        area = this._areas[j];
        if (!area.isDestroyed) {
          newAreas.push(area);
        }
      }
      this._areas = newAreas;
    }

    ctx.restore();

    this._previousFrameStamp = now;
  },

  getImage: function getImage() {
    return canvasContext.getImageData(0, 0, Config.width, Config.height);
  }
};

/**
 * @constructor
 */
var Sprite = function Sprite() {
  /**
   * The coordinates of the center of this object
   */
  this.x = 0;
  this.y = 0;

  /**
   * The radius
   */
  this.radiusPixels = 0;
  this.radiusPercent = null;

  this.fillStyle = null;
  this.strokeStyle = null;
  this.lineWidth = null;
  this.isBouncing = false;
  this.isAround = false;
  this.isDestructible = false;
  this.isDestroyed = false;
};
Sprite.prototype = {
  /**
   * If necessary, recompute the radius of the sprite
   * (used whenver the screen size changes)
   */
  updateRadius: function updateRadius(globalRadius) {
    console.log("Updating radius of", "" + this, globalRadius, this.radiusPercent);
    if (this.radiusPercent == null) {
      return;
    }
    this.radiusPixels = Math.floor(globalRadius * this.radiusPercent);
  },

  show: function show(ctx) {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radiusPixels, 0, Math.PI * 2);
    if (this.lineWidth) {
      ctx.lineWidth = this.lineWidth;
    }
    if (this.fillStyle) {
      ctx.fillStyle = this.fillStyle;
      ctx.fill();
    }
    if (this.strokeStyle) {
      ctx.strokeStyle = this.strokeStyle;
      ctx.stroke();
    }
  },

  /**
   * Detect collisions and, if necessary, bounce
   *
   * @param {boolean} inner If true, the ball is inside the object
   */
  handleCollision: function handleCollision(ball) {
    var axisX = this.x - ball.x;
    var axisY = this.y - ball.y;

    // Distance between centers
    var sqActualDistance = axisX * axisX + axisY * axisY;

    // The distance at which there is a collision
    var sqBounceDistance;
    var bounce;
    if (this.isAround) {
      sqBounceDistance = square(this.radiusPixels - ball.radiusPixels);
      bounce = sqBounceDistance <= sqActualDistance;
    } else {
      sqBounceDistance = square(this.radiusPixels + ball.radiusPixels);
      bounce = sqBounceDistance >= sqActualDistance;
      axisX = - axisX;
      axisY = - axisY;
    }
    if (bounce && this.isDestructible) {
      this.isDestroyed = true;
    }

    if (!bounce) {
      return false;
    }
    if (!this.isBouncing) {
      return true;
    }

    // Ok, we have a collision and we should bounce

    // Normalize axis
    var actualDistance = Math.sqrt(sqActualDistance);
    axisX /= actualDistance;
    axisY /= actualDistance;

    var symX = 2 * axisX - ball.dx;
    var symY = 2 * axisY - ball.dy;

    var symNorm = Math.sqrt(symX * symX + symY * symY);

    ball.dx = symX / symNorm;
    ball.dy = symY / symNorm;
    return true;
  },

  toString: function() {
    return "Sprite: " + this.name;
  },

  name: "unnamed sprite"
};

var Area = function Area() {
};
Area.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "area"
};

var Ball = function Ball() {
  Sprite.call(this);
  this.dx = 0;
  this.dy = 1;
  this.velocity = 0.1;
  this.radiusPixels = 5;
  this.fillStyle = "white";
};
Ball.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "ball"
};

var Pad = function Pad() {
  Sprite.call(this);
  this.posRad = Math.PI / 2;// Position in radians
  this.destRad = Math.PI / 2; // Destination in radians
  this.radiusPixels = 20;
  this.isBouncing = true;
  this.fillStyle = "white";
};
Pad.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "pad"
};

var Border = function Border() {
  Sprite.call(this);
  this.radiusPercent = 1;
  this.strokeStyle = "rgb(0, 0, 255)";
  this.isBouncing = true;
  this.isAround = true;
  this.lineWidth = 10;
};
Border.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "border",
  handleCollision: function handleCollision(ball) {
    if (Sprite.prototype.handleCollision.call(this, ball)) {
      this.showCollision();
      return true;
    }
    return false;
  },
  showCollision: function showCollision() {
    if ("vibrate" in window.navigator) {
      window.navigator.vibrate(100);
    }
  }
};

var eltCanvas = document.getElementById("canvas");
var canvasContext = eltCanvas.getContext("2d");

var Input = {
  mouseX: 0,
  mouseY: 0,
  changed: false
};

var onmousemove = function onmousemove(event) {
  event.stopPropagation();
  Input.mouseX = event.clientX;
  Input.mouseY = event.clientY;
  Input.changed = true;
};

eltCanvas.addEventListener("mousemove", onmousemove);
document.getElementById("background").addEventListener("mousemove", onmousemove);

// Configure
var Circular;
if ("Circular" in window) {
  Circular = window.Circular;
} else {
  Circular = window.Circular = {};
}

var Config = window.Circular.Config;
Config.init(eltCanvas);

var Statistics = window.Circular.Statistics;

// Exports

Circular.Engine = Engine;

})();<commit_msg>[Tweak] Regeneration<commit_after>(function() {

"use strict";

var requestAnimationFrame =
   window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.ieRequestAnimationFrame;

// Utility function
var square = function square(x) {
  return x * x;
};

/**
 * @constructor
 */
var Engine = function Engine() {
  // A map from the various kinds of events to their observers
  this._listeners = {
    ":levelComplete": [],
    ":textShown": [],
    ":textHidden": []
  };

  // The balls
  this._balls = [];
  this._areas = [];
  this._previousFrameStamp = null;
  this._pad = new Pad();
  this._border = new Border();
  this._step = null;
  this._complete = false;
  this._health = 100;
};

Engine.prototype = {
  /**
   * Call this to notify that the level is complete
   *
   * @param {boolean} victory If true, the player won, otherwise, the player lost
   */
  levelComplete: function levelComplete(victory) {
    console.log("Informing that level is complete", victory);
    if (this._complete) {
      return;
    }
    this._step = function() { }; // Level is complete, nothing left to do
    this._complete = true;
    this._fireEvent(":levelComplete", {kind: "levelComplete", victory: victory});
  },

  // Handling events
  _fireEvent: function _fireEvent(key, event) {
    console.log("Informing observers of event", key);
    var set = this._listeners[key];
    if (!set) {
      console.error("Event key", key, "does not exist");
    }
    set.forEach(function(listener) {
      listener(event);
    });
  },
  addEventListener: function addEventListener(kind, listener) {
    var set = this._listeners[":" + kind];
    if (set == null) {
      throw new Error("Event kind " + kind + " does not exist");
    }
    if (set.indexOf(listener) == -1) {
      set.push(listener);
    }
  },
  removeEventListener: function removeEventListener(kind, listener) {
    var set = this._listeners[":" + kind];
    if (set == null) {
      throw new Error("Event kind " + kind + " does not exist");
    }
    var index = set.indexOf(listener);
    if (index != -1) {
      delete set[index];
    }
  },

  // Displaying text
  showText: function showText(text) {
    var eltText;
    var self = this;
    if (!this._eltText) {
      var eltBackground = document.getElementById("background");
      this._eltText = eltText = document.createElement("div");
      eltBackground.appendChild(eltText);
    } else {
      eltText = this._eltText;
    }
    eltText.textContent = text;
    eltText.classList.add("mayappear");
    eltText.classList.add("hidden");
    window.setTimeout(function() {
      eltText.classList.remove("hidden");
      eltText.classList.add("shown");
      eltText.addEventListener("transitionend", onshown);
      eltText.addEventListener("webkitTransitionEnd", onshown);
    });
    var onshown = function onshown() {
      eltText.removeEventListener("transitionend", onshown);
      eltText.removeEventListener("webkitTransitionEnd", onshown);
      self._fireEvent(":textShown", null);
    };
  },
  hideText: function hideText() {
    var eltText = this._eltText;
    if (!eltText) {
      return;
    }
    var self = this;
    eltText.classList.remove("shown");
    eltText.classList.add("hidden");
    var onhidden = function onhidden() {
      eltText.removeEventListener("transitionend", onhidden);
      eltText.removeEventListener("webkitTransitionEnd", onhidden);
      self._fireEvent(":textHidden", null);
    };
    eltText.addEventListener("transitionend", onhidden);
    eltText.addEventListener("webkitTransitionEnd", onhidden);
  },

  // Sprites
  /**
   * @return Ball
   */
  addBall: function addBall() {
    var ball = new Ball();
    this._balls.push(ball);
    return ball;
  },
  addArea: function addArea() {
    var area = new Area();
    this._areas.push(area);
    return area;
  },

  // Running the game (should be called by the level)
  run: function run(level) {
    var self = this;
    this._step = function() {
      Statistics.fps.begin();
      level.step(self);
      Statistics.fps.end();
      requestAnimationFrame(self._step);
    };
    var config = function config() {
      if (self._complete) {
        Config.removeEventListener("screenChanged", config);
        return;
      }
      console.log("Updating radius of just about everything");
      var radius = Math.min(Config.width, Config.height) / 2;
      self._pad.updateRadius(radius);
      self._balls.forEach(function (ball) {
        ball.updateRadius(radius);
      });
      self._areas.forEach(function (area) {
        area.updateRadius(radius);
      });
      self._border.updateRadius(radius);
    };
    Config.addEventListener("screenChanged", config);
    this._previousFrameStamp = Date.now();
    requestAnimationFrame(this._step);
  },

  // One step of the game (should be called by the level)
  step: function() {
    var now = Date.now();

    var midX = Config.width / 2;
    var midY = Config.height / 2;
    var radius = Math.min(Config.width, Config.height) / 2;

    var delta = now - this._previousFrameStamp;

    var pad = this._pad;

    ////// Handle inputs
    if (Input.changed) {
      if (Input.mouseX == midX) {
        if (Input.mouseY >= midY) {
          pad.destRad = Math.PI / 2;
        } else {
          pad.destRad = - Math.PI / 2;
        }
      } else {
        var div = (Input.mouseY - midY) / (Input.mouseX - midX);
        if (Input.mouseX >= midX) {
          pad.destRad = Math.atan(div);
        } else {
          pad.destRad = Math.PI + Math.atan(div);
        }
      }
      Input.changed = false;
    }

    ////// Handle movements

    if (pad.destRad - pad.posRad < pad.posRad - pad.destRad) {
      pad.posRad = Math.min(pad.destRad, pad.posRad + 0.01 * delta);
    } else {
      pad.posRad = Math.max(pad.destRad, pad.posRad - 0.01 * delta);
    }
    pad.x = radius * Math.cos(this._pad.posRad);
    pad.y = radius * Math.sin(this._pad.posRad);

    for (var i = 0; i < this._balls.length; ++i) {
      var ball = this._balls[i];
      ball.x += ball.dx * ( ball.velocity * delta) ;
      ball.y += ball.dy * ( ball.velocity * delta) ;
    }

    ////// Handle display
    var ctx = canvasContext;
    // Clear and display game zone
    ctx.clearRect(0, 0, Config.width, Config.height);

    ctx.save();
    ctx.translate(midX, midY);

    // Display center
    for (i = 0; i < this._areas.length; ++i) {
      this._areas[i].show(ctx);
    }

    // Display border
    this._border.show(ctx);

    // Display paddle
    this._pad.show(ctx);

    // Display ball
    for (i = 0; i < this._balls.length; ++i) {
      this._balls[i].show(ctx);
    }

    ////// Handle collisions
    var destructions = false;
    var adjustHealth = false;
    for (i = 0; i < this._balls.length; ++i) {
      var collisions = 0;
      ball = this._balls[i];
      if (this._pad.handleCollision(ball)) collisions++;
      if (this._border.handleCollision(ball)) {
        ball.velocity = ball.velocity * 0.9;
        this._health -= 10;
        adjustHealth = true;
      }
      for (var j = 0; j < this._areas.length; ++j) {
        var area = this._areas[j];
        if (area.handleCollision(ball)) collisions++;
        if (area.isDestroyed) {
          destructions = true;
        }
      }
      // FIXME: Collision with other balls

      // Increase speed progressively
      if (collisions > 0) {
        ball.velocity += 0.001;
        this._health = Math.min(100, this._health + 1);
        adjustHealth = true;
      }
    }
    if (adjustHealth) {
      console.log("Health changed to", this._health, Math.floor(this._health * 255 / 100));
      this._border.strokeStyle = "rgb(0, 0, " + Math.floor(this._health * 255 / 100) + ")";
      this._border.lineWidth = Math.ceil(this._health / 10);
      if (this._health <= 0) {
        this.levelComplete(false);
      }
    }

    // FIXME: This won't scale if we have too many areas
    if (destructions) {
      var newAreas = [];
      for (j = 0; j < this._areas.length; ++j) {
        area = this._areas[j];
        if (!area.isDestroyed) {
          newAreas.push(area);
        }
      }
      this._areas = newAreas;
    }

    ctx.restore();

    this._previousFrameStamp = now;
  },

  getImage: function getImage() {
    return canvasContext.getImageData(0, 0, Config.width, Config.height);
  }
};

/**
 * @constructor
 */
var Sprite = function Sprite() {
  /**
   * The coordinates of the center of this object
   */
  this.x = 0;
  this.y = 0;

  /**
   * The radius
   */
  this.radiusPixels = 0;
  this.radiusPercent = null;

  this.fillStyle = null;
  this.strokeStyle = null;
  this.lineWidth = null;
  this.isBouncing = false;
  this.isAround = false;
  this.isDestructible = false;
  this.isDestroyed = false;
};
Sprite.prototype = {
  /**
   * If necessary, recompute the radius of the sprite
   * (used whenver the screen size changes)
   */
  updateRadius: function updateRadius(globalRadius) {
    console.log("Updating radius of", "" + this, globalRadius, this.radiusPercent);
    if (this.radiusPercent == null) {
      return;
    }
    this.radiusPixels = Math.floor(globalRadius * this.radiusPercent);
  },

  show: function show(ctx) {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radiusPixels, 0, Math.PI * 2);
    if (this.lineWidth) {
      ctx.lineWidth = this.lineWidth;
    }
    if (this.fillStyle) {
      ctx.fillStyle = this.fillStyle;
      ctx.fill();
    }
    if (this.strokeStyle) {
      ctx.strokeStyle = this.strokeStyle;
      ctx.stroke();
    }
  },

  /**
   * Detect collisions and, if necessary, bounce
   *
   * @param {boolean} inner If true, the ball is inside the object
   */
  handleCollision: function handleCollision(ball) {
    var axisX = this.x - ball.x;
    var axisY = this.y - ball.y;

    // Distance between centers
    var sqActualDistance = axisX * axisX + axisY * axisY;

    // The distance at which there is a collision
    var sqBounceDistance;
    var bounce;
    if (this.isAround) {
      sqBounceDistance = square(this.radiusPixels - ball.radiusPixels);
      bounce = sqBounceDistance <= sqActualDistance;
    } else {
      sqBounceDistance = square(this.radiusPixels + ball.radiusPixels);
      bounce = sqBounceDistance >= sqActualDistance;
      axisX = - axisX;
      axisY = - axisY;
    }
    if (bounce && this.isDestructible) {
      this.isDestroyed = true;
    }

    if (!bounce) {
      return false;
    }
    if (!this.isBouncing) {
      return true;
    }

    // Ok, we have a collision and we should bounce

    // Normalize axis
    var actualDistance = Math.sqrt(sqActualDistance);
    axisX /= actualDistance;
    axisY /= actualDistance;

    var symX = 2 * axisX - ball.dx;
    var symY = 2 * axisY - ball.dy;

    var symNorm = Math.sqrt(symX * symX + symY * symY);

    ball.dx = symX / symNorm;
    ball.dy = symY / symNorm;
    return true;
  },

  toString: function() {
    return "Sprite: " + this.name;
  },

  name: "unnamed sprite"
};

var Area = function Area() {
};
Area.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "area"
};

var Ball = function Ball() {
  Sprite.call(this);
  this.dx = 0;
  this.dy = 1;
  this.velocity = 0.1;
  this.radiusPixels = 5;
  this.fillStyle = "white";
};
Ball.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "ball"
};

var Pad = function Pad() {
  Sprite.call(this);
  this.posRad = Math.PI / 2;// Position in radians
  this.destRad = Math.PI / 2; // Destination in radians
  this.radiusPixels = 20;
  this.isBouncing = true;
  this.fillStyle = "white";
};
Pad.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "pad"
};

var Border = function Border() {
  Sprite.call(this);
  this.radiusPercent = 1;
  this.strokeStyle = "rgb(0, 0, 255)";
  this.isBouncing = true;
  this.isAround = true;
  this.lineWidth = 10;
};
Border.prototype = {
  __proto__: Object.create(Sprite.prototype),
  name: "border",
  handleCollision: function handleCollision(ball) {
    if (Sprite.prototype.handleCollision.call(this, ball)) {
      this.showCollision();
      return true;
    }
    return false;
  },
  showCollision: function showCollision() {
    if ("vibrate" in window.navigator) {
      window.navigator.vibrate(100);
    }
  }
};

var eltCanvas = document.getElementById("canvas");
var canvasContext = eltCanvas.getContext("2d");

var Input = {
  mouseX: 0,
  mouseY: 0,
  changed: false
};

var onmousemove = function onmousemove(event) {
  event.stopPropagation();
  Input.mouseX = event.clientX;
  Input.mouseY = event.clientY;
  Input.changed = true;
};

eltCanvas.addEventListener("mousemove", onmousemove);
document.getElementById("background").addEventListener("mousemove", onmousemove);

// Configure
var Circular;
if ("Circular" in window) {
  Circular = window.Circular;
} else {
  Circular = window.Circular = {};
}

var Config = window.Circular.Config;
Config.init(eltCanvas);

var Statistics = window.Circular.Statistics;

// Exports

Circular.Engine = Engine;

})();<|endoftext|>


<commit_before>/* Engine.js
 * This file provides the game loop functionality (update entities and render),
 * draws the initial game board on the screen, and then calls the update and
 * render methods on your player and enemy objects (defined in your app.js).
 *
 * A game engine works by drawing the entire game screen over and over, kind of
 * like a flipbook you may have created as a kid. When your player moves across
 * the screen, it may look like just that image/character is moving or being
 * drawn but that is not the case. What's really happening is the entire "scene"
 * is being drawn over and over, presenting the illusion of animation.
 *
 * This engine is available globally via the Engine variable and it also makes
 * the canvas' context (ctx) object globally available to make writing app.js
 * a little simpler to work with.
 */

var Engine = (function(global) {
    /* Predefine the variables we'll be using within this scope,
     * create the canvas element, grab the 2D context for that canvas
     * set the canvas elements height/width and add it to the DOM.
     */
    var doc = global.document,
        win = global.window,
        canvas = doc.createElement('canvas'),
        ctx = canvas.getContext('2d'),
        lastTime;

        canvas.width = 1200;
        canvas.height =900;
        doc.body.appendChild(canvas);

    /* This function serves as the kickoff point for the game loop itself
     * and handles properly calling the update and render methods.
     */
    function main() {
        /* Get our time delta information which is required if your game
         * requires smooth animation. Because everyone's computer processes
         * instructions at different speeds we need a constant value that
         * would be the same for everyone (regardless of how fast their
         * computer is) - hurray time!
         */
        var now = Date.now(),
            dt = (now - lastTime) / 1000.0;

        /* Call our update/render functions, pass along the time delta to
         * our update function since it may be used for smooth animation.
         */
        player.handleInput(null);
        update(dt);
        render();

        /* Set our lastTime variable which is used to determine the time delta
         * for the next time this function is called.
         */
        lastTime = now;

        /* Use the browser's requestAnimationFrame function to call this
         * function again as soon as the browser is able to draw another frame.
         */
        win.requestAnimationFrame(main);
    }

    /* This function does some initial setup that should only occur once,
     * particularly setting the lastTime variable that is required for the
     * game loop.
     */
    function init() {
        reset();
        lastTime = Date.now();
        main();
    }

    /* This function is called by main (our game loop) and itself calls all
     * of the functions which may need to update entity's data. Based on how
     * you implement your collision detection (when two entities occupy the
     * same space, for instance when your character should die), you may find
     * the need to add an additional function call here. For now, we've left
     * it commented out - you may or may not want to implement this
     * functionality this way (you could just implement collision detection
     * on the entities themselves within your app.js file).
     */
    function update(dt) {
        updateEntities(dt);
        // checkCollisions();
    }

    /* This is called by the update function  and loops through all of the
     * objects within your allEnemies array as defined in app.js and calls
     * their update() methods. It will then call the update function for your
     * player object. These update methods should focus purely on updating
     * the data/properties related to  the object. Do your drawing in your
     * render methods.
     */
    function updateEntities(dt) {
        allEnemies.forEach(function(enemy) {
            enemy.update(dt);

            bullets.forEach(function(bullet) {
                if (bullet.type === "player" && enemy.display === true) {
                    if (collisionTest(enemy,bullet)) {
                        bullets.splice(bullet.num,1);
                        enemy.display = false;
                    }
                }
            })
        });

        bullets.forEach(function(bullet) {
            bullet.move();
            bullet.update(dt);
            if (bullet.y > 900 || bullet.y < 0) {
                bullets.splice(this.num,1);
            }
        });

        player.update(dt);

    }

    /* This function initially draws the "game level", it will then call
     * the renderEntities function. Remember, this function is called every
     * game tick (or loop of the game engine) because that's how games work -
     * they are flipbooks creating the illusion of animation but in reality
     * they are just drawing the entire screen over and over.
     */
    function render() {
        /* This array holds the relative URL to the image used
         * for that particular row of the game level.
         */
        ctx.rect(0,0,canvas.width,canvas.height);
        ctx.fillStyle = 'black';
        ctx.fill();
        renderEntities();

    }

    /* This function is called by the render function and is called on each game
     * tick. It's purpose is to then call the render functions you have defined
     * on your enemy and player entities within app.js
     */
    function renderEntities() {
        /* Loop through all of the objects within the allEnemies array and call
         * the render function you have defined.
         */
        allEnemies.forEach(function(enemy) {
            enemy.render();
        });

        bullets.forEach(function(bullet) {
            bullet.render();
        });

        player.render();

        barriers.forEach(function(barrier) {
            barrier.render();
        });
    }

    /* This function does nothing but it could have been a good place to
     * handle game reset states - maybe a new game menu or a game over screen
     * those sorts of things. It's only called once by the init() method.
     */
    function reset() {
        // noop
    }

    /* Go ahead and load all of the images we know we're going to need to
     * draw our game level. Then set init as the callback method, so that when
     * all of these images are properly loaded our game will start.
     */
    Resources.load([
        'images/char-boy.png',
        'images/enemy1.png',
        'images/player.png',
        'images/barrier_full.png',
        'images/bullet.png'
    ]);
    Resources.onReady(init);

    /* Assign the canvas' context object to the global variable (the window
     * object when run in a browser) so that developer's can use it more easily
     * from within their app.js files.
     */
    global.ctx = ctx;
})(this);
<commit_msg>style: change formatting<commit_after>/* Engine.js
 * This file provides the game loop functionality (update entities and render),
 * draws the initial game board on the screen, and then calls the update and
 * render methods on your player and enemy objects (defined in your app.js).
 *
 * A game engine works by drawing the entire game screen over and over, kind of
 * like a flipbook you may have created as a kid. When your player moves across
 * the screen, it may look like just that image/character is moving or being
 * drawn but that is not the case. What's really happening is the entire "scene"
 * is being drawn over and over, presenting the illusion of animation.
 *
 * This engine is available globally via the Engine variable and it also makes
 * the canvas' context (ctx) object globally available to make writing app.js
 * a little simpler to work with.
 */
var Engine = (function(global) {
    /* Predefine the variables we'll be using within this scope,
     * create the canvas element, grab the 2D context for that canvas
     * set the canvas elements height/width and add it to the DOM.
     */
    var doc = global.document,
        win = global.window,
        canvas = doc.createElement('canvas'),
        ctx = canvas.getContext('2d'),
        lastTime;

    canvas.width = 1200;
    canvas.height = 900;
    doc.body.appendChild(canvas);

    /* This function serves as the kickoff point for the game loop itself
     * and handles properly calling the update and render methods.
     */
    function main() {
        /* Get our time delta information which is required if your game
         * requires smooth animation. Because everyone's computer processes
         * instructions at different speeds we need a constant value that
         * would be the same for everyone (regardless of how fast their
         * computer is) - hurray time!
         */
        var now = Date.now(),
            dt = (now - lastTime) / 1000.0;

        /* Call our update/render functions, pass along the time delta to
         * our update function since it may be used for smooth animation.
         */
        player.handleInput(null);
        update(dt);
        render();

        /* Set our lastTime variable which is used to determine the time delta
         * for the next time this function is called.
         */
        lastTime = now;

        /* Use the browser's requestAnimationFrame function to call this
         * function again as soon as the browser is able to draw another frame.
         */
        win.requestAnimationFrame(main);
    }

    /* This function does some initial setup that should only occur once,
     * particularly setting the lastTime variable that is required for the
     * game loop.
     */
    function init() {
        reset();
        lastTime = Date.now();
        main();
    }

    /* This function is called by main (our game loop) and itself calls all
     * of the functions which may need to update entity's data. Based on how
     * you implement your collision detection (when two entities occupy the
     * same space, for instance when your character should die), you may find
     * the need to add an additional function call here. For now, we've left
     * it commented out - you may or may not want to implement this
     * functionality this way (you could just implement collision detection
     * on the entities themselves within your app.js file).
     */
    function update(dt) {
        updateEntities(dt);
        // checkCollisions();
    }

    /* This is called by the update function  and loops through all of the
     * objects within your allEnemies array as defined in app.js and calls
     * their update() methods. It will then call the update function for your
     * player object. These update methods should focus purely on updating
     * the data/properties related to  the object. Do your drawing in your
     * render methods.
     */
    function updateEntities(dt) {
        allEnemies.forEach(function(enemy) {
            enemy.update(dt);

            bullets.forEach(function(bullet) {
                if (bullet.type === "player" && enemy.display === true) {
                    if (collisionTest(enemy, bullet)) {
                        bullets.splice(bullet.num, 1);
                        enemy.display = false;
                    }
                }
            })
        });

        bullets.forEach(function(bullet) {
            bullet.move();
            bullet.update(dt);
            if (bullet.y > 900 || bullet.y < 0) {
                bullets.splice(this.num, 1);
            }
        });

        player.update(dt);

    }

    /* This function initially draws the "game level", it will then call
     * the renderEntities function. Remember, this function is called every
     * game tick (or loop of the game engine) because that's how games work -
     * they are flipbooks creating the illusion of animation but in reality
     * they are just drawing the entire screen over and over.
     */
    function render() {
        /* This array holds the relative URL to the image used
         * for that particular row of the game level.
         */
        ctx.rect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = 'black';
        ctx.fill();
        renderEntities();

    }

    /* This function is called by the render function and is called on each game
     * tick. It's purpose is to then call the render functions you have defined
     * on your enemy and player entities within app.js
     */
    function renderEntities() {
        /* Loop through all of the objects within the allEnemies array and call
         * the render function you have defined.
         */
        allEnemies.forEach(function(enemy) {
            enemy.render();
        });

        bullets.forEach(function(bullet) {
            bullet.render();
        });

        player.render();

        barriers.forEach(function(barrier) {
            barrier.render();
        });
    }

    /* This function does nothing but it could have been a good place to
     * handle game reset states - maybe a new game menu or a game over screen
     * those sorts of things. It's only called once by the init() method.
     */
    function reset() {
        // noop
    }

    /* Go ahead and load all of the images we know we're going to need to
     * draw our game level. Then set init as the callback method, so that when
     * all of these images are properly loaded our game will start.
     */
    Resources.load([
        'images/char-boy.png',
        'images/enemy1.png',
        'images/player.png',
        'images/barrier_full.png',
        'images/bullet.png'
    ]);
    Resources.onReady(init);

    /* Assign the canvas' context object to the global variable (the window
     * object when run in a browser) so that developer's can use it more easily
     * from within their app.js files.
     */
    global.ctx = ctx;
})(this);<|endoftext|>


<commit_before>/*

Jappix - An open social platform
These are the filtering JS script for Jappix

-------------------------------------------------

License: AGPL
Authors: Valérian Saliou, Maranda
Contact: http://project.jappix.com/contact
Last revision: 31/10/10

*/

// Generates a given emoticon HTML code
function emoteImage(image, text, after) {
	return '  ' + after;
}

// Filters a given message
function filterThisMessage(neutralMessage, nick, html_encode) {
	var filteredMessage = neutralMessage;

	// We encode the HTML special chars
	if(html_encode)
		filteredMessage = filteredMessage.htmlEnc();

	// /me command
	filteredMessage = filteredMessage.replace(/((^)|((.+)(>)))(\/me )([^<]+)/, nick + ' $7')

	// We replace the smilies text into images
	.replace(/(:-@)($|\s|<)/gi, emoteImage('angry', '$1', '$2'))
	.replace(/(:-\[)($|\s|<)/gi, emoteImage('bat', '$1', '$2'))
	.replace(/(\(B\))($|\s|<)/g, emoteImage('beer', '$1', '$2'))
	.replace(/((:-?D)|(XD))($|\s|<)/gi, emoteImage('biggrin', '$1', '$4'))
	.replace(/(:-\$)($|\s|<)/gi, emoteImage('blush', '$1', '$2'))
	.replace(/(\(Z\))($|\s|<)/g, emoteImage('boy', '$1', '$2'))
	.replace(/(\(W\))($|\s|<)/g, emoteImage('brflower', '$1', '$2'))
	.replace(/((<\/3)|(\(U\)))($|\s|<)/g, emoteImage('brheart', '$1', '$4'))
	.replace(/(\(C\))($|\s|<)/g, emoteImage('coffee', '$1', '$2'))
	.replace(/((8-\))|(\(H\)))($|\s|<)/g, emoteImage('coolglasses', '$1', '$4'))
	.replace(/(:'-\()($|\s|<)/gi, emoteImage('cry', '$1', '$2'))
	.replace(/(\(%\))($|\s|<)/g, emoteImage('cuffs', '$1', '$2'))
	.replace(/(\]:->)($|\s|<)/gi, emoteImage('devil', '$1', '$2'))
	.replace(/(\(D\))($|\s|<)/g, emoteImage('drink', '$1', '$2'))
	.replace(/(@}->--)($|\s|<)/gi, emoteImage('flower', '$1', '$2'))
	.replace(/((:-\/)|(:S))($|\s|<)/gi, emoteImage('frowning', '$1', '$4'))
	.replace(/(\(X\))($|\s|<)/g, emoteImage('girl', '$1', '$2'))
	.replace(/((<3)|(\(L\)))($|\s|<)/g, emoteImage('heart', '$1', '$4'))
	.replace(/(\(}\))($|\s|<)/g, emoteImage('hugleft', '$1', '$2'))
	.replace(/(\({\))($|\s|<)/g, emoteImage('hugright', '$1', '$2'))
	.replace(/(:-{})($|\s|<)/gi, emoteImage('kiss', '$1', '$2'))
	.replace(/(\(I\))($|\s|<)/g, emoteImage('lamp', '$1', '$2'))
	.replace(/(:3)($|\s|<)/gi, emoteImage('lion', '$1', '$2'))
	.replace(/(\(E\))($|\s|<)/g, emoteImage('mail', '$1', '$2'))
	.replace(/(\(S\))($|\s|<)/g, emoteImage('moon', '$1', '$2'))
	.replace(/(\(8\))($|\s|<)/g, emoteImage('music', '$1', '$2'))
	.replace(/((=-?O)|(:-?O))($|\s|<)/gi, emoteImage('oh', '$1', '$4'))
	.replace(/(\(T\))($|\s|<)/g, emoteImage('phone', '$1', '$2'))
	.replace(/(\(P\))($|\s|<)/g, emoteImage('photo', '$1', '$2'))
	.replace(/(:-!)($|\s|<)/gi, emoteImage('puke', '$1', '$2'))
	.replace(/(\(@\))($|\s|<)/g, emoteImage('pussy', '$1', '$2'))
	.replace(/(\(R\))($|\s|<)/g, emoteImage('rainbow', '$1', '$2'))
	.replace(/(:-?\))($|\s|<)/gi, emoteImage('smile', '$1', '$2'))
	.replace(/(\(\*\))($|\s|<)/g, emoteImage('star', '$1', '$2'))
	.replace(/(:-?\|)($|\s|<)/gi, emoteImage('stare', '$1', '$2'))
	.replace(/(\(N\))($|\s|<)/g, emoteImage('thumbdown', '$1', '$2'))
	.replace(/(\(Y\))($|\s|<)/g, emoteImage('thumbup', '$1', '$2'))
	.replace(/(:-?P)($|\s|<)/gi, emoteImage('tongue', '$1', '$2'))
	.replace(/(:-?\()($|\s|<)/gi, emoteImage('unhappy', '$1', '$2'))
	.replace(/(;-?\))($|\s|<)/gi, emoteImage('wink', '$1', '$2'))

	// Text in bold
	.replace(/(^|\s|>)(\*)([^<>'"]+)(\*)($|\s|<)/gi, '$1$3$5')

	// Italic text
	.replace(/(^|\s|>)(\/)([^<>'"]+)(\/)($|\s|<)/gi, '$1$3$5')

	// Underlined text
	.replace(/(^|\s|>)(_)([^<>'"]+)(_)($|\s|<)/gi, '$1$3$5');

	// This is not yet a xHTML message (to avoid generating a link over another one)
	if(html_encode) {
		// Mail address
		filteredMessage = filteredMessage.replace(/(\s|
|^)(([a-zA-Z0-9\._-]+)@([a-zA-Z0-9\._-]+)\.([a-zA-Z0-9\._-]+))(\s|$)/gi, '$1$2 [✉]$6')

		// YouTube video box
		.replace(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))(\S+)((&\S)|(&\S)|\s|$)/gim, '$& ')

		// Dailymotion video box
		.replace(/(\w{3,5})(:)(\S+)\.dailymotion\.com\/video\/([\w\-]+)((\#[\w\-]+)|\s|$)/gim, '$& ')

		// Vimeo video box
		.replace(/(\w{3,5})(:)(\S+)(vimeo|www\.vimeo)\.com\/([\w\-]+)/gim, '$& ')

		// Theora video box
		.replace(/(\w{3,5})(:)(\S+)(\.)(ogv|ogg)/gim, '$&')

		// Vorbis audio box
		.replace(/(\w{3,5})(:)(\S+)(\.oga)/gim, '$&')

		// Image box
		.replace(/(\w{3,5})(:)(\S+)(\.)(jpg|jpeg|png|gif|tif|bmp)/gim, '$&')

		// Simple link
		.replace(/(\s|
|^)((\w{3,5})(:)([^<>'"\s]+))/gim, '$1$2');
	}

	return filteredMessage;
}

// Filters a xHTML message to be displayed in Jappix
function filterThisXHTML(code) {
	// Allowed elements array
	var elements = new Array(
				 'a',
				 'abbr',
			         'acronym',
			         'address',
			         'blockquote',
				 'body',
				 'br',
				 'cite',
			         'code',
			         'dd',
			         'dfn',
			         'div',
			         'dt',
			         'em',
			         'h1',
			         'h2',
			         'h3',
			         'h4',
			         'h5',
			         'h6',
			         'head',
			         'html',
			         'img',
			         'kbd',
			         'li',
			         'ol',
			         'p',
			         'pre',
			         'q',
			         'samp',
			         'span',
			         'strong',
			         'title',
			         'ul',
			         'var'
			        );

	// Allowed attributes array
	var attributes = new Array(
				   'accesskey',
				   'alt',
				   'charset',
				   'cite',
				   'class',
				   'height',
				   'href',
				   'hreflang',
				   'id',
				   'longdesc',
				   'profile',
				   'rel',
				   'rev',
				   'src',
				   'style',
				   'tabindex',
				   'title',
				   'type',
				   'uri',
				   'version',
				   'width',
				   'xml:lang',
				   'xmlns'
				  );

	// Remove forbidden elements
	$(code).find('*').each(function() {
		// This element is not authorized
		if(elements.indexOf((this).nodeName) == -1)
			$(this).remove();
	});

	// Remove forbidden attributes
	$(code).find('*').each(function() {
		// Put a pointer on this element (jQuery way & normal way)
		var cSelector = $(this);
		var cElement = (this);

		// Loop the attributes of the current element
		$(cElement.attributes).each(function(index) {
			// Read the current attribute
			var cAttr = cElement.attributes[index];
			var cName = cAttr.name;
			var cVal = cAttr.value;

			// This attribute is not authorized, or contains JS code
			if((attributes.indexOf(cName) == -1) || (cVal.match(/(^|"|')javascript:/)))
				cSelector.removeAttr(cName);
		});
	});

	// Filter some other elements
	$(code).find('p:first').css('display', 'inline');
	$(code).find('a').attr('target', '_blank');

	return $(code).find('html body').html();
}
<commit_msg>Filter regex update for */_<commit_after>/*

Jappix - An open social platform
These are the filtering JS script for Jappix

-------------------------------------------------

License: AGPL
Authors: Valérian Saliou, Maranda
Contact: http://project.jappix.com/contact
Last revision: 31/10/10

*/

// Generates a given emoticon HTML code
function emoteImage(image, text, after) {
	return '  ' + after;
}

// Filters a given message
function filterThisMessage(neutralMessage, nick, html_encode) {
	var filteredMessage = neutralMessage;

	// We encode the HTML special chars
	if(html_encode)
		filteredMessage = filteredMessage.htmlEnc();

	// /me command
	filteredMessage = filteredMessage.replace(/((^)|((.+)(>)))(\/me )([^<]+)/, nick + ' $7')

	// We replace the smilies text into images
	.replace(/(:-@)($|\s|<)/gi, emoteImage('angry', '$1', '$2'))
	.replace(/(:-\[)($|\s|<)/gi, emoteImage('bat', '$1', '$2'))
	.replace(/(\(B\))($|\s|<)/g, emoteImage('beer', '$1', '$2'))
	.replace(/((:-?D)|(XD))($|\s|<)/gi, emoteImage('biggrin', '$1', '$4'))
	.replace(/(:-\$)($|\s|<)/gi, emoteImage('blush', '$1', '$2'))
	.replace(/(\(Z\))($|\s|<)/g, emoteImage('boy', '$1', '$2'))
	.replace(/(\(W\))($|\s|<)/g, emoteImage('brflower', '$1', '$2'))
	.replace(/((<\/3)|(\(U\)))($|\s|<)/g, emoteImage('brheart', '$1', '$4'))
	.replace(/(\(C\))($|\s|<)/g, emoteImage('coffee', '$1', '$2'))
	.replace(/((8-\))|(\(H\)))($|\s|<)/g, emoteImage('coolglasses', '$1', '$4'))
	.replace(/(:'-\()($|\s|<)/gi, emoteImage('cry', '$1', '$2'))
	.replace(/(\(%\))($|\s|<)/g, emoteImage('cuffs', '$1', '$2'))
	.replace(/(\]:->)($|\s|<)/gi, emoteImage('devil', '$1', '$2'))
	.replace(/(\(D\))($|\s|<)/g, emoteImage('drink', '$1', '$2'))
	.replace(/(@}->--)($|\s|<)/gi, emoteImage('flower', '$1', '$2'))
	.replace(/((:-\/)|(:S))($|\s|<)/gi, emoteImage('frowning', '$1', '$4'))
	.replace(/(\(X\))($|\s|<)/g, emoteImage('girl', '$1', '$2'))
	.replace(/((<3)|(\(L\)))($|\s|<)/g, emoteImage('heart', '$1', '$4'))
	.replace(/(\(}\))($|\s|<)/g, emoteImage('hugleft', '$1', '$2'))
	.replace(/(\({\))($|\s|<)/g, emoteImage('hugright', '$1', '$2'))
	.replace(/(:-{})($|\s|<)/gi, emoteImage('kiss', '$1', '$2'))
	.replace(/(\(I\))($|\s|<)/g, emoteImage('lamp', '$1', '$2'))
	.replace(/(:3)($|\s|<)/gi, emoteImage('lion', '$1', '$2'))
	.replace(/(\(E\))($|\s|<)/g, emoteImage('mail', '$1', '$2'))
	.replace(/(\(S\))($|\s|<)/g, emoteImage('moon', '$1', '$2'))
	.replace(/(\(8\))($|\s|<)/g, emoteImage('music', '$1', '$2'))
	.replace(/((=-?O)|(:-?O))($|\s|<)/gi, emoteImage('oh', '$1', '$4'))
	.replace(/(\(T\))($|\s|<)/g, emoteImage('phone', '$1', '$2'))
	.replace(/(\(P\))($|\s|<)/g, emoteImage('photo', '$1', '$2'))
	.replace(/(:-!)($|\s|<)/gi, emoteImage('puke', '$1', '$2'))
	.replace(/(\(@\))($|\s|<)/g, emoteImage('pussy', '$1', '$2'))
	.replace(/(\(R\))($|\s|<)/g, emoteImage('rainbow', '$1', '$2'))
	.replace(/(:-?\))($|\s|<)/gi, emoteImage('smile', '$1', '$2'))
	.replace(/(\(\*\))($|\s|<)/g, emoteImage('star', '$1', '$2'))
	.replace(/(:-?\|)($|\s|<)/gi, emoteImage('stare', '$1', '$2'))
	.replace(/(\(N\))($|\s|<)/g, emoteImage('thumbdown', '$1', '$2'))
	.replace(/(\(Y\))($|\s|<)/g, emoteImage('thumbup', '$1', '$2'))
	.replace(/(:-?P)($|\s|<)/gi, emoteImage('tongue', '$1', '$2'))
	.replace(/(:-?\()($|\s|<)/gi, emoteImage('unhappy', '$1', '$2'))
	.replace(/(;-?\))($|\s|<)/gi, emoteImage('wink', '$1', '$2'))

	// Text in bold
	.replace(/(^|\s|>)(\*)([^<>'"]+)(\*)($|\s|<)/gi, '$1$2$3$4$5')

	// Italic text
	.replace(/(^|\s|>)(\/)([^<>'"]+)(\/)($|\s|<)/gi, '$1$2$3$4$5')

	// Underlined text
	.replace(/(^|\s|>)(_)([^<>'"]+)(_)($|\s|<)/gi, '$1$2$3$4$5');

	// This is not yet a xHTML message (to avoid generating a link over another one)
	if(html_encode) {
		// Mail address
		filteredMessage = filteredMessage.replace(/(\s|
|^)(([a-zA-Z0-9\._-]+)@([a-zA-Z0-9\._-]+)\.([a-zA-Z0-9\._-]+))(\s|$)/gi, '$1$2 [✉]$6')

		// YouTube video box
		.replace(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))(\S+)((&\S)|(&\S)|\s|$)/gim, '$& ')

		// Dailymotion video box
		.replace(/(\w{3,5})(:)(\S+)\.dailymotion\.com\/video\/([\w\-]+)((\#[\w\-]+)|\s|$)/gim, '$& ')

		// Vimeo video box
		.replace(/(\w{3,5})(:)(\S+)(vimeo|www\.vimeo)\.com\/([\w\-]+)/gim, '$& ')

		// Theora video box
		.replace(/(\w{3,5})(:)(\S+)(\.)(ogv|ogg)/gim, '$&')

		// Vorbis audio box
		.replace(/(\w{3,5})(:)(\S+)(\.oga)/gim, '$&')

		// Image box
		.replace(/(\w{3,5})(:)(\S+)(\.)(jpg|jpeg|png|gif|tif|bmp)/gim, '$&')

		// Simple link
		.replace(/(\s|
|^)((\w{3,5})(:)([^<>'"\s]+))/gim, '$1$2');
	}

	return filteredMessage;
}

// Filters a xHTML message to be displayed in Jappix
function filterThisXHTML(code) {
	// Allowed elements array
	var elements = new Array(
				 'a',
				 'abbr',
			         'acronym',
			         'address',
			         'blockquote',
				 'body',
				 'br',
				 'cite',
			         'code',
			         'dd',
			         'dfn',
			         'div',
			         'dt',
			         'em',
			         'h1',
			         'h2',
			         'h3',
			         'h4',
			         'h5',
			         'h6',
			         'head',
			         'html',
			         'img',
			         'kbd',
			         'li',
			         'ol',
			         'p',
			         'pre',
			         'q',
			         'samp',
			         'span',
			         'strong',
			         'title',
			         'ul',
			         'var'
			        );

	// Allowed attributes array
	var attributes = new Array(
				   'accesskey',
				   'alt',
				   'charset',
				   'cite',
				   'class',
				   'height',
				   'href',
				   'hreflang',
				   'id',
				   'longdesc',
				   'profile',
				   'rel',
				   'rev',
				   'src',
				   'style',
				   'tabindex',
				   'title',
				   'type',
				   'uri',
				   'version',
				   'width',
				   'xml:lang',
				   'xmlns'
				  );

	// Remove forbidden elements
	$(code).find('*').each(function() {
		// This element is not authorized
		if(elements.indexOf((this).nodeName) == -1)
			$(this).remove();
	});

	// Remove forbidden attributes
	$(code).find('*').each(function() {
		// Put a pointer on this element (jQuery way & normal way)
		var cSelector = $(this);
		var cElement = (this);

		// Loop the attributes of the current element
		$(cElement.attributes).each(function(index) {
			// Read the current attribute
			var cAttr = cElement.attributes[index];
			var cName = cAttr.name;
			var cVal = cAttr.value;

			// This attribute is not authorized, or contains JS code
			if((attributes.indexOf(cName) == -1) || (cVal.match(/(^|"|')javascript:/)))
				cSelector.removeAttr(cName);
		});
	});

	// Filter some other elements
	$(code).find('p:first').css('display', 'inline');
	$(code).find('a').attr('target', '_blank');

	return $(code).find('html body').html();
}
<|endoftext|>


<commit_before>"use strict";

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange')
const { ExchangeError } = require ('./base/errors')

//  ---------------------------------------------------------------------------

module.exports = class gemini extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'gemini',
            'name': 'Gemini',
            'countries': 'US',
            'rateLimit': 1500, // 200 for private API
            'version': 'v1',
            'hasCORS': false,
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg',
                'api': 'https://api.gemini.com',
                'www': 'https://gemini.com',
                'doc': [
                    'https://docs.gemini.com/rest-api',
                    'https://docs.sandbox.gemini.com',
                ],
                'test': 'https://api.sandbox.gemini.com',
                'fees': [
                    'https://gemini.com/fee-schedule/',
                    'https://gemini.com/transfer-fees/',
                ],
            },
            'api': {
                'public': {
                    'get': [
                        'symbols',
                        'pubticker/{symbol}',
                        'book/{symbol}',
                        'trades/{symbol}',
                        'auction/{symbol}',
                        'auction/{symbol}/history',
                    ],
                },
                'private': {
                    'post': [
                        'order/new',
                        'order/cancel',
                        'order/cancel/session',
                        'order/cancel/all',
                        'order/status',
                        'orders',
                        'mytrades',
                        'tradevolume',
                        'balances',
                        'deposit/{currency}/newAddress',
                        'withdraw/{currency}',
                        'heartbeat',
                    ],
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetSymbols ();
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let id = markets[p];
            let market = id;
            let uppercase = market.toUpperCase ();
            let base = uppercase.slice (0, 3);
            let quote = uppercase.slice (3, 6);
            let symbol = base + '/' + quote;
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
                'taker': 0.0025
            });
        }
        return result;
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetBookSymbol (this.extend ({
            'symbol': this.marketId (symbol),
        }, params));
        return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetPubtickerSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        let timestamp = ticker['volume']['timestamp'];
        let baseVolume = market['base'];
        let quoteVolume = market['quote'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': parseFloat (ticker['bid']),
            'ask': parseFloat (ticker['ask']),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['last']),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': parseFloat (ticker['volume'][baseVolume]),
            'quoteVolume': parseFloat (ticker['volume'][quoteVolume]),
            'info': ticker,
        };
    }

    parseTrade (trade, market) {
        let timestamp = trade['timestampms'];
        return {
            'id': trade['tid'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['type'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTradesSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let balances = await this.privatePostBalances ();
        let result = { 'info': balances };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'];
            let account = {
                'free': parseFloat (balance['available']),
                'used': 0.0,
                'total': parseFloat (balance['amount']),
            };
            account['used'] = account['total'] - account['free'];
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        if (type == 'market')
            throw new ExchangeError (this.id + ' allows limit orders only');
        let nonce = this.nonce ();
        let order = {
            'client_order_id': nonce.toString (),
            'symbol': this.marketId (symbol),
            'amount': amount.toString (),
            'price': price.toString (),
            'side': side,
            'type': 'exchange limit', // gemini allows limit orders only
        };
        let response = await this.privatePostOrderNew (this.extend (order, params));
        return {
            'info': response,
            'id': response['order_id'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostCancelOrder ({ 'order_id': id });
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = '/' + this.version + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api == 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            let request = this.extend ({
                'request': url,
                'nonce': nonce,
            }, query);
            let payload = this.json (request);
            payload = this.stringToBase64 (this.encode (payload));
            let signature = this.hmac (payload, this.encode (this.secret), 'sha384');
            headers = {
                'Content-Type': 'text/plain',
                'X-GEMINI-APIKEY': this.apiKey,
                'X-GEMINI-PAYLOAD': this.decode (payload),
                'X-GEMINI-SIGNATURE': signature,
            };
        }
        url = this.urls['api'] + url;
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('result' in response)
            if (response['result'] == 'error')
                throw new ExchangeError (this.id + ' ' + this.json (response));
        return response;
    }
}
<commit_msg>gemini: withdraw<commit_after>"use strict";

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange')
const { ExchangeError } = require ('./base/errors')

//  ---------------------------------------------------------------------------

module.exports = class gemini extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'gemini',
            'name': 'Gemini',
            'countries': 'US',
            'rateLimit': 1500, // 200 for private API
            'version': 'v1',
            'hasCORS': false,
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27816857-ce7be644-6096-11e7-82d6-3c257263229c.jpg',
                'api': 'https://api.gemini.com',
                'www': 'https://gemini.com',
                'doc': [
                    'https://docs.gemini.com/rest-api',
                    'https://docs.sandbox.gemini.com',
                ],
                'test': 'https://api.sandbox.gemini.com',
                'fees': [
                    'https://gemini.com/fee-schedule/',
                    'https://gemini.com/transfer-fees/',
                ],
            },
            'api': {
                'public': {
                    'get': [
                        'symbols',
                        'pubticker/{symbol}',
                        'book/{symbol}',
                        'trades/{symbol}',
                        'auction/{symbol}',
                        'auction/{symbol}/history',
                    ],
                },
                'private': {
                    'post': [
                        'order/new',
                        'order/cancel',
                        'order/cancel/session',
                        'order/cancel/all',
                        'order/status',
                        'orders',
                        'mytrades',
                        'tradevolume',
                        'balances',
                        'deposit/{currency}/newAddress',
                        'withdraw/{currency}',
                        'heartbeat',
                    ],
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetSymbols ();
        let result = [];
        for (let p = 0; p < markets.length; p++) {
            let id = markets[p];
            let market = id;
            let uppercase = market.toUpperCase ();
            let base = uppercase.slice (0, 3);
            let quote = uppercase.slice (3, 6);
            let symbol = base + '/' + quote;
            result.push ({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'info': market,
                'taker': 0.0025
            });
        }
        return result;
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetBookSymbol (this.extend ({
            'symbol': this.marketId (symbol),
        }, params));
        return this.parseOrderBook (orderbook, undefined, 'bids', 'asks', 'price', 'amount');
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetPubtickerSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        let timestamp = ticker['volume']['timestamp'];
        let baseVolume = market['base'];
        let quoteVolume = market['quote'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': parseFloat (ticker['bid']),
            'ask': parseFloat (ticker['ask']),
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': parseFloat (ticker['last']),
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': parseFloat (ticker['volume'][baseVolume]),
            'quoteVolume': parseFloat (ticker['volume'][quoteVolume]),
            'info': ticker,
        };
    }

    parseTrade (trade, market) {
        let timestamp = trade['timestampms'];
        return {
            'id': trade['tid'].toString (),
            'info': trade,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['type'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTradesSymbol (this.extend ({
            'symbol': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let balances = await this.privatePostBalances ();
        let result = { 'info': balances };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'];
            let account = {
                'free': parseFloat (balance['available']),
                'used': 0.0,
                'total': parseFloat (balance['amount']),
            };
            account['used'] = account['total'] - account['free'];
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        if (type == 'market')
            throw new ExchangeError (this.id + ' allows limit orders only');
        let nonce = this.nonce ();
        let order = {
            'client_order_id': nonce.toString (),
            'symbol': this.marketId (symbol),
            'amount': amount.toString (),
            'price': price.toString (),
            'side': side,
            'type': 'exchange limit', // gemini allows limit orders only
        };
        let response = await this.privatePostOrderNew (this.extend (order, params));
        return {
            'info': response,
            'id': response['order_id'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostCancelOrder ({ 'order_id': id });
    }

    async withdraw (code, amount, address, params = {}) {
        await this.loadMarkets ();
        let currency = this.currency (code);
        let response = await this.privatePostWithdrawCurrency (this.extend ({
            'currency': currency,
            'amount': amount,
            'address': address,
        }, params));
        return {
            'info': response,
            'id': this.safeString (response, 'txHash'),
        };
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = '/' + this.version + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api == 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ();
            let request = this.extend ({
                'request': url,
                'nonce': nonce,
            }, query);
            let payload = this.json (request);
            payload = this.stringToBase64 (this.encode (payload));
            let signature = this.hmac (payload, this.encode (this.secret), 'sha384');
            headers = {
                'Content-Type': 'text/plain',
                'X-GEMINI-APIKEY': this.apiKey,
                'X-GEMINI-PAYLOAD': this.decode (payload),
                'X-GEMINI-SIGNATURE': signature,
            };
        }
        url = this.urls['api'] + url;
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('result' in response)
            if (response['result'] == 'error')
                throw new ExchangeError (this.id + ' ' + this.json (response));
        return response;
    }
}
<|endoftext|>