<commit_before>$(function () {
     $("#spinner").show();
     $("#spinnerTwo").show();
     $.getJSON('https://sadbox.org/geekhack/postsbyminute', function (data) {
        Highcharts.setOptions({
            global : {
                useUTC : false
            }
        });
        $("#spinner").hide();
        $("#postsByMinute").show();
        var currentime = new Date();
        $('#postsByMinute').highcharts({
            chart: {
                type: 'area'
            },
            title: {
                text: 'Activity in channel by time of day'
            },
            xAxis: {
                type: 'datetime',
                dateTimeLabelFormats: {
                    day: '%H:%M'
                },
                title: {
                    text: "Time of Post (UTC Offset: "+(-currentime.getTimezoneOffset()/60)+")"
                }
            },
            yAxis: {
                title: {
                    text: 'Posts Per Minute'
                }
            },
            tooltip: {
                formatter: function() {
                    return ''+this.y.toPrecision(3)+' posts per minute at '+Highcharts.dateFormat('%H:%M', this.x)+''
                }
            },
            legend: {
                enabled: false
            },
            credits: {
                enabled: false
            },
            plotOptions: {
                area: {
                    pointStart: Date.UTC(0,0,0),
                    pointInterval: 60 * 1000,
                    marker: {
                        enabled: false,
                        symbol: 'circle',
                        radius: 2,
                        states: {
                            hover: {
                                enabled: true
                            }
                        }
                    }
                }
            },
            series: [data]
        });
    });
     $.getJSON('https://sadbox.org/geekhack/postsbydayall', function (data) {
        $("#spinnerTwo").hide();
        $("#postsByDayAll").show();
        $('#postsByDayAll').highcharts({
            chart: {
                type: 'area',
                zoomType: 'x'
            },
            title: {
                text: 'Activity in channel over time'
            },
            xAxis: {
                type: 'datetime',
                title: {
                    text: "Date"
                }
            },
            yAxis: {
                title: {
                    text: 'Posts'
                }
            },
            tooltip: {
                formatter: function() {
                    return ''+this.y+' posts on '+Highcharts.dateFormat('%b %d, %Y', this.x)+''
                }
            },
            legend: {
                enabled: false
            },
            credits: {
                enabled: false
            },
            plotOptions: {
                area: {
                    marker: {
                        enabled: false,
                        symbol: 'circle',
                        radius: 2,
                        states: {
                            hover: {
                                enabled: true
                            }
                        }
                    }
                }
            },
            series: [data]
        });
    });
});
<commit_msg>relative path for fetching chart data so that it can load without https if necessary<commit_after>$(function () {
     $("#spinner").show();
     $("#spinnerTwo").show();
     $.getJSON('/geekhack/postsbyminute', function (data) {
        Highcharts.setOptions({
            global : {
                useUTC : false
            }
        });
        $("#spinner").hide();
        $("#postsByMinute").show();
        var currentime = new Date();
        $('#postsByMinute').highcharts({
            chart: {
                type: 'area'
            },
            title: {
                text: 'Activity in channel by time of day'
            },
            xAxis: {
                type: 'datetime',
                dateTimeLabelFormats: {
                    day: '%H:%M'
                },
                title: {
                    text: "Time of Post (UTC Offset: "+(-currentime.getTimezoneOffset()/60)+")"
                }
            },
            yAxis: {
                title: {
                    text: 'Posts Per Minute'
                }
            },
            tooltip: {
                formatter: function() {
                    return ''+this.y.toPrecision(3)+' posts per minute at '+Highcharts.dateFormat('%H:%M', this.x)+''
                }
            },
            legend: {
                enabled: false
            },
            credits: {
                enabled: false
            },
            plotOptions: {
                area: {
                    pointStart: Date.UTC(0,0,0),
                    pointInterval: 60 * 1000,
                    marker: {
                        enabled: false,
                        symbol: 'circle',
                        radius: 2,
                        states: {
                            hover: {
                                enabled: true
                            }
                        }
                    }
                }
            },
            series: [data]
        });
    });
     $.getJSON('/geekhack/postsbydayall', function (data) {
        $("#spinnerTwo").hide();
        $("#postsByDayAll").show();
        $('#postsByDayAll').highcharts({
            chart: {
                type: 'area',
                zoomType: 'x'
            },
            title: {
                text: 'Activity in channel over time'
            },
            xAxis: {
                type: 'datetime',
                title: {
                    text: "Date"
                }
            },
            yAxis: {
                title: {
                    text: 'Posts'
                }
            },
            tooltip: {
                formatter: function() {
                    return ''+this.y+' posts on '+Highcharts.dateFormat('%b %d, %Y', this.x)+''
                }
            },
            legend: {
                enabled: false
            },
            credits: {
                enabled: false
            },
            plotOptions: {
                area: {
                    marker: {
                        enabled: false,
                        symbol: 'circle',
                        radius: 2,
                        states: {
                            hover: {
                                enabled: true
                            }
                        }
                    }
                }
            },
            series: [data]
        });
    });
});
<|endoftext|>


<commit_before>function render_graph(detail) {
    Chart.defaults.global.tooltips.enabled = true;
    Chart.defaults.global.tooltips.intersect = false;
    Chart.defaults.global.tooltips.position = 'average';
    Chart.defaults.global.tooltips.mode = 'nearest';
    Chart.defaults.global.tooltips.backgroundColor = 'rgba(0,0,0,0.5)'
    Chart.defaults.global.elements.point.radius = 0;
    Chart.defaults.global.elements.line.borderWidth = 1;
    Chart.defaults.global.elements.line.tension = 0;
    Chart.defaults.global.elements.line.fill = false;

    var second_axis_prefix = ['averagePrice', 'housePriceIndex', 'percentage','sales'];
    var axis_need = [false,false,false,false];
    var formatting_function = [
        function(value, index, values) {
            return "£" + (value > 999 ? (value/1000).toFixed(0) + 'k' : value);
        },
        function(value, index, values) {
            return value;
        },
        function(value, index, values) {
            return value + "%";
        },
        function(value, index, values) {
            return value;
        }
    ]
    var chart_data_set = [];
    var idx_begin = region_data[detail.region[0]].refMonth.indexOf(detail.period[0]);
    var idx_end = region_data[detail.region[0]].refMonth.indexOf(detail.period[1], idx_begin);
    detail.region.forEach(function(region_name) {
        detail.data.forEach(function(data_name) {
            var axis_idx = getIndexFromList(data_name, second_axis_prefix);
            axis_need[axis_idx] = true;
            var label = region_name + '-' + data_name;
            chart_data_set.push({
                label: label,
                yAxisID: 'axis'+axis_idx,
                borderColor: getColour(label),
                backgroundColor: getColour(label),
                data: region_data[region_name][data_name].slice(idx_begin, idx_end)
            });
        });
    });

    var scale_option = {yAxes:[]};
    var side = 'left';
    for(var idx = 0 ; idx < axis_need.length ; ++idx) {
        if (axis_need[idx]) {
            scale_option.yAxes.push({
                id: 'axis'+idx,
                type: 'linear',
                position: side,
                ticks: {
                    callback: formatting_function[idx]
                }
            });
            // toggle side
            side = (side == 'left' ? 'right' : 'left');
        }
    }

    // line chart data
    var chart_data = {
        labels: region_data[detail.region[0]].refMonth.slice(idx_begin, idx_end),
        datasets: chart_data_set
    };
    // create the options
    options = {
        fill: false,
        scales: scale_option,
        /*This is how to customize the way the labels look :) */
        tooltipTemplate: "<%if (label){%><%=label%>: <%}%>$<%= value %>",
        label: 'test',
        responsive: true,
        maintainAspectRatio: false,
        title: {
            display: false,
            text: 'House Price Tracker'
        },
        events: ['click', 'mousemove'],
        onClick: function(e) {
            console.log(e);
        },
        hover: {
            onHover: function(e) {
                this.chart.ctx.layerX = e.layerX;
            },
        },
        animation: {
            duration: 2000,
            onProgress: function(animation) {
                var x_pos = this.chart.ctx.layerX;
                if (x_pos > this.chartArea.left && x_pos < this.chartArea.right) {
                    this.chart.ctx.beginPath();
                    this.chart.ctx.moveTo(this.chart.ctx.layerX, this.chartArea.top);
                    this.chart.ctx.setLineDash([3, 3]);
                    this.chart.ctx.strokeStyle = '#888888';
                    this.chart.ctx.lineTo(this.chart.ctx.layerX, this.chartArea.bottom);
                    this.chart.ctx.stroke();
                    this.chart.ctx.setLineDash([]);
                }
            },
        }
    };
    // Get chart panel
    var chart_head_message = detail.region.join(' ') + " | " +
                         detail.data.join(' ') + " | " + detail.period.join('_');
    var chart_id = "hpc_chart_" + chart_id_sequence++;
    var canvas_id = "canvas_" + chart_id;
    var chart_panel = $("

");
    var chart_panel_head = $("
 " + chart_head_message + "
");
    var close_icon = $("");
    close_icon.on('click', function () {
        $('#'+chart_id).slideUp("fast", function() {
            $('#'+chart_id).remove();
        });
    });
    var capture_icon = $("");
    capture_icon.on('click', function () {
        var canvas = document.getElementById(canvas_id);
        $('#hidden_drawing_area').html('');
        $('#hidden_drawing_area').append($('

'));
        var sel = window.getSelection ? window.getSelection() : document.selection;
        if (sel) {
            if (sel.removeAllRanges) {
                sel.removeAllRanges();
            } else if (sel.empty) {
                sel.empty();
            }
        }
        //html2clipboard($('#card').html());
        var range = document.createRange();
        //range.selectNode(tmpEl);
        range.selectNode(document.getElementById("tmp_image_frame"));
        sel.addRange(range);
        // copy
        document.execCommand("copy");
        $('#hidden_drawing_area').html('');
        $('#info_dlg').find('.modal-body').text("Copied to clipboard. Use ctrl-v to paste in any text area.");
        $('#info_dlg').modal('show');
    });
    var download_icon = $("");
    download_icon.on('click', function() {
        var canvas = document.getElementById(canvas_id);
        var a = $('')
            .attr("href", canvas.toDataURL('image/png'))
            .attr("download", "chart.png")
            .appendTo("body");
        a[0].click();
        a.remove();
    });

    chart_panel_head.prepend(capture_icon);
    chart_panel_head.prepend(download_icon);
    chart_panel_head.prepend(close_icon);
    chart_panel.append(chart_panel_head);
    // Create a new canvas and add to the panel to activate it
    var new_canvas = $("<canvas id='" + canvas_id + "'></canvas>");
    chart_panel.append($("

").append(new_canvas));
    chart_panel.prependTo($('#chart-ground')).slideDown("fast");
    //$('#chart-ground').prepend(chart_panel);

    var hpc_chart = new Chart(new_canvas, {
        type: 'line',
        data: chart_data,
        options: options
    });

    $.ajax({type: "POST",
            url: '/api/log/action',
            data: JSON.stringify({type: 'create-chart',
                   detail: chart_head_message}),
            contentType: "application/json; charset=utf-8",
            dataType: 'json',
            success: function() {}});
}<commit_msg>Fix the date range bug on the graph<commit_after>function render_graph(detail) {
    Chart.defaults.global.tooltips.enabled = true;
    Chart.defaults.global.tooltips.intersect = false;
    Chart.defaults.global.tooltips.position = 'average';
    Chart.defaults.global.tooltips.mode = 'nearest';
    Chart.defaults.global.tooltips.backgroundColor = 'rgba(0,0,0,0.5)'
    Chart.defaults.global.elements.point.radius = 0;
    Chart.defaults.global.elements.line.borderWidth = 1;
    Chart.defaults.global.elements.line.tension = 0;
    Chart.defaults.global.elements.line.fill = false;

    var second_axis_prefix = ['averagePrice', 'housePriceIndex', 'percentage','sales'];
    var axis_need = [false,false,false,false];
    var formatting_function = [
        function(value, index, values) {
            return "£" + (value > 999 ? (value/1000).toFixed(0) + 'k' : value);
        },
        function(value, index, values) {
            return value;
        },
        function(value, index, values) {
            return value + "%";
        },
        function(value, index, values) {
            return value;
        }
    ]
    var chart_data_set = [];
    var idx_begin = region_data[detail.region[0]].refMonth.indexOf(detail.period[0]);
    if (idx_begin == -1) { idx_begin = 0; }
    var idx_end = region_data[detail.region[0]].refMonth.indexOf(detail.period[1], idx_begin);
    if (idx_end == -1) { idx_end = region_data[detail.region[0]].refMonth.length;}
    detail.region.forEach(function(region_name) {
        detail.data.forEach(function(data_name) {
            var axis_idx = getIndexFromList(data_name, second_axis_prefix);
            axis_need[axis_idx] = true;
            var label = region_name + '-' + data_name;
            chart_data_set.push({
                label: label,
                yAxisID: 'axis'+axis_idx,
                borderColor: getColour(label),
                backgroundColor: getColour(label),
                data: region_data[region_name][data_name].slice(idx_begin, idx_end + 1)
            });
        });
    });

    var scale_option = {yAxes:[]};
    var side = 'left';
    for(var idx = 0 ; idx < axis_need.length ; ++idx) {
        if (axis_need[idx]) {
            scale_option.yAxes.push({
                id: 'axis'+idx,
                type: 'linear',
                position: side,
                ticks: {
                    callback: formatting_function[idx]
                }
            });
            // toggle side
            side = (side == 'left' ? 'right' : 'left');
        }
    }

    // line chart data
    var chart_data = {
        labels: region_data[detail.region[0]].refMonth.slice(idx_begin, idx_end + 1),
        datasets: chart_data_set
    };
    // create the options
    options = {
        fill: false,
        scales: scale_option,
        /*This is how to customize the way the labels look :) */
        tooltipTemplate: "<%if (label){%><%=label%>: <%}%>$<%= value %>",
        label: 'test',
        responsive: true,
        maintainAspectRatio: false,
        title: {
            display: false,
            text: 'House Price Tracker'
        },
        events: ['click', 'mousemove'],
        onClick: function(e) {
            console.log(e);
        },
        hover: {
            onHover: function(e) {
                this.chart.ctx.layerX = e.layerX;
            },
        },
        animation: {
            duration: 2000,
            onProgress: function(animation) {
                var x_pos = this.chart.ctx.layerX;
                if (x_pos > this.chartArea.left && x_pos < this.chartArea.right) {
                    this.chart.ctx.beginPath();
                    this.chart.ctx.moveTo(this.chart.ctx.layerX, this.chartArea.top);
                    this.chart.ctx.setLineDash([3, 3]);
                    this.chart.ctx.strokeStyle = '#888888';
                    this.chart.ctx.lineTo(this.chart.ctx.layerX, this.chartArea.bottom);
                    this.chart.ctx.stroke();
                    this.chart.ctx.setLineDash([]);
                }
            },
        }
    };
    // Get chart panel
    var chart_head_message = detail.region.join(' ') + " | " +
                         detail.data.join(' ') + " | " + detail.period.join('_');
    var chart_id = "hpc_chart_" + chart_id_sequence++;
    var canvas_id = "canvas_" + chart_id;
    var chart_panel = $("

");
    var chart_panel_head = $("
 " + chart_head_message + "
");
    var close_icon = $("");
    close_icon.on('click', function () {
        $('#'+chart_id).slideUp("fast", function() {
            $('#'+chart_id).remove();
        });
    });
    var capture_icon = $("");
    capture_icon.on('click', function () {
        var canvas = document.getElementById(canvas_id);
        $('#hidden_drawing_area').html('');
        $('#hidden_drawing_area').append($('

'));
        var sel = window.getSelection ? window.getSelection() : document.selection;
        if (sel) {
            if (sel.removeAllRanges) {
                sel.removeAllRanges();
            } else if (sel.empty) {
                sel.empty();
            }
        }
        //html2clipboard($('#card').html());
        var range = document.createRange();
        //range.selectNode(tmpEl);
        range.selectNode(document.getElementById("tmp_image_frame"));
        sel.addRange(range);
        // copy
        document.execCommand("copy");
        $('#hidden_drawing_area').html('');
        $('#info_dlg').find('.modal-body').text("Copied to clipboard. Use ctrl-v to paste in any text area.");
        $('#info_dlg').modal('show');
    });
    var download_icon = $("");
    download_icon.on('click', function() {
        var canvas = document.getElementById(canvas_id);
        var a = $('')
            .attr("href", canvas.toDataURL('image/png'))
            .attr("download", "chart.png")
            .appendTo("body");
        a[0].click();
        a.remove();
    });

    chart_panel_head.prepend(capture_icon);
    chart_panel_head.prepend(download_icon);
    chart_panel_head.prepend(close_icon);
    chart_panel.append(chart_panel_head);
    // Create a new canvas and add to the panel to activate it
    var new_canvas = $("<canvas id='" + canvas_id + "'></canvas>");
    chart_panel.append($("

").append(new_canvas));
    chart_panel.prependTo($('#chart-ground')).slideDown("fast");
    //$('#chart-ground').prepend(chart_panel);

    var hpc_chart = new Chart(new_canvas, {
        type: 'line',
        data: chart_data,
        options: options
    });

    $.ajax({type: "POST",
            url: '/api/log/action',
            data: JSON.stringify({type: 'create-chart',
                   detail: chart_head_message}),
            contentType: "application/json; charset=utf-8",
            dataType: 'json',
            success: function() {}});
}

<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 */

import { ArrayCamera } from '../../cameras/ArrayCamera.js';
import { EventDispatcher } from '../../core/EventDispatcher.js';
import { Group } from '../../objects/Group.js';
import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js';
import { Vector3 } from '../../math/Vector3.js';
import { Vector4 } from '../../math/Vector4.js';
import { WebGLAnimation } from '../webgl/WebGLAnimation.js';

function WebXRManager( renderer, gl ) {

	var scope = this;

	var session = null;

	var framebufferScaleFactor = 1.0;

	var referenceSpace = null;
	var referenceSpaceType = 'local-floor';

	var pose = null;

	var controllers = [];
	var inputSourcesMap = new Map();

	//

	var cameraL = new PerspectiveCamera();
	cameraL.layers.enable( 1 );
	cameraL.viewport = new Vector4();

	var cameraR = new PerspectiveCamera();
	cameraR.layers.enable( 2 );
	cameraR.viewport = new Vector4();

	var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );
	cameraVR.layers.enable( 1 );
	cameraVR.layers.enable( 2 );

	var _currentDepthNear = null;
	var _currentDepthFar = null;

	//

	this.enabled = false;

	this.isPresenting = false;

	this.getController = function ( id ) {

		var controller = controllers[ id ];

		if ( controller === undefined ) {

			controller = {};
			controllers[ id ] = controller;

		}

		if ( controller.targetRay === undefined ) {

			controller.targetRay = new Group();
			controller.targetRay.matrixAutoUpdate = false;
			controller.targetRay.visible = false;

		}

		return controller.targetRay;

	};

	this.getControllerGrip = function ( id ) {

		var controller = controllers[ id ];

		if ( controller === undefined ) {

			controller = {};
			controllers[ id ] = controller;

		}

		if ( controller.grip === undefined ) {

			controller.grip = new Group();
			controller.grip.matrixAutoUpdate = false;
			controller.grip.visible = false;

		}

		return controller.grip;

	};

	//

	function onSessionEvent( event ) {

		var controller = inputSourcesMap.get( event.inputSource );

		if ( controller ) {

			if ( controller.targetRay ) {

				controller.targetRay.dispatchEvent( { type: event.type } );

			}

			if ( controller.grip ) {

				controller.grip.dispatchEvent( { type: event.type } );

			}

		}

	}

	function onSessionEnd() {

		inputSourcesMap.forEach( function ( controller, inputSource ) {

			if ( controller.targetRay ) {

				controller.targetRay.dispatchEvent( { type: 'disconnected', data: inputSource } );
				controller.targetRay.visible = false;

			}

			if ( controller.grip ) {

				controller.grip.dispatchEvent( { type: 'disconnected', data: inputSource } );
				controller.grip.visible = false;

			}

		} );

		inputSourcesMap.clear();

		//

		renderer.setFramebuffer( null );
		renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
		animation.stop();

		scope.isPresenting = false;

		scope.dispatchEvent( { type: 'sessionend' } );

	}

	function onRequestReferenceSpace( value ) {

		referenceSpace = value;

		animation.setContext( session );
		animation.start();

		scope.isPresenting = true;

		scope.dispatchEvent( { type: 'sessionstart' } );

	}

	this.setFramebufferScaleFactor = function ( value ) {

		framebufferScaleFactor = value;

		// Warn if function is used while presenting
		if ( scope.isPresenting == true ) {

			console.warn( "WebXRManager: Cannot change framebuffer scale while presenting VR content" );

		}

	};

	this.setReferenceSpaceType = function ( value ) {

		referenceSpaceType = value;

	};

	this.getReferenceSpace = function () {

		return referenceSpace;

	};

	this.getSession = function () {

		return session;

	};

	this.setSession = function ( value ) {

		session = value;

		if ( session !== null ) {

			session.addEventListener( 'select', onSessionEvent );
			session.addEventListener( 'selectstart', onSessionEvent );
			session.addEventListener( 'selectend', onSessionEvent );
			session.addEventListener( 'squeeze', onSessionEvent );
			session.addEventListener( 'squeezestart', onSessionEvent );
			session.addEventListener( 'squeezeend', onSessionEvent );
			session.addEventListener( 'end', onSessionEnd );

			var attributes = gl.getContextAttributes();

			var layerInit = {
				antialias: attributes.antialias,
				alpha: attributes.alpha,
				depth: attributes.depth,
				stencil: attributes.stencil,
				framebufferScaleFactor: framebufferScaleFactor
			};

			// eslint-disable-next-line no-undef
			var baseLayer = new XRWebGLLayer( session, gl, layerInit );

			session.updateRenderState( { baseLayer: baseLayer } );

			session.requestReferenceSpace( referenceSpaceType ).then( onRequestReferenceSpace );

			//

			session.addEventListener( 'inputsourceschange', updateInputSources );

		}

	};

	function updateInputSources( event ) {

		var inputSources = session.inputSources;

		// Assign inputSources to available controllers

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

			inputSourcesMap.set( inputSources[ i ], controllers[ i ] );

		}

		// Notify disconnected

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

			var inputSource = event.removed[ i ];
			var controller = inputSourcesMap.get( inputSource );

			if ( controller ) {

				if ( controller.targetRay ) {

					controller.targetRay.dispatchEvent( { type: 'disconnected', data: inputSource } );

				}

				if ( controller.grip ) {

					controller.grip.dispatchEvent( { type: 'disconnected', data: inputSource } );

				}

				inputSourcesMap.delete( inputSource );

			}

		}

		// Notify connected

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

			var inputSource = event.added[ i ];
			var controller = inputSourcesMap.get( inputSource );

			if ( controller ) {

				if ( controller.targetRay ) {

					controller.targetRay.dispatchEvent( { type: 'connected', data: inputSource } );

				}

				if ( controller.grip ) {

					controller.grip.dispatchEvent( { type: 'connected', data: inputSource } );

				}

			}

		}

	}

	//

	var cameraLPos = new Vector3();
	var cameraRPos = new Vector3();

	/**
	 * @author jsantell / https://www.jsantell.com/
	 *
	 * Assumes 2 cameras that are parallel and share an X-axis, and that
	 * the cameras' projection and world matrices have already been set.
	 * And that near and far planes are identical for both cameras.
	 * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
	 */
	function setProjectionFromUnion( camera, cameraL, cameraR ) {

		cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
		cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );

		var ipd = cameraLPos.distanceTo( cameraRPos );

		var projL = cameraL.projectionMatrix.elements;
		var projR = cameraR.projectionMatrix.elements;

		// VR systems will have identical far and near planes, and
		// most likely identical top and bottom frustum extents.
		// Use the left camera for these values.
		var near = projL[ 14 ] / ( projL[ 10 ] - 1 );
		var far = projL[ 14 ] / ( projL[ 10 ] + 1 );
		var topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
		var bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];

		var leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
		var rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
		var left = near * leftFov;
		var right = near * rightFov;

		// Calculate the new camera's position offset from the
		// left camera. xOffset should be roughly half `ipd`.
		var zOffset = ipd / ( - leftFov + rightFov );
		var xOffset = zOffset * - leftFov;

		// TODO: Better way to apply this offset?
		cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
		camera.translateX( xOffset );
		camera.translateZ( zOffset );
		camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
		camera.matrixWorldInverse.getInverse( camera.matrixWorld );

		// Find the union of the frustum values of the cameras and scale
		// the values so that the near plane's position does not change in world space,
		// although must now be relative to the new union camera.
		var near2 = near + zOffset;
		var far2 = far + zOffset;
		var left2 = left - xOffset;
		var right2 = right + ( ipd - xOffset );
		var top2 = topFov * far / far2 * near2;
		var bottom2 = bottomFov * far / far2 * near2;

		camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );

	}

	function updateCamera( camera, parent ) {

		if ( parent === null ) {

			camera.matrixWorld.copy( camera.matrix );

		} else {

			camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );

		}

		camera.matrixWorldInverse.getInverse( camera.matrixWorld );

	}

	this.getCamera = function ( camera ) {

		cameraVR.near = cameraR.near = cameraL.near = camera.near;
		cameraVR.far = cameraR.far = cameraL.far = camera.far;

		if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {

			// Note that the new renderState won't apply until the next frame. See #18320

			session.updateRenderState( {
				depthNear: cameraVR.near,
				depthFar: cameraVR.far
			} );

			_currentDepthNear = cameraVR.near;
			_currentDepthFar = cameraVR.far;

		}

		var parent = camera.parent;
		var cameras = cameraVR.cameras;

		updateCamera( cameraVR, parent );

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

			updateCamera( cameras[ i ], parent );

		}

		// update camera and its children

		camera.matrixWorld.copy( cameraVR.matrixWorld );

		var children = camera.children;

		for ( var i = 0, l = children.length; i < l; i ++ ) {

			children[ i ].updateMatrixWorld( true );

		}

		setProjectionFromUnion( cameraVR, cameraL, cameraR );

		return cameraVR;

	};

	// Animation Loop

	var onAnimationFrameCallback = null;

	function onAnimationFrame( time, frame ) {

		pose = frame.getViewerPose( referenceSpace );

		if ( pose !== null ) {

			var views = pose.views;
			var baseLayer = session.renderState.baseLayer;

			renderer.setFramebuffer( baseLayer.framebuffer );

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

				var view = views[ i ];
				var viewport = baseLayer.getViewport( view );

				var camera = cameraVR.cameras[ i ];
				camera.matrix.fromArray( view.transform.matrix );
				camera.projectionMatrix.fromArray( view.projectionMatrix );
				camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );

				if ( i === 0 ) {

					cameraVR.matrix.copy( camera.matrix );

				}

			}

		}

		//

		var inputSources = session.inputSources;

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

			var controller = controllers[ i ];

			var inputSource = inputSources[ i ];

			var inputPose = null;
			var gripPose = null;

			if ( inputSource ) {

				if ( controller.targetRay ) {

					inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );

					if ( inputPose !== null ) {

						controller.targetRay.matrix.fromArray( inputPose.transform.matrix );
						controller.targetRay.matrix.decompose( controller.targetRay.position, controller.targetRay.rotation, controller.targetRay.scale );

					}

				}

				if ( controller.grip && inputSource.gripSpace ) {

					gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );

					if ( gripPose !== null ) {

						controller.grip.matrix.fromArray( gripPose.transform.matrix );
						controller.grip.matrix.decompose( controller.grip.position, controller.grip.rotation, controller.grip.scale );

					}

				}

			}

			if ( controller.targetRay ) {

				controller.targetRay.visible = inputPose !== null;

			}

			if ( controller.grip ) {

				controller.grip.visible = gripPose !== null;

			}

		}

		if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );

	}

	var animation = new WebGLAnimation();
	animation.setAnimationLoop( onAnimationFrame );

	this.setAnimationLoop = function ( callback ) {

		onAnimationFrameCallback = callback;

	};

	this.dispose = function () {};

}

Object.assign( WebXRManager.prototype, EventDispatcher.prototype );

export { WebXRManager };
<commit_msg>WebXRManager: Update camera's local matrix and transform properties.<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 */

import { ArrayCamera } from '../../cameras/ArrayCamera.js';
import { EventDispatcher } from '../../core/EventDispatcher.js';
import { Group } from '../../objects/Group.js';
import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js';
import { Vector3 } from '../../math/Vector3.js';
import { Vector4 } from '../../math/Vector4.js';
import { WebGLAnimation } from '../webgl/WebGLAnimation.js';

function WebXRManager( renderer, gl ) {

	var scope = this;

	var session = null;

	var framebufferScaleFactor = 1.0;

	var referenceSpace = null;
	var referenceSpaceType = 'local-floor';

	var pose = null;

	var controllers = [];
	var inputSourcesMap = new Map();

	//

	var cameraL = new PerspectiveCamera();
	cameraL.layers.enable( 1 );
	cameraL.viewport = new Vector4();

	var cameraR = new PerspectiveCamera();
	cameraR.layers.enable( 2 );
	cameraR.viewport = new Vector4();

	var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );
	cameraVR.layers.enable( 1 );
	cameraVR.layers.enable( 2 );

	var _currentDepthNear = null;
	var _currentDepthFar = null;

	//

	this.enabled = false;

	this.isPresenting = false;

	this.getController = function ( id ) {

		var controller = controllers[ id ];

		if ( controller === undefined ) {

			controller = {};
			controllers[ id ] = controller;

		}

		if ( controller.targetRay === undefined ) {

			controller.targetRay = new Group();
			controller.targetRay.matrixAutoUpdate = false;
			controller.targetRay.visible = false;

		}

		return controller.targetRay;

	};

	this.getControllerGrip = function ( id ) {

		var controller = controllers[ id ];

		if ( controller === undefined ) {

			controller = {};
			controllers[ id ] = controller;

		}

		if ( controller.grip === undefined ) {

			controller.grip = new Group();
			controller.grip.matrixAutoUpdate = false;
			controller.grip.visible = false;

		}

		return controller.grip;

	};

	//

	function onSessionEvent( event ) {

		var controller = inputSourcesMap.get( event.inputSource );

		if ( controller ) {

			if ( controller.targetRay ) {

				controller.targetRay.dispatchEvent( { type: event.type } );

			}

			if ( controller.grip ) {

				controller.grip.dispatchEvent( { type: event.type } );

			}

		}

	}

	function onSessionEnd() {

		inputSourcesMap.forEach( function ( controller, inputSource ) {

			if ( controller.targetRay ) {

				controller.targetRay.dispatchEvent( { type: 'disconnected', data: inputSource } );
				controller.targetRay.visible = false;

			}

			if ( controller.grip ) {

				controller.grip.dispatchEvent( { type: 'disconnected', data: inputSource } );
				controller.grip.visible = false;

			}

		} );

		inputSourcesMap.clear();

		//

		renderer.setFramebuffer( null );
		renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
		animation.stop();

		scope.isPresenting = false;

		scope.dispatchEvent( { type: 'sessionend' } );

	}

	function onRequestReferenceSpace( value ) {

		referenceSpace = value;

		animation.setContext( session );
		animation.start();

		scope.isPresenting = true;

		scope.dispatchEvent( { type: 'sessionstart' } );

	}

	this.setFramebufferScaleFactor = function ( value ) {

		framebufferScaleFactor = value;

		// Warn if function is used while presenting
		if ( scope.isPresenting == true ) {

			console.warn( "WebXRManager: Cannot change framebuffer scale while presenting VR content" );

		}

	};

	this.setReferenceSpaceType = function ( value ) {

		referenceSpaceType = value;

	};

	this.getReferenceSpace = function () {

		return referenceSpace;

	};

	this.getSession = function () {

		return session;

	};

	this.setSession = function ( value ) {

		session = value;

		if ( session !== null ) {

			session.addEventListener( 'select', onSessionEvent );
			session.addEventListener( 'selectstart', onSessionEvent );
			session.addEventListener( 'selectend', onSessionEvent );
			session.addEventListener( 'squeeze', onSessionEvent );
			session.addEventListener( 'squeezestart', onSessionEvent );
			session.addEventListener( 'squeezeend', onSessionEvent );
			session.addEventListener( 'end', onSessionEnd );

			var attributes = gl.getContextAttributes();

			var layerInit = {
				antialias: attributes.antialias,
				alpha: attributes.alpha,
				depth: attributes.depth,
				stencil: attributes.stencil,
				framebufferScaleFactor: framebufferScaleFactor
			};

			// eslint-disable-next-line no-undef
			var baseLayer = new XRWebGLLayer( session, gl, layerInit );

			session.updateRenderState( { baseLayer: baseLayer } );

			session.requestReferenceSpace( referenceSpaceType ).then( onRequestReferenceSpace );

			//

			session.addEventListener( 'inputsourceschange', updateInputSources );

		}

	};

	function updateInputSources( event ) {

		var inputSources = session.inputSources;

		// Assign inputSources to available controllers

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

			inputSourcesMap.set( inputSources[ i ], controllers[ i ] );

		}

		// Notify disconnected

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

			var inputSource = event.removed[ i ];
			var controller = inputSourcesMap.get( inputSource );

			if ( controller ) {

				if ( controller.targetRay ) {

					controller.targetRay.dispatchEvent( { type: 'disconnected', data: inputSource } );

				}

				if ( controller.grip ) {

					controller.grip.dispatchEvent( { type: 'disconnected', data: inputSource } );

				}

				inputSourcesMap.delete( inputSource );

			}

		}

		// Notify connected

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

			var inputSource = event.added[ i ];
			var controller = inputSourcesMap.get( inputSource );

			if ( controller ) {

				if ( controller.targetRay ) {

					controller.targetRay.dispatchEvent( { type: 'connected', data: inputSource } );

				}

				if ( controller.grip ) {

					controller.grip.dispatchEvent( { type: 'connected', data: inputSource } );

				}

			}

		}

	}

	//

	var cameraLPos = new Vector3();
	var cameraRPos = new Vector3();

	/**
	 * @author jsantell / https://www.jsantell.com/
	 *
	 * Assumes 2 cameras that are parallel and share an X-axis, and that
	 * the cameras' projection and world matrices have already been set.
	 * And that near and far planes are identical for both cameras.
	 * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
	 */
	function setProjectionFromUnion( camera, cameraL, cameraR ) {

		cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
		cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );

		var ipd = cameraLPos.distanceTo( cameraRPos );

		var projL = cameraL.projectionMatrix.elements;
		var projR = cameraR.projectionMatrix.elements;

		// VR systems will have identical far and near planes, and
		// most likely identical top and bottom frustum extents.
		// Use the left camera for these values.
		var near = projL[ 14 ] / ( projL[ 10 ] - 1 );
		var far = projL[ 14 ] / ( projL[ 10 ] + 1 );
		var topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
		var bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];

		var leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
		var rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
		var left = near * leftFov;
		var right = near * rightFov;

		// Calculate the new camera's position offset from the
		// left camera. xOffset should be roughly half `ipd`.
		var zOffset = ipd / ( - leftFov + rightFov );
		var xOffset = zOffset * - leftFov;

		// TODO: Better way to apply this offset?
		cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
		camera.translateX( xOffset );
		camera.translateZ( zOffset );
		camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
		camera.matrixWorldInverse.getInverse( camera.matrixWorld );

		// Find the union of the frustum values of the cameras and scale
		// the values so that the near plane's position does not change in world space,
		// although must now be relative to the new union camera.
		var near2 = near + zOffset;
		var far2 = far + zOffset;
		var left2 = left - xOffset;
		var right2 = right + ( ipd - xOffset );
		var top2 = topFov * far / far2 * near2;
		var bottom2 = bottomFov * far / far2 * near2;

		camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );

	}

	function updateCamera( camera, parent ) {

		if ( parent === null ) {

			camera.matrixWorld.copy( camera.matrix );

		} else {

			camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );

		}

		camera.matrixWorldInverse.getInverse( camera.matrixWorld );

	}

	this.getCamera = function ( camera ) {

		cameraVR.near = cameraR.near = cameraL.near = camera.near;
		cameraVR.far = cameraR.far = cameraL.far = camera.far;

		if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {

			// Note that the new renderState won't apply until the next frame. See #18320

			session.updateRenderState( {
				depthNear: cameraVR.near,
				depthFar: cameraVR.far
			} );

			_currentDepthNear = cameraVR.near;
			_currentDepthFar = cameraVR.far;

		}

		var parent = camera.parent;
		var cameras = cameraVR.cameras;

		updateCamera( cameraVR, parent );

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

			updateCamera( cameras[ i ], parent );

		}

		// update camera and its children

		camera.matrixWorld.copy( cameraVR.matrixWorld );
		camera.matrix.copy( cameraVR.matrix );
		camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );

		var children = camera.children;

		for ( var i = 0, l = children.length; i < l; i ++ ) {

			children[ i ].updateMatrixWorld( true );

		}

		setProjectionFromUnion( cameraVR, cameraL, cameraR );

		return cameraVR;

	};

	// Animation Loop

	var onAnimationFrameCallback = null;

	function onAnimationFrame( time, frame ) {

		pose = frame.getViewerPose( referenceSpace );

		if ( pose !== null ) {

			var views = pose.views;
			var baseLayer = session.renderState.baseLayer;

			renderer.setFramebuffer( baseLayer.framebuffer );

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

				var view = views[ i ];
				var viewport = baseLayer.getViewport( view );

				var camera = cameraVR.cameras[ i ];
				camera.matrix.fromArray( view.transform.matrix );
				camera.projectionMatrix.fromArray( view.projectionMatrix );
				camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );

				if ( i === 0 ) {

					cameraVR.matrix.copy( camera.matrix );

				}

			}

		}

		//

		var inputSources = session.inputSources;

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

			var controller = controllers[ i ];

			var inputSource = inputSources[ i ];

			var inputPose = null;
			var gripPose = null;

			if ( inputSource ) {

				if ( controller.targetRay ) {

					inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );

					if ( inputPose !== null ) {

						controller.targetRay.matrix.fromArray( inputPose.transform.matrix );
						controller.targetRay.matrix.decompose( controller.targetRay.position, controller.targetRay.rotation, controller.targetRay.scale );

					}

				}

				if ( controller.grip && inputSource.gripSpace ) {

					gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );

					if ( gripPose !== null ) {

						controller.grip.matrix.fromArray( gripPose.transform.matrix );
						controller.grip.matrix.decompose( controller.grip.position, controller.grip.rotation, controller.grip.scale );

					}

				}

			}

			if ( controller.targetRay ) {

				controller.targetRay.visible = inputPose !== null;

			}

			if ( controller.grip ) {

				controller.grip.visible = gripPose !== null;

			}

		}

		if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );

	}

	var animation = new WebGLAnimation();
	animation.setAnimationLoop( onAnimationFrame );

	this.setAnimationLoop = function ( callback ) {

		onAnimationFrameCallback = callback;

	};

	this.dispose = function () {};

}

Object.assign( WebXRManager.prototype, EventDispatcher.prototype );

export { WebXRManager };
<|endoftext|>


<commit_before>const allEqual = arr => arr.every( v => v === arr[0] );

var shuffle = function (array) {

	var currentIndex = array.length;
	var temporaryValue, randomIndex;

	// While there remain elements to shuffle...
	while (0 !== currentIndex) {
		// Pick a remaining element...
		randomIndex = Math.floor(Math.random() * currentIndex);
		currentIndex -= 1;

		// And swap it with the current element.
		temporaryValue = array[currentIndex];
		array[currentIndex] = array[randomIndex];
		array[randomIndex] = temporaryValue;
	}

	return array;

};

var GameState = function(n_cards, player_names) {
	this.n_images = 128
	this.n_players = player_names.length;
	this.player_names = player_names.slice();
	this.cards_per_player = 6;
	this.n_cards = n_cards;
	this.deck_order = [...Array(this.n_images).keys()];
	this.stage = 0;  // 0 - Mano picks card and text; 1 - Players pick cards; 2 - Votes; 3 - Results
	this.turn = 0;
	this.deck_index = 0;
	this.song = "";
	this.player_hands = [];
	this.candidates = [];
	this.candidates_shown = [];
	this.votes = [];
	this.scores = [];
    // refresh is not really a state but a message field. When we do
	// a state update that does not require refreshing (e.g., a proposal),
	// we still must publish it, it is not enough that the chief keeps
	// track of the change, because if the chief reloads, it will get the
	// state from the topic and forget about the non-refresh state change.
	// So we always publish the new state, but the topic listener checks
	// the refresh field to decide whether to update the display or not.
	// (It is slightly tricky because even if refresh=False we need to
	// display when it is the first message after a reload).
	this.refresh = true;

	this.init = function() {
		if (debug) {
			console.log("Initializing game state");
		}
		shuffle(this.deck_order);
		this.deck_index = 0;

		this.stage = 0;
		this.turn = 0;
		this.song = "";

		// Deal cards
		this.player_hands.length = 0;
		for (var i=0; i<this.n_players; i++) {
			var player_hand = [];
			for (var j=0; j<this.cards_per_player; j++) {
				player_hand.push(this.deck_order[this.deck_index++]);
			}
			this.player_hands.push(player_hand);
		}
		if (debug) {
			console.log("Initial player hands: ")
			console.log(this.player_hands);
		}

		// Clear state
		this.candidates.length = 0;
		this.candidates_shown.length = 0;
		this.votes.length = 0;
		this.scores.length = 0;
		for (var i = 1; i <= this.n_players; i++) {
			this.candidates.push(-1);
			this.votes.push(-1);
			this.scores.push(0);
		}
		return true;  // always refresh
	}

	this.next_round = function() {
		this.stage = 0;
		this.song = "";
		this.turn = (this.turn + 1) % this.n_players;
		if (this.deck_index <= this.n_cards - this.n_players) {
			// Deal one card per player
			for (var i=0; i<this.n_players; i++) {
				this.player_hands[i].push(this.deck_order[this.deck_index++]);
			}
		}
		// Clear state
		this.candidates.length = 0;
		this.candidates_shown.length = 0;
		this.votes.length = 0;
		for (var i = 1; i <= this.n_players; i++) {
			this.candidates.push(-1);
			this.votes.push(-1);
		}
		return true;  // always refresh
	}

	this.propose = function(player_idx, card_id) {
		if(debug) {
			console.log("Proposing, player_idx=" + player_idx + ", card_id=" + card_id);
			console.log("Player_hands are:");
			console.log(this.player_hands);
		}
		if(this.player_hands[player_idx].indexOf(card_id) == -1) {
			throw ("Card id " + card_id + " not in player " +
				   player_idx + "'s hand, " + this.player_hands[player_idx]);
		}
		if (this.stage > 1) {
			throw "Got a proposal in stage " + this.stage;
		}
		if(this.stage == 0 && player_idx != this.turn) {
			throw ("In stage 0, proposal from player " + player_idx +
				   " while mano is "+ this.turn);
		}
		if(this.stage == 1 && player_idx == this.turn) {
				throw ("In stage 1, proposal from mano (player " + player_idx + ")");
		}

		this.candidates[player_idx] = card_id;

		if (this.stage == 0 && this.song) {
			this.stage = 1;
			return true;
		}
		if (this.stage == 1 &&
			this.candidates.every( v => v != -1 )) {  // everyone has proposed
			if(debug) console.log("All players have proposed");
			for (var i=0; i < this.n_players; i++) {
				var card_idx = this.player_hands[i].indexOf(this.candidates[i]);
				if (card_idx == -1) throw (
					"After all players proposed (candidates " + this.candidates +
					"), found that card id " + card_id + " is not in player " +
					i + "'s hand, " + this.player_hands[i]);
				this.player_hands[i].splice(card_idx, 1);
			}
			this.candidates_shown = shuffle(this.candidates.slice());
			this.stage = 2;
			return true;
		}
		return false;
	}

	this.sing = function(player_idx, song) {
		if(debug) {
			console.log("Player " + player_idx + " sings " + song);
		}
		if(this.stage == 0) {  // propuesta del mano
			if (player_idx != this.turn) {
				throw ("Song from player " + player_idx + " while mano is "+ this.turn);
			}
			this.song = song;
			if(this.candidates[player_idx] != -1) {
				this.stage = 1;
				return true;
			} else {
				return false;
			}
		}
		throw "Got a song in stage " + this.stage;
	}

	this.vote = function(player_idx, card_id) {
		if(this.stage != 2) { // wrong stage
			throw "Got a vote in stage " + this.stage;
		}
		if (player_idx == this.turn) {
			throw ("Got a vote from the mano player, " + player_idx);
		}
		if(card_id < 0 || card_id >= this.n_images) {
			throw ("Card id " + card_id + " out of bounds for deck of " +
				   this.n_images + " images")
		}
		var idx = this.candidates.indexOf(card_id);
		if(idx == -1) {
			throw ("Player " + player_idx + " voted for card id " + card_id +
				   " but available cards are " + this.candidates);
		}
		this.votes[player_idx] = card_id;
		var n_votes = 0;
		var not_voted_idx = -1;
		for (var i = 0; i < this.n_players; i++) {
			if (this.votes[i] >=0) {
				n_votes ++;
			} else {
				not_voted_idx = i;
			}
		}
		if (n_votes == this.n_players) {
			throw "Apparently all players (even the mano) have voted! Votes are " + this.votes;
		}
		if (n_votes != this.n_players - 1) return false;

		// Everyone (save the mano) has voted
		if (not_voted_idx != this.turn) {
			throw ("Looks like the mano (player " + this.turn + " is not the " +
				   "one player that didn't vote, the votes are " + this.votes);
		}
		// Update scores
		var mano_votes = 0;
		for (var i = 0; i < this.n_players; i++) {
			if (i == this.turn) continue;
			var candidate_idx = this.candidates.indexOf(this.votes[i]);
			if (candidate_idx == -1) {
				throw ("Vote for non-existing candidate, candidates are " + this.candidates +
					   ", votes are " + this.votes);
			}
			if (candidate_idx == this.turn) {
				this.scores[i] += 3;  // player guessed the mano's card
				mano_votes += 1;
			} else {
				this.scores[candidate_idx] += 1;  // voted for candidate_idx, candidate gets a point
			}
		}
		if (mano_votes > 0 && mano_votes < this.n_players - 1) {
			this.scores[this.turn] += 3;  // someone but not everyone guessed, mano wins
		} else {  // all or none guessed, everyone except mano gets 2 points
			for (var i = 0; i < this.n_players; i++) {
				if (i == this.turn) continue;
				this.scores[i] += 2;
				var candidate_idx = this.candidates.indexOf(this.votes[i]);
				if (candidate_idx == this.turn) {
					this.scores[i] -= 3;  // undo the guess score, everyone guessed
				}
			}
		}
		this.stage = 3;  // show results
		return true;
	}
}
<commit_msg>Update gamestate.js<commit_after>const allEqual = arr => arr.every( v => v === arr[0] );

var shuffle = function (array) {

	var currentIndex = array.length;
	var temporaryValue, randomIndex;

	// While there remain elements to shuffle...
	while (0 !== currentIndex) {
		// Pick a remaining element...
		randomIndex = Math.floor(Math.random() * currentIndex);
		currentIndex -= 1;

		// And swap it with the current element.
		temporaryValue = array[currentIndex];
		array[currentIndex] = array[randomIndex];
		array[randomIndex] = temporaryValue;
	}

	return array;

};

var GameState = function(n_cards, player_names) {
	this.n_images = 138;
	this.n_players = player_names.length;
	this.player_names = player_names.slice();
	this.cards_per_player = 6;
	this.n_cards = n_cards;
	this.deck_order = [...Array(this.n_images).keys()];
	this.stage = 0;  // 0 - Mano picks card and text; 1 - Players pick cards; 2 - Votes; 3 - Results
	this.turn = 0;
	this.deck_index = 0;
	this.song = "";
	this.player_hands = [];
	this.candidates = [];
	this.candidates_shown = [];
	this.votes = [];
	this.scores = [];
    // refresh is not really a state but a message field. When we do
	// a state update that does not require refreshing (e.g., a proposal),
	// we still must publish it, it is not enough that the chief keeps
	// track of the change, because if the chief reloads, it will get the
	// state from the topic and forget about the non-refresh state change.
	// So we always publish the new state, but the topic listener checks
	// the refresh field to decide whether to update the display or not.
	// (It is slightly tricky because even if refresh=False we need to
	// display when it is the first message after a reload).
	this.refresh = true;

	this.init = function() {
		if (debug) {
			console.log("Initializing game state");
		}
		shuffle(this.deck_order);
		this.deck_index = 0;

		this.stage = 0;
		this.turn = 0;
		this.song = "";

		// Deal cards
		this.player_hands.length = 0;
		for (var i=0; i<this.n_players; i++) {
			var player_hand = [];
			for (var j=0; j<this.cards_per_player; j++) {
				player_hand.push(this.deck_order[this.deck_index++]);
			}
			this.player_hands.push(player_hand);
		}
		if (debug) {
			console.log("Initial player hands: ")
			console.log(this.player_hands);
		}

		// Clear state
		this.candidates.length = 0;
		this.candidates_shown.length = 0;
		this.votes.length = 0;
		this.scores.length = 0;
		for (var i = 1; i <= this.n_players; i++) {
			this.candidates.push(-1);
			this.votes.push(-1);
			this.scores.push(0);
		}
		return true;  // always refresh
	}

	this.next_round = function() {
		this.stage = 0;
		this.song = "";
		this.turn = (this.turn + 1) % this.n_players;
		if (this.deck_index <= this.n_cards - this.n_players) {
			// Deal one card per player
			for (var i=0; i<this.n_players; i++) {
				this.player_hands[i].push(this.deck_order[this.deck_index++]);
			}
		}
		// Clear state
		this.candidates.length = 0;
		this.candidates_shown.length = 0;
		this.votes.length = 0;
		for (var i = 1; i <= this.n_players; i++) {
			this.candidates.push(-1);
			this.votes.push(-1);
		}
		return true;  // always refresh
	}

	this.propose = function(player_idx, card_id) {
		if(debug) {
			console.log("Proposing, player_idx=" + player_idx + ", card_id=" + card_id);
			console.log("Player_hands are:");
			console.log(this.player_hands);
		}
		if(this.player_hands[player_idx].indexOf(card_id) == -1) {
			throw ("Card id " + card_id + " not in player " +
				   player_idx + "'s hand, " + this.player_hands[player_idx]);
		}
		if (this.stage > 1) {
			throw "Got a proposal in stage " + this.stage;
		}
		if(this.stage == 0 && player_idx != this.turn) {
			throw ("In stage 0, proposal from player " + player_idx +
				   " while mano is "+ this.turn);
		}
		if(this.stage == 1 && player_idx == this.turn) {
				throw ("In stage 1, proposal from mano (player " + player_idx + ")");
		}

		this.candidates[player_idx] = card_id;

		if (this.stage == 0 && this.song) {
			this.stage = 1;
			return true;
		}
		if (this.stage == 1 &&
			this.candidates.every( v => v != -1 )) {  // everyone has proposed
			if(debug) console.log("All players have proposed");
			for (var i=0; i < this.n_players; i++) {
				var card_idx = this.player_hands[i].indexOf(this.candidates[i]);
				if (card_idx == -1) throw (
					"After all players proposed (candidates " + this.candidates +
					"), found that card id " + card_id + " is not in player " +
					i + "'s hand, " + this.player_hands[i]);
				this.player_hands[i].splice(card_idx, 1);
			}
			this.candidates_shown = shuffle(this.candidates.slice());
			this.stage = 2;
			return true;
		}
		return false;
	}

	this.sing = function(player_idx, song) {
		if(debug) {
			console.log("Player " + player_idx + " sings " + song);
		}
		if(this.stage == 0) {  // propuesta del mano
			if (player_idx != this.turn) {
				throw ("Song from player " + player_idx + " while mano is "+ this.turn);
			}
			this.song = song;
			if(this.candidates[player_idx] != -1) {
				this.stage = 1;
				return true;
			} else {
				return false;
			}
		}
		throw "Got a song in stage " + this.stage;
	}

	this.vote = function(player_idx, card_id) {
		if(this.stage != 2) { // wrong stage
			throw "Got a vote in stage " + this.stage;
		}
		if (player_idx == this.turn) {
			throw ("Got a vote from the mano player, " + player_idx);
		}
		if(card_id < 0 || card_id >= this.n_images) {
			throw ("Card id " + card_id + " out of bounds for deck of " +
				   this.n_images + " images")
		}
		var idx = this.candidates.indexOf(card_id);
		if(idx == -1) {
			throw ("Player " + player_idx + " voted for card id " + card_id +
				   " but available cards are " + this.candidates);
		}
		this.votes[player_idx] = card_id;
		var n_votes = 0;
		var not_voted_idx = -1;
		for (var i = 0; i < this.n_players; i++) {
			if (this.votes[i] >=0) {
				n_votes ++;
			} else {
				not_voted_idx = i;
			}
		}
		if (n_votes == this.n_players) {
			throw "Apparently all players (even the mano) have voted! Votes are " + this.votes;
		}
		if (n_votes != this.n_players - 1) return false;

		// Everyone (save the mano) has voted
		if (not_voted_idx != this.turn) {
			throw ("Looks like the mano (player " + this.turn + " is not the " +
				   "one player that didn't vote, the votes are " + this.votes);
		}
		// Update scores
		var mano_votes = 0;
		for (var i = 0; i < this.n_players; i++) {
			if (i == this.turn) continue;
			var candidate_idx = this.candidates.indexOf(this.votes[i]);
			if (candidate_idx == -1) {
				throw ("Vote for non-existing candidate, candidates are " + this.candidates +
					   ", votes are " + this.votes);
			}
			if (candidate_idx == this.turn) {
				this.scores[i] += 3;  // player guessed the mano's card
				mano_votes += 1;
			} else {
				this.scores[candidate_idx] += 1;  // voted for candidate_idx, candidate gets a point
			}
		}
		if (mano_votes > 0 && mano_votes < this.n_players - 1) {
			this.scores[this.turn] += 3;  // someone but not everyone guessed, mano wins
		} else {  // all or none guessed, everyone except mano gets 2 points
			for (var i = 0; i < this.n_players; i++) {
				if (i == this.turn) continue;
				this.scores[i] += 2;
				var candidate_idx = this.candidates.indexOf(this.votes[i]);
				if (candidate_idx == this.turn) {
					this.scores[i] -= 3;  // undo the guess score, everyone guessed
				}
			}
		}
		this.stage = 3;  // show results
		return true;
	}
}
<|endoftext|>


<commit_before>$(document).ready(function(){
	resizeBackground();
	$(window).resize(resizeBackground);

	$('#logo').click(function() {
		$(".main").moveTo(1);
	});

	// $('a.page-scroll').bind('click', function(event) {
	// 	var $anchor = $(this);
	// 	$('html, body').stop().animate({
	// 		scrollTop: $($anchor.attr('href')).offset().top - 42
	// 	}, 1500, 'easeInOutExpo');
	// 	event.preventDefault();
	// });

	// Form: be a partner
	$('#partnerForm').on( "submit", function( event ) {
  		event.preventDefault();
  		var userInputSet = $(this).serializeArray();
  		console.log(userInputSet);
  		var data = {};
  		for (var i=0;i<userInputSet.length;i++) {
  			data[userInputSet[i].name] = escapeHtml(userInputSet[i].value);
  		}

  		$.post("destinationURL", data, function(resp) {
  			// do whatever you want after getting response
		 	alert("Thank you for your response, and we will contact you soon!")
		 	// clean form input
		 	$('#partnerForm').each(function(){
			    this.reset();
			});
		});
	});

	// Form: feedback
	$('#feedbackForm').on( "submit", function( event ) {
  		event.preventDefault();
  		var userInputSet = $(this).serializeArray();
  		console.log(userInputSet);
  		var data = {};
  		for (var i=0;i<userInputSet.length;i++) {
  			data[userInputSet[i].name] = escapeHtml(userInputSet[i].value);
  		}

  		$.post("destinationURL", data, function(resp) {
  			// do whatever you want after getting response
		 	alert("Thank you for your response, and we will contact you soon!")
		 	// clean form input
		 	$('#feedbackForm').each(function(){
			    this.reset();
			});
		});
	});
});

function resizeBackground() {
	var width = $(window).width();
	var height = $(window).height();
	if (width * 0.68 >= height) {
		$('#homepage-background').css('width', '100%');
		$('#homepage-background').css('height', 'auto');
	} else {
		$('#homepage-background').css('height', '100vh');
		$('#homepage-background').css('width', 'auto');
	}
}

function escapeHtml(text) {
  return text
      .replace(/&/g, "&")
      .replace(/</g, "<")
      .replace(/>/g, ">")
      .replace(/"/g, """)
      .replace(/'/g, "'");
}<commit_msg>delete debug output.<commit_after>$(document).ready(function(){
	resizeBackground();
	$(window).resize(resizeBackground);

	$('#logo').click(function() {
		$(".main").moveTo(1);
	});

	// $('a.page-scroll').bind('click', function(event) {
	// 	var $anchor = $(this);
	// 	$('html, body').stop().animate({
	// 		scrollTop: $($anchor.attr('href')).offset().top - 42
	// 	}, 1500, 'easeInOutExpo');
	// 	event.preventDefault();
	// });

	// Form: be a partner
	$('#partnerForm').on( "submit", function( event ) {
  		event.preventDefault();
  		var userInputSet = $(this).serializeArray();
  		var data = {};
  		for (var i=0;i<userInputSet.length;i++) {
  			data[userInputSet[i].name] = escapeHtml(userInputSet[i].value);
  		}

  		$.post("destinationURL", data, function(resp) {
  			// do whatever you want after getting response
		 	alert("Thank you for your response, and we will contact you soon!")
		 	// clean form input
		 	$('#partnerForm').each(function(){
			    this.reset();
			});
		});
	});

	// Form: feedback
	$('#feedbackForm').on( "submit", function( event ) {
  		event.preventDefault();
  		var userInputSet = $(this).serializeArray();
  		var data = {};
  		for (var i=0;i<userInputSet.length;i++) {
  			data[userInputSet[i].name] = escapeHtml(userInputSet[i].value);
  		}

  		$.post("destinationURL", data, function(resp) {
  			// do whatever you want after getting response
		 	alert("Thank you for your response, and we will contact you soon!")
		 	// clean form input
		 	$('#feedbackForm').each(function(){
			    this.reset();
			});
		});
	});
});

function resizeBackground() {
	var width = $(window).width();
	var height = $(window).height();
	if (width * 0.68 >= height) {
		$('#homepage-background').css('width', '100%');
		$('#homepage-background').css('height', 'auto');
	} else {
		$('#homepage-background').css('height', '100vh');
		$('#homepage-background').css('width', 'auto');
	}
}

function escapeHtml(text) {
  return text
      .replace(/&/g, "&")
      .replace(/</g, "<")
      .replace(/>/g, ">")
      .replace(/"/g, """)
      .replace(/'/g, "'");
}<|endoftext|>


<commit_before>var Filter = (function () {

function filter_term(operator, operand) {
    // For legacy reasons we must represent filter_terms as tuples
    // until we phase out all the code that assumes tuples.
    var term = [];
    term[0] = operator;
    term[1] = operand;

    // This is the new style we are phasing in.  (Yes, the same
    // object can be treated like either a tuple or a struct.)
    term.operator = operator;
    term.operand = operand;

    return term;
}

function mit_edu_stream_name_match(message, operand) {
    // MIT users expect narrowing to "social" to also show messages to /^(un)*social(.d)*$/
    // (unsocial, ununsocial, social.d, etc)
    // TODO: hoist the regex compiling out of the closure
    var m = /^(?:un)*(.+?)(?:\.d)*$/i.exec(operand);
    var base_stream_name = operand;
    if (m !== null && m[1] !== undefined) {
        base_stream_name = m[1];
    }
    var related_regexp = new RegExp(/^(un)*/.source + util.escape_regexp(base_stream_name) + /(\.d)*$/.source, 'i');
    return related_regexp.test(message.stream);
}

function mit_edu_topic_name_match(message, operand) {
    // MIT users expect narrowing to topic "foo" to also show messages to /^foo(.d)*$/
    // (foo, foo.d, foo.d.d, etc)
    // TODO: hoist the regex compiling out of the closure
    var m = /^(.*?)(?:\.d)*$/i.exec(operand);
    var base_topic = m[1];
    var related_regexp;

    // Additionally, MIT users expect the empty instance and
    // instance "personal" to be the same.
    if (base_topic === ''
        || base_topic.toLowerCase() === 'personal'
        || base_topic.toLowerCase() === '(instance "")')
    {
        related_regexp = /^(|personal|\(instance ""\))(\.d)*$/i;
    } else {
        related_regexp = new RegExp(/^/.source + util.escape_regexp(base_topic) + /(\.d)*$/.source, 'i');
    }

    return related_regexp.test(message.subject);
}

function message_in_home(message) {
    if (message.type === "private" || message.mentioned ||
        (page_params.narrow_stream !== undefined &&
         message.stream.toLowerCase() === page_params.narrow_stream.toLowerCase())) {
        return true;
    }

    return stream_data.in_home_view(message.stream);
}

function message_matches_search_term(message, operator, operand) {
    switch (operator) {
    case 'is':
        if (operand === 'private') {
            if (message.type !== 'private') {
                return false;
            }
        } else if (operand === 'starred') {
            if (!message.starred) {
                return false;
            }
        } else if (operand === 'mentioned') {
            if (!message.mentioned) {
                return false;
            }
        } else if (operand === 'alerted') {
            if (!message.alerted) {
                return false;
            }
        }

        break;

    case 'in':
        if (operand === 'home') {
            return message_in_home(message);
        }
        else if (operand === 'all') {
            break;
        }
        break;

    case 'near':
        break;

    case 'id':
        if (message.id.toString() !== operand) {
            return false;
        }
        break;

    case 'stream':
        if (message.type !== 'stream') {
            return false;
        }

        operand = operand.toLowerCase();
        if (page_params.domain === "mit.edu") {
            if (!mit_edu_stream_name_match(message, operand)) {
                return false;
            }
        } else if (message.stream.toLowerCase() !== operand) {
            return false;
        }
        break;

    case 'topic':
        if (message.type !== 'stream') {
            return false;
        }

        operand = operand.toLowerCase();
        if (page_params.domain === "mit.edu") {
            if (!mit_edu_topic_name_match(message, operand)) {
                return false;
            }
        } else if (message.subject.toLowerCase() !== operand) {
            return false;
        }
        break;

    case 'sender':
        if ((message.sender_email.toLowerCase() !== operand)) {
            return false;
        }
        break;

    case 'pm-with':
        if ((message.type !== 'private') ||
            message.reply_to.toLowerCase() !== operand.split(',').sort().join(',')) {
            return false;
        }
        break;
    }

    return true;
}

function Filter(operators) {
    if (operators === undefined) {
        this._operators = [];
    } else {
        this._operators = this._canonicalize_operators(operators);
    }
}

var canonical_operators = {"from": "sender", "subject": "topic"};

Filter.canonicalize_operator = function (operator) {
    operator = operator.toLowerCase();
    if (canonical_operators.hasOwnProperty(operator)) {
        return canonical_operators[operator];
    } else {
        return operator;
    }
};

Filter.canonicalize_tuple = function (tuple) {
    var operator = tuple[0];
    var operand = tuple[1];

    operator = Filter.canonicalize_operator(operator);

    switch (operator) {
    case 'stream':
        operand = stream_data.get_name(operand);
        break;
    case 'topic':
        break;
    case 'sender':
    case 'pm-with':
        operand = operand.toString().toLowerCase();
        if (operand === 'me') {
            operand = page_params.email;
        }
        break;
    default:
        operand = operand.toString().toLowerCase();
    }

    // We may want to consider allowing mixed-case operators at some point
    return filter_term(operator, operand);
};

/* We use a variant of URI encoding which looks reasonably
   nice and still handles unambiguously cases such as
   spaces in operands.

   This is just for the search bar, not for saving the
   narrow in the URL fragment.  There we do use full
   URI encoding to avoid problematic characters. */
function encodeOperand(operand) {
    return operand.replace(/%/g,  '%25')
                  .replace(/\+/g, '%2B')
                  .replace(/ /g,  '+');
}

function decodeOperand(encoded, operator) {
    if (operator !== 'pm-with' && operator !== 'sender') {
        encoded = encoded.replace(/\+/g, ' ');
    }
    return util.robust_uri_decode(encoded);
}

// Parse a string into a list of operators (see below).
Filter.parse = function (str) {
    var operators   = [];
    var search_term = [];
    var operand;

    var matches = str.match(/"[^"]+"|\S+/g);
    if (matches === null) {
        return operators;
    }
    _.each(matches, function (token) {
        var parts, operator;
        parts = token.split(':');
        if (token[0] === '"' || parts.length === 1) {
            // Looks like a normal search term.
            search_term.push(token);
        } else {
            // Looks like an operator.
            // FIXME: Should we skip unknown operator names here?
            operator = parts.shift();
            operand = decodeOperand(parts.join(':'), operator);
            operators.push(filter_term(operator, operand));
        }
    });
    // NB: Callers of 'parse' can assume that the 'search' operator is last.
    if (search_term.length > 0) {
        operand = search_term.join(' ');
        operators.push(filter_term('search', operand));
    }
    return operators;
};

/* Convert a list of operators to a string.
   Each operator is a key-value pair like

       ['subject', 'my amazing subject']

   These are not keys in a JavaScript object, because we
   might need to support multiple operators of the same type.
*/
Filter.unparse = function (operators) {
    var parts = _.map(operators, function (elem) {
        var operator = elem[0];
        if (operator === 'search') {
            // Search terms are the catch-all case.
            // All tokens that don't start with a known operator and
            // a colon are glued together to form a search term.
            return elem[1];
        } else {
            return elem[0] + ':' + encodeOperand(elem[1].toString());
        }
    });
    return parts.join(' ');
};

Filter.prototype = {
    predicate: function Filter_predicate() {
        if (this._predicate === undefined) {
            this._predicate = this._build_predicate();
        }
        return this._predicate;
    },

    operators: function Filter_operators() {
        return this._operators;
    },

    public_operators: function Filter_public_operators() {
        var safe_to_return = _.filter(this._operators, function (value) {
            // Filter out the "in" keyword and the embedded narrow (if any).
            return value.operator !== 'in' && !(page_params.narrow_stream !== undefined &&
                                                value.operator === "stream" &&
                                                value.operand.toLowerCase() === page_params.narrow_stream.toLowerCase());
        });
        return safe_to_return;
    },

    operands: function Filter_get_operands(operator) {
        return _.chain(this._operators)
            .filter(function (elem) { return elem.operator === operator; })
            .map(function (elem) { return elem.operand; })
            .value();
    },

    has_operand: function Filter_has_operand(operator, operand) {
        return _.any(this._operators, function (elem) {
            return elem.operator === operator && elem.operand === operand;
        });
    },

    has_operator: function Filter_has_operator(operator) {
        return _.any(this._operators, function (elem) {
            return elem.operator === operator;
        });
    },

    is_search: function Filter_is_search() {
        return this.has_operator('search');
    },

    can_apply_locally: function Filter_can_apply_locally() {
        return ! this.is_search();
    },

    _canonicalize_operators: function Filter__canonicalize_operators(operators_mixed_case) {
        return _.map(operators_mixed_case, function (tuple) {
            return Filter.canonicalize_tuple(tuple);
        });
    },

    // Build a filter function from a list of operators.
    _build_predicate: function Filter__build_predicate() {
        var operators = this._operators;

        if (! this.can_apply_locally()) {
            return function () { return true; };
        }

        // FIXME: This is probably pretty slow.
        // We could turn it into something more like a compiler:
        // build JavaScript code in a string and then eval() it.

        return function (message) {
            return _.all(operators, function (term) {
                return message_matches_search_term(message, term.operator, term.operand);
            });
        };
    }
};

return Filter;

}());
if (typeof module !== 'undefined') {
    module.exports = Filter;
}
<commit_msg>Clean up double negatives in message_matches_search_term<commit_after>var Filter = (function () {

function filter_term(operator, operand) {
    // For legacy reasons we must represent filter_terms as tuples
    // until we phase out all the code that assumes tuples.
    var term = [];
    term[0] = operator;
    term[1] = operand;

    // This is the new style we are phasing in.  (Yes, the same
    // object can be treated like either a tuple or a struct.)
    term.operator = operator;
    term.operand = operand;

    return term;
}

function mit_edu_stream_name_match(message, operand) {
    // MIT users expect narrowing to "social" to also show messages to /^(un)*social(.d)*$/
    // (unsocial, ununsocial, social.d, etc)
    // TODO: hoist the regex compiling out of the closure
    var m = /^(?:un)*(.+?)(?:\.d)*$/i.exec(operand);
    var base_stream_name = operand;
    if (m !== null && m[1] !== undefined) {
        base_stream_name = m[1];
    }
    var related_regexp = new RegExp(/^(un)*/.source + util.escape_regexp(base_stream_name) + /(\.d)*$/.source, 'i');
    return related_regexp.test(message.stream);
}

function mit_edu_topic_name_match(message, operand) {
    // MIT users expect narrowing to topic "foo" to also show messages to /^foo(.d)*$/
    // (foo, foo.d, foo.d.d, etc)
    // TODO: hoist the regex compiling out of the closure
    var m = /^(.*?)(?:\.d)*$/i.exec(operand);
    var base_topic = m[1];
    var related_regexp;

    // Additionally, MIT users expect the empty instance and
    // instance "personal" to be the same.
    if (base_topic === ''
        || base_topic.toLowerCase() === 'personal'
        || base_topic.toLowerCase() === '(instance "")')
    {
        related_regexp = /^(|personal|\(instance ""\))(\.d)*$/i;
    } else {
        related_regexp = new RegExp(/^/.source + util.escape_regexp(base_topic) + /(\.d)*$/.source, 'i');
    }

    return related_regexp.test(message.subject);
}

function message_in_home(message) {
    if (message.type === "private" || message.mentioned ||
        (page_params.narrow_stream !== undefined &&
         message.stream.toLowerCase() === page_params.narrow_stream.toLowerCase())) {
        return true;
    }

    return stream_data.in_home_view(message.stream);
}

function message_matches_search_term(message, operator, operand) {
    switch (operator) {
    case 'is':
        if (operand === 'private') {
            return (message.type === 'private');
        } else if (operand === 'starred') {
            return message.starred;
        } else if (operand === 'mentioned') {
            return message.mentioned;
        } else if (operand === 'alerted') {
            return message.alerted;
        }
        return true; // is:whatever returns true

    case 'in':
        if (operand === 'home') {
            return message_in_home(message);
        }
        else if (operand === 'all') {
            return true;
        }
        return true; // in:whatever returns true

    case 'near':
        // this is all handled server side
        return true;

    case 'id':
        return (message.id.toString() === operand);

    case 'stream':
        if (message.type !== 'stream') {
            return false;
        }

        operand = operand.toLowerCase();
        if (page_params.domain === "mit.edu") {
            return mit_edu_stream_name_match(message, operand);
        } else {
            return (message.stream.toLowerCase() === operand);
        }

    case 'topic':
        if (message.type !== 'stream') {
            return false;
        }

        operand = operand.toLowerCase();
        if (page_params.domain === "mit.edu") {
            return mit_edu_topic_name_match(message, operand);
        } else {
            return (message.subject.toLowerCase() === operand);
        }

    case 'sender':
        return (message.sender_email.toLowerCase() === operand);

    case 'pm-with':
        return (message.type === 'private') &&
            (message.reply_to.toLowerCase() === operand.split(',').sort().join(','));
    }

    return true; // unknown operators return true (effectively ignored)
}

function Filter(operators) {
    if (operators === undefined) {
        this._operators = [];
    } else {
        this._operators = this._canonicalize_operators(operators);
    }
}

var canonical_operators = {"from": "sender", "subject": "topic"};

Filter.canonicalize_operator = function (operator) {
    operator = operator.toLowerCase();
    if (canonical_operators.hasOwnProperty(operator)) {
        return canonical_operators[operator];
    } else {
        return operator;
    }
};

Filter.canonicalize_tuple = function (tuple) {
    var operator = tuple[0];
    var operand = tuple[1];

    operator = Filter.canonicalize_operator(operator);

    switch (operator) {
    case 'stream':
        operand = stream_data.get_name(operand);
        break;
    case 'topic':
        break;
    case 'sender':
    case 'pm-with':
        operand = operand.toString().toLowerCase();
        if (operand === 'me') {
            operand = page_params.email;
        }
        break;
    default:
        operand = operand.toString().toLowerCase();
    }

    // We may want to consider allowing mixed-case operators at some point
    return filter_term(operator, operand);
};

/* We use a variant of URI encoding which looks reasonably
   nice and still handles unambiguously cases such as
   spaces in operands.

   This is just for the search bar, not for saving the
   narrow in the URL fragment.  There we do use full
   URI encoding to avoid problematic characters. */
function encodeOperand(operand) {
    return operand.replace(/%/g,  '%25')
                  .replace(/\+/g, '%2B')
                  .replace(/ /g,  '+');
}

function decodeOperand(encoded, operator) {
    if (operator !== 'pm-with' && operator !== 'sender') {
        encoded = encoded.replace(/\+/g, ' ');
    }
    return util.robust_uri_decode(encoded);
}

// Parse a string into a list of operators (see below).
Filter.parse = function (str) {
    var operators   = [];
    var search_term = [];
    var operand;

    var matches = str.match(/"[^"]+"|\S+/g);
    if (matches === null) {
        return operators;
    }
    _.each(matches, function (token) {
        var parts, operator;
        parts = token.split(':');
        if (token[0] === '"' || parts.length === 1) {
            // Looks like a normal search term.
            search_term.push(token);
        } else {
            // Looks like an operator.
            // FIXME: Should we skip unknown operator names here?
            operator = parts.shift();
            operand = decodeOperand(parts.join(':'), operator);
            operators.push(filter_term(operator, operand));
        }
    });
    // NB: Callers of 'parse' can assume that the 'search' operator is last.
    if (search_term.length > 0) {
        operand = search_term.join(' ');
        operators.push(filter_term('search', operand));
    }
    return operators;
};

/* Convert a list of operators to a string.
   Each operator is a key-value pair like

       ['subject', 'my amazing subject']

   These are not keys in a JavaScript object, because we
   might need to support multiple operators of the same type.
*/
Filter.unparse = function (operators) {
    var parts = _.map(operators, function (elem) {
        var operator = elem[0];
        if (operator === 'search') {
            // Search terms are the catch-all case.
            // All tokens that don't start with a known operator and
            // a colon are glued together to form a search term.
            return elem[1];
        } else {
            return elem[0] + ':' + encodeOperand(elem[1].toString());
        }
    });
    return parts.join(' ');
};

Filter.prototype = {
    predicate: function Filter_predicate() {
        if (this._predicate === undefined) {
            this._predicate = this._build_predicate();
        }
        return this._predicate;
    },

    operators: function Filter_operators() {
        return this._operators;
    },

    public_operators: function Filter_public_operators() {
        var safe_to_return = _.filter(this._operators, function (value) {
            // Filter out the "in" keyword and the embedded narrow (if any).
            return value.operator !== 'in' && !(page_params.narrow_stream !== undefined &&
                                                value.operator === "stream" &&
                                                value.operand.toLowerCase() === page_params.narrow_stream.toLowerCase());
        });
        return safe_to_return;
    },

    operands: function Filter_get_operands(operator) {
        return _.chain(this._operators)
            .filter(function (elem) { return elem.operator === operator; })
            .map(function (elem) { return elem.operand; })
            .value();
    },

    has_operand: function Filter_has_operand(operator, operand) {
        return _.any(this._operators, function (elem) {
            return elem.operator === operator && elem.operand === operand;
        });
    },

    has_operator: function Filter_has_operator(operator) {
        return _.any(this._operators, function (elem) {
            return elem.operator === operator;
        });
    },

    is_search: function Filter_is_search() {
        return this.has_operator('search');
    },

    can_apply_locally: function Filter_can_apply_locally() {
        return ! this.is_search();
    },

    _canonicalize_operators: function Filter__canonicalize_operators(operators_mixed_case) {
        return _.map(operators_mixed_case, function (tuple) {
            return Filter.canonicalize_tuple(tuple);
        });
    },

    // Build a filter function from a list of operators.
    _build_predicate: function Filter__build_predicate() {
        var operators = this._operators;

        if (! this.can_apply_locally()) {
            return function () { return true; };
        }

        // FIXME: This is probably pretty slow.
        // We could turn it into something more like a compiler:
        // build JavaScript code in a string and then eval() it.

        return function (message) {
            return _.all(operators, function (term) {
                return message_matches_search_term(message, term.operator, term.operand);
            });
        };
    }
};

return Filter;

}());
if (typeof module !== 'undefined') {
    module.exports = Filter;
}
<|endoftext|>


<commit_before>Paloquiz.states.Finish = function(game) {
    this.score = 0;
}

Paloquiz.states.Finish.prototype = {

    create: function() {
        getJSON('/status', function(gameStatus) {
            if (gameStatus['status'] !== 'finish') {
                this.status.start('Router');
                return;
            }
            getJSON('/summary', function(summary) {
                this.score = summary['score'];
                this.answers = summary['answers'];
                this.numQuestions = summary['num_questions'];
                this.createScoreText();
                this.createUI();
                fbLogIn(function() {
                    this.hiscoresButton.visible = true;
                    fbGetUserScore(function(currentScore) {
                        if (this.score > currentScore) {
                            fbPublishScore(currentScore);
                        }
                    }, this);
                }, this);
            }, this);
        }, this);
    },

    shutdown: function() {},

    createScoreText: function() {;
    },

    createUI: function() {
        this.hiscoresButton =
            this.add.button(this.world.centerX, this.world.height * .6,
                'finishButton',
                function() {
                    this.state.start('Highscores');
                }, this, 2, 1, 0);
        this.hiscoresButton.smoothed = false;
        this.hiscoresButton.width = this.world.width * .6;
        this.hiscoresButton.height = this.hiscoresButton.width * .3;
        this.hiscoresButton.anchor.setTo(.5, 0);
        this.hiscoresButton.visible = false;

        this.exitButton =
            this.add.button(this.hiscoresButton.x,
                this.hiscoresButton.y + this.hiscoresButton.height * 1.1,
                'finishButton',
                function() {
                    getJSON('/finish', function() {
                        this.state.start('Router');
                    }, this);
                }, this, 2, 1, 0);
        this.exitButton.smoothed = false;
        this.exitButton.width = this.hiscoresButton.width;
        this.exitButton.height = this.hiscoresButton.height;
        this.exitButton.anchor.setTo(.5, 0);
    },

    finishGame: function() {;
    },

    submitScore: function() {;
    }
}<commit_msg>Implemented finish state<commit_after>Paloquiz.states.Finish = function(game) {
    this.score = 0;
    this.hiscoresButton;
    this.hiscoresLabel;
    this.exitButton;
    this.exitLabel;
}

Paloquiz.states.Finish.prototype = {

    create: function() {
        getJSON('/status', function(gameStatus) {
            if (gameStatus['status'] !== 'finish') {
                this.status.start('Router');
                return;
            }
            getJSON('/summary', function(summary) {
                this.score = summary['score'];
                this.answers = summary['answers'];
                this.numQuestions = summary['num_questions'];
                this.createScoreText();
                this.createUI();
                fbLogIn(function() {
                    this.hiscoresButton.visible = true;
                    this.hiscoresLabel.visible = true;
                    fbGetUserScore(function(currentScore) {
                        if (this.score > currentScore) {
                            fbPublishScore(currentScore);
                        }
                    }, this);
                }, this);
            }, this);
        }, this);
    },

    shutdown: function() {},

    createScoreText: function() {;
        var scoreBaseTextSize = Math.round(.06 * this.world.height);
        var scoreTextColor = '#ff4f1a';

        var scoreHeader = this.add.text(
            this.world.centerX,
            this.world.height * .2,
            'Puntuación', {
                font: 'Pixel Art',
                fontSize: scoreBaseTextSize + 'px',
                fill: scoreTextColor,
                align: 'center'
            });
        scoreHeader.anchor.setTo(.5, 0);

        var scoreText = this.add.text(
            this.world.centerX,
            this.world.height * .33,
            this.score, {
                font: 'Pixel Art',
                fontSize: Math.round(scoreBaseTextSize * 2.2) + 'px',
                fill: scoreTextColor,
                align: 'center'
            });
        scoreText.anchor.setTo(.5, 0);
    },

    createUI: function() {
        // Hiscores
        this.hiscoresButton =
            this.add.button(this.world.centerX, this.world.height * .55,
                'finishButton',
                function() {
                    this.state.start('Highscores');
                }, this, 2, 1, 0);
        this.hiscoresButton.smoothed = false;
        this.hiscoresButton.width = this.world.width * .6;
        this.hiscoresButton.height = this.hiscoresButton.width * .3;
        this.hiscoresButton.anchor.setTo(.5, 0);
        this.hiscoresButton.visible = false;

        // Exit
        this.exitButton =
            this.add.button(this.hiscoresButton.x,
                this.hiscoresButton.y + this.hiscoresButton.height * 1.1,
                'finishButton',
                function() {
                    getJSON('/finish', function() {
                        this.state.start('Router');
                    }, this);
                }, this, 2, 1, 0);
        this.exitButton.smoothed = false;
        this.exitButton.width = this.hiscoresButton.width;
        this.exitButton.height = this.hiscoresButton.height;
        this.exitButton.anchor.setTo(.5, 0);

        // Labels
        var labelTextSize = Math.round(this.hiscoresButton.height / 3.0);
        var labelStyle = this.positionTextStyle = {
            font: 'Pixel Art',
            fontSize: labelTextSize + 'px',
            fill: 'white',
            align: 'center'
        };

        this.hiscoresLabel = this.add.text(
            this.world.centerX,
            this.hiscoresButton.y + this.hiscoresButton.height / 2,
            'Clasificación', labelStyle);
        this.hiscoresLabel.anchor.setTo(.5, .5);
        this.hiscoresLabel.visible = false;

        this.exitLabel = this.add.text(
            this.world.centerX,
            this.exitButton.y + this.exitButton.height / 2,
            'Salir', labelStyle);
        this.exitLabel.anchor.setTo(.5, .5);
    }
}<|endoftext|>