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;
}
});