Skip to content

Instantly share code, notes, and snippets.

@ssfang
Created April 4, 2019 13:53
Show Gist options
  • Save ssfang/48c53be926f0ed6bd597062897ee3ec2 to your computer and use it in GitHub Desktop.
Save ssfang/48c53be926f0ed6bd597062897ee3ec2 to your computer and use it in GitHub Desktop.
Colorful Pie Puzzle wheels // source https://jsbin.com/lazidox
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="wheels">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Colorful Pie Puzzle</title>
<style id="jsbin-css">
body {
margin: 0px;
padding: 0px;
width: 100%;
}
/* https://stackoverflow.com/questions/10614481/disable-double-tap-zoom-option-in-browser-on-touch-devices */
.disable-dbl-tap-zoom, .mycanvas {
touch-action: manipulation;
}
</style>
</head>
<body>
<strong>彩盘转转转 Colorful Pies Puzzle</strong>
<p>目标:转动外围彩盘使得颜色和中间彩盘的一致。</p>
<p>Goal: turn surrounding pies to match with the center pie.</p>
<canvas id="mycanvas" class="mycanvas" width="360" height="400"></canvas>
<script>
var mySearchParams = new URLSearchParams(window.location.search);
var spName = mySearchParams.get('name');
if(spName){
var helloElement = document.createElement('p');
helloElement.textContent = 'Hello ' + spName + '!';
document.body.appendChild(helloElement);
}
console.log(spName);
</script>
<script id="jsbin-javascript">
try{ top.document.getElementById('codefund_ad').remove(); } catch(e){ console.log(e); }
/**
* @param {string} html Text that contains text and tags to be converted to a document fragment. e.g. '<div>x</div><span>y</span><br/>'
* @return {DocumentFragment} a minimal document object that has no parent (a lightweight version of `Document`)
* @see https://developer.mozilla.org/en-US/docs/Web/API/Range/selectNodeContents
* @see https://developer.mozilla.org/en-US/docs/Web/API/range/createContextualFragment
* @example document.body.appendChild($j('<div>x</div><span>y</span><br/>'));
* @since IE11
*/
function $j(html) {
/// Way 1
//var temp = document.createElement('template');
//temp.innerHTML = html;
//return temp.content;
/// Way 2
// var range = document.createRange();
// Set body as context node or whatever context the fragment is to be evaluated in.
// range.selectNodeContents(document.body); // range.selectNode(document.body);
// return range.createContextualFragment(html);
return document.createRange().createContextualFragment(html);
}
/**
* @typedef Wheel
* @type {string[] | {x: number, y: number, radius: number, rotate: number, pointer: number, direction: number, originAngle: number}}
* @property {number} x
* @property {number} y
* @property {number} radius
* @property {number} rotate
* @property {number} direction
* @property {number} originAngle the starting angle to draw this wheel
* @property {number|null} pointer The array index of the segment sector pointed to the center
* @property {string} name
*/
/** @type {Wheel} init each array clockwise */
var centerWheel = ['red', 'green', 'gray', 'gold', 'pink', 'purple']; // Idler wheel / driven wheel
/**
* @type {Wheel[]} Each of the top points of the 4 smaller stars are rotated such that they point towards the center point of the larger star.
* init each array counterclockwise and these wheels rotate around their center clockwise
* @see https://en.wikipedia.org/wiki/Regular_polygon#Regular_convex_polygons
*/
var wheels = [ //Drive wheels
['red', 'gold', 'red', 'green', 'pink', 'purple'],
['gray', 'gold', 'gray', 'red', 'pink', 'purple'],
['gray', 'gray', 'green', 'gray', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'purple', 'purple']
];
//centerWheel.length = 4;
//wheels.length = 4;
//wheels.forEach(function (it){ it.length=4; });
/**
* Draw an isosceles triangle as an arrowhead
```
|\
+--------------------------+ \
| > end (locx, locy)
+--------------------------+ /
|/
|<->| sizey
```
* @param {CanvasRenderingContext2D} ctx
* @param {number} locx The x axis of the coordinate for the end of the arrow
* @param {number} locy The y axis of the coordinate for the end of the arrow
* @param {number} angle
* @param {number} sizex The remaining/base/hem side's length, not the two equal sides (legs).
* @param {number} sizey The Height, i.e. the distance from top to bottom. Or at right angles
* from any base to the furthest corner.
* @see https://stackoverflow.com/questions/6576827/html-canvas-draw-curved-arrows#6577443
* @see https://github.com/frogcat/canvas-arrow
* @see https://en.wikipedia.org/wiki/Arrow#Arrowhead
* @see https://en.wikipedia.org/wiki/Isosceles_triangle
*/
function drawArrowhead(ctx, locx, locy, angle, sizex, sizey) {
var hx = sizex / 2;
var hy = sizey / 2;
ctx.save();
ctx.translate(locx, locy);
ctx.rotate(angle);
ctx.translate(-hx, -hy);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, sizey);
ctx.lineTo(sizex, hy);
ctx.closePath();
ctx.fill();
ctx.restore();
}
/**
*
* @param {CanvasRenderingContext2D} ctx
* @param {string[]&{x:number, y:number, radius:number}} wheel
* @param {string | CanvasGradient | CanvasPattern} strokeStyle
* @param {number} directionAngle The angle of the line of the center wheel and this wheel's centers,
* measured clockwise around the center wheel from the positive x-axis and expressed in radians.
*/
function drawPie(ctx, wheel, strokeStyle) {
if(wheel.action){
wheel.action.act(ctx.deltaTime, wheel);
}
const lineWidth = 1;
const innerRadius = 15;
const innerStartRadius = innerRadius - lineWidth / 2;
ctx.lineWidth = lineWidth;
var slices = wheel.length;
var sliceAngle = Math.PI / slices;
//https://en.wikipedia.org/wiki/Angular_distance //
var spaceAngle = sliceAngle; //the angle between the end side of the slice and the start side of the next adjacent slice.
for (var idxSlice = 0, angle = wheel.directionAngle + Math.PI - sliceAngle / 2 + wheel.rotate; idxSlice < slices; ++idxSlice) {
// Begin a path as the segment consists of an arc and 2 lines.
ctx.beginPath();
if (null == wheel.direction) {
ctx.moveTo(wheel.x, wheel.y); // ctx.moveTo(centerX, centerY);
} else {
// Work out the x and y values for the starting point of the segment which is at its starting angle
// but out from the center point of the wheel by the value of the innerRadius. Some correction for line width is needed.
// Now move here relative to the center point of the wheel.
ctx.moveTo(wheel.x + Math.cos(angle) * innerStartRadius, wheel.y + Math.sin(angle) * innerStartRadius);
}
// Draw the outer arc of the segment clockwise in direction -->
// `startAngle`: The angle at which the arc starts, measured clockwise from the positive x-axis and expressed in radians.
ctx.arc(wheel.x, wheel.y, wheel.radius, angle, angle + sliceAngle);
if (null != wheel.direction) {
// Draw another arc, this time anticlockwise <-- at the innerRadius between the end angle and the start angle.
// Canvas will draw a connecting line from the end of the outer arc to the beginning of the inner arc completing the shape.
ctx.arc(wheel.x, wheel.y, innerRadius, angle + sliceAngle, angle, true);
} else {
// If no inner radius then we draw a line back to the center of the wheel.
// Draw a line back to the center of the wheel. `closePath` automatically connects the shape's first and last points.
//ctx.closePath(); ctx.lineTo(centerX, centerY);
}
ctx.closePath();
ctx.fillStyle = wheel[idxSlice];
// Fill and stroke the segment.
if (idxSlice === wheel.pointer) {
var shadowColor = ctx.shadowColor;
//https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur
//https://jsbin.com/gavepub/edit?html,js,output
ctx.shadowBlur = 25;
ctx.shadowColor = 'blue';
//Shadows are only drawn if the shadowColor property is set to a non-transparent value. One of the
//shadowBlur, shadowOffsetX, or shadowOffsetY properties must be non-zero, as well.
//ctx.shadowOffsetX = 0; // The default value is 0 (no vertical offset).
//ctx.shadowOffsetY = 0; // The default value is 0 (no vertical offset).
ctx.fill();
ctx.strokeStyle = 'blue';
ctx.stroke();
ctx.shadowBlur = 0; //The default value is 0.
ctx.shadowColor = shadowColor; //The default value is fully-transparent black 'rgba(0, 0, 0, 0)'
} else {
ctx.fill();
ctx.strokeStyle = strokeStyle;
ctx.stroke();
}
// only for debug test
// https://stackoverflow.com/questions/18092753/change-font-size-of-canvas-without-knowing-font-family
ctx.font = ctx.font.replace(/\d+px/, '24px');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = "white";
// var textMetrics = ctx.measureText(this.x); // TextMetrics object
// ctx.fillText(wheel.x, wheel.y - textMetrics.width / 2, oy);
var halfwayAngle = angle + sliceAngle / 2; // symmetry axis
var textRadius = 0.8 * wheel.radius;
ctx.fillText(idxSlice, wheel.x + textRadius * Math.cos(halfwayAngle), wheel.y + textRadius * Math.sin(halfwayAngle));
angle -= sliceAngle + spaceAngle;
}
// draw Circle border
ctx.beginPath();
ctx.arc(wheel.x, wheel.y, wheel.radius, 0, Math.PI * 2);
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = 2;
ctx.stroke();
if (null != wheel.direction) {
// draw the rotation direction indicator
ctx.beginPath();
ctx.fillStyle = 'rgba(55, 217, 56,1)';
ctx.strokeStyle = 'rgba(55, 217, 56,1)';
var endAngle = wheel.directionAngle + Math.PI / 10;
ctx.arc(wheel.x, wheel.y, wheel.radius + 8, wheel.directionAngle - Math.PI / 10, endAngle);
ctx.stroke();
var lastX = wheel.x + (wheel.radius + 8) * Math.cos(endAngle);
var lastY = wheel.y + (wheel.radius + 8) * Math.sin(endAngle);
// var ang = findAngle(sx, sy, ex, ey);
// ctx.fillRect(lastX, lastY, 4, 4);
drawArrowhead(ctx, lastX, lastY, Math.PI / 2 + endAngle, 12, 12);
}
// only for debug test
if (null != wheel.name) {
// https://stackoverflow.com/questions/18092753/change-font-size-of-canvas-without-knowing-font-family
ctx.font = ctx.font.replace(/\d+px/, '24px');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = "cyan";
// var textMetrics = ctx.measureText(this.x); // TextMetrics object
// ctx.fillText(wheel.x, wheel.y - textMetrics.width / 2, oy);
ctx.fillText(wheel.name, wheel.x, wheel.y);
}
// if (null != wheel.pointer) {
// ctx.fillStyle = 'blue';
// ctx.fillText(wheel.pointer, wheel.x + 38, wheel.y);
// }
}
function startRotateByAction(wheel, delta) {
var action;
if(wheel.action){
action = wheel.action;
action.finish(wheel);
} else{
action = {};
wheel.action = action;
}
action.time = 0;
action.duration = 500;
action.start = wheel.rotate;
action.delta = delta;
action.finish = function (wheel){
const radian360 = 2 * Math.PI;
const endAngle = this.start + this.delta;
//https://stackoverflow.com/questions/1628386/normalise-orientation-between-0-and-360
wheel.rotate = (endAngle % radian360) + (endAngle < 0 ? radian360 : 0);
};
action.act = function(interval, wheel){
if(this.time < this.duration){
this.time += interval;
wheel.rotate = this.start + this.time/this.duration * this.delta;
} else {
this.finish(wheel);
wheel.action = null;
}
};
}
function startLoop() {
var steps = 0; // counts the rotation numbers, number of revolution
/** @type {HTMLCanvasElement} */
var canvas = document.getElementById('mycanvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.className = 'mycanvas';
document.body.appendChild(canvas);
}
canvas.addEventListener('click', onDriveWheelClicked);
//canvas.addEventListener('mozfullscreenchange', onFullScreenChange);
//canvas.addEventListener('webkitfullscreenchange', onFullScreenChange);
//requestFullScreen(canvas); called when event is triggered
//https://stackoverflow.com/questions/13157586/full-screen-canvas-on-mobile-devices
function requestFullScreen(el) {
if (el.webkitRequestFullScreen) {
// Google Chrome Version 73.0.3683.86 (Official Build) (32-bit)
//Failed to execute 'requestFullscreen' on 'Element': API can only be initiated by a user gesture.
el.webkitRequestFullScreen();
} else {
el.mozRequestFullScreen();
}
}
var context = canvas.getContext('2d');
var count = wheels.length;
var centerAngle = 2 * Math.PI / count;
var cx = canvas.width / 2,
cy = canvas.height / 2;
var radius = 120;
// calculate the starting angle to make it symmetric with respect to the y-axis
//But draw piles clockwise from the positive y axis (12 oclock).
angle = count % 2 ? Math.PI / -2 : Math.PI / -2 - centerAngle / 2; // is odd?
centerWheel.rotate = 0;
centerWheel.x = cx;
centerWheel.y = cy;
centerWheel.radius = radius / 2;
centerWheel.directionAngle = Math.PI + angle;
// https://www.mathsisfun.com/geometry/regular-polygons.html
for (var idxPie = 0; idxPie < count; ++idxPie, angle += centerAngle) {
var wheel = wheels[idxPie];
//angle = startAngle + (idx * centerAngle);
var vx = cx + radius * Math.cos(angle);
var vy = cy + radius * Math.sin(angle);
wheel.direction = idxPie;
wheel.name = String.fromCharCode(65+idxPie);
wheel.x = vx;
wheel.y = vy;
wheel.radius = radius / 2;
wheel.rotate = 0; //Math.PI + idxPie * centerAngle. clamp the angle to the range 0 to 1 in turns
wheel.directionAngle = angle;
drawPie(context, wheel, 'black', angle);
}
drawPie(context, centerWheel, 'black');
requestAnimationFrame(animate);
/**
* @param {MouseEvent} me
* @see https://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
*/
function onDriveWheelClicked(me) {
// Modern browser's now handle this for you. Chrome, IE9, and Firefox support the offsetX/Y
// like this, passing in the event from the click handler.
var x = me.offsetX,
y = me.offsetY;
// if (me.pageX || me.pageY) {
// x = me.pageX;
// y = me.pageY;
// } else {
// x = me.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
// y = me.clientY + document.body.scrollTop + document.documentElement.scrollTop;
// }
// var canvasElement = me.currentTarget;
// x -= canvasElement.offsetLeft;
// y -= canvasElement.offsetTop;
const radian360 = 2 * Math.PI;
const pieCount = wheels.length;
const deltaAngle = radian360 / pieCount;
for (var idxPie = 0; idxPie < pieCount; ++idxPie, angle += centerAngle) {
var wheel = wheels[idxPie];
if (Math.pow(x - wheel.x, 2) + Math.pow(y - wheel.y, 2) <= Math.pow(wheel.radius, 2)) {
//var rotate = wheel.rotate + deltaAngle; //clockwise
//wheel.rotate = rotate % radian360; // clamp the angle to the range 0 to `2 * Math.PI in radians
// ClampAngle Limit the angle to the range 0 to 1 in turns
//rotate = centerWheel.rotate - deltaAngle; // Counterclockwise
//https://stackoverflow.com/questions/1628386/normalise-orientation-between-0-and-360
//centerWheel.rotate = (rotate % radian360) + (rotate < 0 ? radian360 : 0);
startRotateByAction(wheel, deltaAngle);
startRotateByAction(centerWheel, -deltaAngle);
++steps;
}
}
var event = me || window.event;
event.preventDefault();
event.stopPropagation();
}
//https://jlongster.com/Going-Fullscreen-with-Canvas
function onFullScreenChange(evt) {
var canvas = evt.currentTarget;
if (document.mozFullScreen || document.webkitIsFullScreen) {
var rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
} else {
canvas.width = 500;
canvas.height = 400;
}
}
var lastTime;
function animate(timestamp) {
context.deltaTime = lastTime ? timestamp - lastTime : 0;
lastTime = timestamp;
//The clearRect() method sets the pixels in a rectangular area to transparent black (rgba(0,0,0,0)).
// context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#E3E8E6';
context.fillRect(0, 0, canvas.width, canvas.height);
drawPie(context, centerWheel, 'black');
//var logs = [];
var score = 0;
var count = centerWheel.length;
var centralAngle = 2 * Math.PI / count;
var angle = count % 2 ? Math.floor(count / 2) * centralAngle - Math.PI / 2 : centralAngle / 2; // is odd?
for (var idx = 0; idx < count; ++idx) {
// `center.rotate` is a normalized angle clamped in [0, 2 * Math.PI), so `index` is an integer in the range [0, count).
var index = Math.floor((centerWheel.rotate) / centralAngle); // direction0's (the first direction) index in center colors;
index -= idx; // the idx-th direction's index in center colors;
if (index < 0) {
index += count;
}
var wheel = wheels[idx];
// Retrieves the index of the slice pointed to the center wheel
var pointer = Math.floor(wheel.rotate / centralAngle); //The array index of the segment sector pointed to the center
var matched = centerWheel[index] == wheel[pointer];
wheel.pointer = matched ? pointer : null;
if (matched) {
++score;
}
//logs.push(`direction_${idx}: ${index}=${centerWheel[index]} vs ${pointer}=${wheel[pointer]}`);
drawPie(context, wheel, 'black', angle);
angle += centralAngle;
}
//console.log(logs.join(', '));
//https://www.tombraiderchronicles.com/underworld/walkthrough/level02.html#pastthedoorpuzzle
if (count === score) {
context.font = context.font.replace(/\d+px/, '56px');
context.textAlign = 'center';
context.textBaseline = 'top';
context.fillStyle = 'rgba(55, 217, 56,1)';
context.fillText('You win!', canvas.width / 2, 1);
}
//draw the number of steps
context.font = context.font.replace(/\d+px/, '28px');
context.textAlign = 'center';
context.textBaseline = 'bottom';
context.fillStyle = 'rgba(55, 217, 56,1)';
context.fillText('Steps: ' + steps, canvas.width / 2, canvas.height - 1);
requestAnimationFrame(animate);
}
}
startLoop();
//initWheels(wheels);
//console.log( wheels );
//console.log(getIndex(wheels[0]));
/**
* https://developer.mozilla.org/en-US/docs/Web/CSS/conic-gradient#Customizing_Gradients
* @see https://github.com/leaverou/conic-gradient
*/
//element.style.maxWidth = "100px";
//transform: rotate(45deg);
//ELEMENT.style.setProperty('--element-width', NEW_VALUE);
//pieChart(wheel);
// Center X and center Y are the coordinates of the center point of the polygon.
// Set initially to 550, 550. Note that the y coordinate is positive downwards,
// to conform to the convention in most computer software. Positive x is to the right.
// Start angle (degrees): Start angle is the position of the first vertex.
// This angle is in degrees and is the angle starting at 3 o'clock going counter
// clockwise. So for example if you want the first vertex to be at 12 o'clock, set
// this to 90. Set initially to blank (auto).
// If you leave this blank it will be set automatically: If the number of sides is
// odd, (e.g. a pentagon), the first vertex will be at 12 o'clock. If even, e.g. an
// octagon, the top and bottom sides will be horizontal on the page.
/**
* This calculator takes the parameters of a regular polygon and calculates its coordinates.
* It produces both the coordinates of the vertices and the coordinates of the line segments
* making up the sides of the polygon.
*
* Note that the y coordinates are positive downwards, to conform to the convention in most
* computer software. Positive x is to the right.
*
* @param {number} nsides The number of sides. Must be greater than 2. Set initially to 5.
* @param {number} radius The distance from the center to a vertex. Set initially to 100.
* @param {number} startAngle
*/
function calculatePolygonCoordinates(nsides, radius, startAngle, cx, cy) {
// collect inputs
// if(isNaN(radius) || radius<1) { alert("Radius must be a number greater than 0"); return; }
// if(isNaN(n) || n<3) { alert("Number of sides must be a number greater than 2"); return; }
// if(isNaN(startAng)) { alert("Starting angle must be a number"); return; }
var centerAngle = 2 * Math.PI / nsides;
//calculate the default start angle
if (!startAngle) { //none supplied
if (isOdd(nsides))
startAngle = Math.PI / 2; //12 oclock
else
startAngle = Math.PI / 2 - centerAngle / 2;
}
var angle = startAngle,
vertex = []; // new Array();
for (var idx = 0; idx < nsides; idx++, angle += centerAngle) {
//angle = startAngle + (idx * centerAngle);
var vx = Math.round(cx + radius * Math.cos(angle));
var vy = Math.round(cy - radius * Math.sin(angle));
vertex.push({
x: vx,
y: vy
});
}
function isOdd(n) {
return (n % 2 == 1);
}
function toRadians(degs) {
return Math.PI * degs / 180;
}
return vertex;
}
</script>
<script id="jsbin-source-css" type="text/css">body {
margin: 0px;
padding: 0px;
width: 100%;
}
/* https://stackoverflow.com/questions/10614481/disable-double-tap-zoom-option-in-browser-on-touch-devices */
.disable-dbl-tap-zoom, .mycanvas {
touch-action: manipulation;
}</script>
<script id="jsbin-source-javascript" type="text/javascript">try{ top.document.getElementById('codefund_ad').remove(); } catch(e){ console.log(e); }
/**
* @param {string} html Text that contains text and tags to be converted to a document fragment. e.g. '<div>x</div><span>y</span><br/>'
* @return {DocumentFragment} a minimal document object that has no parent (a lightweight version of `Document`)
* @see https://developer.mozilla.org/en-US/docs/Web/API/Range/selectNodeContents
* @see https://developer.mozilla.org/en-US/docs/Web/API/range/createContextualFragment
* @example document.body.appendChild($j('<div>x</div><span>y</span><br/>'));
* @since IE11
*/
function $j(html) {
/// Way 1
//var temp = document.createElement('template');
//temp.innerHTML = html;
//return temp.content;
/// Way 2
// var range = document.createRange();
// Set body as context node or whatever context the fragment is to be evaluated in.
// range.selectNodeContents(document.body); // range.selectNode(document.body);
// return range.createContextualFragment(html);
return document.createRange().createContextualFragment(html);
}
/**
* @typedef Wheel
* @type {string[] | {x: number, y: number, radius: number, rotate: number, pointer: number, direction: number, originAngle: number}}
* @property {number} x
* @property {number} y
* @property {number} radius
* @property {number} rotate
* @property {number} direction
* @property {number} originAngle the starting angle to draw this wheel
* @property {number|null} pointer The array index of the segment sector pointed to the center
* @property {string} name
*/
/** @type {Wheel} init each array clockwise */
var centerWheel = ['red', 'green', 'gray', 'gold', 'pink', 'purple']; // Idler wheel / driven wheel
/**
* @type {Wheel[]} Each of the top points of the 4 smaller stars are rotated such that they point towards the center point of the larger star.
* init each array counterclockwise and these wheels rotate around their center clockwise
* @see https://en.wikipedia.org/wiki/Regular_polygon#Regular_convex_polygons
*/
var wheels = [ //Drive wheels
['red', 'gold', 'red', 'green', 'pink', 'purple'],
['gray', 'gold', 'gray', 'red', 'pink', 'purple'],
['gray', 'gray', 'green', 'gray', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'purple', 'purple']
];
//centerWheel.length = 4;
//wheels.length = 4;
//wheels.forEach(function (it){ it.length=4; });
/**
* Draw an isosceles triangle as an arrowhead
```
|\
+--------------------------+ \
| > end (locx, locy)
+--------------------------+ /
|/
|<->| sizey
```
* @param {CanvasRenderingContext2D} ctx
* @param {number} locx The x axis of the coordinate for the end of the arrow
* @param {number} locy The y axis of the coordinate for the end of the arrow
* @param {number} angle
* @param {number} sizex The remaining/base/hem side's length, not the two equal sides (legs).
* @param {number} sizey The Height, i.e. the distance from top to bottom. Or at right angles
* from any base to the furthest corner.
* @see https://stackoverflow.com/questions/6576827/html-canvas-draw-curved-arrows#6577443
* @see https://github.com/frogcat/canvas-arrow
* @see https://en.wikipedia.org/wiki/Arrow#Arrowhead
* @see https://en.wikipedia.org/wiki/Isosceles_triangle
*/
function drawArrowhead(ctx, locx, locy, angle, sizex, sizey) {
var hx = sizex / 2;
var hy = sizey / 2;
ctx.save();
ctx.translate(locx, locy);
ctx.rotate(angle);
ctx.translate(-hx, -hy);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, sizey);
ctx.lineTo(sizex, hy);
ctx.closePath();
ctx.fill();
ctx.restore();
}
/**
*
* @param {CanvasRenderingContext2D} ctx
* @param {string[]&{x:number, y:number, radius:number}} wheel
* @param {string | CanvasGradient | CanvasPattern} strokeStyle
* @param {number} directionAngle The angle of the line of the center wheel and this wheel's centers,
* measured clockwise around the center wheel from the positive x-axis and expressed in radians.
*/
function drawPie(ctx, wheel, strokeStyle) {
if(wheel.action){
wheel.action.act(ctx.deltaTime, wheel);
}
const lineWidth = 1;
const innerRadius = 15;
const innerStartRadius = innerRadius - lineWidth / 2;
ctx.lineWidth = lineWidth;
var slices = wheel.length;
var sliceAngle = Math.PI / slices;
//https://en.wikipedia.org/wiki/Angular_distance //
var spaceAngle = sliceAngle; //the angle between the end side of the slice and the start side of the next adjacent slice.
for (var idxSlice = 0, angle = wheel.directionAngle + Math.PI - sliceAngle / 2 + wheel.rotate; idxSlice < slices; ++idxSlice) {
// Begin a path as the segment consists of an arc and 2 lines.
ctx.beginPath();
if (null == wheel.direction) {
ctx.moveTo(wheel.x, wheel.y); // ctx.moveTo(centerX, centerY);
} else {
// Work out the x and y values for the starting point of the segment which is at its starting angle
// but out from the center point of the wheel by the value of the innerRadius. Some correction for line width is needed.
// Now move here relative to the center point of the wheel.
ctx.moveTo(wheel.x + Math.cos(angle) * innerStartRadius, wheel.y + Math.sin(angle) * innerStartRadius);
}
// Draw the outer arc of the segment clockwise in direction -->
// `startAngle`: The angle at which the arc starts, measured clockwise from the positive x-axis and expressed in radians.
ctx.arc(wheel.x, wheel.y, wheel.radius, angle, angle + sliceAngle);
if (null != wheel.direction) {
// Draw another arc, this time anticlockwise <-- at the innerRadius between the end angle and the start angle.
// Canvas will draw a connecting line from the end of the outer arc to the beginning of the inner arc completing the shape.
ctx.arc(wheel.x, wheel.y, innerRadius, angle + sliceAngle, angle, true);
} else {
// If no inner radius then we draw a line back to the center of the wheel.
// Draw a line back to the center of the wheel. `closePath` automatically connects the shape's first and last points.
//ctx.closePath(); ctx.lineTo(centerX, centerY);
}
ctx.closePath();
ctx.fillStyle = wheel[idxSlice];
// Fill and stroke the segment.
if (idxSlice === wheel.pointer) {
var shadowColor = ctx.shadowColor;
//https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur
//https://jsbin.com/gavepub/edit?html,js,output
ctx.shadowBlur = 25;
ctx.shadowColor = 'blue';
//Shadows are only drawn if the shadowColor property is set to a non-transparent value. One of the
//shadowBlur, shadowOffsetX, or shadowOffsetY properties must be non-zero, as well.
//ctx.shadowOffsetX = 0; // The default value is 0 (no vertical offset).
//ctx.shadowOffsetY = 0; // The default value is 0 (no vertical offset).
ctx.fill();
ctx.strokeStyle = 'blue';
ctx.stroke();
ctx.shadowBlur = 0; //The default value is 0.
ctx.shadowColor = shadowColor; //The default value is fully-transparent black 'rgba(0, 0, 0, 0)'
} else {
ctx.fill();
ctx.strokeStyle = strokeStyle;
ctx.stroke();
}
// only for debug test
// https://stackoverflow.com/questions/18092753/change-font-size-of-canvas-without-knowing-font-family
ctx.font = ctx.font.replace(/\d+px/, '24px');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = "white";
// var textMetrics = ctx.measureText(this.x); // TextMetrics object
// ctx.fillText(wheel.x, wheel.y - textMetrics.width / 2, oy);
var halfwayAngle = angle + sliceAngle / 2; // symmetry axis
var textRadius = 0.8 * wheel.radius;
ctx.fillText(idxSlice, wheel.x + textRadius * Math.cos(halfwayAngle), wheel.y + textRadius * Math.sin(halfwayAngle));
angle -= sliceAngle + spaceAngle;
}
// draw Circle border
ctx.beginPath();
ctx.arc(wheel.x, wheel.y, wheel.radius, 0, Math.PI * 2);
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = 2;
ctx.stroke();
if (null != wheel.direction) {
// draw the rotation direction indicator
ctx.beginPath();
ctx.fillStyle = 'rgba(55, 217, 56,1)';
ctx.strokeStyle = 'rgba(55, 217, 56,1)';
var endAngle = wheel.directionAngle + Math.PI / 10;
ctx.arc(wheel.x, wheel.y, wheel.radius + 8, wheel.directionAngle - Math.PI / 10, endAngle);
ctx.stroke();
var lastX = wheel.x + (wheel.radius + 8) * Math.cos(endAngle);
var lastY = wheel.y + (wheel.radius + 8) * Math.sin(endAngle);
// var ang = findAngle(sx, sy, ex, ey);
// ctx.fillRect(lastX, lastY, 4, 4);
drawArrowhead(ctx, lastX, lastY, Math.PI / 2 + endAngle, 12, 12);
}
// only for debug test
if (null != wheel.name) {
// https://stackoverflow.com/questions/18092753/change-font-size-of-canvas-without-knowing-font-family
ctx.font = ctx.font.replace(/\d+px/, '24px');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = "cyan";
// var textMetrics = ctx.measureText(this.x); // TextMetrics object
// ctx.fillText(wheel.x, wheel.y - textMetrics.width / 2, oy);
ctx.fillText(wheel.name, wheel.x, wheel.y);
}
// if (null != wheel.pointer) {
// ctx.fillStyle = 'blue';
// ctx.fillText(wheel.pointer, wheel.x + 38, wheel.y);
// }
}
function startRotateByAction(wheel, delta) {
var action;
if(wheel.action){
action = wheel.action;
action.finish(wheel);
} else{
action = {};
wheel.action = action;
}
action.time = 0;
action.duration = 500;
action.start = wheel.rotate;
action.delta = delta;
action.finish = function (wheel){
const radian360 = 2 * Math.PI;
const endAngle = this.start + this.delta;
//https://stackoverflow.com/questions/1628386/normalise-orientation-between-0-and-360
wheel.rotate = (endAngle % radian360) + (endAngle < 0 ? radian360 : 0);
};
action.act = function(interval, wheel){
if(this.time < this.duration){
this.time += interval;
wheel.rotate = this.start + this.time/this.duration * this.delta;
} else {
this.finish(wheel);
wheel.action = null;
}
};
}
function startLoop() {
var steps = 0; // counts the rotation numbers, number of revolution
/** @type {HTMLCanvasElement} */
var canvas = document.getElementById('mycanvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.className = 'mycanvas';
document.body.appendChild(canvas);
}
canvas.addEventListener('click', onDriveWheelClicked);
//canvas.addEventListener('mozfullscreenchange', onFullScreenChange);
//canvas.addEventListener('webkitfullscreenchange', onFullScreenChange);
//requestFullScreen(canvas); called when event is triggered
//https://stackoverflow.com/questions/13157586/full-screen-canvas-on-mobile-devices
function requestFullScreen(el) {
if (el.webkitRequestFullScreen) {
// Google Chrome Version 73.0.3683.86 (Official Build) (32-bit)
//Failed to execute 'requestFullscreen' on 'Element': API can only be initiated by a user gesture.
el.webkitRequestFullScreen();
} else {
el.mozRequestFullScreen();
}
}
var context = canvas.getContext('2d');
var count = wheels.length;
var centerAngle = 2 * Math.PI / count;
var cx = canvas.width / 2,
cy = canvas.height / 2;
var radius = 120;
// calculate the starting angle to make it symmetric with respect to the y-axis
//But draw piles clockwise from the positive y axis (12 oclock).
angle = count % 2 ? Math.PI / -2 : Math.PI / -2 - centerAngle / 2; // is odd?
centerWheel.rotate = 0;
centerWheel.x = cx;
centerWheel.y = cy;
centerWheel.radius = radius / 2;
centerWheel.directionAngle = Math.PI + angle;
// https://www.mathsisfun.com/geometry/regular-polygons.html
for (var idxPie = 0; idxPie < count; ++idxPie, angle += centerAngle) {
var wheel = wheels[idxPie];
//angle = startAngle + (idx * centerAngle);
var vx = cx + radius * Math.cos(angle);
var vy = cy + radius * Math.sin(angle);
wheel.direction = idxPie;
wheel.name = String.fromCharCode(65+idxPie);
wheel.x = vx;
wheel.y = vy;
wheel.radius = radius / 2;
wheel.rotate = 0; //Math.PI + idxPie * centerAngle. clamp the angle to the range 0 to 1 in turns
wheel.directionAngle = angle;
drawPie(context, wheel, 'black', angle);
}
drawPie(context, centerWheel, 'black');
requestAnimationFrame(animate);
/**
* @param {MouseEvent} me
* @see https://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
*/
function onDriveWheelClicked(me) {
// Modern browser's now handle this for you. Chrome, IE9, and Firefox support the offsetX/Y
// like this, passing in the event from the click handler.
var x = me.offsetX,
y = me.offsetY;
// if (me.pageX || me.pageY) {
// x = me.pageX;
// y = me.pageY;
// } else {
// x = me.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
// y = me.clientY + document.body.scrollTop + document.documentElement.scrollTop;
// }
// var canvasElement = me.currentTarget;
// x -= canvasElement.offsetLeft;
// y -= canvasElement.offsetTop;
const radian360 = 2 * Math.PI;
const pieCount = wheels.length;
const deltaAngle = radian360 / pieCount;
for (var idxPie = 0; idxPie < pieCount; ++idxPie, angle += centerAngle) {
var wheel = wheels[idxPie];
if (Math.pow(x - wheel.x, 2) + Math.pow(y - wheel.y, 2) <= Math.pow(wheel.radius, 2)) {
//var rotate = wheel.rotate + deltaAngle; //clockwise
//wheel.rotate = rotate % radian360; // clamp the angle to the range 0 to `2 * Math.PI in radians
// ClampAngle Limit the angle to the range 0 to 1 in turns
//rotate = centerWheel.rotate - deltaAngle; // Counterclockwise
//https://stackoverflow.com/questions/1628386/normalise-orientation-between-0-and-360
//centerWheel.rotate = (rotate % radian360) + (rotate < 0 ? radian360 : 0);
startRotateByAction(wheel, deltaAngle);
startRotateByAction(centerWheel, -deltaAngle);
++steps;
}
}
var event = me || window.event;
event.preventDefault();
event.stopPropagation();
}
//https://jlongster.com/Going-Fullscreen-with-Canvas
function onFullScreenChange(evt) {
var canvas = evt.currentTarget;
if (document.mozFullScreen || document.webkitIsFullScreen) {
var rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
} else {
canvas.width = 500;
canvas.height = 400;
}
}
var lastTime;
function animate(timestamp) {
context.deltaTime = lastTime ? timestamp - lastTime : 0;
lastTime = timestamp;
//The clearRect() method sets the pixels in a rectangular area to transparent black (rgba(0,0,0,0)).
// context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#E3E8E6';
context.fillRect(0, 0, canvas.width, canvas.height);
drawPie(context, centerWheel, 'black');
//var logs = [];
var score = 0;
var count = centerWheel.length;
var centralAngle = 2 * Math.PI / count;
var angle = count % 2 ? Math.floor(count / 2) * centralAngle - Math.PI / 2 : centralAngle / 2; // is odd?
for (var idx = 0; idx < count; ++idx) {
// `center.rotate` is a normalized angle clamped in [0, 2 * Math.PI), so `index` is an integer in the range [0, count).
var index = Math.floor((centerWheel.rotate) / centralAngle); // direction0's (the first direction) index in center colors;
index -= idx; // the idx-th direction's index in center colors;
if (index < 0) {
index += count;
}
var wheel = wheels[idx];
// Retrieves the index of the slice pointed to the center wheel
var pointer = Math.floor(wheel.rotate / centralAngle); //The array index of the segment sector pointed to the center
var matched = centerWheel[index] == wheel[pointer];
wheel.pointer = matched ? pointer : null;
if (matched) {
++score;
}
//logs.push(`direction_${idx}: ${index}=${centerWheel[index]} vs ${pointer}=${wheel[pointer]}`);
drawPie(context, wheel, 'black', angle);
angle += centralAngle;
}
//console.log(logs.join(', '));
//https://www.tombraiderchronicles.com/underworld/walkthrough/level02.html#pastthedoorpuzzle
if (count === score) {
context.font = context.font.replace(/\d+px/, '56px');
context.textAlign = 'center';
context.textBaseline = 'top';
context.fillStyle = 'rgba(55, 217, 56,1)';
context.fillText('You win!', canvas.width / 2, 1);
}
//draw the number of steps
context.font = context.font.replace(/\d+px/, '28px');
context.textAlign = 'center';
context.textBaseline = 'bottom';
context.fillStyle = 'rgba(55, 217, 56,1)';
context.fillText('Steps: ' + steps, canvas.width / 2, canvas.height - 1);
requestAnimationFrame(animate);
}
}
startLoop();
//initWheels(wheels);
//console.log( wheels );
//console.log(getIndex(wheels[0]));
/**
* https://developer.mozilla.org/en-US/docs/Web/CSS/conic-gradient#Customizing_Gradients
* @see https://github.com/leaverou/conic-gradient
*/
//element.style.maxWidth = "100px";
//transform: rotate(45deg);
//ELEMENT.style.setProperty('--element-width', NEW_VALUE);
//pieChart(wheel);
// Center X and center Y are the coordinates of the center point of the polygon.
// Set initially to 550, 550. Note that the y coordinate is positive downwards,
// to conform to the convention in most computer software. Positive x is to the right.
// Start angle (degrees): Start angle is the position of the first vertex.
// This angle is in degrees and is the angle starting at 3 o'clock going counter
// clockwise. So for example if you want the first vertex to be at 12 o'clock, set
// this to 90. Set initially to blank (auto).
// If you leave this blank it will be set automatically: If the number of sides is
// odd, (e.g. a pentagon), the first vertex will be at 12 o'clock. If even, e.g. an
// octagon, the top and bottom sides will be horizontal on the page.
/**
* This calculator takes the parameters of a regular polygon and calculates its coordinates.
* It produces both the coordinates of the vertices and the coordinates of the line segments
* making up the sides of the polygon.
*
* Note that the y coordinates are positive downwards, to conform to the convention in most
* computer software. Positive x is to the right.
*
* @param {number} nsides The number of sides. Must be greater than 2. Set initially to 5.
* @param {number} radius The distance from the center to a vertex. Set initially to 100.
* @param {number} startAngle
*/
function calculatePolygonCoordinates(nsides, radius, startAngle, cx, cy) {
// collect inputs
// if(isNaN(radius) || radius<1) { alert("Radius must be a number greater than 0"); return; }
// if(isNaN(n) || n<3) { alert("Number of sides must be a number greater than 2"); return; }
// if(isNaN(startAng)) { alert("Starting angle must be a number"); return; }
var centerAngle = 2 * Math.PI / nsides;
//calculate the default start angle
if (!startAngle) { //none supplied
if (isOdd(nsides))
startAngle = Math.PI / 2; //12 oclock
else
startAngle = Math.PI / 2 - centerAngle / 2;
}
var angle = startAngle,
vertex = []; // new Array();
for (var idx = 0; idx < nsides; idx++, angle += centerAngle) {
//angle = startAngle + (idx * centerAngle);
var vx = Math.round(cx + radius * Math.cos(angle));
var vy = Math.round(cy - radius * Math.sin(angle));
vertex.push({
x: vx,
y: vy
});
}
function isOdd(n) {
return (n % 2 == 1);
}
function toRadians(degs) {
return Math.PI * degs / 180;
}
return vertex;
}
</script></body>
</html>
body {
margin: 0px;
padding: 0px;
width: 100%;
}
/* https://stackoverflow.com/questions/10614481/disable-double-tap-zoom-option-in-browser-on-touch-devices */
.disable-dbl-tap-zoom, .mycanvas {
touch-action: manipulation;
}
try{ top.document.getElementById('codefund_ad').remove(); } catch(e){ console.log(e); }
/**
* @param {string} html Text that contains text and tags to be converted to a document fragment. e.g. '<div>x</div><span>y</span><br/>'
* @return {DocumentFragment} a minimal document object that has no parent (a lightweight version of `Document`)
* @see https://developer.mozilla.org/en-US/docs/Web/API/Range/selectNodeContents
* @see https://developer.mozilla.org/en-US/docs/Web/API/range/createContextualFragment
* @example document.body.appendChild($j('<div>x</div><span>y</span><br/>'));
* @since IE11
*/
function $j(html) {
/// Way 1
//var temp = document.createElement('template');
//temp.innerHTML = html;
//return temp.content;
/// Way 2
// var range = document.createRange();
// Set body as context node or whatever context the fragment is to be evaluated in.
// range.selectNodeContents(document.body); // range.selectNode(document.body);
// return range.createContextualFragment(html);
return document.createRange().createContextualFragment(html);
}
/**
* @typedef Wheel
* @type {string[] | {x: number, y: number, radius: number, rotate: number, pointer: number, direction: number, originAngle: number}}
* @property {number} x
* @property {number} y
* @property {number} radius
* @property {number} rotate
* @property {number} direction
* @property {number} originAngle the starting angle to draw this wheel
* @property {number|null} pointer The array index of the segment sector pointed to the center
* @property {string} name
*/
/** @type {Wheel} init each array clockwise */
var centerWheel = ['red', 'green', 'gray', 'gold', 'pink', 'purple']; // Idler wheel / driven wheel
/**
* @type {Wheel[]} Each of the top points of the 4 smaller stars are rotated such that they point towards the center point of the larger star.
* init each array counterclockwise and these wheels rotate around their center clockwise
* @see https://en.wikipedia.org/wiki/Regular_polygon#Regular_convex_polygons
*/
var wheels = [ //Drive wheels
['red', 'gold', 'red', 'green', 'pink', 'purple'],
['gray', 'gold', 'gray', 'red', 'pink', 'purple'],
['gray', 'gray', 'green', 'gray', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'pink', 'purple'],
['gold', 'green', 'gray', 'red', 'purple', 'purple']
];
//centerWheel.length = 4;
//wheels.length = 4;
//wheels.forEach(function (it){ it.length=4; });
/**
* Draw an isosceles triangle as an arrowhead
```
|\
+--------------------------+ \
| > end (locx, locy)
+--------------------------+ /
|/
|<->| sizey
```
* @param {CanvasRenderingContext2D} ctx
* @param {number} locx The x axis of the coordinate for the end of the arrow
* @param {number} locy The y axis of the coordinate for the end of the arrow
* @param {number} angle
* @param {number} sizex The remaining/base/hem side's length, not the two equal sides (legs).
* @param {number} sizey The Height, i.e. the distance from top to bottom. Or at right angles
* from any base to the furthest corner.
* @see https://stackoverflow.com/questions/6576827/html-canvas-draw-curved-arrows#6577443
* @see https://github.com/frogcat/canvas-arrow
* @see https://en.wikipedia.org/wiki/Arrow#Arrowhead
* @see https://en.wikipedia.org/wiki/Isosceles_triangle
*/
function drawArrowhead(ctx, locx, locy, angle, sizex, sizey) {
var hx = sizex / 2;
var hy = sizey / 2;
ctx.save();
ctx.translate(locx, locy);
ctx.rotate(angle);
ctx.translate(-hx, -hy);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, sizey);
ctx.lineTo(sizex, hy);
ctx.closePath();
ctx.fill();
ctx.restore();
}
/**
*
* @param {CanvasRenderingContext2D} ctx
* @param {string[]&{x:number, y:number, radius:number}} wheel
* @param {string | CanvasGradient | CanvasPattern} strokeStyle
* @param {number} directionAngle The angle of the line of the center wheel and this wheel's centers,
* measured clockwise around the center wheel from the positive x-axis and expressed in radians.
*/
function drawPie(ctx, wheel, strokeStyle) {
if(wheel.action){
wheel.action.act(ctx.deltaTime, wheel);
}
const lineWidth = 1;
const innerRadius = 15;
const innerStartRadius = innerRadius - lineWidth / 2;
ctx.lineWidth = lineWidth;
var slices = wheel.length;
var sliceAngle = Math.PI / slices;
//https://en.wikipedia.org/wiki/Angular_distance //
var spaceAngle = sliceAngle; //the angle between the end side of the slice and the start side of the next adjacent slice.
for (var idxSlice = 0, angle = wheel.directionAngle + Math.PI - sliceAngle / 2 + wheel.rotate; idxSlice < slices; ++idxSlice) {
// Begin a path as the segment consists of an arc and 2 lines.
ctx.beginPath();
if (null == wheel.direction) {
ctx.moveTo(wheel.x, wheel.y); // ctx.moveTo(centerX, centerY);
} else {
// Work out the x and y values for the starting point of the segment which is at its starting angle
// but out from the center point of the wheel by the value of the innerRadius. Some correction for line width is needed.
// Now move here relative to the center point of the wheel.
ctx.moveTo(wheel.x + Math.cos(angle) * innerStartRadius, wheel.y + Math.sin(angle) * innerStartRadius);
}
// Draw the outer arc of the segment clockwise in direction -->
// `startAngle`: The angle at which the arc starts, measured clockwise from the positive x-axis and expressed in radians.
ctx.arc(wheel.x, wheel.y, wheel.radius, angle, angle + sliceAngle);
if (null != wheel.direction) {
// Draw another arc, this time anticlockwise <-- at the innerRadius between the end angle and the start angle.
// Canvas will draw a connecting line from the end of the outer arc to the beginning of the inner arc completing the shape.
ctx.arc(wheel.x, wheel.y, innerRadius, angle + sliceAngle, angle, true);
} else {
// If no inner radius then we draw a line back to the center of the wheel.
// Draw a line back to the center of the wheel. `closePath` automatically connects the shape's first and last points.
//ctx.closePath(); ctx.lineTo(centerX, centerY);
}
ctx.closePath();
ctx.fillStyle = wheel[idxSlice];
// Fill and stroke the segment.
if (idxSlice === wheel.pointer) {
var shadowColor = ctx.shadowColor;
//https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur
//https://jsbin.com/gavepub/edit?html,js,output
ctx.shadowBlur = 25;
ctx.shadowColor = 'blue';
//Shadows are only drawn if the shadowColor property is set to a non-transparent value. One of the
//shadowBlur, shadowOffsetX, or shadowOffsetY properties must be non-zero, as well.
//ctx.shadowOffsetX = 0; // The default value is 0 (no vertical offset).
//ctx.shadowOffsetY = 0; // The default value is 0 (no vertical offset).
ctx.fill();
ctx.strokeStyle = 'blue';
ctx.stroke();
ctx.shadowBlur = 0; //The default value is 0.
ctx.shadowColor = shadowColor; //The default value is fully-transparent black 'rgba(0, 0, 0, 0)'
} else {
ctx.fill();
ctx.strokeStyle = strokeStyle;
ctx.stroke();
}
// only for debug test
// https://stackoverflow.com/questions/18092753/change-font-size-of-canvas-without-knowing-font-family
ctx.font = ctx.font.replace(/\d+px/, '24px');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = "white";
// var textMetrics = ctx.measureText(this.x); // TextMetrics object
// ctx.fillText(wheel.x, wheel.y - textMetrics.width / 2, oy);
var halfwayAngle = angle + sliceAngle / 2; // symmetry axis
var textRadius = 0.8 * wheel.radius;
ctx.fillText(idxSlice, wheel.x + textRadius * Math.cos(halfwayAngle), wheel.y + textRadius * Math.sin(halfwayAngle));
angle -= sliceAngle + spaceAngle;
}
// draw Circle border
ctx.beginPath();
ctx.arc(wheel.x, wheel.y, wheel.radius, 0, Math.PI * 2);
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = 2;
ctx.stroke();
if (null != wheel.direction) {
// draw the rotation direction indicator
ctx.beginPath();
ctx.fillStyle = 'rgba(55, 217, 56,1)';
ctx.strokeStyle = 'rgba(55, 217, 56,1)';
var endAngle = wheel.directionAngle + Math.PI / 10;
ctx.arc(wheel.x, wheel.y, wheel.radius + 8, wheel.directionAngle - Math.PI / 10, endAngle);
ctx.stroke();
var lastX = wheel.x + (wheel.radius + 8) * Math.cos(endAngle);
var lastY = wheel.y + (wheel.radius + 8) * Math.sin(endAngle);
// var ang = findAngle(sx, sy, ex, ey);
// ctx.fillRect(lastX, lastY, 4, 4);
drawArrowhead(ctx, lastX, lastY, Math.PI / 2 + endAngle, 12, 12);
}
// only for debug test
if (null != wheel.name) {
// https://stackoverflow.com/questions/18092753/change-font-size-of-canvas-without-knowing-font-family
ctx.font = ctx.font.replace(/\d+px/, '24px');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = "cyan";
// var textMetrics = ctx.measureText(this.x); // TextMetrics object
// ctx.fillText(wheel.x, wheel.y - textMetrics.width / 2, oy);
ctx.fillText(wheel.name, wheel.x, wheel.y);
}
// if (null != wheel.pointer) {
// ctx.fillStyle = 'blue';
// ctx.fillText(wheel.pointer, wheel.x + 38, wheel.y);
// }
}
function startRotateByAction(wheel, delta) {
var action;
if(wheel.action){
action = wheel.action;
action.finish(wheel);
} else{
action = {};
wheel.action = action;
}
action.time = 0;
action.duration = 500;
action.start = wheel.rotate;
action.delta = delta;
action.finish = function (wheel){
const radian360 = 2 * Math.PI;
const endAngle = this.start + this.delta;
//https://stackoverflow.com/questions/1628386/normalise-orientation-between-0-and-360
wheel.rotate = (endAngle % radian360) + (endAngle < 0 ? radian360 : 0);
};
action.act = function(interval, wheel){
if(this.time < this.duration){
this.time += interval;
wheel.rotate = this.start + this.time/this.duration * this.delta;
} else {
this.finish(wheel);
wheel.action = null;
}
};
}
function startLoop() {
var steps = 0; // counts the rotation numbers, number of revolution
/** @type {HTMLCanvasElement} */
var canvas = document.getElementById('mycanvas');
if (!canvas) {
canvas = document.createElement('canvas');
canvas.className = 'mycanvas';
document.body.appendChild(canvas);
}
canvas.addEventListener('click', onDriveWheelClicked);
//canvas.addEventListener('mozfullscreenchange', onFullScreenChange);
//canvas.addEventListener('webkitfullscreenchange', onFullScreenChange);
//requestFullScreen(canvas); called when event is triggered
//https://stackoverflow.com/questions/13157586/full-screen-canvas-on-mobile-devices
function requestFullScreen(el) {
if (el.webkitRequestFullScreen) {
// Google Chrome Version 73.0.3683.86 (Official Build) (32-bit)
//Failed to execute 'requestFullscreen' on 'Element': API can only be initiated by a user gesture.
el.webkitRequestFullScreen();
} else {
el.mozRequestFullScreen();
}
}
var context = canvas.getContext('2d');
var count = wheels.length;
var centerAngle = 2 * Math.PI / count;
var cx = canvas.width / 2,
cy = canvas.height / 2;
var radius = 120;
// calculate the starting angle to make it symmetric with respect to the y-axis
//But draw piles clockwise from the positive y axis (12 oclock).
angle = count % 2 ? Math.PI / -2 : Math.PI / -2 - centerAngle / 2; // is odd?
centerWheel.rotate = 0;
centerWheel.x = cx;
centerWheel.y = cy;
centerWheel.radius = radius / 2;
centerWheel.directionAngle = Math.PI + angle;
// https://www.mathsisfun.com/geometry/regular-polygons.html
for (var idxPie = 0; idxPie < count; ++idxPie, angle += centerAngle) {
var wheel = wheels[idxPie];
//angle = startAngle + (idx * centerAngle);
var vx = cx + radius * Math.cos(angle);
var vy = cy + radius * Math.sin(angle);
wheel.direction = idxPie;
wheel.name = String.fromCharCode(65+idxPie);
wheel.x = vx;
wheel.y = vy;
wheel.radius = radius / 2;
wheel.rotate = 0; //Math.PI + idxPie * centerAngle. clamp the angle to the range 0 to 1 in turns
wheel.directionAngle = angle;
drawPie(context, wheel, 'black', angle);
}
drawPie(context, centerWheel, 'black');
requestAnimationFrame(animate);
/**
* @param {MouseEvent} me
* @see https://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
*/
function onDriveWheelClicked(me) {
// Modern browser's now handle this for you. Chrome, IE9, and Firefox support the offsetX/Y
// like this, passing in the event from the click handler.
var x = me.offsetX,
y = me.offsetY;
// if (me.pageX || me.pageY) {
// x = me.pageX;
// y = me.pageY;
// } else {
// x = me.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
// y = me.clientY + document.body.scrollTop + document.documentElement.scrollTop;
// }
// var canvasElement = me.currentTarget;
// x -= canvasElement.offsetLeft;
// y -= canvasElement.offsetTop;
const radian360 = 2 * Math.PI;
const pieCount = wheels.length;
const deltaAngle = radian360 / pieCount;
for (var idxPie = 0; idxPie < pieCount; ++idxPie, angle += centerAngle) {
var wheel = wheels[idxPie];
if (Math.pow(x - wheel.x, 2) + Math.pow(y - wheel.y, 2) <= Math.pow(wheel.radius, 2)) {
//var rotate = wheel.rotate + deltaAngle; //clockwise
//wheel.rotate = rotate % radian360; // clamp the angle to the range 0 to `2 * Math.PI in radians
// ClampAngle Limit the angle to the range 0 to 1 in turns
//rotate = centerWheel.rotate - deltaAngle; // Counterclockwise
//https://stackoverflow.com/questions/1628386/normalise-orientation-between-0-and-360
//centerWheel.rotate = (rotate % radian360) + (rotate < 0 ? radian360 : 0);
startRotateByAction(wheel, deltaAngle);
startRotateByAction(centerWheel, -deltaAngle);
++steps;
}
}
var event = me || window.event;
event.preventDefault();
event.stopPropagation();
}
//https://jlongster.com/Going-Fullscreen-with-Canvas
function onFullScreenChange(evt) {
var canvas = evt.currentTarget;
if (document.mozFullScreen || document.webkitIsFullScreen) {
var rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
} else {
canvas.width = 500;
canvas.height = 400;
}
}
var lastTime;
function animate(timestamp) {
context.deltaTime = lastTime ? timestamp - lastTime : 0;
lastTime = timestamp;
//The clearRect() method sets the pixels in a rectangular area to transparent black (rgba(0,0,0,0)).
// context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#E3E8E6';
context.fillRect(0, 0, canvas.width, canvas.height);
drawPie(context, centerWheel, 'black');
//var logs = [];
var score = 0;
var count = centerWheel.length;
var centralAngle = 2 * Math.PI / count;
var angle = count % 2 ? Math.floor(count / 2) * centralAngle - Math.PI / 2 : centralAngle / 2; // is odd?
for (var idx = 0; idx < count; ++idx) {
// `center.rotate` is a normalized angle clamped in [0, 2 * Math.PI), so `index` is an integer in the range [0, count).
var index = Math.floor((centerWheel.rotate) / centralAngle); // direction0's (the first direction) index in center colors;
index -= idx; // the idx-th direction's index in center colors;
if (index < 0) {
index += count;
}
var wheel = wheels[idx];
// Retrieves the index of the slice pointed to the center wheel
var pointer = Math.floor(wheel.rotate / centralAngle); //The array index of the segment sector pointed to the center
var matched = centerWheel[index] == wheel[pointer];
wheel.pointer = matched ? pointer : null;
if (matched) {
++score;
}
//logs.push(`direction_${idx}: ${index}=${centerWheel[index]} vs ${pointer}=${wheel[pointer]}`);
drawPie(context, wheel, 'black', angle);
angle += centralAngle;
}
//console.log(logs.join(', '));
//https://www.tombraiderchronicles.com/underworld/walkthrough/level02.html#pastthedoorpuzzle
if (count === score) {
context.font = context.font.replace(/\d+px/, '56px');
context.textAlign = 'center';
context.textBaseline = 'top';
context.fillStyle = 'rgba(55, 217, 56,1)';
context.fillText('You win!', canvas.width / 2, 1);
}
//draw the number of steps
context.font = context.font.replace(/\d+px/, '28px');
context.textAlign = 'center';
context.textBaseline = 'bottom';
context.fillStyle = 'rgba(55, 217, 56,1)';
context.fillText('Steps: ' + steps, canvas.width / 2, canvas.height - 1);
requestAnimationFrame(animate);
}
}
startLoop();
//initWheels(wheels);
//console.log( wheels );
//console.log(getIndex(wheels[0]));
/**
* https://developer.mozilla.org/en-US/docs/Web/CSS/conic-gradient#Customizing_Gradients
* @see https://github.com/leaverou/conic-gradient
*/
//element.style.maxWidth = "100px";
//transform: rotate(45deg);
//ELEMENT.style.setProperty('--element-width', NEW_VALUE);
//pieChart(wheel);
// Center X and center Y are the coordinates of the center point of the polygon.
// Set initially to 550, 550. Note that the y coordinate is positive downwards,
// to conform to the convention in most computer software. Positive x is to the right.
// Start angle (degrees): Start angle is the position of the first vertex.
// This angle is in degrees and is the angle starting at 3 o'clock going counter
// clockwise. So for example if you want the first vertex to be at 12 o'clock, set
// this to 90. Set initially to blank (auto).
// If you leave this blank it will be set automatically: If the number of sides is
// odd, (e.g. a pentagon), the first vertex will be at 12 o'clock. If even, e.g. an
// octagon, the top and bottom sides will be horizontal on the page.
/**
* This calculator takes the parameters of a regular polygon and calculates its coordinates.
* It produces both the coordinates of the vertices and the coordinates of the line segments
* making up the sides of the polygon.
*
* Note that the y coordinates are positive downwards, to conform to the convention in most
* computer software. Positive x is to the right.
*
* @param {number} nsides The number of sides. Must be greater than 2. Set initially to 5.
* @param {number} radius The distance from the center to a vertex. Set initially to 100.
* @param {number} startAngle
*/
function calculatePolygonCoordinates(nsides, radius, startAngle, cx, cy) {
// collect inputs
// if(isNaN(radius) || radius<1) { alert("Radius must be a number greater than 0"); return; }
// if(isNaN(n) || n<3) { alert("Number of sides must be a number greater than 2"); return; }
// if(isNaN(startAng)) { alert("Starting angle must be a number"); return; }
var centerAngle = 2 * Math.PI / nsides;
//calculate the default start angle
if (!startAngle) { //none supplied
if (isOdd(nsides))
startAngle = Math.PI / 2; //12 oclock
else
startAngle = Math.PI / 2 - centerAngle / 2;
}
var angle = startAngle,
vertex = []; // new Array();
for (var idx = 0; idx < nsides; idx++, angle += centerAngle) {
//angle = startAngle + (idx * centerAngle);
var vx = Math.round(cx + radius * Math.cos(angle));
var vy = Math.round(cy - radius * Math.sin(angle));
vertex.push({
x: vx,
y: vy
});
}
function isOdd(n) {
return (n % 2 == 1);
}
function toRadians(degs) {
return Math.PI * degs / 180;
}
return vertex;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment