1
0
choose/www/index.html
2020-04-11 18:00:44 -04:00

502 lines
13 KiB
HTML

<!doctype html>
<html manifest="choose.appcache">
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="shortcut icon" href="favicon.ico" />
<title>Choose app</title>
<style type="text/css">
body {
background: black;
color: white;
}
#canvas {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 6px;
width: 100%
height: calc(100%-6px);
z-index: 1;
border-bottom: 6px solid #1a8000;
}
#ruler {
position: absolute;
z-index: 0;
width: 1cm;
height: 1cm;
}
.intro {
position: absolute;
z-index: 0;
top: 50%;
transform: translate(0, -50%);
text-align: center;
font-size: 10vw;
text-transform: uppercase;
font-family: sans-serif;
color: white;
}
#about {
position: absolute;
z-index: 2;
bottom: 0;
right: 0;
font-size: 3vw;
text-decoration: none;
display: none;
}
.circle {
position: absolute;
border-radius: 50%;
z-index: 1;
}
</style>
</head>
<body>
<span id = "intro" class = "intro">All players place a finger on the screen</span>
<a id = "about" target = "_blank" href = "https://github.com/dperelman/choose">About Choose</a>
<canvas id = "canvas"></canvas>
<div id = "ruler"></div>
<script >
var touchInfos = {};
var complete = false;
var TOUCH_WAIT_TIME = 2000;
var TEXT_FADE_TIME = 750;
// Based on http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
var lastHue = Math.random();
var GOLDEN_RATIO_CONJUGATE = 0.618033988749895;
function nextColor() {
var h = lastHue;
h += GOLDEN_RATIO_CONJUGATE;
h %= 1;
lastHue = h;
return toHexColor(hsvToRgb(h, 0.5, 0.95));
}
// From http://snipplr.com/view.php?codeview&id=14590
/**
* HSV to RGB color conversion
*
* H runs from 0 to 360 degrees
* S and V run from 0 to 1
*
* Ported from the excellent java algorithm by Eugene Vishnevsky at:
* http://www.cs.rit.edu/~ncs/color/t_convert.html
*/
function hsvToRgb(h, s, v) {
var r, g, b;
var i;
var f, p, q, t;
// Make sure our arguments stay in-range
h = Math.max(0, Math.min(1, h));
s = Math.max(0, Math.min(1, s));
v = Math.max(0, Math.min(1, v));
if(s == 0) {
// Achromatic (grey)
r = g = b = v;
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
h *= 6; // sector 0 to 5
i = Math.floor(h);
f = h - i; // factorial part of h
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch(i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
default: // case 5:
r = v;
g = p;
b = q;
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function toHexColor(rgb) {
var res = '#';
for (var i = 0; i < 3; i++) {
res += ("0" + Math.round(rgb[i]).toString(16)).slice(-2);
}
return res;
}
function distance(x1, y1, x2, y2) {
var x = x1-x2;
var y = y1-y2;
return Math.sqrt(x*x + y*y);
}
// From http://stackoverflow.com/a/2450976
function shuffle(array) {
var currentIndex = array.length, 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;
}
function clearExpiredTouchInfos() {
var now = Date.now();
for(var tid in touchInfos) {
var touch = touchInfos[tid];
if(!touch.active && touch.expirationTime < now) {
document.body.removeChild(touchInfos[tid].div);
delete touchInfos[tid];
}
}
}
function areAllTouchInfosActive() {
for(var tid in touchInfos) {
var touch = touchInfos[tid];
if(!touch.active) {
return false;
}
}
return true;
}
function computeTouchInfoCharge(touch) {
var now = Date.now();
if(!touch.active) {
return Math.min(TOUCH_WAIT_TIME, Math.max(0, touch.expirationTime - now));
} else {
return Math.min(TOUCH_WAIT_TIME, now - touch.touchStartTime);
}
}
function touchInfoMaxCharge() {
var max = 0;
for(var tid in touchInfos) {
var touch = touchInfos[tid];
max = Math.max(max, computeTouchInfoCharge(touch));
}
return max;
}
function touchInfoMaxPairCharge() {
if(Object.keys(touchInfos).length < 2) {
return 0;
}
var charges = [];
for(var tid in touchInfos) {
var touch = touchInfos[tid];
var charge = computeTouchInfoCharge(touch);
charges.push(charge);
}
charges.sort(function(a,b) { return a-b; });
return charges[charges.length-2];
}
function touchInfoMinActiveTime() {
var max = 0;
for(var tid in touchInfos) {
var touch = touchInfos[tid];
if(!touch.active) {
return 0;
} else {
max = Math.max(max, touch.touchStartTime);
}
}
return Date.now() - max;
}
function checkComplete() {
now = Date.now();
if(!complete) {
clearExpiredTouchInfos();
if(Object.keys(touchInfos).length > 1
&& areAllTouchInfosActive()
&& touchInfoMinActiveTime() > TOUCH_WAIT_TIME) {
complete = true;
var players = [];
for (var id in touchInfos) {
players.push(id);
}
shuffle(players);
for (var i = 0; i < players.length; i++) {
touchInfos[players[i]].turnOrder = i + 1;
}
if(navigator.vibrate) {
navigator.vibrate(200);
}
}
}
}
document.body.addEventListener('touchmove', function(event) {
// prevent scrolling (from http://www.html5rocks.com/en/mobile/touch/ )
event.preventDefault();
if(!complete) {
for (var i = 0; i < event.changedTouches.length; i++) {
var touch = event.changedTouches[i];
var id = touch.identifier;
touchInfos[id].x = touch.pageX;
touchInfos[id].y = touch.pageY;
}
}
}, false);
document.body.addEventListener('touchstart', function(event) {
if(!complete) {
event.preventDefault();
clearExpiredTouchInfos();
var now = Date.now();
for (var i = 0; i < event.changedTouches.length; i++) {
var touch = event.changedTouches[i];
var id = touch.identifier;
var x = touch.pageX;
var y = touch.pageY;
var newTouch = undefined;
var foundNearTouch = false;
for (var tid in touchInfos) {
var ti = touchInfos[tid];
if(!foundNearTouch
&& !ti.active
&& distance(x, y, ti.x, ti.y) < MAX_TOUCH_RECOVERY_DISTANCE) {
foundNearTouch = true;
var newTouch = ti;
var charge = computeTouchInfoCharge(ti);
newTouch.touchStartTime = now - charge;
delete touchInfos[tid];
break;
}
}
if(!foundNearTouch) {
var newTouch = {};
newTouch.color = nextColor();
newTouch.touchStartTime = now;
newTouch.div = null;
}
newTouch.x = x;
newTouch.y = y;
newTouch.active = true;
newTouch.expirationTime = undefined;
touchInfos[id] = newTouch;
}
}
}, false);
document.body.addEventListener('touchend', function(event) {
if(!complete) {
event.preventDefault();
var now = Date.now();
for (var i = 0; i < event.changedTouches.length; i++) {
var id = event.changedTouches[i].identifier;
var touch = touchInfos[id];
var chargeTime = computeTouchInfoCharge(touch);
touch.active = false;
touch.expirationTime = now + chargeTime;
delete touchInfos[id];
touchInfos[id + '-expired-' + now] = touch;
}
}
}, false);
// From http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById('canvas');
// From http://stackoverflow.com/a/8876069
canvas.width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
canvas.height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - 6;
var context = canvas.getContext('2d');
var RADIUS = 0;
var ruler = document.getElementById('ruler');
var MAX_TOUCH_RECOVERY_DISTANCE = 0;
document.body.onload = function() {
var CM_IN_PX = document.defaultView.getComputedStyle(ruler).width.slice(0, -2);
ruler.style.display = 'none';
RADIUS = 1*CM_IN_PX;
MAX_TOUCH_RECOVERY_DISTANCE = 2*RADIUS;
}
function drawCircle(centerX, centerY, radius, lineWidth, color, fill) {
// From http://www.html5canvastutorials.com/tutorials/html5-canvas-circles/
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
if(fill) {
context.fillStyle = color;
context.fill();
}
context.lineWidth = 5;
context.strokeStyle = color;
context.stroke();
context.closePath();
}
var randomRotations = [];
for(var i = 0; i < 10; i++) {
randomRotations[i] = Math.PI * 2 * Math.random();
}
// From http://www.arungudelli.com/html5/html5-canvas-polygon/
function drawRegularPolygon(ctx, x, y, radius, sides) {
if (sides < 3) return;
ctx.save();
ctx.beginPath();
var a = ((Math.PI * 2)/sides);
var off = randomRotations[sides];
ctx.translate(x,y);
ctx.rotate(off);
ctx.moveTo(radius,0);
for (var i = 1; i < sides; i++) {
var theta = a*i;
ctx.lineTo(radius*Math.cos(theta),radius*Math.sin(theta));
}
ctx.closePath();
ctx.fill();
ctx.restore();
}
function render() {
checkComplete();
var currentTime = Date.now();
if(complete) {
context.clearRect(0, 0, canvas.width, canvas.height);
document.getElementById('about').style.display = 'inherit';
document.body.style.background = 'white';
canvas.style.border = 'white';
for (var id in touchInfos) {
var touch = touchInfos[id];
document.body.removeChild(touch.div);
var turn = touch.turnOrder;
var color = touch.color;
if(turn == 1) {
drawCircle(touch.x, touch.y, 1.5*RADIUS, '1vw', color, true);
} else if(turn == 2) {
drawCircle(touch.x, touch.y, 0.6*RADIUS, '1vw', color, true);
drawCircle(touch.x, touch.y, RADIUS, '1vw', color, false);
} else {
context.fillStyle = color;
context.strokeStyle = color;
drawRegularPolygon(context, touch.x, touch.y, RADIUS, turn);
}
if(turn == 1) {
context.font = 2*RADIUS + "px sans-serif";
} else {
context.font = RADIUS + "px sans-serif";
}
context.fillStyle = 'black';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText(turn, touch.x, touch.y);
}
} else {
var intro = document.getElementById('intro');
var pairMaxCharge = touchInfoMaxPairCharge();
if(pairMaxCharge == 0) {
intro.style.display = '';
intro.style.color = 'white';
document.body.style.background = 'black';
} else {
if(pairMaxCharge < TEXT_FADE_TIME) {
intro.style.display = '';
var brightness = 255 - Math.round(1.0 * pairMaxCharge / TEXT_FADE_TIME * 255.0);
intro.style.color = toHexColor([brightness, brightness, brightness]);
} else {
intro.style.display = 'none';
intro.style.color = 'black';
}
var progress = Math.max(0.0, 1.0 * (pairMaxCharge - TEXT_FADE_TIME)/TOUCH_WAIT_TIME);
var progressColor = toHexColor(hsvToRgb(0.3, 1, Math.pow(progress, 1.5)/2));
document.body.style.background = progressColor;
}
for (var id in touchInfos) {
var touch = touchInfos[id];
var charge = computeTouchInfoCharge(touch);
var radius = charge/TOUCH_WAIT_TIME * RADIUS;
var color = touch.color;
if(touch.div == null) {
var d = document.createElement('div');
d.className = 'circle';
d.style.background = color;
d.style.position = 'absolute';
document.body.appendChild(d);
touch.div = d;
}
touch.div.style.width = 2*radius + "px";
touch.div.style.height = 2*radius + "px";
touch.div.style.top = touch.y-radius + "px";
touch.div.style.left = touch.x-radius + "px";
}
}
return !complete;
}
(function animloop(){
if(render()) {
requestAnimFrame(animloop);
}
})();
if(navigator.vibrate) {
// Do a vibrate on page load to request vibration permissions in browsers
// that show a confirmation dialog for it.
navigator.vibrate(1);
}
</script>
</body>
</html>