<commit_before>/**
 * Expose an element as a view accessible by URL hash/fragment.
 *
 * @license MIT (See LICENSE for details)
 * @author  Rasmus Andersson <http://hunch.se/>
 */
jQuery.fn.expose = function(path, handler, add_switch_handler) {
	var did_have_index = false;
	var r = this.each(function(){
		jQuery.exposed.expose(this, path, handler, add_switch_handler, function(ent){
			did_have_index = true;
		});
	});

	// make sure we get the onhashchange events
	if (jQuery.exposed.initialized === true && !jQuery.exposed._observeHashChange() && did_have_index)
		jQuery.exposed.onHashChange();

	return r;
};

/**
 * Expose the app -- call this once when your app is starting (after you've set
 * up all possible entry routes).
 */
jQuery.expose = function(path, handler, add_switch_handler) {
	if (path === undefined) {
		// no arguments: initialize
		if (jQuery.exposed.initialized === true)
			return;
		jQuery.exposed.initialized = true;
		jQuery.exposed._observeHashChange();
	}
	else {
		// expose a handler without attaching it to an element
		if (typeof path !== 'string' && jQuery.exposed._isRegExpObject(path) === false)
			throw new Error('first argument to jQuery.expose must be a string or a RegExp object');
		if (typeof handler !== 'function')
			throw new Error('second argument to jQuery.expose must be a function');
		// when exposing w/o an element, the default value of add_switch_handler is false
		if (add_switch_handler === undefined)
			add_switch_handler = false;
		jQuery.exposed.expose(null, path, handler, add_switch_handler);
	}
	return jQuery;
}

/**
 * This is where mappings and per-session functionality lives.
 */
jQuery.exposed = {
	/** Map of path => element ( => [handler, ...] ) */
	routes: [],

	/** Current path */
	path: '',

	/** jQuery.expose been called? */
	initialized: false,

	expose: function(element, path, handler, add_switch_handler, index_cb) {
		var entry = null;

		// parse arguments
		if (typeof element !== 'object')
			element = null;
		if (add_switch_handler === undefined)
			add_switch_handler = true;

		// already exists?
		if (element !== null) {
			for (var i in jQuery.exposed.routes) {
				var e = jQuery.exposed.routes[i];
				if (e.element === element) {
					entry = e;
					break;
				}
			}
		}

		// create entry if not exists
		if (entry === null) {
			entry = {
				paths: [],
				handlers: [],
				element: element,
				args: []
			};
			jQuery.exposed.routes.push(entry);
		}

		// add path and handler
		var did_set_path = false;
		if (typeof path === 'function' && typeof path.test !== 'function') {
			entry.handlers.push(function(){ return path.call(elem); });
		}
		else {
			if (path !== undefined &&
			    (typeof path === 'string' || typeof path.test === 'function'))
			{
				did_set_path = true;
				if (typeof path === 'string')
					path = path.replace(/^#/, '');
				entry.paths.push(path);
			}
			if (typeof handler === 'function')
				entry.handlers.push(handler);
		}

		// set path from id (or last resort: "")
		if (did_set_path === false) {
			path = (element.id !== undefined) ? element.id : '';
			if (typeof path === 'string')
				path = path.replace(/^#/, '');
			entry.paths.push(path);
		}

		// path is index?
		if ((path === '' || (typeof path.test === 'function' && path.test('') === true))
			&& typeof index_cb === 'function')
		{
			index_cb(entry);
		}

		// add switch handler
		if (add_switch_handler)
			entry.handlers.push(jQuery.exposed.switchHandler);

		return entry;
	},

	_isRegExpObject: function(x) {
		return (typeof x === 'function' && typeof x.test === 'function');
	},

	/**
	 * The handler which is responsible for switching views.
	 *
	 * You can simply replace this with your own, before calling
	 * expose() on anything.
	 */
	switchHandler: function() {
		var had_visible = false;
		var q = null;
		for (var k in jQuery.exposed.routes) {
			var r = jQuery.exposed.routes[k];
			if (q === null)
				q = $(r.element);
			else
				q = q.add(r.element);
		}
		q = q.filter(':visible').not(this);
		if (q.length) {
			var inelem = this;
			q.fadeOut(100, function(){
				$(inelem).fadeIn(100);
			});
		}
		else {
			$(this).fadeIn(200);
		}
	},

	resolve: function(path) {
		var x = {
			route: null,
			route404: null,
			args: []
		};
		for (var k in jQuery.exposed.routes) {
			var r = jQuery.exposed.routes[k];
			for (var i in r.paths) {
				var pa = r.paths[i];
				var t = typeof pa;
				if (t === 'string') {
					if (pa === path) {
						x.route = r;
						if (r.args)
							x.args = x.args.concat(r.args); // avoid reference
						break;
					}
					else if (pa === '404') {
						x.route404 = r;
					}
				}
				else {
					var m = path.match(pa);
					//console.log(path, pa, m);
					if (m && m.length) {
						x.route = r;
						if (m.length > 1)
							x.args = m.slice(1, m.length);
						if (r.args)
							x.args = x.args.concat(r.args);
						break;
					}
				}
			}
		}
		return x;
	},

	triggerRoute: function(x) {
		// no joy
		if (x.route === null) {
			if (x.route404 !== null)
				x.route = x.route404;
			else
				return;
		}

		// call handlers
		for (var k in x.route.handlers) {
			try {
				x.route.handlers[k].apply(x.route.element, x.args);
			} catch (e) {
				if (window.console !== undefined)
					console.log('[exposed] error when calling handler', x.route.handlers[k], e);
				throw e;
			}
		}
	},

	routeCurrentPath: function() {
		jQuery.exposed.path = decodeURIComponent(document.location.hash.substr(1));
		return jQuery.exposed.resolve(jQuery.exposed.path);
	},

	/**
	 * Called when location.hash has changed.
	 */
	onHashChange: function(ev) {
		var x = jQuery.exposed.routeCurrentPath();
		jQuery.exposed.triggerRoute(x);
	},

	/// Abstraction of location.hash observation
	_observeHashChange: function() {
		if (jQuery.exposed._prevhash !== undefined)
			return false;
		jQuery.exposed._prevhash = '';
		if ("onhashchange" in window) {
			$(window).bind('hashchange', jQuery.exposed.onHashChange);
		}
		else {
			setInterval(function(){
				if (jQuery.exposed._prevhash != document.location.hash){
					jQuery.exposed._prevhash = document.location.hash;
					jQuery.exposed.onHashChange();
				}
			}, 50);
		}
		if (document.location.hash === '' || document.location.hash != jQuery.exposed._prevhash)
			jQuery.exposed.onHashChange();
		return true;
	}
};
<commit_msg>using constructor comparison to tell if something is a RegExp<commit_after>/**
 * Expose an element as a view accessible by URL hash/fragment.
 *
 * @license MIT (See LICENSE for details)
 * @author  Rasmus Andersson <http://hunch.se/>
 */
jQuery.fn.expose = function(path, handler, add_switch_handler) {
	var did_have_index = false;
	var r = this.each(function(){
		jQuery.exposed.expose(this, path, handler, add_switch_handler, function(ent){
			did_have_index = true;
		});
	});

	// make sure we get the onhashchange events
	if (jQuery.exposed.initialized === true && !jQuery.exposed._observeHashChange() && did_have_index)
		jQuery.exposed.onHashChange();

	return r;
};

/**
 * Expose the app -- call this once when your app is starting (after you've set
 * up all possible entry routes).
 */
jQuery.expose = function(path, handler, add_switch_handler) {
	if (path === undefined) {
		// no arguments: initialize
		if (jQuery.exposed.initialized === true)
			return;
		jQuery.exposed.initialized = true;
		jQuery.exposed._observeHashChange();
	}
	else {
		// expose a handler without attaching it to an element
		if (typeof path !== 'string' && path.constructor !== RegExp)
			throw new Error('first argument to jQuery.expose must be a string or a RegExp object');
		if (typeof handler !== 'function')
			throw new Error('second argument to jQuery.expose must be a function');
		// when exposing w/o an element, the default value of add_switch_handler is false
		if (add_switch_handler === undefined)
			add_switch_handler = false;
		jQuery.exposed.expose(null, path, handler, add_switch_handler);
	}
	return jQuery;
}

/**
 * This is where mappings and per-session functionality lives.
 */
jQuery.exposed = {
	/** Map of path => element ( => [handler, ...] ) */
	routes: [],

	/** Current path */
	path: '',

	/** jQuery.expose been called? */
	initialized: false,

	expose: function(element, path, handler, add_switch_handler, index_cb) {
		var entry = null;

		// parse arguments
		if (typeof element !== 'object')
			element = null;
		if (add_switch_handler === undefined)
			add_switch_handler = true;

		// already exists?
		if (element !== null) {
			for (var i in jQuery.exposed.routes) {
				var e = jQuery.exposed.routes[i];
				if (e.element === element) {
					entry = e;
					break;
				}
			}
		}

		// create entry if not exists
		if (entry === null) {
			entry = {
				paths: [],
				handlers: [],
				element: element,
				args: []
			};
			jQuery.exposed.routes.push(entry);
		}

		// add path and handler
		var did_set_path = false;
		if (typeof path === 'function' && path.constructor !== RegExp) {
			entry.handlers.push(function(){ return path.call(elem); });
		}
		else if (path !== undefined) {
			if (typeof path === 'string' || path.constructor === RegExp) {
				did_set_path = true;
				if (typeof path === 'string')
					path = path.replace(/^#/, '');
				entry.paths.push(path);
			}
			if (typeof handler === 'function')
				entry.handlers.push(handler);
		}

		// set path from id (or last resort: "")
		if (did_set_path === false) {
			path = (element.id !== undefined) ? element.id : '';
			if (typeof path === 'string')
				path = path.replace(/^#/, '');
			entry.paths.push(path);
		}

		// path is index?
		if ((path === '' || (typeof path.test === 'function' && path.test('') === true))
			&& typeof index_cb === 'function')
		{
			index_cb(entry);
		}

		// add switch handler
		if (add_switch_handler)
			entry.handlers.push(jQuery.exposed.switchHandler);

		return entry;
	},

	/**
	 * The handler which is responsible for switching views.
	 *
	 * You can simply replace this with your own, before calling
	 * expose() on anything.
	 */
	switchHandler: function() {
		var had_visible = false;
		var q = null;
		for (var k in jQuery.exposed.routes) {
			var r = jQuery.exposed.routes[k];
			if (q === null)
				q = $(r.element);
			else
				q = q.add(r.element);
		}
		q = q.filter(':visible').not(this);
		if (q.length) {
			var inelem = this;
			q.fadeOut(100, function(){
				$(inelem).fadeIn(100);
			});
		}
		else {
			$(this).fadeIn(200);
		}
	},

	resolve: function(path) {
		var x = {
			route: null,
			route404: null,
			args: []
		};
		for (var k in jQuery.exposed.routes) {
			var r = jQuery.exposed.routes[k];
			for (var i in r.paths) {
				var pa = r.paths[i];
				var t = typeof pa;
				if (t === 'string') {
					if (pa === path) {
						x.route = r;
						if (r.args)
							x.args = x.args.concat(r.args); // avoid reference
						break;
					}
					else if (pa === '404') {
						x.route404 = r;
					}
				}
				else {
					var m = path.match(pa);
					//console.log(path, pa, m);
					if (m && m.length) {
						x.route = r;
						if (m.length > 1)
							x.args = m.slice(1, m.length);
						if (r.args)
							x.args = x.args.concat(r.args);
						break;
					}
				}
			}
		}
		return x;
	},

	triggerRoute: function(x) {
		// no joy
		if (x.route === null) {
			if (x.route404 !== null)
				x.route = x.route404;
			else
				return;
		}

		// call handlers
		for (var k in x.route.handlers) {
			try {
				x.route.handlers[k].apply(x.route.element, x.args);
			} catch (e) {
				if (window.console !== undefined)
					console.log('[exposed] error when calling handler', x.route.handlers[k], e);
				throw e;
			}
		}
	},

	routeCurrentPath: function() {
		jQuery.exposed.path = decodeURIComponent(document.location.hash.substr(1));
		return jQuery.exposed.resolve(jQuery.exposed.path);
	},

	/**
	 * Called when location.hash has changed.
	 */
	onHashChange: function(ev) {
		var x = jQuery.exposed.routeCurrentPath();
		jQuery.exposed.triggerRoute(x);
	},

	/// Abstraction of location.hash observation
	_observeHashChange: function() {
		if (jQuery.exposed._prevhash !== undefined)
			return false;
		jQuery.exposed._prevhash = '';
		if ("onhashchange" in window) {
			$(window).bind('hashchange', jQuery.exposed.onHashChange);
		}
		else {
			setInterval(function(){
				if (jQuery.exposed._prevhash != document.location.hash){
					jQuery.exposed._prevhash = document.location.hash;
					jQuery.exposed.onHashChange();
				}
			}, 50);
		}
		if (document.location.hash === '' || document.location.hash != jQuery.exposed._prevhash)
			jQuery.exposed.onHashChange();
		return true;
	}
};
<|endoftext|>


<commit_before>/**
 * jquery.github.js <https://github.com/kmacrow/jquery.github.js>
 *
 * Copyright (C) Kalan MacRow, 2013
 * Simple GitHub widget to display most recent commits to a user's repos.
 *
 * This code is MIT licensed <http://opensource.org/licenses/MIT>
 * You may use it for personal or commercial projects with or without
 * attribution.
*/

(function($)
{
	"use strict";

	var api_base = 'https://api.github.com';

	// defaults
	var settings = {
		'display': 5,
		'user': null
	};

	function debug(msg)
	{
		console.log('github:', msg);
	}

	// for date as iso8601 for github
	function iso8601(date)
	{
		var  year = date.getFullYear(),
		    month = '' + date.getMonth() + 1,
		      day = '' + date.getDate();

		return year + '-'
			   + (month.length == 1 ? '0' + month : month) + '-' +
			   + (day.length   == 1 ? '0' + day   : day);
	}

	// generates string indicating how many
	// minutes/hours/days/months/years ago date was
	function ago(date)
	{
		var diff = Date.now() - date.getTime();

		function pl(qt, unit)
		{
			var m = Math.ceil(diff/qt), u = unit;
			if(m > 1) u += 's'
			return m + ' ' + u + ' ago';
		}

		if(diff < 60*1000){ // a minute
			return 'less than a minute ago';
		}

		diff = Math.ceil(diff/1000); // seconds

		if(diff < 60*60){ // an hour
			return pl(60, 'minute');
		}

		if(diff < 60*60*24){ // a day
			return pl(60*60, 'hour');
		}

		if(diff < 60*60*24*31){ // a month
			return pl(60*60*24, 'day');
		}

		if(diff < 60*60*24*365){ // a year
			return pl(60*60*24*31, 'month');
		}

		return pl(60*60*24*365, 'year');
	}

	// [(name,url)] of all user's repos
	function getRepos(cb)
	{
		var repos = [];

		$.ajax({
				   'url': api_base + '/users/' + settings.user + '/repos',
			   'success': function(data)
			   {
			   		if( !(data instanceof Array) ) {
			   			cb(repos)
			   			return;
			   		}

			   		for(var i = 0; i < data.length; i++) {
			   			var repo = data[i];
			   			repos.push({
			   				   'name': repo.name,
			   				    'url': repo.html_url
			   			});
			   		}

			   		cb(repos);
			   }
		});
	}

	// [(repo,msg,link)] of n most recent commits
	function getCommits(repos, cb)
	{
		var   index = {},
			commits = [],
			    bar = repos.length,
			  since = iso8601(new Date(Date.now() - 2629740000));;

		function lower() {
			if(--bar == 0) {
				complete();
			}
		}

		function complete() {
			// sort by time
			commits.sort(function(a, b) {
				return b.date.getTime() - a.date.getTime();
			});

			cb(commits.slice(0, settings.display));
		}

		$.each(repos, function(i, repo){

			$.ajax({
				    'url': (api_base + '/repos/'
							         + settings.user
							         + '/' + repo.name + '/commits'),
				   'data': {'since': since},
				'success': function(data)
				{
					if( !(data instanceof Array) ) {
						lower();
						return;
					}

					for(var i = 0; i < data.length; i++) {
						var commit = data[i];
						commits.push({
							'repo': repo,
							'user': {
								  'name': commit.author.login,
								'avatar': commit.author.avatar_url,
								   'url': 'https://github.com/'
								   		  + commit.author.login
							 },
							 'msg': commit.commit.message,
							 'sha': commit.sha,
							 'url': ('https://github.com/' + settings.user
							 		+ '/' + repo.name + '/commit/' + commit.sha),
							'tree': ('https://github.com/' + settings.user
									+ '/' + repo.name + '/tree/' + commit.sha),
							'date': new Date(Date.parse(commit.commit.author.date))
						});
					}

					lower();
				}
			});
		});

	}

	$.fn.github = function(options)
	{
		var $this = this;

		for(var key in settings) {
			if(options.hasOwnProperty(key)
				&& typeof options[key] != 'undefined') {
				settings[key] = options[key];
			}
		}

		if(!settings.user) {
			console.error('github: please provide username');
			return;
		}

		debug(settings);

		getRepos(function(repos)
		{
			getCommits(repos, function(commits)
			{
				var html = '
'
						  +'
'
						  	+''
						  		+' '
						  		+':stream / '
						  		+''
						  		+ settings.user+''
						  	+'	'
						  +'
';

				for(var i = 0; i < commits.length; i++) {

					var commit = commits[i];
					html += '
';
								+''
								+'	'
								+''
									+'' + commit.msg + '
'
									+'
'
										+ commit.user.name + ' '
										+ ago(commit.date) + ' to '
										+''+commit.repo.name+''
									+'
'
								+'	'
								+''
									+''
										+ commit.sha.substring(0,10) + '
'
									+'Browse code »'
									+ ''
								+'	'
							+'
';
				}

				html += '
';
				$this.html(html);
			});
		});
	}

})(jQuery);
<commit_msg>bug fix to markup generation<commit_after>/**
 * jquery.github.js <https://github.com/kmacrow/jquery.github.js>
 *
 * Copyright (C) Kalan MacRow, 2013
 * Simple GitHub widget to display most recent commits to a user's repos.
 *
 * This code is MIT licensed <http://opensource.org/licenses/MIT>
 * You may use it for personal or commercial projects with or without
 * attribution.
*/

(function($)
{
	"use strict";

	var api_base = 'https://api.github.com';

	// defaults
	var settings = {
		'display': 5,
		'user': null
	};

	function debug(msg)
	{
		console.log('github:', msg);
	}

	// for date as iso8601 for github
	function iso8601(date)
	{
		var  year = date.getFullYear(),
		    month = '' + date.getMonth() + 1,
		      day = '' + date.getDate();

		return year + '-'
			   + (month.length == 1 ? '0' + month : month) + '-' +
			   + (day.length   == 1 ? '0' + day   : day);
	}

	// generates string indicating how many
	// minutes/hours/days/months/years ago date was
	function ago(date)
	{
		var diff = Date.now() - date.getTime();

		function pl(qt, unit)
		{
			var m = Math.ceil(diff/qt), u = unit;
			if(m > 1) u += 's'
			return m + ' ' + u + ' ago';
		}

		if(diff < 60*1000){ // a minute
			return 'less than a minute ago';
		}

		diff = Math.ceil(diff/1000); // seconds

		if(diff < 60*60){ // an hour
			return pl(60, 'minute');
		}

		if(diff < 60*60*24){ // a day
			return pl(60*60, 'hour');
		}

		if(diff < 60*60*24*31){ // a month
			return pl(60*60*24, 'day');
		}

		if(diff < 60*60*24*365){ // a year
			return pl(60*60*24*31, 'month');
		}

		return pl(60*60*24*365, 'year');
	}

	// [(name,url)] of all user's repos
	function getRepos(cb)
	{
		var repos = [];

		$.ajax({
				   'url': api_base + '/users/' + settings.user + '/repos',
			   'success': function(data)
			   {
			   		if( !(data instanceof Array) ) {
			   			cb(repos)
			   			return;
			   		}

			   		for(var i = 0; i < data.length; i++) {
			   			var repo = data[i];
			   			repos.push({
			   				   'name': repo.name,
			   				    'url': repo.html_url
			   			});
			   		}

			   		cb(repos);
			   }
		});
	}

	// [(repo,msg,link)] of n most recent commits
	function getCommits(repos, cb)
	{
		var   index = {},
			commits = [],
			    bar = repos.length,
			  since = iso8601(new Date(Date.now() - 2629740000));;

		function lower() {
			if(--bar == 0) {
				complete();
			}
		}

		function complete() {
			// sort by time
			commits.sort(function(a, b) {
				return b.date.getTime() - a.date.getTime();
			});

			cb(commits.slice(0, settings.display));
		}

		$.each(repos, function(i, repo){

			$.ajax({
				    'url': (api_base + '/repos/'
							         + settings.user
							         + '/' + repo.name + '/commits'),
				   'data': {'since': since},
				'success': function(data)
				{
					if( !(data instanceof Array) ) {
						lower();
						return;
					}

					for(var i = 0; i < data.length; i++) {
						var commit = data[i];
						commits.push({
							'repo': repo,
							'user': {
								  'name': commit.author.login,
								'avatar': commit.author.avatar_url,
								   'url': 'https://github.com/'
								   		  + commit.author.login
							 },
							 'msg': commit.commit.message,
							 'sha': commit.sha,
							 'url': ('https://github.com/' + settings.user
							 		+ '/' + repo.name + '/commit/' + commit.sha),
							'tree': ('https://github.com/' + settings.user
									+ '/' + repo.name + '/tree/' + commit.sha),
							'date': new Date(Date.parse(commit.commit.author.date))
						});
					}

					lower();
				}
			});
		});

	}

	$.fn.github = function(options)
	{
		var $this = this;

		for(var key in settings) {
			if(options.hasOwnProperty(key)
				&& typeof options[key] != 'undefined') {
				settings[key] = options[key];
			}
		}

		if(!settings.user) {
			console.error('github: please provide username');
			return;
		}

		debug(settings);

		getRepos(function(repos)
		{
			debug(repos);

			getCommits(repos, function(commits)
			{
				debug(commits);

				var html = '
'
						  +'
'
						  	+''
						  		+' '
						  		+':stream / '
						  		+''
						  		+ settings.user+''
						  	+'	'
						  +'
';

				for(var i = 0; i < commits.length; i++) {

					var commit = commits[i];
					html += '
'
								+''
								+'	'
								+''
									+'' + commit.msg + '
'
									+'
'
										+ commit.user.name + ' '
										+ ago(commit.date) + ' to '
										+''+commit.repo.name+''
									+'
'
								+'	'
								+''
									+''
										+ commit.sha.substring(0,10) + '
'
									+'Browse code »'
									+ ''
								+'	'
							+'
';
				}

				html += '
';
				$this.html(html);
			});
		});
	}

})(jQuery);
<|endoftext|>


<commit_before>import Button from 'rebass/src/Button';
import Card from 'rebass/src/Card';
import Checkbox from 'rebass/src/Checkbox';
import Heading from 'rebass/src/Heading';
import Input from 'rebass/src/Input';
import Panel from 'rebass/src/Panel';
import PanelHeader from 'rebass/src/PanelHeader';
import Radio from 'rebass/src/Radio';

export default class CreateGame extends React.Component {
	constructor(p) {
		super(p);
		this.timerOption = this.timerOption.bind(this);
		this.state = {
            timer: '1st+30s',
            conveyors: true,
            board_lasers: false,
            express_conveyors: false,
            pushers: false,
            gears: false,
            start_with_4_lives: false,
            start_with_2_damage: false,
            choose_1_of_3_options: false,
            start_with_option: false,
            no_power_down: false,
            option_for_heal: false,
        };
        this.create = this.create.bind(this);
	}

	timerOption(e) {
		this.setState({timer: e.target.value});
	}

    check(field) {
        let state = {};
        state[field] = !this.state[field];
        this.setState(state);
    }

    create() {
        if (this.name.value) {
            let msg = Object.assign({cmd: 'create_game', name: this.name.value}, this.state);
            delete msg['error'];
            ws.send(msg);
        }
        else {
            this.setState({error: 'Name is required'});
            this.name.focus();
        }
    }

    on_create_game(msg) {
        if (this.name.value === msg.name) {
            ws.send({cmd: 'join', name: msg.name});
        }
    }

    on_error(msg) {
        this.setState({error: msg.error});
    }

	render() { return (
<Panel theme="info">
	<PanelHeader>Create Game</PanelHeader>
	<Input label="Name" name="name" placeholder="Game Name"
        message={this.state.error} baseRef={r=>this.name=r}/>
	<Card>
        <Heading>Timer options</Heading>
	<Radio name="timer" checked={this.state.timer==='standard'} onChange={this.timerOption} label="Standard (30 seconds after penultimate completion)" value='standard'/>
	<Radio name="timer" checked={this.state.timer==='1st+30s'} onChange={this.timerOption} label="Fast (30 seconds after first player finishes)" value='1st+30s'/>
	<Radio name="timer" checked={this.state.timer==='1m'} onChange={this.timerOption} label="Faster (1 minute)" value="1m"/>
	<Radio name="timer" checked={this.state.timer==='30s'} onChange={this.timerOption} label="Fastest (30 seconds)" value="30s"/>
    </Card>
	<Card>
        <Heading>Active Elements</Heading>
	    <Checkbox theme="success" name="l" label="Board lasers" checked={this.state.board_lasers} onClick={this.check.bind(this, 'board_lasers')} readOnly/>
	    <Checkbox theme="success" name="c" label="Conveyor Belts" checked={this.state.conveyors} onClick={this.check.bind(this, 'conveyors')} readOnly/>
	    <Checkbox theme="success" name="e" label="Express Conveyor Belts" checked={this.state.express_conveyors} onClick={this.check.bind(this, 'express_conveyors')} readOnly/>
	    <Checkbox theme="success" name="p" label="Pushers" checked={this.state.pushers} onClick={this.check.bind(this, 'pushers')} readOnly/>
	    <Checkbox theme="success" name="g" label="Gears" checked={this.state.gears} onClick={this.check.bind(this, 'gears')} readOnly/>
    </Card>
	<Card>
        <Heading>Special Rules</Heading>
	    <Checkbox theme="success" name="4" label="All robots start with 4 Lives" checked={this.state.start_with_4_lives} onClick={this.check.bind(this, 'start_with_4_lives')} readOnly/>
	    <Checkbox theme="success" name="2" label="All robots start with 2 Damage" checked={this.state.start_with_2_damage} onClick={this.check.bind(this, 'start_with_2_damage')} readOnly/>
	    <Checkbox theme="success" name="1" label="All robots start with one option" checked={this.state.start_with_option} onClick={this.check.bind(this, 'start_with_option')} readOnly/>
	    <Checkbox theme="success" name="3" label="All robots choose from one of three options" checked={this.state.choose_1_of_3_options} onClick={this.check.bind(this, 'choose_1_of_3_options')} readOnly/>
	    <Checkbox theme="success" name="x" label="Robots can't power down" checked={this.state.no_power_down} onClick={this.check.bind(this, 'no_power_down')} readOnly/>
	    <Checkbox theme="success" name="o" label="Wrench/flag gives option card instead of repairing" checked={this.state.option_for_heal} onClick={this.check.bind(this, 'option_for_heal')} readOnly/>
    </Card>
    <Button theme="primary" onClick={this.create}>Create Game</Button>
    <Button theme="error" onClick={this.props.back}>Nevermind</Button>
</Panel>
	)}
}

<commit_msg>Set autoFocus on name when creating game<commit_after>import Button from 'rebass/src/Button';
import Card from 'rebass/src/Card';
import Checkbox from 'rebass/src/Checkbox';
import Heading from 'rebass/src/Heading';
import Input from 'rebass/src/Input';
import Panel from 'rebass/src/Panel';
import PanelHeader from 'rebass/src/PanelHeader';
import Radio from 'rebass/src/Radio';

export default class CreateGame extends React.Component {
	constructor(p) {
		super(p);
		this.timerOption = this.timerOption.bind(this);
		this.state = {
            timer: '1st+30s',
            conveyors: true,
            board_lasers: false,
            express_conveyors: false,
            pushers: false,
            gears: false,
            start_with_4_lives: false,
            start_with_2_damage: false,
            choose_1_of_3_options: false,
            start_with_option: false,
            no_power_down: false,
            option_for_heal: false,
        };
        this.create = this.create.bind(this);
	}

	timerOption(e) {
		this.setState({timer: e.target.value});
	}

    check(field) {
        let state = {};
        state[field] = !this.state[field];
        this.setState(state);
    }

    create() {
        if (this.name.value) {
            let msg = Object.assign({cmd: 'create_game', name: this.name.value}, this.state);
            delete msg['error'];
            ws.send(msg);
        }
        else {
            this.setState({error: 'Name is required'});
            this.name.focus();
        }
    }

    on_create_game(msg) {
        if (this.name.value === msg.name) {
            ws.send({cmd: 'join', name: msg.name});
        }
    }

    on_error(msg) {
        this.setState({error: msg.error});
    }

	render() { return (
<Panel theme="info">
	<PanelHeader>Create Game</PanelHeader>
	<Input autoFocus label="Name" name="name" placeholder="Game Name"
        message={this.state.error} baseRef={r=>this.name=r}/>
	<Card>
        <Heading>Timer options</Heading>
	<Radio name="timer" checked={this.state.timer==='standard'} onChange={this.timerOption} label="Standard (30 seconds after penultimate completion)" value='standard'/>
	<Radio name="timer" checked={this.state.timer==='1st+30s'} onChange={this.timerOption} label="Fast (30 seconds after first player finishes)" value='1st+30s'/>
	<Radio name="timer" checked={this.state.timer==='1m'} onChange={this.timerOption} label="Faster (1 minute)" value="1m"/>
	<Radio name="timer" checked={this.state.timer==='30s'} onChange={this.timerOption} label="Fastest (30 seconds)" value="30s"/>
    </Card>
	<Card>
        <Heading>Active Elements</Heading>
	    <Checkbox theme="success" name="l" label="Board lasers" checked={this.state.board_lasers} onClick={this.check.bind(this, 'board_lasers')} readOnly/>
	    <Checkbox theme="success" name="c" label="Conveyor Belts" checked={this.state.conveyors} onClick={this.check.bind(this, 'conveyors')} readOnly/>
	    <Checkbox theme="success" name="e" label="Express Conveyor Belts" checked={this.state.express_conveyors} onClick={this.check.bind(this, 'express_conveyors')} readOnly/>
	    <Checkbox theme="success" name="p" label="Pushers" checked={this.state.pushers} onClick={this.check.bind(this, 'pushers')} readOnly/>
	    <Checkbox theme="success" name="g" label="Gears" checked={this.state.gears} onClick={this.check.bind(this, 'gears')} readOnly/>
    </Card>
	<Card>
        <Heading>Special Rules</Heading>
	    <Checkbox theme="success" name="4" label="All robots start with 4 Lives" checked={this.state.start_with_4_lives} onClick={this.check.bind(this, 'start_with_4_lives')} readOnly/>
	    <Checkbox theme="success" name="2" label="All robots start with 2 Damage" checked={this.state.start_with_2_damage} onClick={this.check.bind(this, 'start_with_2_damage')} readOnly/>
	    <Checkbox theme="success" name="1" label="All robots start with one option" checked={this.state.start_with_option} onClick={this.check.bind(this, 'start_with_option')} readOnly/>
	    <Checkbox theme="success" name="3" label="All robots choose from one of three options" checked={this.state.choose_1_of_3_options} onClick={this.check.bind(this, 'choose_1_of_3_options')} readOnly/>
	    <Checkbox theme="success" name="x" label="Robots can't power down" checked={this.state.no_power_down} onClick={this.check.bind(this, 'no_power_down')} readOnly/>
	    <Checkbox theme="success" name="o" label="Wrench/flag gives option card instead of repairing" checked={this.state.option_for_heal} onClick={this.check.bind(this, 'option_for_heal')} readOnly/>
    </Card>
    <Button theme="primary" onClick={this.create}>Create Game</Button>
    <Button theme="error" onClick={this.props.back}>Nevermind</Button>
</Panel>
	)}
}

<|endoftext|>


<commit_before>'use strict';
// loadData converts JSON dump of VARServer gen_stat records into array of gen_stat objects
exports.loadData = function(rawData) {
    // rawData is an array
    if( !Array.isArray(rawData) ) {
        throw new TypeError('rawData is not an array');
    }
    if( rawData.length < 2 ) {
        throw new TypeError('rawData array is missing header record');
    }
    // first row is header
    return rawData.slice(1);
};
<commit_msg>replaced exports with module.exports<commit_after>'use strict';
// loadData converts JSON dump of VARServer gen_stat records into array of gen_stat objects
module.exports.loadData = function(rawData) {
    // rawData is an array
    if( !Array.isArray(rawData) ) {
        throw new TypeError('rawData is not an array');
    }
    // first row is header
    if( rawData.length < 2 ) {
        throw new TypeError('rawData array is missing header record');
    }
    //TODO: parse raw data
    return rawData.slice(1);
};
<|endoftext|>


<commit_before>// game engine
FloatyDuck.prototype = new FloatyElement();

function FloatyDuck() {
  // call extender class
  FloatyElement.apply(this,['play_area']);

  this.DEBUG = true;
  this.UPDATES_PER_SECOND = 60;
  this.DELAY_BEFORE_START = 6000;

  this.frameCount = 0;

  this.size = { w: 320, h: 480 };

  // need to track only once per press
  this.registeredDown = false;
  $(document).keyup(function(e) {
    switch(e.keyCode) {
      case 40: this.registeredDown = false; break;
    }
  }.bind(this))
}

// Initialize
FloatyDuck.prototype.init = function() {
  if(this.DEBUG) {
    $('#debug_data').css('display', 'block');
  }

  // generate game area
  this.Scroll = new Scroll();

  this.Scroll.setSize('w',this.getSize('w')*1+10);
  this.Scroll.setSize('h',this.getSize('h')-2);

  // set structure
  $('body').append(this.obj);
  this.obj.append(this.Scroll.obj);

  this.Duck = new Duck();
  this.obj.append(this.Duck.obj);

  // position duck in center
  this.Duck.setPos('x',this.getSize('w')/2);
  this.Duck.setPos('y',this.getSize('h')/2);

  this.Keyboard = new Keyboard();

  if(this.DEBUG) {
    this.obj.css('overflow','visible');
  }
}

// This method updates the world (i.e., input, physics, etc)
FloatyDuck.prototype.update = function() {

  // update scroll area
  this.Scroll.update();
  this.Duck.update();

  if (this.Keyboard.isDownPressed()) {
    if( this.registeredDown == false ) {
      this.registeredDown = true;
      this.Duck.setSpeed(6);
    }
  }

}

// This method draws the current scene
FloatyDuck.prototype.render = function() {
  this.Duck.render();
  this.Scroll.render();

  // render play area
  this.obj.css('width',this.getSize('w')+'px').css('height',this.getSize('h')+'px');

  if(this.DEBUG) {

    // Record current frame render for debug
    this.frameCount++;

    // Write out debug data
    $('#frame_count').html(this.frameCount);
    var activeKeys = "";
    if(this.Keyboard.upPressed) {
      activeKeys = activeKeys + "UP, ";
    }
    if(this.Keyboard.downPressed) {
      activeKeys += "DOWN, ";
    }
    $('#active_keys').html(activeKeys);
    // TODO: Duck X and Y
    // TODO: Canvas size
  }
}

FloatyDuck.prototype.run = function() {
  this.init();

  var updateEvery = 1000 / this.UPDATES_PER_SECOND;
  var lastUpdate = Date.now();

  setInterval(function() {
    this.update();
    this.render();
  }.bind(this), updateEvery);
}

// Keyboard input manager
Keyboard = function() {
  this.leftPressed = false;
  this.rightPressed = false;
  this.upPressed = false;
  this.downPressed = false;

  $(document).keydown(function(e) {
    switch(e.keyCode) {
      case 37: this.leftPressed = true; break;
      case 38: this.upPressed = true; break;
      case 39: this.rightPressed = true; break;
      case 40: this.downPressed = true; break;
    }
  }.bind(this))

  $(document).keyup(function(e) {
    switch(e.keyCode) {
      case 37: this.leftPressed = false; break;
      case 38: this.upPressed = false; break;
      case 39: this.rightPressed = false; break;
      case 40: this.downPressed = false; break;
    }
  }.bind(this))
}

Keyboard.prototype.isLeftPressed = function() {
  return this.leftPressed;
}

Keyboard.prototype.isRightPressed = function() {
  return this.rightPressed;
}

Keyboard.prototype.isUpPressed = function() {
  return this.upPressed;
}

Keyboard.prototype.isDownPressed = function() {
  return this.downPressed;
}
<commit_msg>Added basic start state<commit_after>// game engine
FloatyDuck.prototype = new FloatyElement();

function FloatyDuck() {
  // call extender class
  FloatyElement.apply(this,['play_area']);

  this.DEBUG = true;
  this.UPDATES_PER_SECOND = 60;
  this.DELAY_BEFORE_START = 6000;

  this.frameCount = 0;

  this.size = { w: 320, h: 480 };

  // need to track only once per press
  this.registeredDown = false;
  $(document).keyup(function(e) {
    switch(e.keyCode) {
      case 40: this.registeredDown = false; break;
    }
  }.bind(this))
}

// Initialize
FloatyDuck.prototype.init = function() {
  if(this.DEBUG) {
    $('#debug_data').css('display', 'block');
  }

  // generate game area
  this.Scroll = new Scroll();

  this.Scroll.setSize('w',this.getSize('w')*1+10);
  this.Scroll.setSize('h',this.getSize('h')-2);

  // set structure
  $('body').append(this.obj);
  this.obj.append(this.Scroll.obj);

  this.gameStarted = false;

  this.Duck = new Duck();
  this.obj.append(this.Duck.obj);

  // position duck in center
  this.Duck.setPos('x',this.getSize('w')/2);
  this.Duck.setPos('y',this.getSize('h')/2);

  this.Keyboard = new Keyboard();

  if(this.DEBUG) {
    this.obj.css('overflow','visible');
  }
}

// This method updates the world (i.e., input, physics, etc)
FloatyDuck.prototype.update = function() {

  // update scroll area
  this.Scroll.update();

  if(this.gameStarted) {
    this.Duck.update();
  }

  if (this.Keyboard.isDownPressed()) {
    if(!this.gameStarted) {
      this.gameStarted = true;
    }

    if( this.registeredDown == false ) {
      this.registeredDown = true;
      this.Duck.setSpeed(6);
    }
  }

}

// This method draws the current scene
FloatyDuck.prototype.render = function() {
  this.Duck.render();
  this.Scroll.render();

  // render play area
  this.obj.css('width',this.getSize('w')+'px').css('height',this.getSize('h')+'px');

  if(this.DEBUG) {

    // Record current frame render for debug
    this.frameCount++;

    // Write out debug data
    $('#frame_count').html(this.frameCount);
    var activeKeys = "";
    if(this.Keyboard.upPressed) {
      activeKeys = activeKeys + "UP, ";
    }
    if(this.Keyboard.downPressed) {
      activeKeys += "DOWN, ";
    }
    $('#active_keys').html(activeKeys);
    // TODO: Duck X and Y
    // TODO: Canvas size
  }
}

FloatyDuck.prototype.run = function() {
  this.init();

  var updateEvery = 1000 / this.UPDATES_PER_SECOND;
  var lastUpdate = Date.now();

  setInterval(function() {
    this.update();
    this.render();
  }.bind(this), updateEvery);
}

// Keyboard input manager
Keyboard = function() {
  this.leftPressed = false;
  this.rightPressed = false;
  this.upPressed = false;
  this.downPressed = false;

  $(document).keydown(function(e) {
    switch(e.keyCode) {
      case 37: this.leftPressed = true; break;
      case 38: this.upPressed = true; break;
      case 39: this.rightPressed = true; break;
      case 40: this.downPressed = true; break;
    }
  }.bind(this))

  $(document).keyup(function(e) {
    switch(e.keyCode) {
      case 37: this.leftPressed = false; break;
      case 38: this.upPressed = false; break;
      case 39: this.rightPressed = false; break;
      case 40: this.downPressed = false; break;
    }
  }.bind(this))
}

Keyboard.prototype.isLeftPressed = function() {
  return this.leftPressed;
}

Keyboard.prototype.isRightPressed = function() {
  return this.rightPressed;
}

Keyboard.prototype.isUpPressed = function() {
  return this.upPressed;
}

Keyboard.prototype.isDownPressed = function() {
  return this.downPressed;
}
<|endoftext|>


<commit_before>//  Copyright 2002-2014, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating continuously, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen.
 * Note that the number of images falling is constant, so if the screen is stretched out vertically (tall thin window) they will be less dense.
 *
 * There are two ways to run the animation step function.  The client code can manually call step(dt), or the client code can pass in an Events instance that triggers events on 'step'.
 * In the latter case, the listener will automatically be removed when the animation is complete.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var CanvasNode = require( 'SCENERY/nodes/CanvasNode' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var StarNode = require( 'SCENERY_PHET/StarNode' );
  var FaceNode = require( 'SCENERY_PHET/FaceNode' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Node = require( 'SCENERY/nodes/Node' );

  //This debug flag shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
  var debug = false;

  function RewardNode( options ) {
    var rewardNode = this;

    //Bounds in which to render the canvas.  Should be the full screen.  See below for how this is computed based on ScreenView bounds and relative transforms
    this.canvasDisplayBounds = new Bounds2( 0, 0, ScreenView.DEFAULT_LAYOUT_BOUNDS.width, ScreenView.DEFAULT_LAYOUT_BOUNDS.height );

    this.options = options = _.extend( {

      //Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      //Nodes to appear in the reward node.  They will be cached as images to improve performance
      nodes: RewardNode.createRandomNodes( [
        new FaceNode( 40, {headStroke: 'black', headLineWidth: 1.5} ),
        new StarNode()
      ], 150 ),

      //If you pass in a stepSource, which conforms to the Events interface, the RewardNode will register for events through that source
      //TODO: Make it so the client doesn't pass in the entire model
      stepSource: null
    }, options );

    //If you pass in a stepSource, which conforms to the Events interface, the RewardNode will register for events through that source
    if ( options.stepSource ) {
      this.stepCallback = function( dt ) {rewardNode.step( dt );};
      options.stepSource.on( 'step', this.stepCallback );
    }

    //Cache each unique node as an image for faster rendering in canvas.  Use an intermediate imageWrapper since the images will be returned later asynchronously
    //And we need a place to store them, and know when they have arrived
    this.imageWrappers = [];

    //find the unique nodes in the array
    var uniqueNodes = _.uniq( this.options.nodes );

    uniqueNodes.forEach( function( node, i ) {
      rewardNode.imageWrappers.push(
        {

          //The image to be rendered in the canvas, will be filled in by toImage callback
          image: null,

          //Record the width and height so the nodes can be positioned before the toImage call has completed
          width: node.width,
          height: node.height,

          //The node itself is recorded in the wrapper so the wrapper can be looked up based on the original node
          node: node
        } );
      var parent = new Node( {children: [node], scale: options.scaleForResolution} );
      parent.toImage( function( image ) {
        rewardNode.imageWrappers[i].image = image;
      } );
    } );

    CanvasNode.call( this, options );

    //Some initialization must occur after this node is attached to the scene graph, see documentation for RewardNode.init below.
    this.inited = false;
  }

  return inherit( CanvasNode, RewardNode, {

      //Paint the rewards on the canvas
      // @param {CanvasContextWrapper} wrapper
      paintCanvas: function( wrapper ) {
        var context = wrapper.context;

        //If the debugging flag is on, show the bounds of the canvas
        if ( debug ) {
          var bounds = this.options.canvasDisplayBounds;

          //Fill the canvas with gray
          context.fillStyle = 'rgba(50,50,50,0.5)';
          context.fillRect( bounds.minX, bounds.minY, bounds.width, bounds.height );

          //Stroke the canvas border with blue
          context.strokeStyle = "#0000ff";
          context.lineWidth = 5;
          context.strokeRect( bounds.minX, bounds.minY, bounds.width, bounds.height );
        }
        context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

        //Display the rewards, but check that they exist first.  They do not exist when attached to the timer with stepSource
        if ( this.rewards ) {
          for ( var i = 0; i < this.rewards.length; i++ ) {
            var reward = this.rewards[i];
            if ( reward.imageWrapper.image ) {
              context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
            }
          }
        }
      },

      //Find the root of the scene tree
      getScene: function() {
        return this.getUniqueTrail().nodes[0];
      },

      //Find the first parent that is a ScreenView so we can listen for its transform, see https://github.com/phetsims/vegas/issues/4
      getScreenView: function() {
        var nodes = this.getUniqueTrail().nodes.slice( 0 ).reverse();
        return _.find( nodes, function( node ) {return node instanceof ScreenView;} );
      },

      //Only init after being attached to the scene graph, since we must ascertain the local bounds such that they take up the global screen.
      // 1. listen to the size of the scene/display
      // 2. record the trail between the scene and your CanvasNode, and
      // 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).
      //
      // @jonathanolson said: for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on changes to be precise (since you need them anyways to draw)
      init: function() {
        var rewardNode = this;
        var scene = this.getScene();

        //Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
        var updateBounds = function() {

          var local = rewardNode.globalToLocalBounds( scene.sceneBounds );
          rewardNode.setCanvasBounds( local );

          //Also, store the bounds in the options so the debug flag can render the bounds
          rewardNode.canvasDisplayBounds = local;
        };

        //When the scene is resized, update the bounds
        scene.addEventListener( 'resize', updateBounds );

        //When the ScreenView transform changes, update the bounds.  This prevents a "behind by one" problem, see https://github.com/phetsims/vegas/issues/4
        this.getScreenView().getTransform().addTransformListener( {before: function() {}, after: function() {updateBounds();}} );

        //Set the initial bounds
        updateBounds();

        //Store each reward, which has an imageWrapper (see above), x, y, speed
        this.rewards = [];
        for ( var i = 0; i < this.options.nodes.length; i++ ) {

          var node = this.options.nodes[i];
          (function( node ) {

            //find the image wrapper corresponding to the node
            var imageWrapper = _.find( rewardNode.imageWrappers, function( imageWrapper ) {return imageWrapper.node === node;} );
            rewardNode.rewards.push( {
              imageWrapper: imageWrapper,
              x: (Math.random() * rewardNode.canvasDisplayBounds.width + rewardNode.canvasDisplayBounds.left) * rewardNode.options.scaleForResolution - imageWrapper.width / 2,
              y: rewardNode.canvasDisplayBounds.top - Math.random() * rewardNode.canvasDisplayBounds.height * 2 - 200,
              speed: (Math.random() + 1) * 200
            } );
          })( node );
        }

        this.inited = true;
      },

      //Move the rewards down according to their speed
      step: function( dt ) {
        if ( !this.inited && this.getScene() !== null ) {
          this.init();
        }

        //Update all of the rewards
        var maxY = this.canvasDisplayBounds.height * this.options.scaleForResolution;
        for ( var i = 0; i < this.rewards.length; i++ ) {
          var reward = this.rewards[i];

          //Move each node straight down at constant speed
          reward.y += reward.speed * dt;

          //Move back to the top after the node falls off the bottom
          if ( reward.y > maxY ) {
            reward.y = this.canvasDisplayBounds.top - Math.random() * this.canvasDisplayBounds.height * 2 - 200;
          }
        }
        this.invalidatePaint();
      }
    },

    //Static methods
    {
      /**
       * Convenience factory method to create an array of the specified nodes in an even distribution.
       * @param {Array[Node]} nodes array of nodes to use
       * @param {Number} count
       */
      createRandomNodes: function( nodes, count ) {
        var array = [];
        for ( var i = 0; i < count; i++ ) {
          array.push( nodes[i % nodes.length] );
        }
        return array;
      }
    } );
} );<commit_msg>Added a way to stop the animation callbacks, see #4<commit_after>//  Copyright 2002-2014, University of Colorado Boulder

/**
 * Reward node that shows many nodes animating continuously, for fun!  Shown when a perfect score is achieved in a game.
 * You can also test this by running vegas/vegas_en.html and clicking on the "Reward" screen.
 * Note that the number of images falling is constant, so if the screen is stretched out vertically (tall thin window) they will be less dense.
 *
 * There are two ways to run the animation step function.  The client code can manually call step(dt), or the client code can pass in an Events instance that triggers events on 'step'.
 * In the latter case, the listener will automatically be removed when the animation is complete.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var CanvasNode = require( 'SCENERY/nodes/CanvasNode' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var StarNode = require( 'SCENERY_PHET/StarNode' );
  var FaceNode = require( 'SCENERY_PHET/FaceNode' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Node = require( 'SCENERY/nodes/Node' );

  //This debug flag shows a gray rectangle for the CanvasNode to help ensure that its bounds are accurate
  var debug = false;

  function RewardNode( options ) {
    var rewardNode = this;

    //Bounds in which to render the canvas.  Should be the full screen.  See below for how this is computed based on ScreenView bounds and relative transforms
    this.canvasDisplayBounds = new Bounds2( 0, 0, ScreenView.DEFAULT_LAYOUT_BOUNDS.width, ScreenView.DEFAULT_LAYOUT_BOUNDS.height );

    this.options = options = _.extend( {

      //Scale things up for rasterization and back down for rendering so they have nice resolution on retina
      scaleForResolution: 2,

      //Nodes to appear in the reward node.  They will be cached as images to improve performance
      nodes: RewardNode.createRandomNodes( [
        new FaceNode( 40, {headStroke: 'black', headLineWidth: 1.5} ),
        new StarNode()
      ], 150 ),

      //If you pass in a stepSource, which conforms to the Events interface, the RewardNode will register for events through that source
      //TODO: Make it so the client doesn't pass in the entire model
      stepSource: null
    }, options );

    //If you pass in a stepSource, which conforms to the Events interface, the RewardNode will register for events through that source
    if ( options.stepSource ) {
      this.stepCallback = function( dt ) {rewardNode.step( dt );};
      options.stepSource.on( 'step', this.stepCallback );
    }

    //Cache each unique node as an image for faster rendering in canvas.  Use an intermediate imageWrapper since the images will be returned later asynchronously
    //And we need a place to store them, and know when they have arrived
    this.imageWrappers = [];

    //find the unique nodes in the array
    var uniqueNodes = _.uniq( this.options.nodes );

    uniqueNodes.forEach( function( node, i ) {
      rewardNode.imageWrappers.push(
        {

          //The image to be rendered in the canvas, will be filled in by toImage callback
          image: null,

          //Record the width and height so the nodes can be positioned before the toImage call has completed
          width: node.width,
          height: node.height,

          //The node itself is recorded in the wrapper so the wrapper can be looked up based on the original node
          node: node
        } );
      var parent = new Node( {children: [node], scale: options.scaleForResolution} );
      parent.toImage( function( image ) {
        rewardNode.imageWrappers[i].image = image;
      } );
    } );

    CanvasNode.call( this, options );

    //Some initialization must occur after this node is attached to the scene graph, see documentation for RewardNode.init below.
    this.inited = false;
  }

  return inherit( CanvasNode, RewardNode, {

      //Paint the rewards on the canvas
      // @param {CanvasContextWrapper} wrapper
      paintCanvas: function( wrapper ) {
        var context = wrapper.context;

        //If the debugging flag is on, show the bounds of the canvas
        if ( debug ) {
          var bounds = this.options.canvasDisplayBounds;

          //Fill the canvas with gray
          context.fillStyle = 'rgba(50,50,50,0.5)';
          context.fillRect( bounds.minX, bounds.minY, bounds.width, bounds.height );

          //Stroke the canvas border with blue
          context.strokeStyle = "#0000ff";
          context.lineWidth = 5;
          context.strokeRect( bounds.minX, bounds.minY, bounds.width, bounds.height );
        }
        context.scale( 1 / this.options.scaleForResolution, 1 / this.options.scaleForResolution );

        //Display the rewards, but check that they exist first.  They do not exist when attached to the timer with stepSource
        if ( this.rewards ) {
          for ( var i = 0; i < this.rewards.length; i++ ) {
            var reward = this.rewards[i];
            if ( reward.imageWrapper.image ) {
              context.drawImage( reward.imageWrapper.image, reward.x, reward.y );
            }
          }
        }
      },

      //Find the root of the scene tree
      getScene: function() {
        return this.getUniqueTrail().nodes[0];
      },

      //Find the first parent that is a ScreenView so we can listen for its transform, see https://github.com/phetsims/vegas/issues/4
      getScreenView: function() {
        var nodes = this.getUniqueTrail().nodes.slice( 0 ).reverse();
        return _.find( nodes, function( node ) {return node instanceof ScreenView;} );
      },

      //Only init after being attached to the scene graph, since we must ascertain the local bounds such that they take up the global screen.
      // 1. listen to the size of the scene/display
      // 2. record the trail between the scene and your CanvasNode, and
      // 3. apply the inverse of that transform to the CanvasNode (whenever an ancestor's transform changes, or when the scene/display size changes).
      //
      // @jonathanolson said: for implementing now, I'd watch the iso transform, compute the inverse, and set bounds on changes to be precise (since you need them anyways to draw)
      init: function() {
        var rewardNode = this;
        var scene = this.getScene();

        //Listen to the bounds of the scene, so the canvas can be resized if the window is reshaped
        var updateBounds = function() {

          var local = rewardNode.globalToLocalBounds( scene.sceneBounds );
          rewardNode.setCanvasBounds( local );

          //Also, store the bounds in the options so the debug flag can render the bounds
          rewardNode.canvasDisplayBounds = local;
        };

        //When the scene is resized, update the bounds
        scene.addEventListener( 'resize', updateBounds );

        //When the ScreenView transform changes, update the bounds.  This prevents a "behind by one" problem, see https://github.com/phetsims/vegas/issues/4
        this.getScreenView().getTransform().addTransformListener( {before: function() {}, after: function() {updateBounds();}} );

        //Set the initial bounds
        updateBounds();

        //Store each reward, which has an imageWrapper (see above), x, y, speed
        this.rewards = [];
        for ( var i = 0; i < this.options.nodes.length; i++ ) {

          var node = this.options.nodes[i];
          (function( node ) {

            //find the image wrapper corresponding to the node
            var imageWrapper = _.find( rewardNode.imageWrappers, function( imageWrapper ) {return imageWrapper.node === node;} );
            rewardNode.rewards.push( {
              imageWrapper: imageWrapper,
              x: (Math.random() * rewardNode.canvasDisplayBounds.width + rewardNode.canvasDisplayBounds.left) * rewardNode.options.scaleForResolution - imageWrapper.width / 2,
              y: rewardNode.canvasDisplayBounds.top - Math.random() * rewardNode.canvasDisplayBounds.height * 2 - 200,
              speed: (Math.random() + 1) * 200
            } );
          })( node );
        }

        this.inited = true;
      },

      stop: function() {
        this.options.stepSource.off( 'step', this.stepCallback );
      },

      //Move the rewards down according to their speed
      step: function( dt ) {
        if ( !this.inited && this.getScene() !== null ) {
          this.init();
        }

        //Update all of the rewards
        var maxY = this.canvasDisplayBounds.height * this.options.scaleForResolution;
        for ( var i = 0; i < this.rewards.length; i++ ) {
          var reward = this.rewards[i];

          //Move each node straight down at constant speed
          reward.y += reward.speed * dt;

          //Move back to the top after the node falls off the bottom
          if ( reward.y > maxY ) {
            reward.y = this.canvasDisplayBounds.top - Math.random() * this.canvasDisplayBounds.height * 2 - 200;
          }
        }
        this.invalidatePaint();
      }
    },

    //Static methods
    {
      /**
       * Convenience factory method to create an array of the specified nodes in an even distribution.
       * @param {Array[Node]} nodes array of nodes to use
       * @param {Number} count
       */
      createRandomNodes: function( nodes, count ) {
        var array = [];
        for ( var i = 0; i < count; i++ ) {
          array.push( nodes[i % nodes.length] );
        }
        return array;
      }
    } );
} );<|endoftext|>


<commit_before>// Copyright 2013-2015, University of Colorado Boulder

/**
 * The view portion of a Screen, specifies the layout strategy.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var Node = require( 'SCENERY/nodes/Node' );
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var inherit = require( 'PHET_CORE/inherit' );
  var platform = require( 'PHET_CORE/platform' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Events = require( 'AXON/Events' );
  var joist = require( 'JOIST/joist' );

  /*
   * Default width and height for iPad2, iPad3, iPad4 running Safari with default tabs and decorations
   * These bounds were added in Sep 2014 and are based on a screenshot from a non-Retina iPad, in Safari, iOS7.
   * It therefore accounts for the nav bar on the bottom and the space consumed by the browser on the top.
   * As of this writing, this is the resolution being used by PhET's sim designers for their mockups.
   * For more information see https://github.com/phetsims/joist/issues/126
   */
  var DEFAULT_LAYOUT_BOUNDS = new Bounds2( 0, 0, 1024, 618 );

  function ScreenView( options ) {

    options = _.extend( {
      layoutBounds: DEFAULT_LAYOUT_BOUNDS.copy()
    }, options );
    this.layoutBounds = options.layoutBounds;

    Node.call( this, _.extend( {
      renderer: platform.edge ? 'canvas' : null,
      layerSplit: true, // so we're not in the same layer as the navbar, etc.
      excludeInvisible: true, // so we don't keep invisible screens in the SVG tree
      accessibleContent: {
        createPeer: function( accessibleInstance ) {
          return new ScreenViewAccessiblePeer( accessibleInstance, options.screenDescription, options.screenLabel );
        }
      }
    }, options ) );

    // @public - event channel for notifications
    this.events = new Events();
  }

  joist.register( 'ScreenView', ScreenView );

  inherit( Node, ScreenView, {

      /**
       * Get the scale to use for laying out the sim components and the navigation bar, so its size will track
       * with the sim size
       * @param {number} width
       * @param {number} height
       * @returns {number}
       * @public (joist-internal)
       */
      getLayoutScale: function( width, height ) {
        return Math.min( width / this.layoutBounds.width, height / this.layoutBounds.height );
      },

      /**
       * Default layout function uses the layoutWidth and layoutHeight to scale the content (based on whichever is more limiting: width or height)
       * and centers the content in the screen vertically and horizontally
       * This function can be replaced by subclasses that wish to perform their own custom layout.
       * @param {number} width
       * @param {number} height
       * @public (joist-internal)
       */
      layout: function( width, height ) {
        this.resetTransform();

        var scale = this.getLayoutScale( width, height );
        this.setScaleMagnitude( scale );

        var dx = 0;
        var dy = 0;

        //center vertically
        if ( scale === width / this.layoutBounds.width ) {
          dy = (height / scale - this.layoutBounds.height ) / 2;
        }

        //center horizontally
        else if ( scale === height / this.layoutBounds.height ) {
          dx = (width / scale - this.layoutBounds.width ) / 2;
        }

        this.translate( dx, dy );

        /**
         * Trigger notifications that the visible bounds have changed, in case any clients need to update the views.
         *
         * @param {number} dx - the horizontal offset in stage coordinates
         * @param {number} dy - the vertical offset in stage coordinates
         * @param {number} width - the width of the entire visible area in stage coordinates
         * @param {number} height - the height of the entire visible area in stage coordinates
         * @param {number} scale - the scale factor between stage coordinates and pixel coordinates
         * @public
         */
        this.events.trigger( 'layoutFinished', dx, dy, width / scale, height / scale, scale );
      }
    },

    //statics
    {
      // @public
      DEFAULT_LAYOUT_BOUNDS: DEFAULT_LAYOUT_BOUNDS,

      /**
       *
       * @param {AccessibleInstance} accessibleInstance
       * @param {string} screenDescription
       * @param {string} screenLabel
       * @returns {ScreenViewAccessiblePeer}
       * @constructor
       * @public
       */
      ScreenViewAccessiblePeer: function( accessibleInstance, screenDescription, screenLabel ) {
        return new ScreenViewAccessiblePeer( accessibleInstance, screenDescription, screenLabel );
      }
    }
  );

  /**
   * An accessible peer for handling the parallel DOM for screen views. See https://github.com/phetsims/scenery/issues/461
   * Provides a description, and nesting with 'hidden' attribute to help with visibility.
   *
   * @param {AccessibleInstance} accessibleInstance
   * @param {string} screenDescription - Invisible description of the simulation at sim start up which is presented to
   *                                     accessible technologies.
   * @param {string} screenLabel - Short label for the screen view, invisible but accessible
   */
  function ScreenViewAccessiblePeer( accessibleInstance, screenDescription, screenLabel ) {
    this.initialize( accessibleInstance, screenDescription, screenLabel );
  }

  inherit( AccessiblePeer, ScreenViewAccessiblePeer, {
    /**
     * Initialized dom elements and its attributes for the screen view in the parallel DOM.
     *
     * @param {AccessibleInstance} accessibleInstance
     * @param {string} screenDescription - invisible auditory description of sim state at sim start up.
     * @param {string} screenLabel - Short label for the screen view, invisible but accessible
     * @public (accessibility)
     */
    initialize: function( accessibleInstance, screenDescription, screenLabel ) {
      var trail = accessibleInstance.trail;
      var uniqueId = trail.getUniqueId();
      this.node = trail.lastNode(); // @private TODO: may be namespace conflict in future?

      // we want the representative element in the Parallel DOM to look like this:
      //

      //
      //
Short label for the whole screen view

      //
      //

      //
Long description for layout and components of screen view...

      //

      //
      //
      //
...

      this.domElement = document.createElement( 'div' ); // @protected
      this.domElement.className = 'ScreenView';
      this.initializeAccessiblePeer( accessibleInstance, this.domElement );

      // create the header element which will contain the accessible label and description
      var headerElement = document.createElement( 'header' );
      headerElement.setAttribute( 'role', 'banner' );
      this.domElement.appendChild( headerElement );

      if ( screenLabel ) {
        // create the heading for the label, giving it a unique id for ARIA
        var labelElement = document.createElement( 'h1' );
        labelElement.id = 'scene-label-' + uniqueId;

        // link the label to the header element with aria-labelledby
        headerElement.setAttribute( 'aria-labelledby', labelElement.id );

        // set the textContent from the translatable label string
        labelElement.textContent = screenLabel;

        // add the label as a child of the header element
        headerElement.appendChild( labelElement );
      }

      if ( screenDescription ) {
        // create the container div for the description element and its paragraph
        var descriptionElement = document.createElement( 'div' );
        var descriptionParagraph = document.createElement( 'p' );

        // assign the div with an id for ARIA, and set the header attribute
        descriptionElement.id = 'scene-descripion-' + uniqueId;
        headerElement.setAttribute( 'aria-describedby', descriptionElement.id );

        // set the textContent for the description paragraph
        descriptionParagraph.textContent = screenDescription;

        // structure the description
        descriptionElement.appendChild( descriptionParagraph );

        // add the description element as a child of the header
        headerElement.appendChild( descriptionElement );
      }

      // Separate container for children needed, since the description can be a child
      this.containerDOMElement = document.createElement( 'div' ); // @private

      this.containerDOMElement.className = 'ScreenViewContainer';
      this.containerDOMElement.setAttribute( 'role', 'main' );
      this.domElement.appendChild( this.containerDOMElement );

      this.visibilityListener = this.updateVisibility.bind( this ); // @private
      this.node.onStatic( 'visibility', this.visibilityListener );

      this.updateVisibility();
    },

    /**
     * Make unused screen views unavailable to a screen reader by setting the html attribute hidden.
     * @public (accessibility)
     */
    updateVisibility: function() {
      this.domElement.hidden = !this.node.visible;
    },

    /**
     * @public (accessibility)
     */
    dispose: function() {
      AccessiblePeer.prototype.dispose.call( this );

      this.node.offStatic( 'visibility', this.visibilityListener );
    }
  } );

  return ScreenView;
} );<commit_msg>ScreenView accessible content is now a main element<commit_after>// Copyright 2013-2015, University of Colorado Boulder

/**
 * The view portion of a Screen, specifies the layout strategy.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var Node = require( 'SCENERY/nodes/Node' );
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var inherit = require( 'PHET_CORE/inherit' );
  var platform = require( 'PHET_CORE/platform' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Events = require( 'AXON/Events' );
  var joist = require( 'JOIST/joist' );

  /*
   * Default width and height for iPad2, iPad3, iPad4 running Safari with default tabs and decorations
   * These bounds were added in Sep 2014 and are based on a screenshot from a non-Retina iPad, in Safari, iOS7.
   * It therefore accounts for the nav bar on the bottom and the space consumed by the browser on the top.
   * As of this writing, this is the resolution being used by PhET's sim designers for their mockups.
   * For more information see https://github.com/phetsims/joist/issues/126
   */
  var DEFAULT_LAYOUT_BOUNDS = new Bounds2( 0, 0, 1024, 618 );

  function ScreenView( options ) {

    options = _.extend( {
      layoutBounds: DEFAULT_LAYOUT_BOUNDS.copy()
    }, options );
    this.layoutBounds = options.layoutBounds;

    Node.call( this, _.extend( {
      renderer: platform.edge ? 'canvas' : null,
      layerSplit: true, // so we're not in the same layer as the navbar, etc.
      excludeInvisible: true, // so we don't keep invisible screens in the SVG tree
      accessibleContent: {
        createPeer: function( accessibleInstance ) {
          return new ScreenViewAccessiblePeer( accessibleInstance, options.screenDescription, options.screenLabel );
        }
      }
    }, options ) );

    // @public - event channel for notifications
    this.events = new Events();
  }

  joist.register( 'ScreenView', ScreenView );

  inherit( Node, ScreenView, {

      /**
       * Get the scale to use for laying out the sim components and the navigation bar, so its size will track
       * with the sim size
       * @param {number} width
       * @param {number} height
       * @returns {number}
       * @public (joist-internal)
       */
      getLayoutScale: function( width, height ) {
        return Math.min( width / this.layoutBounds.width, height / this.layoutBounds.height );
      },

      /**
       * Default layout function uses the layoutWidth and layoutHeight to scale the content (based on whichever is more limiting: width or height)
       * and centers the content in the screen vertically and horizontally
       * This function can be replaced by subclasses that wish to perform their own custom layout.
       * @param {number} width
       * @param {number} height
       * @public (joist-internal)
       */
      layout: function( width, height ) {
        this.resetTransform();

        var scale = this.getLayoutScale( width, height );
        this.setScaleMagnitude( scale );

        var dx = 0;
        var dy = 0;

        //center vertically
        if ( scale === width / this.layoutBounds.width ) {
          dy = (height / scale - this.layoutBounds.height ) / 2;
        }

        //center horizontally
        else if ( scale === height / this.layoutBounds.height ) {
          dx = (width / scale - this.layoutBounds.width ) / 2;
        }

        this.translate( dx, dy );

        /**
         * Trigger notifications that the visible bounds have changed, in case any clients need to update the views.
         *
         * @param {number} dx - the horizontal offset in stage coordinates
         * @param {number} dy - the vertical offset in stage coordinates
         * @param {number} width - the width of the entire visible area in stage coordinates
         * @param {number} height - the height of the entire visible area in stage coordinates
         * @param {number} scale - the scale factor between stage coordinates and pixel coordinates
         * @public
         */
        this.events.trigger( 'layoutFinished', dx, dy, width / scale, height / scale, scale );
      }
    },

    //statics
    {
      // @public
      DEFAULT_LAYOUT_BOUNDS: DEFAULT_LAYOUT_BOUNDS,

      /**
       *
       * @param {AccessibleInstance} accessibleInstance
       * @param {string} screenDescription
       * @param {string} screenLabel
       * @returns {ScreenViewAccessiblePeer}
       * @constructor
       * @public
       */
      ScreenViewAccessiblePeer: function( accessibleInstance, screenDescription, screenLabel ) {
        return new ScreenViewAccessiblePeer( accessibleInstance, screenDescription, screenLabel );
      }
    }
  );

  /**
   * An accessible peer for handling the parallel DOM for screen views. See https://github.com/phetsims/scenery/issues/461
   * Provides a description, and nesting with 'hidden' attribute to help with visibility.
   *
   * @param {AccessibleInstance} accessibleInstance
   * @param {string} screenDescription - Invisible description of the simulation at sim start up which is presented to
   *                                     accessible technologies.
   * @param {string} screenLabel - Short label for the screen view, invisible but accessible
   */
  function ScreenViewAccessiblePeer( accessibleInstance, screenDescription, screenLabel ) {
    this.initialize( accessibleInstance, screenDescription, screenLabel );
  }

  inherit( AccessiblePeer, ScreenViewAccessiblePeer, {
    /**
     * Initialized dom elements and its attributes for the screen view in the parallel DOM.
     *
     * @param {AccessibleInstance} accessibleInstance
     * @param {string} screenDescription - invisible auditory description of sim state at sim start up.
     * @param {string} screenLabel - Short label for the screen view, invisible but accessible
     * @public (accessibility)
     */
    initialize: function( accessibleInstance, screenDescription, screenLabel ) {
      var trail = accessibleInstance.trail;
      var uniqueId = trail.getUniqueId();
      this.node = trail.lastNode(); // @private TODO: may be namespace conflict in future?

      // we want the representative element in the Parallel DOM to look like this:
      //

      //
      //
Short label for the whole screen view

      //
      //

      //
Long description for layout and components of screen view...

      //

      //
      //
      //
...

      this.domElement = document.createElement( 'div' ); // @protected
      this.domElement.className = 'ScreenView';
      this.initializeAccessiblePeer( accessibleInstance, this.domElement );

      // create the header element which will contain the accessible label and description
      var headerElement = document.createElement( 'header' );
      headerElement.setAttribute( 'role', 'banner' );
      this.domElement.appendChild( headerElement );

      if ( screenLabel ) {
        // create the heading for the label, giving it a unique id for ARIA
        var labelElement = document.createElement( 'h1' );
        labelElement.id = 'scene-label-' + uniqueId;

        // link the label to the header element with aria-labelledby
        headerElement.setAttribute( 'aria-labelledby', labelElement.id );

        // set the textContent from the translatable label string
        labelElement.textContent = screenLabel;

        // add the label as a child of the header element
        headerElement.appendChild( labelElement );
      }

      if ( screenDescription ) {
        // create the container div for the description element and its paragraph
        var descriptionElement = document.createElement( 'div' );
        var descriptionParagraph = document.createElement( 'p' );

        // assign the div with an id for ARIA, and set the header attribute
        descriptionElement.id = 'scene-descripion-' + uniqueId;
        headerElement.setAttribute( 'aria-describedby', descriptionElement.id );

        // set the textContent for the description paragraph
        descriptionParagraph.textContent = screenDescription;

        // structure the description
        descriptionElement.appendChild( descriptionParagraph );

        // add the description element as a child of the header
        headerElement.appendChild( descriptionElement );
      }

      // Separate container for children needed, since the description can be a child
      this.containerDOMElement = document.createElement( 'main' ); // @private

      this.containerDOMElement.className = 'ScreenViewContainer';
      this.domElement.appendChild( this.containerDOMElement );

      this.visibilityListener = this.updateVisibility.bind( this ); // @private
      this.node.onStatic( 'visibility', this.visibilityListener );

      this.updateVisibility();
    },

    /**
     * Make unused screen views unavailable to a screen reader by setting the html attribute hidden.
     * @public (accessibility)
     */
    updateVisibility: function() {
      this.domElement.hidden = !this.node.visible;
    },

    /**
     * @public (accessibility)
     */
    dispose: function() {
      AccessiblePeer.prototype.dispose.call( this );

      this.node.offStatic( 'visibility', this.visibilityListener );
    }
  } );

  return ScreenView;
} );<|endoftext|>


<commit_before>// Copyright 2019, University of Colorado Boulder

/**
 * Handles serializing and deserializing versions for simulations.
 *
 **************************************************************
 * IMPORTANT NOTE: This file is copied from PERENNIAL to CHIPPER frequently. If this is in CHIPPER, then it is AUTO
 * GENERATED; do not edit, as it will be overwritten. Make sure that any change here is supported by usages of
 * CHIPPER's SimVersion (i.e. in JOIST/updateCheck.js), see https://github.com/phetsims/perennial/issues/111 for more
 **************************************************************
 *
 * See https://github.com/phetsims/chipper/issues/560 for discussion on version ID definition.
 *
 * The canonical description of our general versions:
 *
 * Each version string has the form: {{MAJOR}}.{{MINOR}}.{{MAINTENANCE}}[-{{TEST_TYPE}}.{{TEST_NUMBER}}] where:
 *
 * MAJOR: Sequential integer, starts at 1, and is generally incremented when there are significant changes to a simulation.
 * MINOR: Sequential integer, starts at 0, and is generally incremented when there are smaller changes to a simulation.
 *   Resets to 0 whenever the major number is incremented.
 * MAINTENANCE: Sequential integer, starts at 0, and is incremented whenever we build with the same major/minor (but with different SHAs).
 *   Resets to 0 whenever the minor number is incremented.
 * TEST_TYPE (when present): Indicates that this is a non-production build when present. Typically will take the values:
 *   'dev' - A normal dev deployment, which goes to phet-dev.colorado.edu/html/
 *   'rc' -  A release-candidate deployment (off of a release branch). Also goes to phet-dev.colorado.edu/html/ only.
 *   anything else - A one-off deployment name, which is the same name as the branch it was deployed from.
 * TEST_NUMBER (when present): Indicates the version of the test/one-off type (gets incremented for every deployment).
 *   starts at 0 in package.json, but since it is incremented on every deploy, the first version published will be 1.
 *
 * It used to be (pre-chipper-2.0) that sometimes a shortened form of the (non-'phet') brand would be added to the end
 * (e.g. '1.3.0-dev.1-phetio' or '1.3.0-dev.1-adaptedfromphet'), or as a direct prefix for the TEST_TYPE (e.g.
 * 1.1.0-phetiodev.1 or 1.1.0-phetio). We have since moved to a deployment model where there are
 * subdirectories for each brand, so this is no longer part of the version. Since this was not used for any production sim
 * builds that we need statistics from, it is excluded in SimVersion.js or its description.
 *
 * Examples:
 *
 * 1.5.0                - Production simulation version (no test type). Major = 1, minor = 5, maintenance = 0
 * 1.5.0-rc.1           - Example of a release-candidate build version that would be published before '1.5.0' for testing.
 * 1.5.0-dev.1          - Example of a dev build that would be from master.
 * 1.5.0-sonification.1 - Example of a one-off build (which would be from the branch 'sonification')
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

/* eslint-env browser, node */
'use strict';

// To support loading in Node.js and the browser
const assert = typeof module !== 'undefined' ? require( 'assert' ) : window && window.assert;

( function( global, assert ) {

  class SimVersion {
    /**
     * @constructor
     *
     * @param {number} major - The major part of the version (the 3 in 3.1.2)
     * @param {number} minor - The minor part of the version (the 1 in 3.1.2)
     * @param {number} maintenance - The maintenance part of the version (the 2 in 3.1.2)
     * @param {Object} [options]
     */
    constructor( major, minor, maintenance, options ) {
      const {
        // {string|null} - If provided, indicates the time at which the sim file was built
        buildTimestamp = null,

        // {string|null} - The test name, e.g. the 'rc' in rc.1. Also can be the one-off version name, if provided.
        testType = null,

        // {number|null} - The test number, e.g. the 1 in rc.1
        testNumber = null
      } = options || {};

      assert && assert( typeof major === 'number' && major >= 0 && major % 1 === 0, 'major version should be a non-negative integer' );
      assert && assert( typeof minor === 'number' && minor >= 0 && minor % 1 === 0, 'minor version should be a non-negative integer' );
      assert && assert( typeof maintenance === 'number' && maintenance >= 0 && maintenance % 1 === 0, 'maintenance version should be a non-negative integer' );
      assert && assert( typeof testType !== 'string' || typeof testNumber === 'number', 'if testType is provided, testNumber should be a number' );

      // @public {number}
      this.major = major;

      // @public {number}
      this.minor = minor;

      // @public {number}
      this.maintenance = maintenance;

      // @public {string|null}
      this.testType = testType;

      // @public {number|null}
      this.testNumber = testNumber;

      // @public {string|null} - If provided, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
      this.buildTimestamp = buildTimestamp;
    }

    /**
     * Convert into a plain JS object meant for JSON serialization.
     * @public
     *
     * @returns {Object}
     */
    serialize() {
      return {
        major: this.major,
        minor: this.minor,
        maintenance: this.maintenance,
        testType: this.testType,
        testNumber: this.testNumber,
        buildTimestamp: this.buildTimestamp
      };
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimNotPublished() {
      return this.major < 1 || // e.g. 0.0.0-dev.1
             ( this.major === 1 && // e.g. 1.0.0-dev.1
               this.minor === 0 &&
               this.maintenance === 0 &&
               this.testType );
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimPublished() {
      return !this.isSimNotPublished;
    }

    /**
     * Takes a serialized form of the SimVersion and returns an actual instance.
     * @public
     *
     * @param {Object}
     * @returns {SimVersion}
     */
    static deserialize( { major, minor, maintenance, testType, testNumber, buildTimestamp } ) {
      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Compares versions, returning -1 if this version is before the passed in version, 0 if equal, or 1 if this version
     * is after.
     * @public
     *
     * This function only compares major/minor/maintenance, leaving other details to the client.
     *
     * @param {SimVersion} version
     */
    compareNumber( version ) {
      if ( this.major < version.major ) { return -1; }
      if ( this.major > version.major ) { return 1; }
      if ( this.minor < version.minor ) { return -1; }
      if ( this.minor > version.minor ) { return 1; }
      if ( this.maintenance < version.maintenance ) { return -1; }
      if ( this.maintenance > version.maintenance ) { return 1; }
      return 0; // equal
    }

    /**
     * Returns the string form of the version.
     * @public
     *
     * @returns {string}
     */
    toString() {
      let str = `${this.major}.${this.minor}.${this.maintenance}`;
      if ( typeof this.testType === 'string' ) {
        str += `-${this.testType}.${this.testNumber}`;
      }
      return str;
    }

    /**
     * Parses a sim version from a string form.
     * @public
     *
     * @param {string} versionString - e.g. '1.0.0', '1.0.1-dev.3', etc.
     * @param {string} [buildTimestamp] - Optional build timestamp, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
     * @returns {SimVersion}
     */
    static parse( versionString, buildTimestamp ) {
      const matches = versionString.match( /^(\d+)\.(\d+)\.(\d+)(-(([^.-]+)\.(\d+)))?(-([^.-]+))?$/ );

      if ( !matches ) {
        throw new Error( 'could not parse version: ' + versionString );
      }

      const major = parseInt( matches[ 1 ], 10 );
      const minor = parseInt( matches[ 2 ], 10 );
      const maintenance = parseInt( matches[ 3 ], 10 );
      const testType = matches[ 6 ];
      const testNumber = matches[ 7 ] === undefined ? matches[ 7 ] : parseInt( matches[ 7 ], 10 );

      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Parses a branch in the form {{MAJOR}}.{{MINOR}} and returns a corresponding version. Uses 0 for the maintenance version (unknown).
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     * @returns {SimVersion}
     */
    static fromBranch( branch ) {
      const bits = branch.split( '.' );
      assert && assert( bits.length === 2, `Bad branch, should be {{MAJOR}}.{{MINOR}}, had: ${branch}` );

      const major = parseInt( branch.split( '.' )[ 0 ], 10 );
      const minor = parseInt( branch.split( '.' )[ 1 ], 10 );

      return new SimVersion( major, minor, 0 );
    }

    /**
     * Ensures that a branch name is ok to be a release branch.
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     */
    static ensureReleaseBranch( branch ) {
      const version = SimVersion.fromBranch( branch );
      assert && assert( version.major > 0, 'Major version for a branch should be greater than zero' );
      assert && assert( version.minor >= 0, 'Minor version for a branch should be greater than (or equal) to zero' );
    }
  }

  // Node.js-compatible definition
  if ( typeof module !== 'undefined' ) {
    module.exports = SimVersion;
  }
  else {

    // Browser support, assign with
    window.phet = window.phet || {};
    window.phet.preloads = window.phet.preloads || {};
    window.phet.preloads.chipper = window.phet.preloads.chipper || {};
    window.phet.preloads.chipper.SimVersion = SimVersion;
  }
} )( ( 1, eval )( 'this' ), assert );
// Indirect eval usage done since babel likes to wrap things in strict mode.
// See http://perfectionkills.com/unnecessarily-comprehensive-look-into-a-rather-insignificant-issue-of-global-objects-creation/#ecmascript_5_strict_mode
<commit_msg>update copyright dates from daily grunt work<commit_after>// Copyright 2019-2020, University of Colorado Boulder

/**
 * Handles serializing and deserializing versions for simulations.
 *
 **************************************************************
 * IMPORTANT NOTE: This file is copied from PERENNIAL to CHIPPER frequently. If this is in CHIPPER, then it is AUTO
 * GENERATED; do not edit, as it will be overwritten. Make sure that any change here is supported by usages of
 * CHIPPER's SimVersion (i.e. in JOIST/updateCheck.js), see https://github.com/phetsims/perennial/issues/111 for more
 **************************************************************
 *
 * See https://github.com/phetsims/chipper/issues/560 for discussion on version ID definition.
 *
 * The canonical description of our general versions:
 *
 * Each version string has the form: {{MAJOR}}.{{MINOR}}.{{MAINTENANCE}}[-{{TEST_TYPE}}.{{TEST_NUMBER}}] where:
 *
 * MAJOR: Sequential integer, starts at 1, and is generally incremented when there are significant changes to a simulation.
 * MINOR: Sequential integer, starts at 0, and is generally incremented when there are smaller changes to a simulation.
 *   Resets to 0 whenever the major number is incremented.
 * MAINTENANCE: Sequential integer, starts at 0, and is incremented whenever we build with the same major/minor (but with different SHAs).
 *   Resets to 0 whenever the minor number is incremented.
 * TEST_TYPE (when present): Indicates that this is a non-production build when present. Typically will take the values:
 *   'dev' - A normal dev deployment, which goes to phet-dev.colorado.edu/html/
 *   'rc' -  A release-candidate deployment (off of a release branch). Also goes to phet-dev.colorado.edu/html/ only.
 *   anything else - A one-off deployment name, which is the same name as the branch it was deployed from.
 * TEST_NUMBER (when present): Indicates the version of the test/one-off type (gets incremented for every deployment).
 *   starts at 0 in package.json, but since it is incremented on every deploy, the first version published will be 1.
 *
 * It used to be (pre-chipper-2.0) that sometimes a shortened form of the (non-'phet') brand would be added to the end
 * (e.g. '1.3.0-dev.1-phetio' or '1.3.0-dev.1-adaptedfromphet'), or as a direct prefix for the TEST_TYPE (e.g.
 * 1.1.0-phetiodev.1 or 1.1.0-phetio). We have since moved to a deployment model where there are
 * subdirectories for each brand, so this is no longer part of the version. Since this was not used for any production sim
 * builds that we need statistics from, it is excluded in SimVersion.js or its description.
 *
 * Examples:
 *
 * 1.5.0                - Production simulation version (no test type). Major = 1, minor = 5, maintenance = 0
 * 1.5.0-rc.1           - Example of a release-candidate build version that would be published before '1.5.0' for testing.
 * 1.5.0-dev.1          - Example of a dev build that would be from master.
 * 1.5.0-sonification.1 - Example of a one-off build (which would be from the branch 'sonification')
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

/* eslint-env browser, node */
'use strict';

// To support loading in Node.js and the browser
const assert = typeof module !== 'undefined' ? require( 'assert' ) : window && window.assert;

( function( global, assert ) {

  class SimVersion {
    /**
     * @constructor
     *
     * @param {number} major - The major part of the version (the 3 in 3.1.2)
     * @param {number} minor - The minor part of the version (the 1 in 3.1.2)
     * @param {number} maintenance - The maintenance part of the version (the 2 in 3.1.2)
     * @param {Object} [options]
     */
    constructor( major, minor, maintenance, options ) {
      const {
        // {string|null} - If provided, indicates the time at which the sim file was built
        buildTimestamp = null,

        // {string|null} - The test name, e.g. the 'rc' in rc.1. Also can be the one-off version name, if provided.
        testType = null,

        // {number|null} - The test number, e.g. the 1 in rc.1
        testNumber = null
      } = options || {};

      assert && assert( typeof major === 'number' && major >= 0 && major % 1 === 0, 'major version should be a non-negative integer' );
      assert && assert( typeof minor === 'number' && minor >= 0 && minor % 1 === 0, 'minor version should be a non-negative integer' );
      assert && assert( typeof maintenance === 'number' && maintenance >= 0 && maintenance % 1 === 0, 'maintenance version should be a non-negative integer' );
      assert && assert( typeof testType !== 'string' || typeof testNumber === 'number', 'if testType is provided, testNumber should be a number' );

      // @public {number}
      this.major = major;

      // @public {number}
      this.minor = minor;

      // @public {number}
      this.maintenance = maintenance;

      // @public {string|null}
      this.testType = testType;

      // @public {number|null}
      this.testNumber = testNumber;

      // @public {string|null} - If provided, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
      this.buildTimestamp = buildTimestamp;
    }

    /**
     * Convert into a plain JS object meant for JSON serialization.
     * @public
     *
     * @returns {Object}
     */
    serialize() {
      return {
        major: this.major,
        minor: this.minor,
        maintenance: this.maintenance,
        testType: this.testType,
        testNumber: this.testNumber,
        buildTimestamp: this.buildTimestamp
      };
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimNotPublished() {
      return this.major < 1 || // e.g. 0.0.0-dev.1
             ( this.major === 1 && // e.g. 1.0.0-dev.1
               this.minor === 0 &&
               this.maintenance === 0 &&
               this.testType );
    }

    /**
     * @returns {boolean}
     * @public
     */
    get isSimPublished() {
      return !this.isSimNotPublished;
    }

    /**
     * Takes a serialized form of the SimVersion and returns an actual instance.
     * @public
     *
     * @param {Object}
     * @returns {SimVersion}
     */
    static deserialize( { major, minor, maintenance, testType, testNumber, buildTimestamp } ) {
      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Compares versions, returning -1 if this version is before the passed in version, 0 if equal, or 1 if this version
     * is after.
     * @public
     *
     * This function only compares major/minor/maintenance, leaving other details to the client.
     *
     * @param {SimVersion} version
     */
    compareNumber( version ) {
      if ( this.major < version.major ) { return -1; }
      if ( this.major > version.major ) { return 1; }
      if ( this.minor < version.minor ) { return -1; }
      if ( this.minor > version.minor ) { return 1; }
      if ( this.maintenance < version.maintenance ) { return -1; }
      if ( this.maintenance > version.maintenance ) { return 1; }
      return 0; // equal
    }

    /**
     * Returns the string form of the version.
     * @public
     *
     * @returns {string}
     */
    toString() {
      let str = `${this.major}.${this.minor}.${this.maintenance}`;
      if ( typeof this.testType === 'string' ) {
        str += `-${this.testType}.${this.testNumber}`;
      }
      return str;
    }

    /**
     * Parses a sim version from a string form.
     * @public
     *
     * @param {string} versionString - e.g. '1.0.0', '1.0.1-dev.3', etc.
     * @param {string} [buildTimestamp] - Optional build timestamp, like '2015-06-12 16:05:03 UTC' (phet.chipper.buildTimestamp)
     * @returns {SimVersion}
     */
    static parse( versionString, buildTimestamp ) {
      const matches = versionString.match( /^(\d+)\.(\d+)\.(\d+)(-(([^.-]+)\.(\d+)))?(-([^.-]+))?$/ );

      if ( !matches ) {
        throw new Error( 'could not parse version: ' + versionString );
      }

      const major = parseInt( matches[ 1 ], 10 );
      const minor = parseInt( matches[ 2 ], 10 );
      const maintenance = parseInt( matches[ 3 ], 10 );
      const testType = matches[ 6 ];
      const testNumber = matches[ 7 ] === undefined ? matches[ 7 ] : parseInt( matches[ 7 ], 10 );

      return new SimVersion( major, minor, maintenance, {
        testType: testType,
        testNumber: testNumber,
        buildTimestamp: buildTimestamp
      } );
    }

    /**
     * Parses a branch in the form {{MAJOR}}.{{MINOR}} and returns a corresponding version. Uses 0 for the maintenance version (unknown).
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     * @returns {SimVersion}
     */
    static fromBranch( branch ) {
      const bits = branch.split( '.' );
      assert && assert( bits.length === 2, `Bad branch, should be {{MAJOR}}.{{MINOR}}, had: ${branch}` );

      const major = parseInt( branch.split( '.' )[ 0 ], 10 );
      const minor = parseInt( branch.split( '.' )[ 1 ], 10 );

      return new SimVersion( major, minor, 0 );
    }

    /**
     * Ensures that a branch name is ok to be a release branch.
     * @public
     *
     * @param {string} branch - e.g. '1.0'
     */
    static ensureReleaseBranch( branch ) {
      const version = SimVersion.fromBranch( branch );
      assert && assert( version.major > 0, 'Major version for a branch should be greater than zero' );
      assert && assert( version.minor >= 0, 'Minor version for a branch should be greater than (or equal) to zero' );
    }
  }

  // Node.js-compatible definition
  if ( typeof module !== 'undefined' ) {
    module.exports = SimVersion;
  }
  else {

    // Browser support, assign with
    window.phet = window.phet || {};
    window.phet.preloads = window.phet.preloads || {};
    window.phet.preloads.chipper = window.phet.preloads.chipper || {};
    window.phet.preloads.chipper.SimVersion = SimVersion;
  }
} )( ( 1, eval )( 'this' ), assert );
// Indirect eval usage done since babel likes to wrap things in strict mode.
// See http://perfectionkills.com/unnecessarily-comprehensive-look-into-a-rather-insignificant-issue-of-global-objects-creation/#ecmascript_5_strict_mode
<|endoftext|>