<commit_before>'use strict';
angular.module('app')
.directive('lrVis', ['$rootScope', '$dialog', 'stdData', function($rootScope, $dialog, stdData) {
  return {
    // The directive can only be invoked by using tag in the template.
    restrict: 'E',
    // Attributes bound to the scope of the directive.
    scope: {
      bind: '='
    },
    link: function (scope, elem, attrs) {
      // Initialization, done once per my-directive tag in template.
      // If my-directive is within an ng-repeat-ed template then it will be
      // called every time ngRepeat creates a new copy of the template.
      // Constants
      var width  = window.innerWidth,
          height = window.innerHeight - 40,
          radius = Math.min(width, height) / 2,
          twoPI  = 2 * Math.PI,
          color  = d3.scale.category20c(),
          closed = false,
          PI     = Math.PI,
          node   = null,
          link   = null,
          data   = null,
          selectedNode = null;

      function conceal(elem){
        elem.css('display', 'none');
      }

      function reveal(elem){
        elem.css('display', 'block');
      }

      // HUD, display the HUD around a node.
      var HUD = {
        d: null,
        i: null,
        visible: false,

        hud: null,
        arc1: d3.svg.arc()
          .innerRadius(50)
          .outerRadius(200)
          .startAngle(45 * (PI / 180)) // degs to radians
          .endAngle(135 * (PI / 180)),
        arc2: d3.svg.arc()
          .innerRadius(50)
          .outerRadius(160)
          .startAngle(135 * (PI / 180))
          .endAngle(225 * (PI / 180)),
        arc3: d3.svg.arc()
          .innerRadius(50)
          .outerRadius(180)
          .startAngle(225 * (PI / 180))
          .endAngle(405 * (PI / 180)),

        labels: ['COMPARER', 'ANALYSER'],

        setHUD: function(hud){
          HUD.hud = hud;
          HUD.hud.style('fill', '#000');
        },

        setNode: function(d,i){
          HUD.d = d;
          HUD.i = i;
        },

        display: function(){
          HUD.hud
            .attr('fill', '#141414')
            .attr('stroke', '#5e1111');

          HUD.hud.append('svg:path')
            .attr('d', HUD.arc1)
            .on('click', function(){
              console.log('ANALYSER' + "\t" + HUD.d.PNAME + "\t" + HUD.d.UNAME);
              openDialogWindow( 'views/vis-analyser.html', HUD.d, true );
            });

          HUD.hud.append('svg:path')
            .attr('d', HUD.arc2);

          HUD.hud.append('svg:path')
            .attr('d', HUD.arc3)
            .on('click', function(){
              console.log('COMPARER' + "\t" + HUD.d.PNAME);
              openDialogWindow( 'views/vis-comparer.html', HUD.d, false );
            });

          // UNIVERSITY & PROGRAM LABEL
          var labels = HUD.hud.append('svg:g')
            .style('fill', '#fff')
            .style('font-size', 20)
            .style('stroke-width', 0);

        // LABEL
        HUD.hud.append('foreignObject')
          .attr('width', 800)
          .attr('height', 200)
          .attr('transform', function(d) {
            var x = -400,
                y = -240;
            return 'translate('+ x + ',' + y +')';
          })
          .append("xhtml:div")
            .attr('class', 'hudInnerText')
            .style('font-size', 30)
            .html(function(d) { return HUD.d.UNAMEL + "
" + HUD.d.PNAME + "" } );

          // ARCS LABEL
          labels.append('svg:text')
            .attr('x', -100)
            .attr('y', -80)
            .text(HUD.labels[0]);

          labels.append('svg:text')
            .attr('x', 85)
            .text(HUD.labels[1]);

          HUD.hud.attr('transform', function(){ return "translate("+ HUD.d.x +","+ HUD.d.y +")"; });

          HUD.visible = true;
        },

        move: function(){
          if(HUD.visible){
            HUD.hud.attr('transform', function(){ return "translate("+ HUD.d.x +","+ HUD.d.y +")"; });
          }
        },

        remove: function(){
          HUD.hud.remove();
          HUD.visible = false;
        }
      };

      var openDialogWindow = function(templateUrl, data, single){
        var d = $dialog.dialog({
          templateUrl: templateUrl,
          controller: 'DialogCtrl',
          data: data,
          single: single
        });

        // We need to apply ourselves!
        if(!scope.$$phase){
          scope.$apply(function(){
            d.open();
          });
        }
        else{
          d.open();
        }
      }

      var force = d3.layout.force()
        .on('tick', tick)
        .gravity(0.5)
        .charge(-3750)
        .linkDistance(height / 3.5 )
        .size([width, height]);

      var draggable = d3.select(elem[0]).append('svg:svg')
        .attr( 'width', width )
        .attr( 'height', height )
        .attr( 'pointer-events', 'all' );

      var svg = draggable.append( 'svg:g' )
          // .call( d3.behavior.zoom().on( 'zoom', rescale ) )
          // .on( 'dblclick.zoom', null )
          .append( 'svg:g' );

      svg.append( 'svg:rect' )
        .attr( 'width', width )
        .attr( 'height', height )
        .attr( 'fill', 'none' );

      // // Set up the initial svg, full width and height.
      // var svg = d3.select(elem[0])
      //   .append('svg')
      //     .attr('width', width)
      //     .attr('height', height);

      conceal(angular.element(svg[0]));

      var loading = d3.select(elem[0])
        .append('svg')
          .attr('width', width)
          .attr('height', height);
      var loadingText = loading.append('text')
        .attr('class', 'loadingIndicator')
        .attr('x', width / 2)
        .attr('y', height / 2)
        .attr('dy', '.35em')
        .attr('text-anchor', 'middle')
        .text('chargement');

      function rescale(){
        var trans  = d3.event.translate
            ,scale = d3.event.scale;

        svg.attr( 'transform',
            'translate(' + trans + ')'
            + ' scale(' + scale + ')');
      }

      function tick(tick){
        // HUD.move();

        // var nodes = flatten(data),
        //     q = d3.geom.quadtree(nodes),
        //     i = 0,
        //     n = nodes.length;

        // while (++i < n) {
        //   q.visit(collide(nodes[i]));
        // }

        // link
        //   .attr('x1', function(d){ return d.source.x; })
        //   .attr('y1', function(d){ return d.source.y; })
        //   .attr('x2', function(d){ return d.target.x; })
        //   .attr('y2', function(d){ return d.target.y; });

        node
          .attr('transform', function(d){ return "translate("+ d.x +","+ d.y +")"; });

        // "Notification" when it is almost done.
        if(!closed){
          if(tick.alpha < 0.08){
            loading.remove();

            reveal(angular.element(svg[0]));

            closeAll();
            closed = true;
          }
        }
      }

      function collide(node) {
        var r = node.radius + 16,
            nx1 = node.x - r,
            nx2 = node.x + r,
            ny1 = node.y - r,
            ny2 = node.y + r;
        return function(quad, x1, y1, x2, y2) {
          if (quad.point && (quad.point !== node)) {
            var x = node.x - quad.point.x,
                y = node.y - quad.point.y,
                l = Math.sqrt(x * x + y * y),
                r = node.radius + quad.point.radius;
            if (l < r) {
              l = (l - r) / l * .5;
              node.x -= x *= l;
              node.y -= y *= l;
              quad.point.x += x;
              quad.point.y += y;
            }
          }
          return x1 > nx2
              || x2 < nx1
              || y1 > ny2
              || y2 < ny1;
        };
      }

      function closeAll(){
        angular.forEach(data.children, function(v,k){
          if(v.children){
            v._children = v.children;
            v.children  = null;
          }
        });
        update();
      }

      // Color leaf nodes orange, and packages white or blue.
      function color(d){
        return d._children ? 'red1' : d.children ? 'red2' : 'green';
      }

      function border(d){
            if(typeof d.image !== 'undefined'){
              // console.log(d);
              if(d._children){
                return "#b42121";
              }
              else{
                return "#fff";
              }
            }
            else{
              return '#b42121';
            }
      }

      // Toggle children on click.
      function click(d, i){
        // // If HUD is NOT visible.
        // if(!HUD.visible){
        //   var shouldClose = true;
        //   // If we clicked on a program.
        //   if(d.hasOwnProperty("UID")){
        //     HUD.setHUD(svg.insert('g', 'node'));
        //     HUD.setNode(d, i);
        //     HUD.display();
        //     shouldClose = false;
        //   }

        //   if(d.children){
        //     // Open children
        //     d._children = d.children;
        //     d.children  = null;
        //   }
        //   else{
        //     // Close children.
        //     if(shouldClose) closeAll();
        //     d.children  = d._children;
        //     d._children = null;
        //   }

        //   update();
        // }
        // else{
        //   HUD.remove();
        // }

          var shouldClose = true;
          // If we clicked on a program.
          if(d.hasOwnProperty("UID")){
            // HUD.setHUD(svg.insert('g', 'node'));
            // HUD.setNode(d, i);
            // HUD.display();
            openDialogWindow( 'views/vis-comparer.html', d, false );
            shouldClose = false;
          }
          else {
            if(d.children){
              selectedNode = null;
            }
            else{
              selectedNode = d.id;
            }
          }

          if(d.children){
            // Open children
            d._children = d.children;
            d.children  = null;
          }
          else{
            // Close children.
            if(shouldClose) closeAll();
            d.children  = d._children;
            d._children = null;
          }

          update();

      }

      // Returns a list of all nodes under the root.
      function flatten(root){
        var nodes = [],
            i     = 0;

        function recurse(node){
          if(node.children) node.children.forEach(recurse);
          if(!node.id) node.id = ++i;
          nodes.push(node);
        }

        recurse(root);
        return nodes;
      }

      function getRadius(d){
        if(typeof d.image !== 'undefined'){
          return 25;
        }
        else{
          var r = d.name.length * 2.5;
          return r > 85 ? 85 : r < 40 ? 40 : r;
          // return Math.sqrt(d.salaireHebdoBrut) * 2.5 || 50;
        }

      }

      // Update the force layout.
      function update(){
        var nodes = flatten(data),
            links = d3.layout.tree().links(nodes);

        // Restart the force layout.
        force
          .nodes(nodes)
          .links([])
          .start();

        // // Update the links.
        // link = svg.selectAll('line.link')
        //   .data(links, function(d){ return d.target.id; });

        // Enter any new links.
        // link.enter().insert('svg:line', '.node')
        //   .attr('class', 'link')
        //   .attr('x1', function(d){ return d.source.x; })
        //   .attr('y1', function(d){ return d.source.y; })
        //   .attr('x2', function(d){ return d.target.x; })
        //   .attr('y3', function(d){ return d.target.y; });

        // // Exit any old links.
        // link.exit().remove();

        // Update the nodes.
        node = svg.selectAll('g.node')
          .data(nodes, function(d){ return d.id; });

        node
          .classed( 'selected', function( d ) { return d.id === selectedNode ? true : false; });

        var group = node.enter().append('g')
          .attr('class', function(d, i){
            var c = [ 'node' ];
            if ( d.name === 'root' )        c.push( 'hidden' );
            return c.join( ' ' );
          })
          .attr('transform', function(d){ return "translate("+ d.x +","+ d.y +")"; })
          .call(force.drag)
          .on('click', click);

        // OUTER CIRCLE
        group.append('circle')
          .attr('r', function(d){ return getRadius(d) } )
          // .attr('r', function(d){ return Math.random(100,150); } )
          .attr('class', color)
          .style('stroke', border)
          .style('stroke-width', '0.5');

        var innerGroup = group.append('g')
          .attr('transform', 'translate(0,-8)');

        // LABEL
        innerGroup.append('foreignObject')
          .attr('width', 180)
          .attr('height', 180)
          .attr('transform', function(d) {
            var x = !d.image ? -75 : -90,
                y = !d.image ? -63 : 10;
            return 'translate('+ x + ',' + y +')';
          })
          .append('xhtml:div')
            .attr('class', function( d ) {
              var c = [ 'aligncenter' ];
              if ( !d.image ) c.push( 'inner' );
              return c.join( ' ' );
            })
            .html(function(d) { return d.name; } );

        // IMAGE
        innerGroup.append('image')
          .attr('xlink:href', function(d){
            if(typeof d.image !== 'undefined'){
              return d.image;
            }
            else{
              return 'img/U/_pixel.gif';
            }
          })
          .attr('x', -8)
          .attr('y', -8)
          .attr('width', 16)
          .attr('height', 16);

        // Exit any old nodes.
        node.exit().remove();
      } // end update()

      // Whenever the bound 'exp' expression changes, execute this
      scope.$watch('bind', function (newData, oldData) {
        // Bail out early if no new data.
        if(!newData){
          return;
        }
        else{
          data = newData;
          update();
        }
      });
    }
  };
}]);
<commit_msg>Fixed node text centering<commit_after>'use strict';
angular.module('app')
.directive('lrVis', ['$rootScope', '$dialog', 'stdData', function($rootScope, $dialog, stdData) {
  return {
    // The directive can only be invoked by using tag in the template.
    restrict: 'E',
    // Attributes bound to the scope of the directive.
    scope: {
      bind: '='
    },
    link: function (scope, elem, attrs) {
      // Initialization, done once per my-directive tag in template.
      // If my-directive is within an ng-repeat-ed template then it will be
      // called every time ngRepeat creates a new copy of the template.
      // Constants
      var width  = window.innerWidth,
          height = window.innerHeight - 40,
          radius = Math.min(width, height) / 2,
          twoPI  = 2 * Math.PI,
          color  = d3.scale.category20c(),
          closed = false,
          PI     = Math.PI,
          node   = null,
          link   = null,
          data   = null,
          selectedNode = null;

      function conceal(elem){
        elem.css('display', 'none');
      }

      function reveal(elem){
        elem.css('display', 'block');
      }

      // HUD, display the HUD around a node.
      var HUD = {
        d: null,
        i: null,
        visible: false,

        hud: null,
        arc1: d3.svg.arc()
          .innerRadius(50)
          .outerRadius(200)
          .startAngle(45 * (PI / 180)) // degs to radians
          .endAngle(135 * (PI / 180)),
        arc2: d3.svg.arc()
          .innerRadius(50)
          .outerRadius(160)
          .startAngle(135 * (PI / 180))
          .endAngle(225 * (PI / 180)),
        arc3: d3.svg.arc()
          .innerRadius(50)
          .outerRadius(180)
          .startAngle(225 * (PI / 180))
          .endAngle(405 * (PI / 180)),

        labels: ['COMPARER', 'ANALYSER'],

        setHUD: function(hud){
          HUD.hud = hud;
          HUD.hud.style('fill', '#000');
        },

        setNode: function(d,i){
          HUD.d = d;
          HUD.i = i;
        },

        display: function(){
          HUD.hud
            .attr('fill', '#141414')
            .attr('stroke', '#5e1111');

          HUD.hud.append('svg:path')
            .attr('d', HUD.arc1)
            .on('click', function(){
              console.log('ANALYSER' + "\t" + HUD.d.PNAME + "\t" + HUD.d.UNAME);
              openDialogWindow( 'views/vis-analyser.html', HUD.d, true );
            });

          HUD.hud.append('svg:path')
            .attr('d', HUD.arc2);

          HUD.hud.append('svg:path')
            .attr('d', HUD.arc3)
            .on('click', function(){
              console.log('COMPARER' + "\t" + HUD.d.PNAME);
              openDialogWindow( 'views/vis-comparer.html', HUD.d, false );
            });

          // UNIVERSITY & PROGRAM LABEL
          var labels = HUD.hud.append('svg:g')
            .style('fill', '#fff')
            .style('font-size', 20)
            .style('stroke-width', 0);

        // LABEL
        HUD.hud.append('foreignObject')
          .attr('width', 800)
          .attr('height', 200)
          .attr('transform', function(d) {
            var x = -400,
                y = -240;
            return 'translate('+ x + ',' + y +')';
          })
          .append("xhtml:div")
            .attr('class', 'hudInnerText')
            .style('font-size', 30)
            .html(function(d) { return HUD.d.UNAMEL + "
" + HUD.d.PNAME + "" } );

          // ARCS LABEL
          labels.append('svg:text')
            .attr('x', -100)
            .attr('y', -80)
            .text(HUD.labels[0]);

          labels.append('svg:text')
            .attr('x', 85)
            .text(HUD.labels[1]);

          HUD.hud.attr('transform', function(){ return "translate("+ HUD.d.x +","+ HUD.d.y +")"; });

          HUD.visible = true;
        },

        move: function(){
          if(HUD.visible){
            HUD.hud.attr('transform', function(){ return "translate("+ HUD.d.x +","+ HUD.d.y +")"; });
          }
        },

        remove: function(){
          HUD.hud.remove();
          HUD.visible = false;
        }
      };

      var openDialogWindow = function(templateUrl, data, single){
        var d = $dialog.dialog({
          templateUrl: templateUrl,
          controller: 'DialogCtrl',
          data: data,
          single: single
        });

        // We need to apply ourselves!
        if(!scope.$$phase){
          scope.$apply(function(){
            d.open();
          });
        }
        else{
          d.open();
        }
      }

      var force = d3.layout.force()
        .on('tick', tick)
        .gravity(0.5)
        .charge(-3750)
        .linkDistance(height / 3.5 )
        .size([width, height]);

      var draggable = d3.select(elem[0]).append('svg:svg')
        .attr( 'width', width )
        .attr( 'height', height )
        .attr( 'pointer-events', 'all' );

      var svg = draggable.append( 'svg:g' )
          // .call( d3.behavior.zoom().on( 'zoom', rescale ) )
          // .on( 'dblclick.zoom', null )
          .append( 'svg:g' );

      svg.append( 'svg:rect' )
        .attr( 'width', width )
        .attr( 'height', height )
        .attr( 'fill', 'none' );

      // // Set up the initial svg, full width and height.
      // var svg = d3.select(elem[0])
      //   .append('svg')
      //     .attr('width', width)
      //     .attr('height', height);

      conceal(angular.element(svg[0]));

      var loading = d3.select(elem[0])
        .append('svg')
          .attr('width', width)
          .attr('height', height);
      var loadingText = loading.append('text')
        .attr('class', 'loadingIndicator')
        .attr('x', width / 2)
        .attr('y', height / 2)
        .attr('dy', '.35em')
        .attr('text-anchor', 'middle')
        .text('chargement');

      function rescale(){
        var trans  = d3.event.translate
            ,scale = d3.event.scale;

        svg.attr( 'transform',
            'translate(' + trans + ')'
            + ' scale(' + scale + ')');
      }

      function tick(tick){
        // HUD.move();

        // var nodes = flatten(data),
        //     q = d3.geom.quadtree(nodes),
        //     i = 0,
        //     n = nodes.length;

        // while (++i < n) {
        //   q.visit(collide(nodes[i]));
        // }

        // link
        //   .attr('x1', function(d){ return d.source.x; })
        //   .attr('y1', function(d){ return d.source.y; })
        //   .attr('x2', function(d){ return d.target.x; })
        //   .attr('y2', function(d){ return d.target.y; });

        node
          .attr('transform', function(d){ return "translate("+ d.x +","+ d.y +")"; });

        // "Notification" when it is almost done.
        if(!closed){
          if(tick.alpha < 0.08){
            loading.remove();

            reveal(angular.element(svg[0]));

            closeAll();
            closed = true;
          }
        }
      }

      function collide(node) {
        var r = node.radius + 16,
            nx1 = node.x - r,
            nx2 = node.x + r,
            ny1 = node.y - r,
            ny2 = node.y + r;
        return function(quad, x1, y1, x2, y2) {
          if (quad.point && (quad.point !== node)) {
            var x = node.x - quad.point.x,
                y = node.y - quad.point.y,
                l = Math.sqrt(x * x + y * y),
                r = node.radius + quad.point.radius;
            if (l < r) {
              l = (l - r) / l * .5;
              node.x -= x *= l;
              node.y -= y *= l;
              quad.point.x += x;
              quad.point.y += y;
            }
          }
          return x1 > nx2
              || x2 < nx1
              || y1 > ny2
              || y2 < ny1;
        };
      }

      function closeAll(){
        angular.forEach(data.children, function(v,k){
          if(v.children){
            v._children = v.children;
            v.children  = null;
          }
        });
        update();
      }

      // Color leaf nodes orange, and packages white or blue.
      function color(d){
        return d._children ? 'red1' : d.children ? 'red2' : 'green';
      }

      function border(d){
            if(typeof d.image !== 'undefined'){
              // console.log(d);
              if(d._children){
                return "#b42121";
              }
              else{
                return "#fff";
              }
            }
            else{
              return '#b42121';
            }
      }

      // Toggle children on click.
      function click(d, i){
        // // If HUD is NOT visible.
        // if(!HUD.visible){
        //   var shouldClose = true;
        //   // If we clicked on a program.
        //   if(d.hasOwnProperty("UID")){
        //     HUD.setHUD(svg.insert('g', 'node'));
        //     HUD.setNode(d, i);
        //     HUD.display();
        //     shouldClose = false;
        //   }

        //   if(d.children){
        //     // Open children
        //     d._children = d.children;
        //     d.children  = null;
        //   }
        //   else{
        //     // Close children.
        //     if(shouldClose) closeAll();
        //     d.children  = d._children;
        //     d._children = null;
        //   }

        //   update();
        // }
        // else{
        //   HUD.remove();
        // }

          var shouldClose = true;
          // If we clicked on a program.
          if(d.hasOwnProperty("UID")){
            // HUD.setHUD(svg.insert('g', 'node'));
            // HUD.setNode(d, i);
            // HUD.display();
            openDialogWindow( 'views/vis-comparer.html', d, false );
            shouldClose = false;
          }
          else {
            if(d.children){
              selectedNode = null;
            }
            else{
              selectedNode = d.id;
            }
          }

          if(d.children){
            // Open children
            d._children = d.children;
            d.children  = null;
          }
          else{
            // Close children.
            if(shouldClose) closeAll();
            d.children  = d._children;
            d._children = null;
          }

          update();

      }

      // Returns a list of all nodes under the root.
      function flatten(root){
        var nodes = [],
            i     = 0;

        function recurse(node){
          if(node.children) node.children.forEach(recurse);
          if(!node.id) node.id = ++i;
          nodes.push(node);
        }

        recurse(root);
        return nodes;
      }

      function getRadius(d){
        if(typeof d.image !== 'undefined'){
          return 25;
        }
        else{
          var r = d.name.length * 2.5;
          return r > 85 ? 85 : r < 50 ? 50 : r;
          // return Math.sqrt(d.salaireHebdoBrut) * 2.5 || 50;
        }

      }

      // Update the force layout.
      function update(){
        var nodes = flatten(data),
            links = d3.layout.tree().links(nodes);

        // Restart the force layout.
        force
          .nodes(nodes)
          .links([])
          .start();

        // // Update the links.
        // link = svg.selectAll('line.link')
        //   .data(links, function(d){ return d.target.id; });

        // Enter any new links.
        // link.enter().insert('svg:line', '.node')
        //   .attr('class', 'link')
        //   .attr('x1', function(d){ return d.source.x; })
        //   .attr('y1', function(d){ return d.source.y; })
        //   .attr('x2', function(d){ return d.target.x; })
        //   .attr('y3', function(d){ return d.target.y; });

        // // Exit any old links.
        // link.exit().remove();

        // Update the nodes.
        node = svg.selectAll('g.node')
          .data(nodes, function(d){ return d.id; });

        node
          .classed( 'selected', function( d ) { return d.id === selectedNode ? true : false; });

        var group = node.enter().append('g')
          .attr('class', function(d, i){
            var c = [ 'node' ];
            if ( d.name === 'root' )        c.push( 'hidden' );
            return c.join( ' ' );
          })
          .attr('transform', function(d){ return "translate("+ d.x +","+ d.y +")"; })
          .call(force.drag)
          .on('click', click);

        // OUTER CIRCLE
        group.append('circle')
          .attr('r', function(d){ return getRadius(d) } )
          // .attr('r', function(d){ return Math.random(100,150); } )
          .attr('class', color)
          .style('stroke', border)
          .style('stroke-width', '0.5');

        var innerGroup = group.append('g')
          .attr('transform', 'translate(0,-8)');

        // LABEL
        innerGroup.append('foreignObject')
          .attr('width', 140)
          .attr('height', 140)
          .attr('transform', function(d) {
            var x = !d.image ? -70 : -70,
                y = !d.image ? -63 : 10;
            return 'translate('+ x + ',' + y +')';
          })
          .append('xhtml:div')
            .attr('class', function( d ) {
              var c = [ 'aligncenter' ];
              if ( !d.image ) c.push( 'inner' );
              return c.join( ' ' );
            })
            .html(function(d) { return d.name; } );

        // IMAGE
        innerGroup.append('image')
          .attr('xlink:href', function(d){
            if(typeof d.image !== 'undefined'){
              return d.image;
            }
            else{
              return 'img/U/_pixel.gif';
            }
          })
          .attr('x', -8)
          .attr('y', -8)
          .attr('width', 16)
          .attr('height', 16);

        // Exit any old nodes.
        node.exit().remove();
      } // end update()

      // Whenever the bound 'exp' expression changes, execute this
      scope.$watch('bind', function (newData, oldData) {
        // Bail out early if no new data.
        if(!newData){
          return;
        }
        else{
          data = newData;
          update();
        }
      });
    }
  };
}]);
<|endoftext|>


<commit_before>/** @jsx React.DOM */

var React = require('react');

// Generic Countdown Timer UI component
//
// props:
//   - initialTimeRemaining: Number
//       The time remaining for the countdown (in ms).
//
//   - interval: Number (optional -- default: 1000ms)
//       The time between timer ticks (in ms).
//
//   - formatFunc(timeRemaining): Function (optional)
//       A function that formats the timeRemaining.
//
//   - tickCallback(timeRemaining): Function (optional)
//       A function to call each tick.
//
var CountdownTimer = React.createClass({displayName: "CountdownTimer",

  propTypes: {
    initialTimeRemaining: React.PropTypes.number.isRequired,
    interval: React.PropTypes.number,
    formatFunc: React.PropTypes.func,
    tickCallback: React.PropTypes.func,
    completeCallback: React.PropTypes.func
  },

  getDefaultProps: function() {
    return {
      interval: 1000,
      formatFunc: undefined,
      tickCallback: undefined,
      completeCallback: undefined,
    };
  },

  getInitialState: function() {
    // Normally an anti-pattern to use this.props in getInitialState,
    // but these are all initializations (not an anti-pattern).
    return {
      timeRemaining: this.props.initialTimeRemaining,
      timeoutId: undefined,
      prevTime: undefined
    };
  },

  componentWillReceiveProps: function(newProps, oldProps) {
    if (this.state.timeoutId) clearTimeout(this.state.timeoutId);
    this.setState({ prevTime: undefined, timeRemaining: newProps.initialTimeRemaining });
  },

  componentDidMount: function() {
    this.tick();
  },

  componentDidUpdate: function(){
    if ((!this.state.prevTime) && this.state.timeRemaining > 0 && this.isMounted()) {
      this.tick();
    }
  },

  componentWillUnmount: function() {
    clearTimeout(this.state.timeoutId);
  },

  tick: function() {

    var currentTime = Date.now();
    var dt = currentTime - this.state.prevTime || 0;
    var interval = this.props.interval;

    // correct for small variations in actual timeout time
    var timeRemainingInInterval = (interval - (dt % interval));
    var timeout = timeRemainingInInterval;

    if (timeRemainingInInterval < (interval / 2.0)){
      timeout += interval;
    }

    var timeRemaining = Math.max(this.state.timeRemaining - dt, 0);
    var countdownComplete = (this.state.prevTime && timeRemaining <= 0);

    if (this.isMounted()){
      if (this.state.timeoutId) clearTimeout(this.state.timeoutId);
      this.setState({
        timeoutId: countdownComplete ? undefined: setTimeout(this.tick, timeout),
        prevTime: currentTime,
        timeRemaining: timeRemaining
      });
    }

    if (countdownComplete) {
      if (this.props.completeCallback) { this.props.completeCallback() };
      return;
    }

    if (this.props.tickCallback) {
      this.props.tickCallback(timeRemaining);
    }
  },

  getFormattedTime: function(milliseconds) {
    if (this.props.formatFunc) {
      return this.props.formatFunc(milliseconds);
    }

    var totalSeconds = Math.round(milliseconds / 1000);

    var seconds = parseInt(totalSeconds % 60);
    var minutes = parseInt(totalSeconds / 60) % 60;
    var hours = parseInt(totalSeconds / 3600);

    seconds = seconds < 10 ? '0' + seconds : seconds;
    minutes = minutes < 10 ? '0' + minutes : minutes;
    hours = hours < 10 ? '0' + hours : hours;

    return hours + ':' + minutes + ':' + seconds;
  },

  render: function() {
    var timeRemaining = this.state.timeRemaining;

    return (
      React.createElement("div", {className: "timer"},
        this.getFormattedTime(timeRemaining)
      )
    );
  }
});

module.exports = CountdownTimer;
<commit_msg>Copy a160942 to plain .js version<commit_after>var React = require('react');

// Generic Countdown Timer UI component
//
// props:
//   - initialTimeRemaining: Number
//       The time remaining for the countdown (in ms).
//
//   - interval: Number (optional -- default: 1000ms)
//       The time between timer ticks (in ms).
//
//   - formatFunc(timeRemaining): Function (optional)
//       A function that formats the timeRemaining.
//
//   - tickCallback(timeRemaining): Function (optional)
//       A function to call each tick.
//
var CountdownTimer = React.createClass({displayName: "CountdownTimer",

  propTypes: {
    initialTimeRemaining: React.PropTypes.number.isRequired,
    interval: React.PropTypes.number,
    formatFunc: React.PropTypes.func,
    tickCallback: React.PropTypes.func,
    completeCallback: React.PropTypes.func
  },

  getDefaultProps: function() {
    return {
      interval: 1000,
      formatFunc: undefined,
      tickCallback: undefined,
      completeCallback: undefined,
    };
  },

  getInitialState: function() {
    // Normally an anti-pattern to use this.props in getInitialState,
    // but these are all initializations (not an anti-pattern).
    return {
      timeRemaining: this.props.initialTimeRemaining,
      timeoutId: undefined,
      prevTime: undefined
    };
  },

  componentWillReceiveProps: function(newProps, oldProps) {
    if (this.state.timeoutId) clearTimeout(this.state.timeoutId);
    this.setState({ prevTime: undefined, timeRemaining: newProps.initialTimeRemaining });
  },

  componentDidMount: function() {
    this.tick();
  },

  componentDidUpdate: function(){
    if ((!this.state.prevTime) && this.state.timeRemaining > 0 && this.isMounted()) {
      this.tick();
    }
  },

  componentWillUnmount: function() {
    clearTimeout(this.state.timeoutId);
  },

  tick: function() {

    var currentTime = Date.now();
    var dt = currentTime - this.state.prevTime || 0;
    var interval = this.props.interval;

    // correct for small variations in actual timeout time
    var timeRemainingInInterval = (interval - (dt % interval));
    var timeout = timeRemainingInInterval;

    if (timeRemainingInInterval < (interval / 2.0)){
      timeout += interval;
    }

    var timeRemaining = Math.max(this.state.timeRemaining - dt, 0);
    var countdownComplete = (this.state.prevTime && timeRemaining <= 0);

    if (this.isMounted()){
      if (this.state.timeoutId) clearTimeout(this.state.timeoutId);
      this.setState({
        timeoutId: countdownComplete ? undefined: setTimeout(this.tick, timeout),
        prevTime: currentTime,
        timeRemaining: timeRemaining
      });
    }

    if (countdownComplete) {
      if (this.props.completeCallback) { this.props.completeCallback() };
      return;
    }

    if (this.props.tickCallback) {
      this.props.tickCallback(timeRemaining);
    }
  },

  getFormattedTime: function(milliseconds) {
    if (this.props.formatFunc) {
      return this.props.formatFunc(milliseconds);
    }

    var totalSeconds = Math.round(milliseconds / 1000);

    var seconds = parseInt(totalSeconds % 60);
    var minutes = parseInt(totalSeconds / 60) % 60;
    var hours = parseInt(totalSeconds / 3600);

    seconds = seconds < 10 ? '0' + seconds : seconds;
    minutes = minutes < 10 ? '0' + minutes : minutes;
    hours = hours < 10 ? '0' + hours : hours;

    return hours + ':' + minutes + ':' + seconds;
  },

  render: function() {
    var timeRemaining = this.state.timeRemaining;

    return (
      React.createElement("div", {className: "timer"},
        this.getFormattedTime(timeRemaining)
      )
    );
  }
});

module.exports = CountdownTimer;
<|endoftext|>


<commit_before>(function() {
    'use strict';

    /**
     * @name game.GameService
     * @ngdoc service
     * @description A Service for the Game Related operations
     * @requires scrabble.ScrabbleService
     */
    angular.module('GameService', ['ScrabbleService'])

    /**
     * @ngdoc object
     * @name game.GameService:rules
     * @param {number} BINGO THE number of tiles to use to achive the bingo score
     * @param {number} BINGO_SCORE The score when a player uses all seven tiles
     * @description
     *  A constant which holds 'Game values'
     *

     *  if (tiles.length === rules.BINGO) {
     *      score = score + rules.BINGO_SCORE;
     *  }
     *

     */
        .constant('rules', {
            'BINGO': 7,
            'MAX_TILES' : 7,
            'BINGO_SCORE': 50
        })

        .service('GameService', GameService);

    /**
     * @ngdoc method
     * @name GameService
     * @methodOf game.GameService
     * @description The constructor
     * @constructor
     */
    function GameService($log, $q, flux, rules, actions, StateMachineService, DictionaryService, ScrabbleService, WordFinderService, _) {

        $log.info('GameService');

        var service = {};

        service.letterBag = undefined;

        service.currentHand = undefined;

        service.isReady = false;

        service.init = _init;

        service.getLetterBag = _getLetterBag;

        service.getHand = _getHand;

        service.reset = _reset;

        service.start = _start;

        service.stop = _stop;

        service.showBestWord = _showBestWord;

        service.replay = _replay;

        service.updateUserSelection = _updateUserSelection;

        service.evaluateDisplayUserScore = _evaluateDisplayUserScore;

        service.evaluateDisplayTimer = _evaluateDisplayTimer;

        service.evaluateIsDraggable = _evaluateIsDraggable;

        return service;

        /**
         * @ngdoc method
         * @name init
         * @methodOf game.GameService
         * @description Makes operations to initialise Scrabble

         * • Sets up the letter bag

         * • Sets up the Dictionary

         * • Returns a Promise to ensure the Callee that the required items will be ready
         *

         * GameService.init()
         *     .then(function() {
         *         _setUp();
         *     }, function(error) {
         *         $log.error('Init -> Error: ', error);
         *     });
         *

         */
        function _init() {

            var deferred = $q.defer();

            _setUpLetterBag();

            DictionaryService.getDictionary()
                .then(function() {
                    service.wordList = DictionaryService.dictionary;
                    service.isReady = true;
                    deferred.resolve('OK');
                    StateMachineService.makeReady();

                    $log.info('STATE ', StateMachineService.current());
                }, function(error) {
                    deferred.reject('Error ' + error);
                });

            return deferred.promise;
        }

        // == GETTERS ========

        // a collection of Tiles
        function _getLetterBag() {
            return service.letterBag;
        }

        /**
         * @ngdoc method
         * @name setUpLetterBag
         * @methodOf game.GameService
         * @description
         * Creates the letter bag

         * Also, randomises the order if the letters

         * @private
         */
        function _setUpLetterBag() {
            service.letterBag = ScrabbleService.createLetterBag();
            ScrabbleService.shake();
        }

        /**
         * @ngdoc method
         * @methodOf game.GameService
         * @name getHand
         * @description
            Removes a number of Tiles from the collection and returns them as an Array
         * @param {number} number The amount of Tiles to get
         * @returns {Array} an Array of Tiles
         * @private
         */
        function _getHand(number) {

            if(ScrabbleService.letterBagIsEmpty()) {
                // will have to set another State!
                // should have been spotted before!

                $log.info('getHand -> STATE ', StateMachineService.current());

                $log.info('GAME OVER!');

                return [];
            }
            number = number || rules.MAX_TILES;
            var hand = ScrabbleService.getHand(number);

            for (var i = 0, j = hand.length; i < j; i++) {
                flux.dispatch(actions.TILE_ADD, hand[i]);
            }
            service.currentHand = hand;
            return hand;
        }

        /**
         * @ngdoc method
         * @methodOf game.GameService
         * @name reset
         * @description Performs a variety of actions to reset the game
         */
        function _reset() {

            $log.info('GET RESET!');
            StateMachineService.reset();
            $log.info('STATE ', StateMachineService.current());

            // clear the current hand
            // TODO might be un-required if we stick to the model?
            service.currentHand = undefined;

            // clear the user's score
            flux.dispatch(actions.SCORE_UPDATE, 0);

            // clear the bestWord
            flux.dispatch(actions.BESTWORD_SET, '');

            // clear the tiles
            flux.dispatch(actions.TILE_DELETE);

            // TODO perhaps call when this has been confirmed
            StateMachineService.initialise();

            StateMachineService.makeReady();
        }

        // starts the game by staring the timer
        function _start() {
            StateMachineService.play();

            flux.dispatch(actions.TIMER_START);
        }

        // pauses the game (for when the user wants to stop the timer)
        // or the Timer has run out of Time
        function _stop() {

            $log.info('GameService stop');

            StateMachineService.stop();

            flux.dispatch(actions.TIMER_STOP);
        }

        // TODO Rename as we have a side-effect
        /**
         * @name _showBestWord
         * @description Finds the word with the highest score
         * @private
         */
        function _showBestWord() {

            if(ScrabbleService.letterBagIsEmpty()) {
                // TODO we need another State
                $log.info('GAME OVER!');
                StateMachineService.end();
            } else {
                // sets state
                StateMachineService.finish();
            }

            // guard
            if (!service.currentHand) {
                throw('GameService.showBestWord | no currentHand');
            }

            // get the possible collection of Words as an Array
            var result = _getResult(service.currentHand);

            // gets the Words with the highest score from the result
            var bestWords = ScrabbleService.findBestWord(result);

            // currently we just return the first highest scoring Word
            // or none if no word was found
            var bestWord = (bestWords.length) ? (bestWords[0]) : (null);

            // check for Bingo here if all letters being used
            if (bestWord && bestWord.word.length >= rules.BINGO) {
                (bestWord.score = bestWord.score + rules.BINGO_SCORE);
            }

            flux.dispatch(actions.BESTWORD_SET, bestWord);
        }

        /**
         * @ngdoc method
         * @name replay
         * @methodOf game.GameService
         * @description Sets up the required functions to reset the game
         * @private
         */
        function _replay() {
            $log.info('GameService.replay!');

            // need to create a new letter bag and shuffle it

            _setUpLetterBag();

            $log.info('replay -> STATE ', StateMachineService.current());

            _reset();
        }

        /**
         * @ngdoc method
         * @name updateUserSelection
         * @methodOf game.GameService
         * @description Performs the necessary operations once the user updates their selection of tiles
         * @param {array} tiles An Array of Tiles
         * @private
         */
        function _updateUserSelection(tiles) {
            if (!tiles || !tiles.length) {
                flux.dispatch(actions.SCORE_UPDATE, 0);
                return;
            }

            // Note: dictionary does not include one letter words
            var word = _getWord(tiles);

            // check is valid word and evaluate the score
            var score = _wordExists(word) ? _getScore(tiles) : 0;

            flux.dispatch(actions.SCORE_UPDATE, score);
        }

        // == BUSINESS LOGIC ========

        // Evaluates if the user's score field should be visible
        function _evaluateDisplayUserScore() {
            return (StateMachineService.current() === 'playing' || StateMachineService.current() === 'done' || StateMachineService.current().state === 'paused' || StateMachineService.current() === 'paused' );
        }

        // The timer should only be visible when the user is playing the game or has just stopped
        function _evaluateDisplayTimer() {
            return !(StateMachineService.current() === 'ready' || StateMachineService.current() === 'initial' || StateMachineService.current() === 'done');
        }

        // The tiles should onle be draggable when the user is playing the game
        function _evaluateIsDraggable() {
            return StateMachineService.current() === 'playing';
        }

        // == UTILITY FUNCTIONS ========

        // TODO differentiate between the this 'getResult()` and result from the user's set of tiles

        // TODO rename
        /**
         * @description
         * Returns all possible Words from the collection of letters

         * @name _getResult
         * @methodOf game.GameService
         * @param hand A set of Tiles
         * @returns {Array} The Words with the highest possible scores
         * @private
         */
        function _getResult(hand) {

            if (!hand || !hand.length) {
                $log.error('GameService.getResult - No Hand supplied!');
                return [];
            }

            if (!service.wordList || !service.wordList.length) {
                $log.error('GameService.getResult - Dictionary has not been instantiated!');
                return [];
            }

            var a = performance.now();

            var words =  WordFinderService.makeWordFinder(_getLetters(hand), service.wordList);

            var b = performance.now();

            $log.info('GameService.getResult() took ' + (b - a) + ' ms.');

            return  words;
        }

        // gets the word as a String from a given set of tiles
        function _getWord(tiles) {
            var word = tiles
                .map(function(tile) {
                    return tile.letter;
                })
                .reduce(function(a, b) {
                    return a + b;
                });
            return word;
        }

        /**
         * @description returns a collection of letter strings from the supplied set of tiles
         * @param tiles
         * @returns {Array}
         * @private
         */
        function _getLetters(tiles) {
            return tiles
                .map(function(tile) {
                    return tile.letter;
                });
        }

        // gets the total score from a given set of tiles
        function _getScore(tiles) {
            var score = tiles
                .map(function(tile) {
                    return tile.score;
                })
                .reduce(function(a, b) {
                    return a + b;
                });

            // check for bingo all letters being used
            if (tiles.length === rules.BINGO) {
                score = score + rules.BINGO_SCORE;
            }
            return score;
        }

        // gets the total score from a given word
        /**
         *
         * @param word
         * @returns {number|*}
         * @private
         */
        function _getScoreFromWord(word) {

            // get the initial score from the service
            var score = ScrabbleService.getWordScore(word);

            // check if the maximum number of letters has been used
            if (word.length === rules.BINGO) {
                score = score + rules.BINGO_SCORE;
                $log.info('BINGO!');
            }

            return score;
        }

        /**
         * @description Evaluates if a word exists in the Dictionary
         * @name _wordExists
         * @methodOf game.GameService
         * @param {string} word The word to look up
         * @returns {boolean} True if the supplied word exits
         * @private
         */
        function _wordExists(word) {
            if (typeof word === Array) {
                $log.info('Whoops I`m an array!');
                return false;
            }
            return service.wordList.indexOf(word) >= 0;
        }
    }

})();
<commit_msg>Add TODO, fix typos, improve format<commit_after>(function() {
    'use strict';

    /**
     * @name game.GameService
     * @ngdoc service
     * @description A Service for the Game Related operations
     * @requires scrabble.ScrabbleService
     */
    angular.module('GameService', ['ScrabbleService'])

    /**
     * @ngdoc object
     * @name game.GameService:rules
     * @param {number} BINGO THE number of tiles to use to achive the bingo score
     * @param {number} BINGO_SCORE The score when a player uses all seven tiles
     * @description
     *  A constant which holds 'Game values'
     *

     *  if (tiles.length === rules.BINGO) {
     *      score = score + rules.BINGO_SCORE;
     *  }
     *

     */
        .constant('rules', {
            'BINGO': 7,
            'MAX_TILES' : 7,
            'BINGO_SCORE': 50
        })

        .service('GameService', GameService);

    /**
     * @ngdoc method
     * @name GameService
     * @methodOf game.GameService
     * @description The constructor
     * @constructor
     */
    function GameService($log, $q, flux, rules, actions, StateMachineService, DictionaryService, ScrabbleService, WordFinderService, _) {

        $log.info('GameService');

        var service = {};

        service.letterBag = undefined;

        service.currentHand = undefined;

        service.isReady = false;

        service.init = _init;

        service.getLetterBag = _getLetterBag;

        service.getHand = _getHand;

        service.reset = _reset;

        service.start = _start;

        service.stop = _stop;

        service.showBestWord = _showBestWord;

        service.replay = _replay;

        service.updateUserSelection = _updateUserSelection;

        service.evaluateDisplayUserScore = _evaluateDisplayUserScore;

        service.evaluateDisplayTimer = _evaluateDisplayTimer;

        service.evaluateIsDraggable = _evaluateIsDraggable;

        return service;

        /**
         * @ngdoc method
         * @name init
         * @methodOf game.GameService
         * @description Makes operations to initialise Scrabble

         * • Sets up the letter bag

         * • Sets up the Dictionary

         * • Returns a Promise to ensure the Callee that the required items will be ready
         *

         * GameService.init()
         *     .then(function() {
         *         _setUp();
         *     }, function(error) {
         *         $log.error('Init -> Error: ', error);
         *     });
         *

         */
        function _init() {

            var deferred = $q.defer();

            _setUpLetterBag();

            DictionaryService.getDictionary()
                .then(function() {
                    service.wordList = DictionaryService.dictionary;
                    service.isReady = true;
                    deferred.resolve('OK');
                    StateMachineService.makeReady();

                    $log.info('STATE ', StateMachineService.current());
                }, function(error) {
                    deferred.reject('Error ' + error);
                });

            return deferred.promise;
        }

        // == GETTERS ========

        // a collection of Tiles
        function _getLetterBag() {
            return service.letterBag;
        }

        /**
         * @ngdoc method
         * @name setUpLetterBag
         * @methodOf game.GameService
         * @description
         * Creates the letter bag

         * Also, randomises the order of the letters

         * @private
         */
        function _setUpLetterBag() {
            service.letterBag = ScrabbleService.createLetterBag();
            // TODO Check this is actually randomising the correct Letter Bag
            ScrabbleService.shake();
        }

        /**
         * @ngdoc method
         * @methodOf game.GameService
         * @name getHand
         * @description
         * Removes a number of Tiles from the collection and returns them as an Array
         * @param {number} number The amount of Tiles to get
         * @returns {Array} an Array of Tiles
         * @private
         */
        function _getHand(number) {

            if(ScrabbleService.letterBagIsEmpty()) {
                // will have to set another State!
                // should have been spotted before!

                $log.info('getHand -> STATE ', StateMachineService.current());

                $log.info('GAME OVER!');

                return [];
            }
            number = number || rules.MAX_TILES;
            var hand = ScrabbleService.getHand(number);

            for (var i = 0, j = hand.length; i < j; i++) {
                flux.dispatch(actions.TILE_ADD, hand[i]);
            }
            service.currentHand = hand;
            return hand;
        }

        /**
         * @ngdoc method
         * @methodOf game.GameService
         * @name reset
         * @description
         * Performs a variety of actions to reset the game
         */
        function _reset() {

            $log.info('GET RESET!');
            StateMachineService.reset();
            $log.info('STATE ', StateMachineService.current());

            // clear the current hand
            // TODO might be un-required if we stick to the model?
            service.currentHand = undefined;

            // clear the user's score
            flux.dispatch(actions.SCORE_UPDATE, 0);

            // clear the bestWord
            flux.dispatch(actions.BESTWORD_SET, '');

            // clear the tiles
            flux.dispatch(actions.TILE_DELETE);

            // TODO perhaps call when this has been confirmed
            StateMachineService.initialise();

            StateMachineService.makeReady();
        }

        // starts the game by staring the timer
        function _start() {
            StateMachineService.play();

            flux.dispatch(actions.TIMER_START);
        }

        // pauses the game (for when the user wants to stop the timer)
        // or the Timer has run out of Time
        function _stop() {

            $log.info('GameService stop');

            StateMachineService.stop();

            flux.dispatch(actions.TIMER_STOP);
        }

        // TODO Rename as we have a side-effect
        /**
         * @name _showBestWord
         * @description
         * Finds the word with the highest score
         * @private
         */
        function _showBestWord() {

            if(ScrabbleService.letterBagIsEmpty()) {
                // TODO we need another State
                $log.info('GAME OVER!');
                StateMachineService.end();
            } else {
                // sets state
                StateMachineService.finish();
            }

            // guard
            if (!service.currentHand) {
                throw('GameService.showBestWord | no currentHand');
            }

            // get the possible collection of Words as an Array
            var result = _getResult(service.currentHand);

            // gets the Words with the highest score from the result
            var bestWords = ScrabbleService.findBestWord(result);

            // currently we just return the first highest scoring Word
            // or none if no word was found
            var bestWord = (bestWords.length) ? (bestWords[0]) : (null);

            // check for Bingo here if all letters being used
            if (bestWord && bestWord.word.length >= rules.BINGO) {
                (bestWord.score = bestWord.score + rules.BINGO_SCORE);
            }

            flux.dispatch(actions.BESTWORD_SET, bestWord);
        }

        /**
         * @ngdoc method
         * @name replay
         * @methodOf game.GameService
         * @description
         * Sets up the required functions to reset the game
         * @private
         */
        function _replay() {
            $log.info('GameService.replay!');

            // need to create a new letter bag and shuffle it

            _setUpLetterBag();

            $log.info('replay -> STATE ', StateMachineService.current());

            _reset();
        }

        /**
         * @ngdoc method
         * @name updateUserSelection
         * @methodOf game.GameService
         * @description
         * Performs the necessary operations once the user updates their selection of tiles
         * @param {array} tiles An Array of Tiles
         * @private
         */
        function _updateUserSelection(tiles) {
            if (!tiles || !tiles.length) {
                flux.dispatch(actions.SCORE_UPDATE, 0);
                return;
            }

            // Note: dictionary does not include one letter words
            var word = _getWord(tiles);

            // check is valid word and evaluate the score
            var score = _wordExists(word) ? _getScore(tiles) : 0;

            flux.dispatch(actions.SCORE_UPDATE, score);
        }

        // == BUSINESS LOGIC ========

        // Evaluates if the user's score field should be visible
        function _evaluateDisplayUserScore() {
            return (StateMachineService.current() === 'playing' || StateMachineService.current() === 'done' || StateMachineService.current().state === 'paused' || StateMachineService.current() === 'paused' );
        }

        // The timer should only be visible when the user is playing the game or has just stopped
        function _evaluateDisplayTimer() {
            return !(StateMachineService.current() === 'ready' || StateMachineService.current() === 'initial' || StateMachineService.current() === 'done');
        }

        // The tiles should onle be draggable when the user is playing the game
        function _evaluateIsDraggable() {
            return StateMachineService.current() === 'playing';
        }

        // == UTILITY FUNCTIONS ========

        // TODO differentiate between the this 'getResult()` and result from the user's set of tiles

        // TODO rename
        /**
         * @description
         * Returns all possible Words from the collection of letters

         * @name _getResult
         * @methodOf game.GameService
         * @param hand A set of Tiles
         * @returns {Array} The Words with the highest possible scores
         * @private
         */
        function _getResult(hand) {

            if (!hand || !hand.length) {
                $log.error('GameService.getResult - No Hand supplied!');
                return [];
            }

            if (!service.wordList || !service.wordList.length) {
                $log.error('GameService.getResult - Dictionary has not been instantiated!');
                return [];
            }

            var a = performance.now();

            var words =  WordFinderService.makeWordFinder(_getLetters(hand), service.wordList);

            var b = performance.now();

            $log.info('GameService.getResult() took ' + (b - a) + ' ms.');

            return  words;
        }

        // gets the word as a String from a given set of tiles
        function _getWord(tiles) {
            var word = tiles
                .map(function(tile) {
                    return tile.letter;
                })
                .reduce(function(a, b) {
                    return a + b;
                });
            return word;
        }

        /**
         * @description returns a collection of letter strings from the supplied set of tiles
         * @param tiles
         * @returns {Array}
         * @private
         */
        function _getLetters(tiles) {
            return tiles
                .map(function(tile) {
                    return tile.letter;
                });
        }

        // gets the total score from a given set of tiles
        function _getScore(tiles) {
            var score = tiles
                .map(function(tile) {
                    return tile.score;
                })
                .reduce(function(a, b) {
                    return a + b;
                });

            // check for bingo all letters being used
            if (tiles.length === rules.BINGO) {
                score = score + rules.BINGO_SCORE;
            }
            return score;
        }

        // gets the total score from a given word
        /**
         *
         * @param word
         * @returns {number|*}
         * @private
         */
        function _getScoreFromWord(word) {

            // get the initial score from the service
            var score = ScrabbleService.getWordScore(word);

            // check if the maximum number of letters has been used
            if (word.length === rules.BINGO) {
                score = score + rules.BINGO_SCORE;
                $log.info('BINGO!');
            }

            return score;
        }

        /**
         * @description Evaluates if a word exists in the Dictionary
         * @name _wordExists
         * @methodOf game.GameService
         * @param {string} word The word to look up
         * @returns {boolean} True if the supplied word exits
         * @private
         */
        function _wordExists(word) {
            if (typeof word === Array) {
                $log.info('Whoops I`m an array!');
                return false;
            }
            return service.wordList.indexOf(word) >= 0;
        }
    }

})();
<|endoftext|>


<commit_before>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    if (move === 'rock' || 'paper' || 'scissors') {
        return move;
    } else {
        getInput();
    }
}

function getComputerMove(move) {
    if (move === 'rock' || 'paper' || 'scissors') {
        return move;
    } else {
        randomPlay();
    }
}

function getWinner(playerMove,computerMove) {
    var winner;
    if (playerMove === computerMove){
        winner = 'tie';
    } else if (playerMove === 'rock') {
        if (computerMove === 'paper') {
            winner = 'computer';
    } else {
            winner = 'player';
        }
    } else {
        if (playerMove === 'paper') {
        if (computerMove === 'scissors') {
            winner = 'computer';
    } else {
            winner = 'player';
        }
    } else {
        if (playerMove === 'scissors') {
        if (computerMove === 'rock') {
            winner = 'computer';
        } else {
            winner = 'player';
        }
        }
    }
    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<commit_msg>updated code in both playToFive and simplified code in getWinner<commit_after>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    return move || getInput();
    }
function getComputerMove(move) {
    return move || randomPlay();
    }

function getWinner(playerMove,computerMove) {
    var winner;
    if (playerMove === 'rock' && computerMove === "scissors") {
        winner = 'player';
    } else if (playerMove === 'scissors' && computerMove === 'paper') {
        winner = 'player';
    } else if (playerMove === 'paper' && computerMove === 'rock') {
        winner = 'player';
    } else if (playerMove === computerMove) {
        winner = 'tie';
    } else {
        winner = 'computer';
    }
    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    while (playerWins < 5 && computerWins < 5) {
        var playerMove = getPlayerMove();
        var computerMove = getComputerMove();
        var winner = getWinner(playerMove, computerMove);
        console.log("Player played " + playerMove + " and Computer played " + computerMove + "!");
        if (winner === 'player') {
            console.log("You have won this round!");
            playerWins += 1;
        } else if (winner === 'computer') {
            console.log("The computer has won this round!");
            computerWins += 1;
        } else {
            console.log("That round was a tie!");
        }
        console.log("The score is Player: " + playerWins + " to Computer: " + computerWins + "!");
    }
    return [playerWins, computerWins];
}
playToFive();
playToFive();<|endoftext|>