shiro
Version: 
Online quiz game engine, inspired by russian tv show 'What? Where? When?' (Million Dollar Mind Game).
235 lines (194 loc) • 7.21 kB
JavaScript
$.domReady(function()
{
  var primus        = new Primus()
    , formatSeconds = d3.format('02')
    , formatFrac    = d3.format('03')
    , isRotating    = false // board rotating flag
    , currentXY     = [0, 0]
    , rotatedXYZ    = [60, 0, 15]
    , boardPanel    = $('.board')
    , direction     = [0, 0]
    , bodyDim
    , initAngle
    ;
  var transformName = '';
  if (navigator.userAgent.match(/AppleWebKit/))
  {
    vP = '-webkit-';
  }
  // connect to the server
  primus.on('open', function primus_onOpen()
  {
    // say hello
    primus.write({ helo: 'game' });
  });
  // events come from data
  primus.on('data', function primus_onData(data)
  {
    // [game] welcome message
    if (data.game)
    {
      renderTeams(data.game.teams, data.game.questions);
    }
  });
  $('body').on('mousedown', function(e)
  {
    bodyDim = $('body').dim();
    isRotating = true;
    currentXY  = [e.pageX, e.pageY];
    initAngle  = getAngle(e.pageX, e.pageY);
    $('body').addClass('rotating');
  });
  $('body').on('mousemove', function(e)
  {
    var dY, angle;
    if (isRotating)
    {
      angle = getAngle(e.pageX, e.pageY);
      dY = e.pageY - currentXY[1];
      rotatedXYZ[0] -= Math.floor((dY / bodyDim.height * 100) * 0.9);
      currentXY[1] = e.pageY;
      rotatedXYZ[2] += angle - initAngle;
      initAngle = angle;
      boardPanel.css(vP+'transform', 'translateX(-50%) translateY(-50%) rotateX('+rotatedXYZ[0]+'deg) rotateY('+rotatedXYZ[1]+'deg) rotateZ('+rotatedXYZ[2]+'deg)');
    }
  });
  $('body').on('mouseup', function()
  {
    isRotating = false;
    currentXY  = [0, 0];
    $('body').removeClass('rotating');
  });
  function renderTeams(teams, questions)
  {
    // pre
    boardPanel.css('min-width', (questions.length*60)+'px');
    boardPanel.css('min-height', (teams.length*60)+'px');
    var board
      , team
      , round
      , minCellSize  = 26
      , boardDim     = boardPanel.dim()
      , cellWidth    = (100/questions.length)
      , cellHeight   = (100/teams.length)
      , shortSide    = Math.floor(Math.min(boardDim.width/questions.length, boardDim.height/teams.length))
      , cubeSide     = Math.max(minCellSize, shortSide - (shortSide % 50) - 50) // min side - 20px
      , cubeStep     = Math.max(1, Math.floor(cubeSide / 20))
      , current      = {}
      , pseudoStyles = []
      , stylebox
      ;
    // check for custom styles
    if (!stylebox)
    {
      stylebox = $('<style></style>').appendTo('head');
    }
    teams.sort(sortTeams);
    board = d3.select(boardPanel[0]);
    // add content dependent styles
    pseudoStyles.push('.team:before, .team:after { line-height: '+Math.max(minCellSize, Math.floor(boardDim.height/100*cellHeight)-20)+'px; }');
    // team
    team = board.selectAll('.team')
      .data(teams, function(d){ return d.login; })
      .order()
      .attr('class', function(d){ return 'team_'+d.login; })
      .classed('team', true)
      .style('height', cellHeight+'%')
      .attr('data-name', function(d){ return d.name; })
      .attr('data-points', function(d)
      {
        var frac = d.time_bonus && d.points ? Math.round(d.time_bonus / ((d.points - (d.adjustment || 0)) * 60000) * 1000) : 0;
        return d.points + '.' + formatFrac(frac);
      })
      ;
    team.enter().append('div')
      .order()
      .style('height', cellHeight+'%')
      .attr('class', function(d){ return 'team_'+d.login; })
      .classed('team', true)
      .attr('data-name', function(d){ return d.name; })
      .attr('data-points', function(d)
      {
        var frac = d.time_bonus && d.points ? Math.round(d.time_bonus / ((d.points - (d.adjustment || 0)) * 60000) * 1000) : 0;
        return d.points + '.' + formatFrac(frac);
      })
      ;
    team.exit()
      .remove()
      ;
    // round
    round = team.selectAll('.round')
      .data(function(d){ return $.values($.map(questions, function(q){ return d.answers[q.index] ? $.merge(d.answers[q.index], {id: q.index, played: q.played, team: d.login}) : {id: q.index, played: false, team: d.login}; })); })
      .order()
      .style('width', cellWidth+'%')
      .attr('class', function(d){ return 'round_'+d.id; })
      .classed('round', true)
      .classed('played', function(d){ return !!d.played; })
      .attr('data-round', function(d){ return d.id; })
      ;
    round.enter().append('div')
      .order()
      .style('width', cellWidth+'%')
      .attr('class', function(d){ return 'round_'+d.id; })
      .classed('round', true)
      .classed('played', function(d){ return !!d.played; })
      .attr('data-round', function(d){ return d.id; })
      .html(function(d)
      {
        var tall      = d.time ? cubeStep*(d.time[0]) : 0
          , outStyles = []
          , inStyles  = []
          ;
        outStyles.push('width: '+cubeSide+'px');
        outStyles.push('height: '+cubeSide+'px');
        outStyles.push('margin: -'+Math.floor(cubeSide/2)+'px 0px 0px -'+Math.floor(cubeSide/2)+'px');
        outStyles.push('background-color: '+(d.correct === null ? 'rgba(221, 221, 221, 0.5)' : (d.correct ? 'rgba(51, 221, 51, 0.5)' : 'rgba(221, 51, 51, 0.5)')));
        outStyles.push('outline: 1px solid '+(d.correct === null ? '#cccccc' : (d.correct ? 'rgba(51, 221, 51, 0.9)' : 'rgba(221, 51, 51, 0.6)')));
        inStyles.push('line-height: '+cubeSide+'px');
        inStyles.push('font-size: '+Math.floor(cubeSide/2)+'px');
        inStyles.push('color: '+(d.correct === null ? '#999999' : '#ffffff'));
        inStyles.push('-webkit-transform: rotateZ(90deg) translateZ('+tall+'px)');
        inStyles.push('transform: rotateZ(90deg) translateZ('+tall+'px)');
        pseudoStyles.push('.team_'+d.team+' .round_'+d.id+' .cube:before { left: -'+Math.ceil(tall/2)+'px; width: '+tall+'px; } ');
        pseudoStyles.push('.team_'+d.team+' .round_'+d.id+' .cube:after { right: -'+Math.ceil(tall/2)+'px; width: '+tall+'px; } ');
        pseudoStyles.push('.team_'+d.team+' .round_'+d.id+' .cube>i:before { left: -'+Math.ceil(tall/2)+'px; width: '+tall+'px; } ');
        pseudoStyles.push('.team_'+d.team+' .round_'+d.id+' .cube>i:after { right: -'+Math.ceil(tall/2)+'px; width: '+tall+'px; } ');
        return d.time ? '<span style="'+outStyles.join('; ')+';" class="cube"><i style="'+inStyles.join('; ')+';">:'+formatSeconds(d.time[0])+'</i></span>' : '';
      })
      ;
    round.exit()
      .remove()
      ;
    // add styles for pseudo elements
    stylebox.html(pseudoStyles.join('\n'));
    // done
    boardPanel.removeClass('loading');
  }
  function sortTeams(a, b)
  {
    var comp = 0;
    // check points first
    if ((comp = b.points - a.points) == 0)
    {
      // check time_bonus
      if ((comp = b.time_bonus - a.time_bonus) == 0)
      {
        // check names
        comp = (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0));
      }
    }
    return comp;
  }
  // deps: bodyDim
  function getAngle(x, y)
  {
    var centerPoint
      , dX
      , dY
      ;
    centerPoint = [Math.floor(bodyDim.width/2), Math.floor(bodyDim.height/2)];
    dX = centerPoint[0] - x;
    dY = centerPoint[1] - y;
    return Math.atan2(dY, dX) / Math.PI * 180;
  }
});