<commit_before>/* message is representation of the event to be shown on the message board */
/* from may be null, in such case message will be treated as error */
/* types of message: typer, message, error */
function Message(from, text, type){
  this.from=from;
  this.text=text;
  this.type=type;
  this.id=name+text+type+Math.floor(Math.random()*100000);//dummy
  return this;
}

Message.prototype={
  /* returns li that will be inserted in proper place of the message board */
  getLi: function(){
    var li=$("<li/>")
      .addClass("message");
    if(this.from&&
       this.from!=null&&
       this.type!="error"){
      li.append(
	$("<span/>")
	  .addClass("from")
	  .text(this.from)
      );
    }
    li.append(
      $("<span/>")
	.addClass("message_body")
	.text(this.text)
    );
    return li;
  }
};

/* main object that represents room */
function Room(id, name){
  var self=this;
  this.room_id=id;
  /* that very man who is about to enter this room */
  this.self=null;
  /* session keeps session id that is needed in every network operation */
  this.session=null;
  /* queue of messages to be send over network */
  this.message_queue=[];
  /* true if request is being sent */
  this.request_processing=false;
  /* number of errors already accured while processing message */
  this.errors_occured=0;
  this.topic=new Topic("tst");
  this.messages=new Messages();
  this.mates=new Mates([]);
  this.makeInputBox(function(value, isFinal){
		      self.postMessage(value, isFinal);
		      if(isFinal){
			self.clearInputBox();
		      }
		    });
  this.topic.onChange=
    function(value, isFinal){
      console&&console.log(value+":"+isFinal);
    };
  $(window).unload(function(){self.leave();});
  this.enter(name,
	     function(){
	       self.getMates(
		 function(){
		   $("#connecting").hide();
		   $("#chat_section").show();
		   $("#input_line").focus();
		   self.startProcessing(
		     function(event){
		       self.process(event);
		     }
		   );
		 }
	       );
	     });
}

Room.prototype={
  scrollDownChat: function(){
    var height=$("#chat_section .chat_list *").height();
    $("#chat_section .chat_list").get(0).scrollTop=height;
  },
  /* enters to room and gets session id */
  enter: function(name, cb){
    var self=this;
    $.ajax({
	     type: "GET",
	     cache: false,
	     url: "/json/"+this.room_id+"/enter",
	     data: "name="+name,
	     dataType: "json",
	     success: function(data){
	       if(data.result!='ok'){
		 self.showError("Can't enter the room.");
		 return;
	       }
	       self.session=data.session;
	       cb&&cb();
	     },
	     error: function(XMLHttpRequest, textStatus, errorThrown){
	       self.showError("Can't enter the room. ("+textStatus+")");
	     }
	   });
  },
  leave: function(){
    $.ajax({
	     type: "GET",
	     cache: false,
	     url: "/json/"+this.room_id+"/leave",
	     data: "session="+this.session,
	     async: false
	   });
  },
  /* requests are processed one after another to not let the typer
   events come after real message, maybe there shoud be timeout for
   typer event */
  postMessage: function(text, isFinal){
    var self=this;
    var type= isFinal?"message":"typer";
    this.message_queue
      .push({ /* this property is needed just for postMessage() */
	      typer: true,
	      /* standart jquery ajax properties */
	      type: "GET",
	      cache: false,
	      url: "/json/"+this.room_id+"/"+type,
	      data: "session="+this.session+"&"+
		"message="+text,
	      dataType: "json",
	      success: function(data){
		if(data.result!='ok'){
		  self.showError("Can't send the message.");
		}
		self.messages.add(new Message(self.self,
					      text,
					      type));
		self.scrollDownChat();
		if(self.message_queue.length==0){
		  self.request_processing=false;
		}else{
		  self.processQueue();
		}
	      },
	      error: function(XMLHttpRequest,
			      textStatus,
			      errorThrown){
		console&&console.log("Can't send the message.");
		self.showError("Can't send the message. ("+
			       textStatus+")");
			if(self.message_queue.length==0){
		  self.request_processing=false;
		}else{
		  self.processQueue();
		}
	      }
	    });
    this.processQueue();
  },
  processQueue: function(){
    if(!this.request_processing){
      this.request_processing=true;
      $.ajax(this.message_queue.shift());
      processQueue();
    }else{
      /* here we now that last element is unprocessed and if it */
      /* "typer" event we can remove it with new one. */
      if(this.message_queue.length>0 &&
	 this.message_queue[this.message_queue.length-1].typer){
	this.message_queue.pop();
      }
    }
  },
  getMates: function(cb){
    var self=this;
    $.ajax({
	     type: "GET",
	     cache: false,
	     url: "/json/"+this.room_id+"/mates",
	     data: "session="+this.session,
	     dataType: "json",
	     success: function(data){
	       if(data.result!='ok'){
		 self.showError("Can't load room mates.");
		 return;
	       }
	       for(var i in data.mates){
		 self.mates.add(data.mates[i]);
	       }
	       self.self=data.self_id;
	       cb&&cb();
	     },
	     error: function(XMLHttpRequest, textStatus, errorThrown){
	       self.showError("Can't load room mates. ("+textStatus+")");
	     }
	   });
  },
  startProcessing: function(cb){
    var self=this;
    $.ajax({
	     type: "GET",
	     url: "/json/"+this.room_id+"/get",
	     cache: false,
	     data: "session="+this.session,
	     dataType: "json",
	     success: function(data){
	       if(data.result=="timeout"){
		 continueProcessing();
		 return;
	       }
	       cb&&cb(data.event);
	       continueProcessing();
	     },
	     error: function(XMLHttpRequest, textStatus, errorThrown){
	       self.errors_occured++;
	       if(self.errors_occured>5){
		 self.showError('there was some error in getting'+
				'events from server');
	       }
	       setTimeout(continueProcessing, self.errors_occured*2000);
	     }
	   });
    function continueProcessing(){
      self.startProcessing(cb);
    }
  },
  process: function(event){
    var mate=null;
    switch(event.type){
    case "Message_event":
      mate=event.author;
      this.messages.add(new Message(mate, event.message, "message"));
      break;
    case "Typer_event":
       mate=event.author;
      this.messages.add(new Message(mate, event.message, "typer"));
      break;
    case "Mate_event":
      switch(event.status){
      case "enter":
	this.mates.add(event.mate);
	break;
      case "left":
	this.mates.remove(event.mate);
	break;
      default:
	throw "unsoported Mate_event type.";
      }
      break;
    default:
      throw "unsoported Event type.";
    }
    this.scrollDownChat();
  },
  showError: function(text){
    var message=
      new Message(null, text, "error");
    this.messages.add(message);
  },
  clearInputBox: function(){
    $("#input_line").attr("value","");
  },
  makeInputBox: function(cb){
    typer("#input_line", cb);
    $("#send_button").click(
      function(){
	cb($("#input_line").attr("value"), true);
      });
    return this;
  }
};

function Topic(text){
  var self=this;
  this.topic=text;
  this._obj=$("#topic");
  this.change(text);
  this.onChange=null;
  typer("#topic", function(value, isFinal){
	  self.onChange&&self.onChange(value, isFinal);
	});
  return this;
}

Topic.prototype={
  change: function(text){
    this._obj.attr("value", text);
  }
};

function Mates(mates){
  this.mates=mates;
  this._obj=$("#mates ul");
  this.draw();
  return this;
}

Mates.prototype={
  add: function(mate){
    this.mates.push(mate);
    this.draw();
  },
  remove: function(name){
    this.mates=$.grep(this.mates,
		      function(n, i){
			return n!=name;
		      });
    this.draw();
  },
  draw:function(){
    this._obj.empty();
    for(var i in this.mates){
      this._drawMate(this.mates[i]);
    }
  },
  _drawMate:function(mate){
    this._obj.append( $("<li/>").text(mate) );
  }
};

function Messages(){
  return this;
}

Messages.prototype={
  add:function(message){
    var li=message.getLi();
    switch(message.type){
    case "message":
      $("#"+"typer_"+message.from).remove();
      $("#chat").append(li.attr("id", message.id));
      break;
    case "typer":
      var old_typer=$("#"+"typer_"+message.from);
      li.attr("id", "typer_"+message.from);
      if(old_typer.length!=0){
	var prev=old_typer.prev();
	old_typer.after(li);
	old_typer.remove();
	setTimeout(
	  function(){
	    li.fadeOut("slow", function(){
			 li.remove();
		       });
		   }, 10000);
      }else{
	$("#typers").append(li);
      }
      break;
    case "error":
      $("#errors").append(li.attr("id", message.id));
      break;
    default:
      throw "Illigal message type";
    }
  }
};

/* @cb(value, isFinal) is called when on object with selector
 @id onKeypress event. isFinal is false if last key is
 not enter*/
function typer(id, cb){
  var timeout=-1;
  $(id).keydown(
    function(e){
      var value=$(id).attr("value");
      if(e.which==13){
      	if(value!=""){
	  if(timeout!=-1){
	    clearTimeout(timeout);
	  }
	  cb&&cb(value, true);
	}
      }
    });
  $(id).keyup(
    function(e){
      var value=$(id).attr("value");
      if(value!=""){
	if(timeout!=-1){
	  clearTimeout(timeout);
	}
	if(e.which!=13){
	  if(value.length%5==0){
	    cb&&cb(value, false);
	  }else{
	    timeout=setTimeout(
	      function(){
		cb&&cb(value, false);
	      }, 200);
	  }
	}
      }
    });
  $(id).change(
    function(){
      var value=$(id).attr("value");
      if(value!=""){
	cb&&cb(value, true);
      }
    });
}

$(function(){
    $("#enter_section form").submit(
      function(){
	$("#enter_section").hide();
	$("#connecting").show();
	new Room(document.location.pathname.slice(1),
		 $("#mate_name").attr("value"));
	return false;
      });
    $("#mate_name").focus();
  });
<commit_msg>main.js now can handle Topic_event<commit_after>/* message is representation of the event to be shown on the message board */
/* from may be null, in such case message will be treated as error */
/* types of message: typer, message, error */
function Message(from, text, type){
  this.from=from;
  this.text=text;
  this.type=type;
  this.id=name+text+type+Math.floor(Math.random()*100000);//dummy
  return this;
}

Message.prototype={
  /* returns li that will be inserted in proper place of the message board */
  getLi: function(){
    var li=$("<li/>")
      .addClass("message");
    if(this.from&&
       this.from!=null&&
       this.type!="error"){
      li.append(
	$("<span/>")
	  .addClass("from")
	  .text(this.from)
      );
    }
    li.append(
      $("<span/>")
	.addClass("message_body")
	.text(this.text)
    );
    return li;
  }
};

/* main object that represents room */
function Room(id, name){
  var self=this;
  this.room_id=id;
  /* that very man who is about to enter this room */
  this.self=null;
  /* session keeps session id that is needed in every network operation */
  this.session=null;
  /* queue of messages to be send over network */
  this.message_queue=[];
  /* true if request is being sent */
  this.request_processing=false;
  /* number of errors already accured while processing message */
  this.errors_occured=0;
  this.topic=new Topic("tst");
  this.messages=new Messages();
  this.mates=new Mates([]);
  this.makeInputBox(function(value, isFinal){
		      self.postMessage(value, isFinal);
		      if(isFinal){
			self.clearInputBox();
		      }
		    });
  this.topic.onChange=
    function(value, isFinal){
      console&&console.log(value+":"+isFinal);
    };
  $(window).unload(function(){self.leave();});
  this.enter(name,
	     function(){
	       self.getMates(
		 function(){
		   $("#connecting").hide();
		   $("#chat_section").show();
		   $("#input_line").focus();
		   self.startProcessing(
		     function(event){
		       self.process(event);
		     }
		   );
		 }
	       );
	     });
}

Room.prototype={
  scrollDownChat: function(){
    var height=$("#chat_section .chat_list *").height();
    $("#chat_section .chat_list").get(0).scrollTop=height;
  },
  /* enters to room and gets session id */
  enter: function(name, cb){
    var self=this;
    $.ajax({
	     type: "GET",
	     cache: false,
	     url: "/json/"+this.room_id+"/enter",
	     data: "name="+name,
	     dataType: "json",
	     success: function(data){
	       if(data.result!='ok'){
		 self.showError("Can't enter the room.");
		 return;
	       }
	       self.session=data.session;
	       cb&&cb();
	     },
	     error: function(XMLHttpRequest, textStatus, errorThrown){
	       self.showError("Can't enter the room. ("+textStatus+")");
	     }
	   });
  },
  leave: function(){
    $.ajax({
	     type: "GET",
	     cache: false,
	     url: "/json/"+this.room_id+"/leave",
	     data: "session="+this.session,
	     async: false
	   });
  },
  topicChange: function(topic){
    var self=this;
    this.message_queue
      .push({
	      /* this property is needed just for processQueue() */
	      typer: false,
	      /* standart jquery ajax properties */
	      type: "GET",
	      cache: false,
	      url: "/json/"+this.room_id+"/topic",
	      data: "session="+this.session+"&"+
		"message="+text,
	      dataType: "json",
	      success: function(data){
		if(data.result!='ok'){
		  self.showError("Can't change the topic.");
		}
		self.topic.change(text);
		if(self.message_queue.length==0){
		  self.request_processing=false;
		}else{
		  self.processQueue();
		}
	      },
	      error: function(XMLHttpRequest,
			      textStatus,
			      errorThrown){
		console&&console.log("Can't change topic.");
		self.showError("Can't change topic. ("+
			       textStatus+")");
			if(self.message_queue.length==0){
		  self.request_processing=false;
		}else{
		  self.processQueue();
		}
	      }
	    });
  },
  /* requests are processed one after another to not let the typer
   events come after real message, maybe there shoud be timeout for
   typer event */
  postMessage: function(text, isFinal){
    var self=this;
    var type= isFinal?"message":"typer";
    this.message_queue
      .push({
	      /* this property is needed just for processQueue() */
	      typer: isFinal,
	      /* standart jquery ajax properties */
	      type: "GET",
	      cache: false,
	      url: "/json/"+this.room_id+"/"+type,
	      data: "session="+this.session+"&"+
		"message="+text,
	      dataType: "json",
	      success: function(data){
		if(data.result!='ok'){
		  self.showError("Can't send the message.");
		}
		self.messages.add(new Message(self.self,
					      text,
					      type));
		self.scrollDownChat();
		if(self.message_queue.length==0){
		  self.request_processing=false;
		}else{
		  self.processQueue();
		}
	      },
	      error: function(XMLHttpRequest,
			      textStatus,
			      errorThrown){
		console&&console.log("Can't send the message.");
		self.showError("Can't send the message. ("+
			       textStatus+")");
			if(self.message_queue.length==0){
		  self.request_processing=false;
		}else{
		  self.processQueue();
		}
	      }
	    });
    this.processQueue();
  },
  processQueue: function(){
    if(!this.request_processing){
      this.request_processing=true;
      $.ajax(this.message_queue.shift());
      processQueue();
    }else{
      /* here we now that last element is unprocessed and if it */
      /* "typer" event we can remove it with new one. */
      if(this.message_queue.length>0 &&
	 this.message_queue[this.message_queue.length-1].typer){
	this.message_queue.pop();
      }
    }
  },
  getMates: function(cb){
    var self=this;
    $.ajax({
	     type: "GET",
	     cache: false,
	     url: "/json/"+this.room_id+"/mates",
	     data: "session="+this.session,
	     dataType: "json",
	     success: function(data){
	       if(data.result!='ok'){
		 self.showError("Can't load room mates.");
		 return;
	       }
	       for(var i in data.mates){
		 self.mates.add(data.mates[i]);
	       }
	       self.self=data.self_id;
	       cb&&cb();
	     },
	     error: function(XMLHttpRequest, textStatus, errorThrown){
	       self.showError("Can't load room mates. ("+textStatus+")");
	     }
	   });
  },
  startProcessing: function(cb){
    var self=this;
    $.ajax({
	     type: "GET",
	     url: "/json/"+this.room_id+"/get",
	     cache: false,
	     data: "session="+this.session,
	     dataType: "json",
	     success: function(data){
	       if(data.result=="timeout"){
		 continueProcessing();
		 return;
	       }
	       cb&&cb(data.event);
	       continueProcessing();
	     },
	     error: function(XMLHttpRequest, textStatus, errorThrown){
	       self.errors_occured++;
	       if(self.errors_occured>5){
		 self.showError('there was some error in getting'+
				'events from server');
	       }
	       setTimeout(continueProcessing, self.errors_occured*2000);
	     }
	   });
    function continueProcessing(){
      self.startProcessing(cb);
    }
  },
  process: function(event){
    var mate=null;
    switch(event.type){
    case "Message_event":
      mate=event.author;
      this.messages.add(new Message(mate, event.message, "message"));
      break;
    case "Typer_event":
       mate=event.author;
      this.messages.add(new Message(mate, event.message, "typer"));
      break;
    case "Topic_event":
      this.topic.change(event.message);
      break;
    case "Mate_event":
      switch(event.status){
      case "enter":
	this.mates.add(event.mate);
	break;
      case "left":
	this.mates.remove(event.mate);
	break;
      default:
	throw "unsoported Mate_event type.";
      }
      break;
    default:
      throw "unsoported Event type.";
    }
    this.scrollDownChat();
  },
  showError: function(text){
    var message=
      new Message(null, text, "error");
    this.messages.add(message);
  },
  clearInputBox: function(){
    $("#input_line").attr("value","");
  },
  makeInputBox: function(cb){
    typer("#input_line", cb);
    $("#send_button").click(
      function(){
	cb($("#input_line").attr("value"), true);
      });
    return this;
  }
};

function Topic(text){
  var self=this;
  this.topic=text;
  this._obj=$("#topic");
  this.change(text);
  this.onChange=null;
  typer("#topic", function(value, isFinal){
	  self.onChange&&self.onChange(value, isFinal);
	});
  return this;
}

Topic.prototype={
  change: function(text){
    this._obj.attr("value", text);
  }
};

function Mates(mates){
  this.mates=mates;
  this._obj=$("#mates ul");
  this.draw();
  return this;
}

Mates.prototype={
  add: function(mate){
    this.mates.push(mate);
    this.draw();
  },
  remove: function(name){
    this.mates=$.grep(this.mates,
		      function(n, i){
			return n!=name;
		      });
    this.draw();
  },
  draw:function(){
    this._obj.empty();
    for(var i in this.mates){
      this._drawMate(this.mates[i]);
    }
  },
  _drawMate:function(mate){
    this._obj.append( $("<li/>").text(mate) );
  }
};

function Messages(){
  return this;
}

Messages.prototype={
  add:function(message){
    var li=message.getLi();
    switch(message.type){
    case "message":
      $("#"+"typer_"+message.from).remove();
      $("#chat").append(li.attr("id", message.id));
      break;
    case "typer":
      var old_typer=$("#"+"typer_"+message.from);
      li.attr("id", "typer_"+message.from);
      if(old_typer.length!=0){
	var prev=old_typer.prev();
	old_typer.after(li);
	old_typer.remove();
	setTimeout(
	  function(){
	    li.fadeOut("slow", function(){
			 li.remove();
		       });
		   }, 10000);
      }else{
	$("#typers").append(li);
      }
      break;
    case "error":
      $("#errors").append(li.attr("id", message.id));
      break;
    default:
      throw "Illigal message type";
    }
  }
};

/* @cb(value, isFinal) is called when on object with selector
 @id onKeypress event. isFinal is false if last key is
 not enter*/
function typer(id, cb){
  var timeout=-1;
  $(id).keydown(
    function(e){
      var value=$(id).attr("value");
      if(e.which==13){
      	if(value!=""){
	  if(timeout!=-1){
	    clearTimeout(timeout);
	  }
	  cb&&cb(value, true);
	}
      }
    });
  $(id).keyup(
    function(e){
      var value=$(id).attr("value");
      if(value!=""){
	if(timeout!=-1){
	  clearTimeout(timeout);
	}
	if(e.which!=13){
	  if(value.length%5==0){
	    cb&&cb(value, false);
	  }else{
	    timeout=setTimeout(
	      function(){
		cb&&cb(value, false);
	      }, 200);
	  }
	}
      }
    });
  $(id).change(
    function(){
      var value=$(id).attr("value");
      if(value!=""){
	cb&&cb(value, true);
      }
    });
}

$(function(){
    $("#enter_section form").submit(
      function(){
	$("#enter_section").hide();
	$("#connecting").show();
	new Room(document.location.pathname.slice(1),
		 $("#mate_name").attr("value"));
	return false;
      });
    $("#mate_name").focus();
  });
<|endoftext|>


<commit_before>const ESCAPE_CHARS = /([\\`*_\[\]#])/g;

var escapeLinkText = function(text) {
  return text.replace(ESCAPE_CHARS, "\\$1");
};

var Markdown = {
  linkTo: function(title, url, options) {
    options = options || {};

    // used for copying link-in-image
    if (options.escape !== false) {
      options.escape = true;
    }

    if (title === undefined) {
      title = CopyAsMarkdown.getDefaultTitle();
    }

    if (options.escape) {
      title = escapeLinkText(title);
    }

    var result = "[" + title + "](" + url + ")";

    return result;
  },
  imageFor: function(title, url) {
    return "!["+title+"]("+url+")";
  }
};
<commit_msg>also escape < and ><commit_after>const ESCAPE_CHARS = /([\\`*_\[\]<>#])/g;

var escapeLinkText = function(text) {
  return text.replace(ESCAPE_CHARS, "\\$1");
};

var Markdown = {
  linkTo: function(title, url, options) {
    options = options || {};

    // used for copying link-in-image
    if (options.escape !== false) {
      options.escape = true;
    }

    if (title === undefined) {
      title = CopyAsMarkdown.getDefaultTitle();
    }

    if (options.escape) {
      title = escapeLinkText(title);
    }

    var result = "[" + title + "](" + url + ")";

    return result;
  },
  imageFor: function(title, url) {
    return "!["+title+"]("+url+")";
  }
};
<|endoftext|>


<commit_before>import * as PartTime from "./PartTime.js";
import * as ValueMax from "./ValueMax.js";

export {StringArrayToEntities, Layer};

/** Aggregate all progress about downloading, JSON parsing, characters scanning, and weights scanning.  */
class Progress extends ValueMax.Percentage.Aggregate {
  constructor() {
    let children = [
      new ValueMax.Percentage.Concrete(), // Increased when downloading from network.
      new ValueMax.Percentage.Concrete(), // Increased when parsing the downloaded data as JSON.
      new ValueMax.Percentage.Concrete(), // Increased when converting characters to weights.
      new ValueMax.Percentage.Concrete()  // Increased when converting weights to layers.
    ];

    super(children);

    [this.download, this.JSONParse, this.CharCount, this.WeightByteCount] = children;
  }
}

/**
 * Parsing and decoding string array to SeparableConv2d entities.
 *
 * @param  {string[]} encodedStringArray     Every string is an encoded entity.
 * @param  {number}   encodedWeightCharCount Every weight is encoded as string with this length. (e.g. 5 )
 * @param  {number}   encodedWeightBase      Every weight is encoded by this base number. (e.g. 2 or 10 or 16 or 36)
 * @param  {number}   weightValueOffset      The value will be subtracted from the integer weight value.
 * @param  {number}   weightValueDivisor     Divide the integer weight value by this value for converting to floating-point number.
 *
 * @param  {string}   htmlProgressTitle
 *   The title of HTMLProgressElement for reporting progress and onclick will be called when done. If null, no reporting.
 *
 * @return {Promise} A promise resolved as an Object[] which is the decoded entity for separableConv2d().
 *   The entity is an array of SeparableConv2d.Layer.
 */
function StringArrayToEntities(
  encodedStringArray, encodedWeightCharCount, encodedWeightBase, weightValueOffset, weightValueDivisor, htmlProgressTitle) {

  encodedWeightCharCount = Math.max(1, encodedWeightCharCount); /* At least one character for a weight. */

  const suspendWeightCount = 1000; /* Everytime so many weights decoded, yield for releasing CPU time. */
  const suspendLayerCount = 100;   /* Everytime so many layers created, yield for releasing CPU time. */

  /**
   * @param {integer} integerWeight The integer which will be converted to floating-point number by subtract and divide.
   * @return The floating-point number.
   */
  function integerToFloat(integerWeight) {
    return ( integerWeight - weightValueOffset ) / weightValueDivisor;
  }

  /** Input encodedString, yield progress, return integerWeights.
   *
   * @param {ValueMax.Percentage.Aggregate} progressToYield
   *   Return this when every time yield. Usually, this is the container of the progressToAdvance.
   *
   * @param {ValueMax.Percentage.Concrete}  progressToAdvance
   *   Increase this when every time advanced. It will be initialized to zero when decoder starting.
   */
  function* weightGenerator(encodedString, progressToYield, progressToAdvance) {
    let encodedWeightCount = Math.ceil(encodedString.length / encodedWeightCharCount);
    let integerWeights = new Float32Array( encodedWeightCount );

    let collectedCharCount = 0;
    let encodedWeight;
    let weightIndex = 0;
    let weightIndexAfterYield = 0;

    progressToAdvance.accumulation = 0;

    for (let encodedChar of encodedString) {
      progressToAdvance.accumulation++;

      if (0 == collectedCharCount)
        encodedWeight = encodedChar;
      else
        encodedWeight += encodedChar;

      collectedCharCount++;
      if (collectedCharCount < encodedWeightCharCount)
        continue; // Collect characters for one weight.

      collectedCharCount = 0;

      integerWeights[ weightIndex ] = parseInt(encodedWeight, encodedWeightBase); // Decode as integer.
      weightIndex++;
      weightIndexAfterYield++;

      if (weightIndexAfterYield >= suspendWeightCount) { // Every suspendWeightCount, release CPU time.
        yield progressToYield;
        weightIndexAfterYield = 0;
      }
    }

    yield progressToYield; // After weights of one entity converted to integer, release CPU time.
    return integerWeights;
  }

  /** Input integerWeights, yield progress, return entity.
   *
   * @param {ValueMax.Percentage.Aggregate} progressToYield
   *   Return this when every time yield. Usually, this is the container of the progressToAdvance.
   *
   * @param {ValueMax.Percentage.Concrete}  progressToAdvance
   *   Increase this when every time advanced. It will be initialized to zero when decoder starting.
   */
  function* entityGenerator(integerWeights, progressToYield, progressToAdvance) {
    let layerCountAfterYield = 0;

    progressToAdvance.accumulation = 0;

    let byteOffset = integerWeights.byteOffset; // Bounded by the integerWeights.
    let entity = [], inChannels = 4;            // Suppose the first layer's input channel count is always RGBA 4 channels.

    let legalByteOffsetEnd = integerWeights.byteOffset + integerWeights.byteLength;
    while ( byteOffset < legalByteOffsetEnd ) {

      let layer = new Layer(integerWeights, byteOffset, inChannels, integerToFloat);
      if (layer.isValid()) {	// Only collect valid layer.
        entity.push(layer);
        progressToAdvance.accumulation += layer.weightByteCount;
        inChannels = layer.params.outChannels;  // The next layer's input channel count is the previous layer's output channel count.
        byteOffset = layer.byteOffsetEnd;
      } else { // Discard invalid layer. Progress skip to the end of the weight array.
        progressToAdvance.accumulation += ( integerWeights.byteLength - byteOffset );
        byteOffset = legalByteOffsetEnd; // For stopping the loop.
      }

      layerCountAfterYield++; // Count even if layer invalid, because CPU time is still used.

      if (layerCountAfterYield >= suspendLayerCount) { // Every suspendLayerCount, release CPU time.
        yield progressToYield;
        layerCountAfterYield = 0;
      }
    }

    yield progressToYield; // After weights of one entity converted to layers, release CPU time.
    return entity;
  }

  /** Input encodedStringArray, yield progress, return entities. */
  function* entitiesGenerator() {

    let progress = new Progress();

    /* Estimate maximum volume for progress reporting. Parsing has two pass: one for converting characters to weight,
       another for converting weights to layer. So the total volume is sum of these two. */
    for (let encodedString of encodedStringArray) {
      progress.CharCount.total += encodedString.length;
      let encodedWeightByteCount = Math.ceil(encodedString.length / encodedWeightCharCount) * Float32Array.BYTES_PER_ELEMENT;
      progress.WeightByteCount.total += encodedWeightByteCount;
    }

    let entityCount = encodedStringArray.length;
    let entities =    new Array(entityCount);

    yield progress; /* Report initial progress after maximum volume calculated and first memory block allocated. */

    for (let i = 0; i < encodedStringArray.length; i++) {
      let encodedString = encodedStringArray[ i ];
      let integerWeights = yield* weightGenerator(encodedString, progress, progress.CharCount);
      let entity = yield* entityGenerator(integerWeights, progress, progress.WeightByteCount);
      entities[ i ] = entity;
    }
    return entities;
  }

  let progressReceiver = ValueMax.Receiver.HTMLProgress.createByTitle_or_getDummy(this.htmlProgressTitle);
  let theEntitiesGenerator = entitiesGenerator(encodedStringArray);
  let p = PartTime.forOf(theEntitiesGenerator, (valueMax) => {
    progressReceiver.setValueMax(valueMax); /* Report progress to UI. */
  }).then((doneValue) => {
    progressReceiver.informDone(doneValue); /* Inform UI progress done. */
    return doneValue; /* The doneValue will be the entities */
  });
  return p;
}

/**
 * A CNN layer contains three filters: depthwise, pointwise and bias.
 */
class Layer {

  /**
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number}       byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number}       inChannels
   *   The input channel count.
   *
   * @param {Function}     weightConverter
   *   A function which will be applied on every weight (e.g. integerToFloat, or floatToInteger). The result of the
   * function will replace the original value in the weights[] array. If null, there will be no converting.
   */
  constructor(inputFloat32Array, byteOffsetBegin, inChannels, weightConverter) {

    this.params = new Layer.Params(inputFloat32Array, byteOffsetBegin);
    if ( !this.params.isValid() )
      return;

    this.depthwise = new Layer.Filter(
      inputFloat32Array, this.params.byteOffsetEnd,
      [this.params.filterHeight, this.params.filterWidth, inChannels, this.params.channelMultiplier], weightConverter );

    if ( !this.depthwise.isValid() )
      return;

    this.pointwise = new Layer.Filter(
      inputFloat32Array, this.depthwise.byteOffsetEnd,
      [1, 1, inChannels * this.params.channelMultiplier, this.params.outChannels], weightConverter );

    if ( !this.pointwise.isValid() )
      return;

    this.bias = new Layer.Filter(
      inputFloat32Array, this.pointwise.byteOffsetEnd,
      [1, 1, this.params.outChannels], weightConverter );
  }

  isValid() {
    if ( this.bias )
      if ( this.bias.isValid() )
        return true;
    return false;
  }

  get byteOffsetBegin() { return this.params.byteOffsetBegin; }
  get byteOffsetEnd()   { return this.bias.byteOffsetEnd; }
}

/**
 * A class for the CNN (depthwise, pointwise and bias) filter.
 */
Layer.Filter = class {

  /**
   * There will be no filter (this.filter undefined and ( isValid() == false )) when shape is too large (or NaN).
   *
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number}       byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number[]}     shape
   *   The filter shape (element count for every dimension). The shape.length is dimension.
   *
   * @param {Function}     weightConverter
   *   A function which will be applied on every weight (e.g. integerToFloat, or floatToInteger). The result of the
   * function will replace the original value in the weights[] array. If null, there will be no converting.
   */
  constructor(inputFloat32Array, byteOffsetBegin, shape, weightConverter = null) {
    this.shape =           shape;
    let weightCount =      shape.reduce( ( accumulator, currentValue ) => accumulator * currentValue );
    let weightByteCount =  weightCount * Float32Array.BYTES_PER_ELEMENT;
    let byteOffsetEnd =    byteOffsetBegin + weightByteCount;  // Exclusive. As the next filter's begin.

    let legalByteOffsetEnd = inputFloat32Array.byteOffset + inputFloat32Array.byteLength;
    if ( byteOffsetEnd <= legalByteOffsetEnd ) {
      // Share the underlying array buffer. But be bounded by the inputFloat32Array.byteLength.
      this.filter = new Float32Array( inputFloat32Array.buffer, byteOffsetBegin, weightCount );

      if (weightConverter)
        this.filter.forEach((element, i, array) => array[ i ] = weightConverter(element)); // Convert weights.
    } else {
      // No filter when shape is too large (or NaN).
    }
  }

  isValid()              { return ( this.filter ) ? true : false; }
  get byteOffsetBegin()  { return this.filter.byteOffset; }
  get byteOffsetEnd()    { return this.byteOffsetBegin + this.weightByteCount; }
  get weightByteCount()  { return this.filter.byteLength; }
  get weightCount()      { return this.filter.length; }

}

/**
 * A class for the CNN layer parameters.
 */
Layer.Params = class extends Layer.Filter {
  /**
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number}       byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   */
  constructor(inputFloat32Array, byteOffsetBegin) {
    // Fixed 6 weights. And convert the value to positive integer.
    super( inputFloat32Array, byteOffsetBegin, [ 6 ], v => Math.abs(Math.trunc(v)) );
  }

  get filterHeight()      { return this.filter[ 0 ])); }
  get filterWidth()       { return this.filter[ 1 ])); }
  get channelMultiplier() { return this.filter[ 2 ])); }
  get dilationHeight()    { return this.filter[ 3 ])); }
  get dilationWidth()     { return this.filter[ 4 ])); }
  get outChannels()       { return this.filter[ 5 ])); }
}
<commit_msg>Update SeparableConv2d.js<commit_after>import * as PartTime from "./PartTime.js";
import * as ValueMax from "./ValueMax.js";

export {StringArrayToEntities, Layer};

/** Aggregate all progress about downloading, JSON parsing, characters scanning, and weights scanning.  */
class Progress extends ValueMax.Percentage.Aggregate {
  constructor() {
    let children = [
      new ValueMax.Percentage.Concrete(), // Increased when downloading from network.
      new ValueMax.Percentage.Concrete(), // Increased when parsing the downloaded data as JSON.
      new ValueMax.Percentage.Concrete(), // Increased when converting characters to weights.
      new ValueMax.Percentage.Concrete()  // Increased when converting weights to layers.
    ];

    super(children);

    [this.download, this.JSONParse, this.CharCount, this.WeightByteCount] = children;
  }
}

/**
 * Parsing and decoding string array to SeparableConv2d entities.
 *
 * @param  {string[]} encodedStringArray     Every string is an encoded entity.
 * @param  {number}   encodedWeightCharCount Every weight is encoded as string with this length. (e.g. 5 )
 * @param  {number}   encodedWeightBase      Every weight is encoded by this base number. (e.g. 2 or 10 or 16 or 36)
 * @param  {number}   weightValueOffset      The value will be subtracted from the integer weight value.
 * @param  {number}   weightValueDivisor     Divide the integer weight value by this value for converting to floating-point number.
 *
 * @param  {string}   htmlProgressTitle
 *   The title of HTMLProgressElement for reporting progress and onclick will be called when done. If null, no reporting.
 *
 * @return {Promise} A promise resolved as an Object[] which is the decoded entity for separableConv2d().
 *   The entity is an array of SeparableConv2d.Layer.
 */
function StringArrayToEntities(
  encodedStringArray, encodedWeightCharCount, encodedWeightBase, weightValueOffset, weightValueDivisor, htmlProgressTitle) {

  encodedWeightCharCount = Math.max(1, encodedWeightCharCount); /* At least one character for a weight. */

  const suspendWeightCount = 1000; /* Everytime so many weights decoded, yield for releasing CPU time. */
  const suspendLayerCount = 100;   /* Everytime so many layers created, yield for releasing CPU time. */

  /**
   * @param {integer} integerWeight The integer which will be converted to floating-point number by subtract and divide.
   * @return The floating-point number.
   */
  function integerToFloat(integerWeight) {
    return ( integerWeight - weightValueOffset ) / weightValueDivisor;
  }

  /** Input encodedString, yield progress, return integerWeights.
   *
   * @param {ValueMax.Percentage.Aggregate} progressToYield
   *   Return this when every time yield. Usually, this is the container of the progressToAdvance.
   *
   * @param {ValueMax.Percentage.Concrete}  progressToAdvance
   *   Increase this when every time advanced. It will be initialized to zero when decoder starting.
   */
  function* weightGenerator(encodedString, progressToYield, progressToAdvance) {
    let encodedWeightCount = Math.ceil(encodedString.length / encodedWeightCharCount);
    let integerWeights = new Float32Array( encodedWeightCount );

    let collectedCharCount = 0;
    let encodedWeight;
    let weightIndex = 0;
    let weightIndexAfterYield = 0;

    progressToAdvance.accumulation = 0;

    for (let encodedChar of encodedString) {
      progressToAdvance.accumulation++;

      if (0 == collectedCharCount)
        encodedWeight = encodedChar;
      else
        encodedWeight += encodedChar;

      collectedCharCount++;
      if (collectedCharCount < encodedWeightCharCount)
        continue; // Collect characters for one weight.

      collectedCharCount = 0;

      integerWeights[ weightIndex ] = parseInt(encodedWeight, encodedWeightBase); // Decode as integer.
      weightIndex++;
      weightIndexAfterYield++;

      if (weightIndexAfterYield >= suspendWeightCount) { // Every suspendWeightCount, release CPU time.
        yield progressToYield;
        weightIndexAfterYield = 0;
      }
    }

    yield progressToYield; // After weights of one entity converted to integer, release CPU time.
    return integerWeights;
  }

  /** Input integerWeights, yield progress, return entity.
   *
   * @param {ValueMax.Percentage.Aggregate} progressToYield
   *   Return this when every time yield. Usually, this is the container of the progressToAdvance.
   *
   * @param {ValueMax.Percentage.Concrete}  progressToAdvance
   *   Increase this when every time advanced. It will be initialized to zero when decoder starting.
   */
  function* entityGenerator(integerWeights, progressToYield, progressToAdvance) {
    let layerCountAfterYield = 0;

    progressToAdvance.accumulation = 0;

    let byteOffset = integerWeights.byteOffset; // Bounded by the integerWeights.
    let entity = [], inChannels = 4;            // Suppose the first layer's input channel count is always RGBA 4 channels.

    let legalByteOffsetEnd = integerWeights.byteOffset + integerWeights.byteLength;
    while ( byteOffset < legalByteOffsetEnd ) {

      let layer = new Layer(integerWeights, byteOffset, inChannels, integerToFloat);
      if (layer.isValid()) {	// Only collect valid layer.
        entity.push(layer);
        progressToAdvance.accumulation += layer.weightByteCount;
        inChannels = layer.params.outChannels;  // The next layer's input channel count is the previous layer's output channel count.
        byteOffset = layer.byteOffsetEnd;
      } else { // Discard invalid layer. Progress skip to the end of the weight array.
        progressToAdvance.accumulation += ( integerWeights.byteLength - byteOffset );
        byteOffset = legalByteOffsetEnd; // For stopping the loop.
      }

      layerCountAfterYield++; // Count even if layer invalid, because CPU time is still used.

      if (layerCountAfterYield >= suspendLayerCount) { // Every suspendLayerCount, release CPU time.
        yield progressToYield;
        layerCountAfterYield = 0;
      }
    }

    yield progressToYield; // After weights of one entity converted to layers, release CPU time.
    return entity;
  }

  /** Input encodedStringArray, yield progress, return entities. */
  function* entitiesGenerator() {

    let progress = new Progress();

    /* Estimate maximum volume for progress reporting. Parsing has two pass: one for converting characters to weight,
       another for converting weights to layer. So the total volume is sum of these two. */
    for (let encodedString of encodedStringArray) {
      progress.CharCount.total += encodedString.length;
      let encodedWeightByteCount = Math.ceil(encodedString.length / encodedWeightCharCount) * Float32Array.BYTES_PER_ELEMENT;
      progress.WeightByteCount.total += encodedWeightByteCount;
    }

    let entityCount = encodedStringArray.length;
    let entities =    new Array(entityCount);

    yield progress; /* Report initial progress after maximum volume calculated and first memory block allocated. */

    for (let i = 0; i < encodedStringArray.length; i++) {
      let encodedString = encodedStringArray[ i ];
      let integerWeights = yield* weightGenerator(encodedString, progress, progress.CharCount);
      let entity = yield* entityGenerator(integerWeights, progress, progress.WeightByteCount);
      entities[ i ] = entity;
    }
    return entities;
  }

  let progressReceiver = ValueMax.Receiver.HTMLProgress.createByTitle_or_getDummy(this.htmlProgressTitle);
  let theEntitiesGenerator = entitiesGenerator(encodedStringArray);
  let p = PartTime.forOf(theEntitiesGenerator, (valueMax) => {
    progressReceiver.setValueMax(valueMax); /* Report progress to UI. */
  }).then((doneValue) => {
    progressReceiver.informDone(doneValue); /* Inform UI progress done. */
    return doneValue; /* The doneValue will be the entities */
  });
  return p;
}

/**
 * A CNN layer contains one params (this.params) and three filters: depthwise, pointwise and bias.
 */
class Layer {

  /**
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights.
   *
   * @param {number}       byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {number}       inChannels
   *   The input channel count.
   *
   * @param {Function}     weightConverter
   *   A function which will be applied on every weight (e.g. integerToFloat, or floatToInteger). The result of the
   * function will replace the original value in the weights[] array. If null, there will be no converting.
   */
  constructor( inputFloat32Array, byteOffsetBegin, inChannels, weightConverter ) {

    this.params = new Layer.Params( inputFloat32Array, byteOffsetBegin );
    if ( !this.params.isValid() )
      return;

    this.depthwise = new Layer.Filter(
      inputFloat32Array, this.params.byteOffsetEnd, null, 0,
      [this.params.filterHeight, this.params.filterWidth, inChannels, this.params.channelMultiplier], weightConverter );

    if ( !this.depthwise.isValid() )
      return;

    this.pointwise = new Layer.Filter(
      inputFloat32Array, this.depthwise.byteOffsetEnd, null, 0,
      [1, 1, inChannels * this.params.channelMultiplier, this.params.outChannels], weightConverter );

    if ( !this.pointwise.isValid() )
      return;

    this.bias = new Layer.Filter(
      inputFloat32Array, this.pointwise.byteOffsetEnd, null, 0,
      [1, 1, this.params.outChannels], weightConverter );
  }

  isValid() {
    if ( this.bias )
      if ( this.bias.isValid() )
        return true;
    return false;
  }

  get byteOffsetBegin() { return this.params.byteOffsetBegin; }
  get byteOffsetEnd()   { return this.bias.byteOffsetEnd; }
}

/**
 * A class for the CNN (depthwise, pointwise and bias) filter.
 */
Layer.Filter = class {

  /**
   * Create Float32Array weights[] over the primaryInput (or secondaryInput) according to the specific
   * byteOffsetBegin, shape, and weightConverter.
   *
   * @param {Float32Array} primaryInput
   *   The primary input Float32Array. It can not be null. Its byteOffset will be checked against
   * primaryByteOffsetBegin. Its content will be interpret as weights if secondaryInput is null.
   * Otherwise, its content will be ignored if secondaryInput is not null.
   *
   * @param {number}       primaryByteOffsetBegin
   *   The position to start to decode from the inputPrimary. This is relative to the primaryInput.buffer
   * (not to the primaryInput.byteOffset). If this value less than primaryInput.byteOffset, the
   * initialization will fail (i.e. ( isValid() == false ) ).
   *
   * @param {Float32Array} secondaryInput
   *   The secondary input Float32Array. It can be null. If not null, its content will be interpret as weights and
   * the content of inputPrimary will be ignored.
   *
   * @param {number}       secondaryByteOffsetBegin
   *   The position to start to decode from the inputSecondary. This is relative to the secondaryInput.buffer
   * (not to the secondaryInput.byteOffset). If this value less than secondaryInput.byteOffset, the
   * initialization will fail (i.e. ( isValid() == false ) ).
   *
   * @param {number[]}     shape
   *   The filter shape (element count for every dimension). The shape.length is dimension. The initialization will
   * fail (i.e. ( isValid() == false ) ) if shape is too large (or NaN) (exceeds the primaryInput (or, inputSecondary
   * if not null) bounding).
   *
   * @param {Function}     weightConverter
   *   A function which will be applied on every weight (e.g. integerToFloat, or floatToInteger). The result of the
   * function will replace the original value in the weights[] array. If null, there will be no converting.
   */
  constructor(
    primaryInput, primaryByteOffsetBegin, secondaryInput, secondaryByteOffsetBegin, shape, weightConverter = null ) {

    this.primaryInput =   primaryInput;
    this.secondaryInput = secondaryInput;
    this.shape =          shape;

    if ( null == primaryInput )
      return;  // Failed, if no primary input.
    if ( primaryByteOffsetBegin < primaryInput.byteOffset )
      return;  // Failed, if the primary beginning position is illegal (less than bounding).
    if ( secondaryInput && ( secondaryByteOffsetBegin < secondaryInput.byteOffset ) )
      return;  // Failed, if the secondary beginning position is illegal (less than bounding).

    let weightCount =     shape.reduce( ( accumulator, currentValue ) => accumulator * currentValue );
    let weightByteCount = weightCount * Float32Array.BYTES_PER_ELEMENT;

    let input, byteOffsetBegin, byteOffsetEnd;
    if ( null == secondaryInput ) {
      input =                         primaryInput;
      byteOffsetBegin =
      this.primaryByteOffsetBegin =   primaryByteOffsetBegin;
      byteOffsetEnd =
      this.primaryByteOffsetEnd =     primaryByteOffsetBegin + weightByteCount;    // Exclusive. As the next filter's begin.
      this.secondaryByteOffsetBegin = 0;
      this.secondaryByteOffsetEnd =   0;
    } else {
      input =                         secondaryInput;
      this.primaryByteOffsetBegin =   0;
      this.primaryByteOffsetEnd =     0;
      byteOffsetBegin =
      this.secondaryByteOffsetBegin = secondaryByteOffsetBegin;
      byteOffsetEnd =
      this.secondaryByteOffsetEnd =   secondaryByteOffsetBegin + weightByteCount;  // Exclusive. As the next filter's begin.
    }

    let legalByteOffsetEnd = input.byteOffset + input.byteLength;

    if ( byteOffsetEnd > legalByteOffsetEnd )
      return;  // Failed, if shape is too large (or NaN).

    // Share the underlying array buffer. But be bounded by the input.byteLength.
    this.weights = new Float32Array( input.buffer, byteOffsetBegin, weightCount );

    if (weightConverter)  // Convert weights.
      this.weights.forEach( ( element, i, array ) => array[ i ] = weightConverter( element ) );
  }

  isValid()                      { return ( this.weights ) ? true : false; }

  get weightByteCount()          { return this.weights.byteLength; }
  get weightCount()              { return this.weights.length; }
}

/**
 * A class for the CNN separable convolution (2D) layer parameters.
 */
Layer.Params = class extends Layer.Filter {

  /**
   * @param {Float32Array} inputFloat32Array
   *   A Float32Array whose values will be interpret as weights. The weights will be convert to positive integer.
   *
   * @param {number}       byteOffsetBegin
   *   The position to start to decode from the inputFloat32Array. This is relative to the inputFloat32Array.buffer
   * (not to the inputFloat32Array.byteOffset).
   *
   * @param {Array} fixedWeights
   *   If not null, it will be used instead of the super.weights[]. It should have 6 elements: [ filterHeight,
   * filterWidth, channelMultiplier, dilationHeight, dilationWidth, outChannels ].
   */
  constructor( inputFloat32Array, byteOffsetBegin, fixedWeights = null ) {

    function toPositiveInteger( v ) {
      return Math.abs( Math.trunc( v ) );
    }

    let secondaryInput;
    if ( fixedWeights )
      secondaryInput = new Float32Array( fixedWeights );  // Convert to Float32Array.

    // Extract 6 weights from inputFloat32Array or fixedWeights, and convert the values to positive integer.
    super( inputFloat32Array, byteOffsetBegin, secondaryInput, 0, [ 6 ], toPositiveInteger );
  }

  get filterHeight()      { return this.weights[ 0 ])); }
  get filterWidth()       { return this.weights[ 1 ])); }
  get channelMultiplier() { return this.weights[ 2 ])); }
  get dilationHeight()    { return this.weights[ 3 ])); }
  get dilationWidth()     { return this.weights[ 4 ])); }
  get outChannels()       { return this.weights[ 5 ])); }
}
<|endoftext|>


<commit_before>/* Handles interaction between view and model
 */

/// requires
var Player = require("./players"),
	settings = require("./settings"),
	Turn = require("./turn");

/// object
function Controller(board, view) {
	/// public variables
	this.board = board;
	this.view = view;

	// the turn that this client is taking right now
	this.currentTurn = null;

	// the direction the player has chosen to kick the puck
	this.kickDirection = null;

	// an object containing all the event listeners that have been applied to
	// the current state, as well as handlers that are called when they occur
	this.listeners = {};

	// whether or not a message is currently being displayed in the UI
	this.messageShowing = false;

	// an array containing the valid positions the puck can be kicked to
	this.puckTrajectory = null;

	// the actor that is currently highlighted by the UI
	this.selectedActor = null;

	// an object containing all of the possible events in the game, held under
	// the UI state that they should be
	this.states = {
		// events that all states should be subscribed to regardless of UI state
		"global": {
			"close message": function () {
				this.messageShowing = false;
			},

			"message": function (data) {
				this.view.message(data);
				this.messageShowing = true;
			},

			"window resize": function () {
				this.clearUIState();
				this.view.display.resizeBoard();

				if (this.messageShowing) {
					this.view.resizeMessage();
				}

				// resize dialog windows
				this.view.resizeDialogsWindows();
			}
		},

		// when the player is placing the puck...
		"placing puck": {
			"click tile": function (data) {
				var pos = data.element.data("position"),
					self = this,
					tile = this.board.tile(pos);

				if (this.validPuckPositions.indexOf(tile) >= 0) {
					this.board.placePuck(pos);
					this.view.display.createPuck(settings.animationSpeed);

					setTimeout(function () {
						self.view.display.clearPuckGhost();
					}, settings.animationSpeed);

					this.view.events.listenToPuckEvents();
					this.emit("placed puck");
					this.setUIState("playing round");

					// record puck placement
					this.currentTurn.recordMove(this.board.puck, null, pos);
				}
			},

			"destroy state": function () {
				this.view.display.enableActorMouseEvents();
			},

			"init state": function () {
				this.validPuckPositions = this.board.getValidPuckPositions();
				this.view.display.disableActorMouseEvents();

				this.emit("message", {
					life: "placed puck",
					message: "Place the puck."
				});

				this.currentTurn = new Turn(this, this.board.settings.owner);
				this.currentTurn.recordMove("Begin round", null, null);
				this.view.showTurnState(this.board.settings.owner);
			},

			"mouse enter tile": function (data) {
				var pos = data.element.data("position"),
					tile = this.board.tile(pos),
					validPos = this.validPuckPositions.indexOf(tile) >= 0;

				if (tile.type !== "wall" && !validPos) {
					this.view.display.highlightTile(tile, false);
				}

				if (validPos) {
					this.view.display.showPuckGhost(pos);
				}
			},

			"mouse exit tile": function () {
				this.view.display.clearPuckGhost();
				this.view.display.unhighlightTile();
			}
		},

		// after the player has placed the puck and is actually playing a round
		"playing round": {
			"click actor": function (data) {
				var actor = this.board.actors[data.element.data("actor")];

				// if the actor that has been clicked on is the currently
				// selected actor, don't do anything
				if (actor === this.selectedActor) {
					this.clearUIState();
					return false;
				}

				this.clearUIState();

				// prevent the player from moving actors that do not belong to
				// them
				if (actor.owner !== this.board.settings.owner) {
					return false;
				}

				// display the clicked actor as selected
				this.selectActor(actor);
			},

			"click puck": function () {
				this.selectPuck();
			},

			"click tile": function (data) {
				var actor,
					index,
					oldPos,
					pos = data.element.data("position"),
					tile = this.view.display.tiles[pos.x][pos.y];

				// if the tile is clicked and it is a valid move, have the
				// selected actor move to that tile
				if (tile.validMove) {
					actor = this.selectedActor;
					index = this.selectedActorIndex;
					oldPos = {
						x: actor.x,
						y: actor.y
					};

					// clear the UI
					this.clearUIState();

					// move the actor in the model
					actor.move(this.board.tiles[pos.x][pos.y]);

					// move the actor in the view
					this.view.display.moveActor(index, function () {
						// and record this move in the current turn once the
						// move is done rendering
						this.currentTurn.recordMove(actor, oldPos, pos);
					}.bind(this));
				} else if (tile.validKickDirection) {
					this.clearUIState();

					this.kickDirection = tile;
					this.projectKickTrajectory();
				} else if (tile.validKick) {
					oldPos = {
						x: this.board.puck.x,
						y: this.board.puck.y
					};

					this.kickPuck(this.board.tiles[pos.x][pos.y], function () {
						this.currentTurn.recordMove(this.board.puck,
							oldPos, pos);
					}.bind(this));

					this.clearUIState();
				} else {
					this.clearUIState();
				}
			},

			// when this client finishes making a turn, send the turn data to
			// the server -- err, that is, do nothing for now, until the server
			// is implemented
			//TODO: check turn.js:finish (this is non-emulated player move)
			"finish turn": function (turn, scored) {
				// get the turn data in serialized form, ready to be passed to
				// the server
				// var turnData = turn.serialize();

				// pass the turn data to the server

				// and store this turn on the turns list
				this.turns.push(turn);

				// now that we've saved this turn, it is no longer current, so
				// create a new one and pass control of the board to the other
				// player
				this.currentTurn = new Turn(this, Player.Two);
				this.board.settings.owner = Player.Two;
				this.view.showTurnState(Player.Two);

				// if this turn ended by a player scoring, we need to reset the
				// board to allow for a new round to start, and we need to
				// update the score on the screen
				if (scored) {
					var score = this.turns.slice(-1)[0].score();

					// display updated scores
					this.view.updateScore(score);

					// is game won
					if(Math.max(score.player1, score.player2) >= settings.game.scoreToWin) {
						setTimeout(function () {
							this.setUIState('game inactive');
							this.emit('game won', score);
						}.bind(this));
					}else{
						// reset the board
						// this.reset();
					}
				}
			},

			// create a new turn from the data that has been (ostensibly)
			// received from the server
			//TODO: check turn.js:finish (this is emulated server move)
			"receive turn": function (data, scored) {
				var turn = new Turn(this, Player.Two);
				turn.deserialize(data);

				// ordinarily we would want to call turn.display to render the
				// turn to this client, now that it's just been received from
				// the server -- however, because we're faking it and allowing
				// both players to make turns from this one client, it will have
				// already been displayed in the UI, and so there's no reason to
				// try to show it again

				// turn.display();

				this.turns.push(turn);

				// create a new turn and allow the other player to move
				this.currentTurn = new Turn(this, Player.One);
				this.board.settings.owner = Player.One;
				this.view.showTurnState(Player.One);

				// if this turn ended by a player scoring, we need to reset the
				// board to allow for a new round to start, and we need to
				// update the score on the screen
				if (scored) {
					var score = this.turns.slice(-1)[0].score();

					// display updated scores
					this.view.updateScore(score);

					// is game won
					if(Math.max(score.player1, score.player2) >= settings.game.scoreToWin) {
						setTimeout(function () {
							this.setUIState('game inactive');
							this.emit('game won', score);
						}.bind(this));
					}else{
						// reset the board
						this.reset();
					}
				}
			},

			"mouse enter tile": function (data) {
				var pos = data.element.data("position"),
					tile = this.view.display.tiles[pos.x][pos.y];

				if (tile.validMove ||
					tile.validKickDirection ||
					tile.validKick) {

					this.view.display.highlightTile(tile, true);
				}
			},

			"mouse exit tile": function () {
				this.view.display.unhighlightTile();
			},

			"redo": function () {
				// if the current turn is not our turn, don't try to redo it
				if (this.currentTurn.owner !== this.board.settings.owner) {
					return;
				}

				this.currentTurn.redoMove();
			},

			"undo": function () {
				// if the current turn is not our turn, don't try to undo it
				if (this.currentTurn.owner !== this.board.settings.owner) {
					return;
				}

				this.currentTurn.undoMove();
			}
		},

		// state when game is not active
		// for example game finished or not started
		"game inactive": {
			"game won": function (scores) {
				this.view.gameWon(scores);
			},

			"click new game": function () {
				this.view.newGameClicked();
			},

			"click mode 2p local": function () {
				this.view.hideWelcomeWindow();
				this.resetGame();
				/*setTimeout(function () {
					this.setUIState("placing puck");
				}.bind(this));*/
			}
		}
	};

	// an array containing all of the turns that have taken place so far, both
	// turns that this client has made as well as all turns received from the
	// server -- the length of this array represents the amount of turns so far
	this.turns = [];
}

/// public functions
Controller.prototype = {
	addListener: function (eventName, callback) {
		var listener;

		callback = callback.bind(this);

		// if there is no callback we may be being passed an object full of
		// listeners; if so, iterate through them and add each one individually
		if (!callback) {
			for (listener in eventName) {
				this.addListener(listener, eventName[listener]);
			}

			return;
		}

		// add a single event listener to the list without overriding any other
		// event listeners
		if (this.listeners[eventName]) {
			this.listeners[eventName].push(callback);
		} else {
			this.listeners[eventName] = [callback];
		}

		return callback;
	},

	setUIState: function (stateName) {
		var eventName;

		// destroy the old state so we can build the new one
		this.emit("destroy state");
		this.clearListeners();
		this.clearUIState();

		// apply all global listeners, the ones that should apply to all states
		for (eventName in this.states.global) {
			this.addListener(eventName, this.states.global[eventName]);
		}

		// and add the listeners that are specific to this state
		for (eventName in this.states[stateName]) {
			this.addListener(eventName, this.states[stateName][eventName]);
		}

		this.emit("init state");
	},

	clearListeners: function () {
		this.listeners = {};
	},

	clearUIState: function () {
		this.view.display.deselectActor(this.selectedActorIndex);
		this.view.display.clearKickDirections();
		this.view.display.clearKickTrajectory();
		this.view.display.unhighlightTile();

		this.puckSelected = false;
		this.selectedActor = null;
		this.kickDirection = null;
		this.puckTrajectory = null;
	},

	emit: function (eventName) {
		var i;

		if (!this.listeners[eventName]) {
			return;
		}

		for (i = 0; i < this.listeners[eventName].length; i++) {
			this.listeners[eventName][i].apply(this,
				Array.prototype.slice.apply(arguments).slice(1));
		}
	},

	kickPuck: function (target, callback) {
		var trajectory = this.puckTrajectory
			.splice(0, this.puckTrajectory.indexOf(target) + 1);

		this.board.puck.kick(trajectory[trajectory.length - 1]);
		this.view.display.showKick(trajectory, callback);
	},

	projectKickTrajectory: function () {
		this.puckTrajectory = this.board.puck.calculateTrajectory(
			this.kickDirection);

		// if we have only one available move on selected trajectory
		// then don't show single dot, just move there
		if(this.puckTrajectory.length == 1) {
			this.kickPuck(this.puckTrajectory[0], function () {
				//this.currentTurn.recordMove(this.board.puck, oldPos, pos); //TODO recordMove when "undo system" will me made
			});
			return;
		}

		this.view.display.showKickTrajectory(this.puckTrajectory);
	},

	removeListener: function (eventName, callback) {
		var index,
			listeners = this.listeners[eventName];

		if (listeners) {
			index = listeners.indexOf(callback);

			if (index > -1) {
				listeners.splice(index, 1);
			}
		}
	},

	// reset the board after finishing a round
	reset: function () {
		setTimeout(function () {
			this.board.clear();
			this.view.display.clear();

			this.board.placeActors(window.globalVariables
				.mockServerResponse.actors);

			this.view.reshowGame();
			this.view.events.listenToActorEvents();
			this.setUIState("placing puck");
		}.bind(this));
	},

	// reset the game after one of player won
	resetGame: function () {
		// clear turns of previous game
		this.turns = [];

		// clear displayed score
		var scores = {};
		scores[Player.One] = '0';
		scores[Player.Two] = '0';
		this.view.updateScore(scores);

		// new round for new clean game
		this.reset();
	},

	// select an actor and cause the available positions to move to be shown
	selectActor: function (actor) {
		this.selectedActor = actor;
		this.selectedActorIndex = this.board.actors.indexOf(actor);

		this.view.display.selectActor(this.selectedActor);
	},

	selectPuck: function () {
		if (this.puckSelected) {
			this.clearUIState();
			return;
		}

		this.clearUIState();

		this.puckSelected = true;
		this.view.display.showKickDirections(this.board.puck
			.kickDirections(this.board.settings.owner));
	}
};

/// exports
module.exports = Controller;<commit_msg>Bug fix: white's goal is not reseting state machine<commit_after>/* Handles interaction between view and model
 */

/// requires
var Player = require("./players"),
	settings = require("./settings"),
	Turn = require("./turn");

/// object
function Controller(board, view) {
	/// public variables
	this.board = board;
	this.view = view;

	// the turn that this client is taking right now
	this.currentTurn = null;

	// the direction the player has chosen to kick the puck
	this.kickDirection = null;

	// an object containing all the event listeners that have been applied to
	// the current state, as well as handlers that are called when they occur
	this.listeners = {};

	// whether or not a message is currently being displayed in the UI
	this.messageShowing = false;

	// an array containing the valid positions the puck can be kicked to
	this.puckTrajectory = null;

	// the actor that is currently highlighted by the UI
	this.selectedActor = null;

	// an object containing all of the possible events in the game, held under
	// the UI state that they should be
	this.states = {
		// events that all states should be subscribed to regardless of UI state
		"global": {
			"close message": function () {
				this.messageShowing = false;
			},

			"message": function (data) {
				this.view.message(data);
				this.messageShowing = true;
			},

			"window resize": function () {
				this.clearUIState();
				this.view.display.resizeBoard();

				if (this.messageShowing) {
					this.view.resizeMessage();
				}

				// resize dialog windows
				this.view.resizeDialogsWindows();
			}
		},

		// when the player is placing the puck...
		"placing puck": {
			"click tile": function (data) {
				var pos = data.element.data("position"),
					self = this,
					tile = this.board.tile(pos);

				if (this.validPuckPositions.indexOf(tile) >= 0) {
					this.board.placePuck(pos);
					this.view.display.createPuck(settings.animationSpeed);

					setTimeout(function () {
						self.view.display.clearPuckGhost();
					}, settings.animationSpeed);

					this.view.events.listenToPuckEvents();
					this.emit("placed puck");
					this.setUIState("playing round");

					// record puck placement
					this.currentTurn.recordMove(this.board.puck, null, pos);
				}
			},

			"destroy state": function () {
				this.view.display.enableActorMouseEvents();
			},

			"init state": function () {
				this.validPuckPositions = this.board.getValidPuckPositions();
				this.view.display.disableActorMouseEvents();

				this.emit("message", {
					life: "placed puck",
					message: "Place the puck."
				});

				this.currentTurn = new Turn(this, this.board.settings.owner);
				this.currentTurn.recordMove("Begin round", null, null);
				this.view.showTurnState(this.board.settings.owner);
			},

			"mouse enter tile": function (data) {
				var pos = data.element.data("position"),
					tile = this.board.tile(pos),
					validPos = this.validPuckPositions.indexOf(tile) >= 0;

				if (tile.type !== "wall" && !validPos) {
					this.view.display.highlightTile(tile, false);
				}

				if (validPos) {
					this.view.display.showPuckGhost(pos);
				}
			},

			"mouse exit tile": function () {
				this.view.display.clearPuckGhost();
				this.view.display.unhighlightTile();
			}
		},

		// after the player has placed the puck and is actually playing a round
		"playing round": {
			"click actor": function (data) {
				var actor = this.board.actors[data.element.data("actor")];

				// if the actor that has been clicked on is the currently
				// selected actor, don't do anything
				if (actor === this.selectedActor) {
					this.clearUIState();
					return false;
				}

				this.clearUIState();

				// prevent the player from moving actors that do not belong to
				// them
				if (actor.owner !== this.board.settings.owner) {
					return false;
				}

				// display the clicked actor as selected
				this.selectActor(actor);
			},

			"click puck": function () {
				this.selectPuck();
			},

			"click tile": function (data) {
				var actor,
					index,
					oldPos,
					pos = data.element.data("position"),
					tile = this.view.display.tiles[pos.x][pos.y];

				// if the tile is clicked and it is a valid move, have the
				// selected actor move to that tile
				if (tile.validMove) {
					actor = this.selectedActor;
					index = this.selectedActorIndex;
					oldPos = {
						x: actor.x,
						y: actor.y
					};

					// clear the UI
					this.clearUIState();

					// move the actor in the model
					actor.move(this.board.tiles[pos.x][pos.y]);

					// move the actor in the view
					this.view.display.moveActor(index, function () {
						// and record this move in the current turn once the
						// move is done rendering
						this.currentTurn.recordMove(actor, oldPos, pos);
					}.bind(this));
				} else if (tile.validKickDirection) {
					this.clearUIState();

					this.kickDirection = tile;
					this.projectKickTrajectory();
				} else if (tile.validKick) {
					oldPos = {
						x: this.board.puck.x,
						y: this.board.puck.y
					};

					this.kickPuck(this.board.tiles[pos.x][pos.y], function () {
						this.currentTurn.recordMove(this.board.puck,
							oldPos, pos);
					}.bind(this));

					this.clearUIState();
				} else {
					this.clearUIState();
				}
			},

			// when this client finishes making a turn, send the turn data to
			// the server -- err, that is, do nothing for now, until the server
			// is implemented
			//TODO: check turn.js:finish (this is non-emulated player move)
			"finish turn": function (turn, scored) {
				// get the turn data in serialized form, ready to be passed to
				// the server
				// var turnData = turn.serialize();

				// pass the turn data to the server

				// and store this turn on the turns list
				this.turns.push(turn);

				// now that we've saved this turn, it is no longer current, so
				// create a new one and pass control of the board to the other
				// player
				this.currentTurn = new Turn(this, Player.Two);
				this.board.settings.owner = Player.Two;
				this.view.showTurnState(Player.Two);

				// if this turn ended by a player scoring, we need to reset the
				// board to allow for a new round to start, and we need to
				// update the score on the screen
				if (scored) {
					var score = this.turns.slice(-1)[0].score();

					// display updated scores
					this.view.updateScore(score);

					// is game won
					if(Math.max(score.player1, score.player2) >= settings.game.scoreToWin) {
						setTimeout(function () {
							this.setUIState('game inactive');
							this.emit('game won', score);
						}.bind(this));
					}else{
						// reset the board
						this.reset();
					}
				}
			},

			// create a new turn from the data that has been (ostensibly)
			// received from the server
			//TODO: check turn.js:finish (this is emulated server move)
			"receive turn": function (data, scored) {
				var turn = new Turn(this, Player.Two);
				turn.deserialize(data);

				// ordinarily we would want to call turn.display to render the
				// turn to this client, now that it's just been received from
				// the server -- however, because we're faking it and allowing
				// both players to make turns from this one client, it will have
				// already been displayed in the UI, and so there's no reason to
				// try to show it again

				// turn.display();

				this.turns.push(turn);

				// create a new turn and allow the other player to move
				this.currentTurn = new Turn(this, Player.One);
				this.board.settings.owner = Player.One;
				this.view.showTurnState(Player.One);

				// if this turn ended by a player scoring, we need to reset the
				// board to allow for a new round to start, and we need to
				// update the score on the screen
				if (scored) {
					var score = this.turns.slice(-1)[0].score();

					// display updated scores
					this.view.updateScore(score);

					// is game won
					if(Math.max(score.player1, score.player2) >= settings.game.scoreToWin) {
						setTimeout(function () {
							this.setUIState('game inactive');
							this.emit('game won', score);
						}.bind(this));
					}else{
						// reset the board
						this.reset();
					}
				}
			},

			"mouse enter tile": function (data) {
				var pos = data.element.data("position"),
					tile = this.view.display.tiles[pos.x][pos.y];

				if (tile.validMove ||
					tile.validKickDirection ||
					tile.validKick) {

					this.view.display.highlightTile(tile, true);
				}
			},

			"mouse exit tile": function () {
				this.view.display.unhighlightTile();
			},

			"redo": function () {
				// if the current turn is not our turn, don't try to redo it
				if (this.currentTurn.owner !== this.board.settings.owner) {
					return;
				}

				this.currentTurn.redoMove();
			},

			"undo": function () {
				// if the current turn is not our turn, don't try to undo it
				if (this.currentTurn.owner !== this.board.settings.owner) {
					return;
				}

				this.currentTurn.undoMove();
			}
		},

		// state when game is not active
		// for example game finished or not started
		"game inactive": {
			"game won": function (scores) {
				this.view.gameWon(scores);
			},

			"click new game": function () {
				this.view.newGameClicked();
			},

			"click mode 2p local": function () {
				this.view.hideWelcomeWindow();
				this.resetGame();
				/*setTimeout(function () {
					this.setUIState("placing puck");
				}.bind(this));*/
			}
		}
	};

	// an array containing all of the turns that have taken place so far, both
	// turns that this client has made as well as all turns received from the
	// server -- the length of this array represents the amount of turns so far
	this.turns = [];
}

/// public functions
Controller.prototype = {
	addListener: function (eventName, callback) {
		var listener;

		callback = callback.bind(this);

		// if there is no callback we may be being passed an object full of
		// listeners; if so, iterate through them and add each one individually
		if (!callback) {
			for (listener in eventName) {
				this.addListener(listener, eventName[listener]);
			}

			return;
		}

		// add a single event listener to the list without overriding any other
		// event listeners
		if (this.listeners[eventName]) {
			this.listeners[eventName].push(callback);
		} else {
			this.listeners[eventName] = [callback];
		}

		return callback;
	},

	setUIState: function (stateName) {
		var eventName;

		// destroy the old state so we can build the new one
		this.emit("destroy state");
		this.clearListeners();
		this.clearUIState();

		// apply all global listeners, the ones that should apply to all states
		for (eventName in this.states.global) {
			this.addListener(eventName, this.states.global[eventName]);
		}

		// and add the listeners that are specific to this state
		for (eventName in this.states[stateName]) {
			this.addListener(eventName, this.states[stateName][eventName]);
		}

		this.emit("init state");
	},

	clearListeners: function () {
		this.listeners = {};
	},

	clearUIState: function () {
		this.view.display.deselectActor(this.selectedActorIndex);
		this.view.display.clearKickDirections();
		this.view.display.clearKickTrajectory();
		this.view.display.unhighlightTile();

		this.puckSelected = false;
		this.selectedActor = null;
		this.kickDirection = null;
		this.puckTrajectory = null;
	},

	emit: function (eventName) {
		var i;

		if (!this.listeners[eventName]) {
			return;
		}

		for (i = 0; i < this.listeners[eventName].length; i++) {
			this.listeners[eventName][i].apply(this,
				Array.prototype.slice.apply(arguments).slice(1));
		}
	},

	kickPuck: function (target, callback) {
		var trajectory = this.puckTrajectory
			.splice(0, this.puckTrajectory.indexOf(target) + 1);

		this.board.puck.kick(trajectory[trajectory.length - 1]);
		this.view.display.showKick(trajectory, callback);
	},

	projectKickTrajectory: function () {
		this.puckTrajectory = this.board.puck.calculateTrajectory(
			this.kickDirection);

		// if we have only one available move on selected trajectory
		// then don't show single dot, just move there
		if(this.puckTrajectory.length == 1) {
			this.kickPuck(this.puckTrajectory[0], function () {
				//this.currentTurn.recordMove(this.board.puck, oldPos, pos); //TODO recordMove when "undo system" will me made
			});
			return;
		}

		this.view.display.showKickTrajectory(this.puckTrajectory);
	},

	removeListener: function (eventName, callback) {
		var index,
			listeners = this.listeners[eventName];

		if (listeners) {
			index = listeners.indexOf(callback);

			if (index > -1) {
				listeners.splice(index, 1);
			}
		}
	},

	// reset the board after finishing a round
	reset: function () {
		setTimeout(function () {
			this.board.clear();
			this.view.display.clear();

			this.board.placeActors(window.globalVariables
				.mockServerResponse.actors);

			this.view.reshowGame();
			this.view.events.listenToActorEvents();
			this.setUIState("placing puck");
		}.bind(this));
	},

	// reset the game after one of player won
	resetGame: function () {
		// clear turns of previous game
		this.turns = [];

		// clear displayed score
		var scores = {};
		scores[Player.One] = '0';
		scores[Player.Two] = '0';
		this.view.updateScore(scores);

		// new round for new clean game
		this.reset();
	},

	// select an actor and cause the available positions to move to be shown
	selectActor: function (actor) {
		this.selectedActor = actor;
		this.selectedActorIndex = this.board.actors.indexOf(actor);

		this.view.display.selectActor(this.selectedActor);
	},

	selectPuck: function () {
		if (this.puckSelected) {
			this.clearUIState();
			return;
		}

		this.clearUIState();

		this.puckSelected = true;
		this.view.display.showKickDirections(this.board.puck
			.kickDirections(this.board.settings.owner));
	}
};

/// exports
module.exports = Controller;<|endoftext|>


<commit_before>// CONFIG (Move to different file)
var endpoint = {
  points: 'points'
};

var appUrl = window.location.href + 'api/';

// END CONFIG

var request = function (options) {
  var xhr = new XMLHttpRequest();
  xhr.open(options.method, options.url);
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.addEventListener('load', function(event){
    options.callback(JSON.parse(event.target.responseText));
  });
  xhr.send(JSON.stringify(options.data || {}));
};
///////////////////////////////////////////////////

function initialize() {
  var mapCanvas = document.getElementById('map_canvas');
  var mapOptions = {
    center: new google.maps.LatLng(44.5403, -78.5463),
    zoom: 8,
    mapTypeId: google.maps.MapTypeId.HYBRID
  };
  var map = new google.maps.Map(mapCanvas, mapOptions);
  window.map = map;
}

function submitLocation(data, cb) {
  var options = {
    method: 'POST',
    url: appUrl + endpoint.points,
    data: data,
    callback: cb
  };
  request(options);
}

if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(
    function(position) {
    var callback = function(data) {
      var loc = new google.maps.LatLng(data.loc.coordinates[0], data.loc.coordinates[1]);
      map.setCenter(loc);
    };
    var data = {
      name: new Date().toString(),
      coordinates: [position.coords.latitude, position.coords.longitude]
    };
    submitLocation(data, callback);
  },
  function(error) {
    console.log(error);
  },{ timeout: 30000, enableHighAccuracy:false}
  );
} else {
  console.log('Permission denied');
}

initialize();
<commit_msg>Retrieve points and add markers<commit_after>// CONFIG (Move to different file)
var endpoint = {
  points: 'points'
};

var markerImage = 'http://www.meadowsfield.com/wp-content/uploads/2013/09/map-icon-64.png';

var appUrl = window.location.href + 'api/';

// END CONFIG

var request = function (options) {
  var xhr = new XMLHttpRequest();
  xhr.open(options.method, options.url);
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.addEventListener('load', function(event){
    options.callback(JSON.parse(event.target.responseText));
  });
  xhr.send(JSON.stringify(options.data || {}));
};
///////////////////////////////////////////////////

function initialize() {
  var mapCanvas = document.getElementById('map_canvas');
  mapCanvas.style.height = window.innerHeight + 'px';
  var mapOptions = {
    center: new google.maps.LatLng(44.5403, -78.5463),
    zoom: 10,
    mapTypeId: google.maps.MapTypeId.HYBRID
  };
  var map = new google.maps.Map(mapCanvas, mapOptions);
  window.map = map;
}

function getPoints(){
  var options = {
    method: 'GET',
    url: appUrl + endpoint.points,
    callback: function(data) {
      var points = convertPoints(data);
      points.forEach(function(point) {
        addMarker(point);
      });
    }
  };
  request(options);
}

function convertPoints(points){
  return points.map(function(point){
    return {position: new google.maps.LatLng(point.loc.coordinates[0], point.loc.coordinates[1]),
            icon: markerImage,
            map: map
    };
  });
}

function addMarker(feature) {
  var marker = new google.maps.Marker({
    position: feature.position,
    icon: feature.icon,
    map: map
  });
}

function submitLocation(data, cb) {
  var options = {
    method: 'POST',
    url: appUrl + endpoint.points,
    data: data,
    callback: cb
  };
  request(options);
}

if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(
    function(position) {
    var callback = function(data) {
      var loc = new google.maps.LatLng(data.loc.coordinates[0], data.loc.coordinates[1]);
      map.setCenter(loc);
      getPoints();
    };
    var data = {
      name: new Date().toString(),
      coordinates: [position.coords.latitude, position.coords.longitude]
    };
    submitLocation(data, callback);
  },
  function(error) {
    console.log(error);
  },{ timeout: 30000, enableHighAccuracy:false}
  );
} else {
  console.log('Permission denied');
}

initialize();
<|endoftext|>


<commit_before>// Declare ALL vars in this file
// Add subsequent files to Gruntfile AFTER this file
"use strict";
var YRM = YRM || {};
YRM.controller = {};

YRM.globs = {
	apiRoot: '/YRM2015/build/api/',
	speakerCount: 17,
	eventLength: 4
};

YRM.controller.common = {

	/**
	 * Initialise the main controller and
	 * start the page specific task checker
	 */
	init: function() {
		var me = this;
		$(document).ready(function(){
			YRM.controller.tasks.runner();
			YRM.controller.common.runner();
		});
	},

	runner: function() {
		var views = YRM.views;

		// Add the tooltips
		views.addToolTips();
	},

}
YRM.controller.tasks = {

	/**
	 * The central runner for the tasks
	 * for various pages
	 */
	runner: function() {
		var page = $('.container-common')[0]?$('.container-common')[0].id:undefined || $('.home')[0].id;

		// Switch the id of the current container
		// to find out which task to run.
		switch(page) {
			case 'home':
				this.home();
				break;
			case 'participants':
				this.participants();
				break;
			case 'speakers':
				this.speakers();
				break;
			case 'track':
				YRM.controller.router.trackInit();
				break
			case 'schedule':
				this.schedule();
			case 'registration':
				this.registration();
		}
	},

	/**
	 * Request the names from the API
	 * and pass each one to the
	 * rendering function in the views.
	 */
	participants: function() {
		$.getJSON(YRM.globs.apiRoot + 'getRegistrants.php', function(participants) {

			// If there are no participants
			// then display an alternate string
			if(participants.length === 0) {
				YRM.views.renderParticipants(null, true)
				return;
			}
			for (var i = participants.length - 1; i >= 0; i--) {
				YRM.views.renderParticipants(participants[i])
			};
		})
	},

	/**
	 * Main task for the home screen
	 */
	home: function() {

		// Starts the counters
		$.getJSON(YRM.globs.apiRoot + 'getRegistrants.php', function(participants) {
			var anCntrs = YRM.views.animateCounters;

			// Participant Number
			anCntrs($('#participant-no'), participants.length);

			// Speaker Number
			anCntrs($('#speaker-no'), YRM.globs.speakerCount);

			// Day Number
			anCntrs($('#day-no'), YRM.globs.eventLength);

		})
		.error(function(err) {
			$('#participant-no').html('a lot')
			YRM.views.animateCounters($('#speaker-no'), YRM.globs.speakerCount)
			YRM.views.animateCounters($('#day-no'), YRM.globs.eventLength)
		})

	},

	speakers: function(packet) {

		// Check to see if a packet has been
		// passed in to the func
		var packet = packet || {};

		// If there is a filter set up the var
		// for use in the API call
		var filter = packet.filter || "";

		$.getJSON(YRM.globs.apiRoot + 'getSpeakers.php' + filter, function(speakers) {

			for (var i = speakers.length - 1; i >= 0; i--) {

				// For each speaker, render the speaker
				YRM.views.renderSpeakers(speakers[i]);

			};

			packet.callbackParams = packet.callbackParams || {};

			packet.callback ? packet.callback(speakers, packet.callbackParams) : null;
		})
	},

	schedule: function() {

		$.getJSON(YRM.globs.apiRoot + 'getSpeakers.php', function(speakers) {

			var list = $('ul.schedule-track-list');
			var speakersLen = speakers.length;

			// Loop through the speakers adding each
			// seperate track to the list
			for (var i = speakersLen - 1; i >= 0; i--) {

				// Sanity check
				if(!speakers[i].subjects) continue;

				var subjects = speakers[i].subjects;
				var subjectsLen = subjects.length;

				for (var j = subjectsLen - 1; j >= 0; j--) {

					// Append the HTML with the track in
					// onto the list on the schedule page
					list.append(
						"
" + subjects[j] +""
					);
				};
			};

		});

	},

	registration: function() {
		$.getJSON(YRM.globs.apiRoot + 'getConferenceDinnerCount.php', function(res) {
			var state = res.status, count = res.count;

			if(count > 100) {
				YRM.views.renderDinnerText(0, 0);
			} else {
				YRM.views.renderDinnerText(1, 100 - count);
			}
		})
	}
}

YRM.controller.router = {

	trackInit: function() {

		// Get the track name using the hash
		var track = document.location.hash.replace("#", "");

		// Split by - to get each word
		var trackWords    = track.split("-"),
			trackWordsLen = trackWords.length,
			exceptionWords;

		exceptionWords = ['and'];

		// Uppercase the first letter of each word
		for (var i = trackWordsLen - 1; i >= 0; i--) {
			if(exceptionWords.indexOf(trackWords[i]) === -1) {
				trackWords[i] = trackWords[i][0].toUpperCase() + trackWords[i].substr(1);
			}
		};

		var trackWordsJoined = "?subject=" + trackWords.join("%20");

		YRM.controller.tasks.speakers({
			filter: trackWordsJoined,
			callbackParams: trackWords.join(' '),
			callback: YRM.views.track
		});

		window.addEventListener('hashchange', function() {
			document.location.reload();
		})

	}

}

<commit_msg>Participant count is now always participantCount + 23 to compensate for speakers etc, who do not register<commit_after>// Declare ALL vars in this file
// Add subsequent files to Gruntfile AFTER this file
"use strict";
var YRM = YRM || {};
YRM.controller = {};

YRM.globs = {
	apiRoot: '/YRM2015/build/api/',
	speakerCount: 17,
	eventLength: 4
};

YRM.controller.common = {

	/**
	 * Initialise the main controller and
	 * start the page specific task checker
	 */
	init: function() {
		var me = this;
		$(document).ready(function(){
			YRM.controller.tasks.runner();
			YRM.controller.common.runner();
		});
	},

	runner: function() {
		var views = YRM.views;

		// Add the tooltips
		views.addToolTips();
	},

}
YRM.controller.tasks = {

	/**
	 * The central runner for the tasks
	 * for various pages
	 */
	runner: function() {
		var page = $('.container-common')[0]?$('.container-common')[0].id:undefined || $('.home')[0].id;

		// Switch the id of the current container
		// to find out which task to run.
		switch(page) {
			case 'home':
				this.home();
				break;
			case 'participants':
				this.participants();
				break;
			case 'speakers':
				this.speakers();
				break;
			case 'track':
				YRM.controller.router.trackInit();
				break
			case 'schedule':
				this.schedule();
			case 'registration':
				this.registration();
		}
	},

	/**
	 * Request the names from the API
	 * and pass each one to the
	 * rendering function in the views.
	 */
	participants: function() {
		$.getJSON(YRM.globs.apiRoot + 'getRegistrants.php', function(participants) {

			// If there are no participants
			// then display an alternate string
			if(participants.length === 0) {
				YRM.views.renderParticipants(null, true)
				return;
			}
			for (var i = participants.length - 1; i >= 0; i--) {
				YRM.views.renderParticipants(participants[i])
			};
		})
	},

	/**
	 * Main task for the home screen
	 */
	home: function() {

		// Starts the counters
		$.getJSON(YRM.globs.apiRoot + 'getRegistrants.php', function(participants) {
			var anCntrs = YRM.views.animateCounters;

			// Participant Number (23 extra to compensate for speakers etc)
			anCntrs($('#participant-no'), participants.length + 23);

			// Speaker Number
			anCntrs($('#speaker-no'), YRM.globs.speakerCount);

			// Day Number
			anCntrs($('#day-no'), YRM.globs.eventLength);

		})
		.error(function(err) {
			$('#participant-no').html('a lot')
			YRM.views.animateCounters($('#speaker-no'), YRM.globs.speakerCount)
			YRM.views.animateCounters($('#day-no'), YRM.globs.eventLength)
		})

	},

	speakers: function(packet) {

		// Check to see if a packet has been
		// passed in to the func
		var packet = packet || {};

		// If there is a filter set up the var
		// for use in the API call
		var filter = packet.filter || "";

		$.getJSON(YRM.globs.apiRoot + 'getSpeakers.php' + filter, function(speakers) {

			for (var i = speakers.length - 1; i >= 0; i--) {

				// For each speaker, render the speaker
				YRM.views.renderSpeakers(speakers[i]);

			};

			packet.callbackParams = packet.callbackParams || {};

			packet.callback ? packet.callback(speakers, packet.callbackParams) : null;
		})
	},

	schedule: function() {

		$.getJSON(YRM.globs.apiRoot + 'getSpeakers.php', function(speakers) {

			var list = $('ul.schedule-track-list');
			var speakersLen = speakers.length;

			// Loop through the speakers adding each
			// seperate track to the list
			for (var i = speakersLen - 1; i >= 0; i--) {

				// Sanity check
				if(!speakers[i].subjects) continue;

				var subjects = speakers[i].subjects;
				var subjectsLen = subjects.length;

				for (var j = subjectsLen - 1; j >= 0; j--) {

					// Append the HTML with the track in
					// onto the list on the schedule page
					list.append(
						"
" + subjects[j] +""
					);
				};
			};

		});

	},

	registration: function() {
		$.getJSON(YRM.globs.apiRoot + 'getConferenceDinnerCount.php', function(res) {
			var state = res.status, count = res.count;

			if(count > 100) {
				YRM.views.renderDinnerText(0, 0);
			} else {
				YRM.views.renderDinnerText(1, 100 - count);
			}
		})
	}
}

YRM.controller.router = {

	trackInit: function() {

		// Get the track name using the hash
		var track = document.location.hash.replace("#", "");

		// Split by - to get each word
		var trackWords    = track.split("-"),
			trackWordsLen = trackWords.length,
			exceptionWords;

		exceptionWords = ['and'];

		// Uppercase the first letter of each word
		for (var i = trackWordsLen - 1; i >= 0; i--) {
			if(exceptionWords.indexOf(trackWords[i]) === -1) {
				trackWords[i] = trackWords[i][0].toUpperCase() + trackWords[i].substr(1);
			}
		};

		var trackWordsJoined = "?subject=" + trackWords.join("%20");

		YRM.controller.tasks.speakers({
			filter: trackWordsJoined,
			callbackParams: trackWords.join(' '),
			callback: YRM.views.track
		});

		window.addEventListener('hashchange', function() {
			document.location.reload();
		})

	}

}

<|endoftext|>


<commit_before>'use strict';
requirejs.config({

	baseUrl:'/js',
	paths:{

		'text': '../lib/require/text',
		'jquery': '../lib/jquery/jquery',
		'jquery-ui': '../lib/jquery/jquery-ui-1.10.2.min',
		'angular': '../lib/angular/angular',
		'angular-resource': '../lib/angular/angular-resource',
		'angular-route': '../lib/angular/angular-route',
		'angular-cookies':'../lib/angular/angular-cookies',
		'angular-ui-router':'../lib/angular/angular-ui-router.min',
		'angular-ui-bootstrap':'../lib/angular/ui-bootstrap-tpls-0.10.0.min',
		'angular-file-upload':'../lib/angular/angular-file-upload.min',
		'library': '../lib',

		'poll': 'modules/poll',
		'security': 'modules/security',
		'course': 'modules/course',
		'lecture': 'modules/lecture',
		'lapp':	'modules/lapp',
		'rtcCtrl': 'rtcCtrl',

		'domReady': '../lib/require/domReady',
		'chart': '../lib/chart/chart',
		'angular-google-chart': '../lib/angular-google-chart/ng-google-chart',
		'bootstrap': '//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min',

	},

	shim:{
		'angular':{
			deps:['jquery'],
			exports:'angular'
		},
		'jquery-ui': {
			deps: ['jquery']
		},
		'angular-resource':{
			deps:['angular']
		},
		'angular-route':{
			deps:['angular']
		},
		'angular-cookies':{
			deps:['angular']
		},
		'angular-ui-router':{
			deps:['angular']
		},
		'angular-ui-bootstrap':{
			deps:['angular', 'jquery']
		},

		'angular-file-upload':{
			deps:['angular']
		},

		'access-config':{
			deps:['jquery'],
			exports:'accessConfig'
		},
		'app':{
			deps:['angular']
		},
		'auth':{
			deps:['angular']
		},

		'angular-google-chart':{
			deps: ['angular']
		},
		'bootstrap':{
			deps:['jquery'],
		},
	}
});

requirejs( [
		'text',
		'jquery',
		'angular',
		'jquery-ui',

		'angular-resource',
		'angular-route',
		'angular-cookies',
		'angular-ui-router',
		'angular-ui-bootstrap',
		'angular-file-upload',
		'app',
		'routes',
		'bootstrap',
	],

	function (text, $, angular) {

		'use strict';

	     require(['domReady!'], function (document) {
	    	 try {
	    	        // Wrap this call to try/catch
	    		 angular.bootstrap(document, ['kaisquare']);
	    	 }
	    	 catch (e) {
	    	     console.error(e.stack || e.message || e);
	    	 }

	     });

	}
);
<commit_msg>Chart Upcase fix<commit_after>'use strict';
requirejs.config({

	baseUrl:'/js',
	paths:{

		'text': '../lib/require/text',
		'jquery': '../lib/jquery/jquery',
		'jquery-ui': '../lib/jquery/jquery-ui-1.10.2.min',
		'angular': '../lib/angular/angular',
		'angular-resource': '../lib/angular/angular-resource',
		'angular-route': '../lib/angular/angular-route',
		'angular-cookies':'../lib/angular/angular-cookies',
		'angular-ui-router':'../lib/angular/angular-ui-router.min',
		'angular-ui-bootstrap':'../lib/angular/ui-bootstrap-tpls-0.10.0.min',
		'angular-file-upload':'../lib/angular/angular-file-upload.min',
		'library': '../lib',

		'poll': 'modules/poll',
		'security': 'modules/security',
		'course': 'modules/course',
		'lecture': 'modules/lecture',
		'lapp':	'modules/lapp',
		'rtcCtrl': 'rtcCtrl',

		'domReady': '../lib/require/domReady',
		'chart': '../lib/chart/Chart',
		'angular-google-chart': '../lib/angular-google-chart/ng-google-chart',
		'bootstrap': '//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min',

	},

	shim:{
		'angular':{
			deps:['jquery'],
			exports:'angular'
		},
		'jquery-ui': {
			deps: ['jquery']
		},
		'angular-resource':{
			deps:['angular']
		},
		'angular-route':{
			deps:['angular']
		},
		'angular-cookies':{
			deps:['angular']
		},
		'angular-ui-router':{
			deps:['angular']
		},
		'angular-ui-bootstrap':{
			deps:['angular', 'jquery']
		},

		'angular-file-upload':{
			deps:['angular']
		},

		'access-config':{
			deps:['jquery'],
			exports:'accessConfig'
		},
		'app':{
			deps:['angular']
		},
		'auth':{
			deps:['angular']
		},

		'angular-google-chart':{
			deps: ['angular']
		},
		'bootstrap':{
			deps:['jquery'],
		},
	}
});

requirejs( [
		'text',
		'jquery',
		'angular',
		'jquery-ui',

		'angular-resource',
		'angular-route',
		'angular-cookies',
		'angular-ui-router',
		'angular-ui-bootstrap',
		'angular-file-upload',
		'app',
		'routes',
		'bootstrap',
	],

	function (text, $, angular) {

		'use strict';

	     require(['domReady!'], function (document) {
	    	 try {
	    	        // Wrap this call to try/catch
	    		 angular.bootstrap(document, ['kaisquare']);
	    	 }
	    	 catch (e) {
	    	     console.error(e.stack || e.message || e);
	    	 }

	     });

	}
);
<|endoftext|>