mirror of
https://github.com/Sheldan/canvas.git
synced 2026-01-01 23:07:47 +00:00
balls: adding initial version of balls
This commit is contained in:
1293
balls/package-lock.json
generated
Normal file
1293
balls/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
balls/package.json
Normal file
19
balls/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "balls",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"canvas-common": "file:../canvas-common"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^5.1.5"
|
||||
}
|
||||
}
|
||||
22
balls/src/index.html
Normal file
22
balls/src/index.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>balls</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
html, body, div, canvas {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html, body { width:100%; height:100%; }
|
||||
canvas { display:block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
858
balls/src/js/main.js
Normal file
858
balls/src/js/main.js
Normal file
@@ -0,0 +1,858 @@
|
||||
import {
|
||||
toRad, getMousePos, addRGBStyle, docReady, createNormalizedVector, angleBetweenTwoVectors, dotProduct
|
||||
} from "canvas-common";
|
||||
|
||||
var imageData = {};
|
||||
var ctx = {};
|
||||
var canvas = {};
|
||||
var mousePos = {}
|
||||
|
||||
|
||||
var config = {
|
||||
size: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
},
|
||||
balls: {
|
||||
rowElements: window.innerWidth/(window.innerHeight/10),
|
||||
rows: 10,
|
||||
horizontalSize: window.innerHeight / 10,
|
||||
verticalSize: window.innerHeight / 10,
|
||||
balls: 10,
|
||||
speed: 10,
|
||||
reboundBuffChance: 0.25,
|
||||
portalChance: 0.07,
|
||||
simulationDepth: 5000,
|
||||
restartTaps: 3,
|
||||
tapsFactor: 2,
|
||||
resetTapsDecreaseInterval: 2
|
||||
},
|
||||
general: {
|
||||
fps: 30
|
||||
}
|
||||
};
|
||||
|
||||
var mobile = 'ontouchstart' in window;
|
||||
|
||||
|
||||
if(mobile){
|
||||
config.balls.tapsFactor = 3;
|
||||
}
|
||||
|
||||
var timeouts = [];
|
||||
|
||||
var toSpawn = 0;
|
||||
var spawning = false;
|
||||
var newSpawnSet = false;
|
||||
|
||||
var mouseStart = {};
|
||||
var mouseStop = {};
|
||||
var animationId = {};
|
||||
|
||||
var balls = [];
|
||||
|
||||
var rects = [];
|
||||
var gameOver = false;
|
||||
|
||||
var buffs = [];
|
||||
var portals = [];
|
||||
var reboundBalls = 0;
|
||||
var mouseDown = false;
|
||||
var newStartPoint;
|
||||
|
||||
var fourtyFiveDegrees = toRad(45);
|
||||
var portalCounter = 1;
|
||||
var oneHundredThirtyFriveDegrees = toRad(135);
|
||||
var oneHundredEightyDegrees = toRad(180);
|
||||
|
||||
var colors = [
|
||||
{
|
||||
r: 0xf2, g: 0x69, b: 0x12
|
||||
}, {
|
||||
r: 0xf3, g: 0x8c, b: 0x14
|
||||
}, {
|
||||
r: 0xf4, g: 0xaf, b: 0x16
|
||||
}, {
|
||||
r: 0xf4, g: 0xd1, b: 0x17
|
||||
}, {
|
||||
r: 0xf5, g: 0xf3, b: 0x19
|
||||
}, {
|
||||
r: 0xd7, g: 0xf6, b: 0x1b
|
||||
}, {
|
||||
r: 0xb7, g: 0xf6, b: 0x1d
|
||||
}, {
|
||||
r: 0x98, g: 0xf7, b: 0x1f
|
||||
}, {
|
||||
r: 0x79, g: 0xf8, b: 0x21
|
||||
}, {
|
||||
r: 0x5a, g: 0xf8, b: 0x23
|
||||
}
|
||||
];
|
||||
|
||||
colors.forEach(addRGBStyle);
|
||||
|
||||
var restartRect = {
|
||||
x: 0, y: 0, width: config.size.width * 0.2, height: config.size.height * 0.2
|
||||
};
|
||||
|
||||
function isInRect(point, rect){
|
||||
return point.x > rect.x && point.x < (rect.x + rect.width) && point.y > rect.y && rect.y < (rect.y + rect.height);
|
||||
}
|
||||
|
||||
var doRestart = 0;
|
||||
var restarting = false;
|
||||
|
||||
function setPoint(event){
|
||||
|
||||
var point = getMousePos(canvas, event);
|
||||
if(isInRect(point, restartRect)){
|
||||
doRestart++;
|
||||
console.log(doRestart)
|
||||
if(doRestart >= config.balls.restartTaps * config.balls.tapsFactor){
|
||||
console.log('Restarting');
|
||||
timeouts.forEach(function(timeout){
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
timeouts = [];
|
||||
restarting = true;
|
||||
rects = [];
|
||||
buffs = [];
|
||||
portals = [];
|
||||
balls = [];
|
||||
portalCounter = 1;
|
||||
config.balls.balls = 10;
|
||||
mouseDown = false;
|
||||
mouseStop = {};
|
||||
spawnStuff();
|
||||
doRestart = 0;
|
||||
// verrrrry cheap hack
|
||||
// I couldnt get the mouse events to stop, because on mobile twice as much are sent
|
||||
// so after resetting it, balls still spawned, because the event was triggered and therefore came into the lower
|
||||
// lines of code... and spawned balls
|
||||
timeouts.push(setTimeout(function(){
|
||||
restarting = false;
|
||||
spawning = false;
|
||||
console.log('reseting restarting flag...')
|
||||
}, 250));
|
||||
return;
|
||||
}
|
||||
}
|
||||
//setShaft(event)
|
||||
mouseStart = newStartPoint;
|
||||
if(spawning) return;
|
||||
if(!mouseDown){
|
||||
mouseDown = true;
|
||||
mouseStop = point;
|
||||
} else {
|
||||
mouseDown = false;
|
||||
spawning = true;
|
||||
setTip(event);
|
||||
}
|
||||
}
|
||||
|
||||
function startBall(cnt){
|
||||
toSpawn = cnt;
|
||||
if(cnt == 0) return;
|
||||
var vec = createNormalizedVector(mouseStop, mouseStart);
|
||||
var ball = {
|
||||
x: mouseStart.x,
|
||||
y: mouseStart.y,
|
||||
radius: 4,
|
||||
vec: vec,
|
||||
simulation: false,
|
||||
maxBounces: -1
|
||||
};
|
||||
if(!restarting) {
|
||||
balls.push(ball);
|
||||
timeouts.push(setTimeout(function(){
|
||||
if(restarting) return;
|
||||
startBall(cnt - 1);
|
||||
}, 250))
|
||||
}
|
||||
}
|
||||
|
||||
function setShaft(event) {
|
||||
mouseStart = getMousePos(canvas, event);
|
||||
}
|
||||
|
||||
function setTip(event) {
|
||||
|
||||
toSpawn = config.balls.balls;
|
||||
startBall(config.balls.balls);
|
||||
}
|
||||
|
||||
function updateCanvas() {
|
||||
ctx.clearRect(0, 0, config.size.width, config.size.height);
|
||||
if(gameOver){
|
||||
ctx.fillText('gameover: ' + config.balls.balls, config.size.width / 2, config.size.height / 2);
|
||||
}
|
||||
|
||||
var caseApplied = false;
|
||||
objectsToDisplay.forEach(function(object){
|
||||
if(object.startCondition()){
|
||||
caseApplied = true;
|
||||
object.running = true;
|
||||
object.applied = true;
|
||||
}
|
||||
if(object.condition() && object.running){
|
||||
caseApplied = true;
|
||||
object.fun();
|
||||
object.applied = true;
|
||||
}
|
||||
else {
|
||||
object.running = false;
|
||||
if(object.applied){
|
||||
object.postFun();
|
||||
}
|
||||
object.applied = false;
|
||||
}
|
||||
});
|
||||
|
||||
if(!caseApplied){
|
||||
if(!gameOver && !restarting){
|
||||
paintRects();
|
||||
paintPortals();
|
||||
paintBuffs();
|
||||
paintBalls();
|
||||
paintIndicator();
|
||||
ballsAct();
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
animationId = requestAnimationFrame(updateCanvas);
|
||||
}, 1000 / config.general.fps)
|
||||
}
|
||||
|
||||
function paintPortals(){
|
||||
portals.forEach(paintPortal)
|
||||
}
|
||||
function paintPortal(portal){
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'orange';
|
||||
ctx.rect(portal.source.xPos, portal.source.yPos, portal.source.width, portal.source.height);
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'lightBlue';
|
||||
ctx.rect(portal.target.xPos, portal.target.yPos, portal.target.width, portal.target.height);
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(portal.source.linkedWith, portal.source.xPos, portal.source.yPos + portal.source.height / 2);
|
||||
ctx.fillText(portal.target.linkedWith, portal.target.xPos, portal.target.yPos + portal.target.height / 2);
|
||||
ctx.fill();
|
||||
|
||||
}
|
||||
|
||||
var objectsToDisplay = [];
|
||||
objectsToDisplay.push({
|
||||
fun: drawPlaceHolder,
|
||||
startCondition: function(){
|
||||
return rects.length == 0
|
||||
},
|
||||
condition: function(){
|
||||
return placeHolder.y > 0
|
||||
},
|
||||
applied: false,
|
||||
running: false,
|
||||
postFun: function(){
|
||||
rectsAct();
|
||||
newStartPoint = {x: balls[0].x, y: config.size.height - 25};
|
||||
balls = [];
|
||||
},
|
||||
reset: function(){
|
||||
placeHolder = {x: config.size.width / 2, y: config.size.height}
|
||||
}
|
||||
});
|
||||
|
||||
function paintBuffs(){
|
||||
buffs.forEach(paintBuff);
|
||||
}
|
||||
|
||||
function paintBuff(buff){
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(buff.xPos, buff.yPos);
|
||||
ctx.fillStyle = buildBuffGradient(buff);
|
||||
ctx.rect(buff.xPos, buff.yPos, buff.width, buff.height);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(buff.duration, buff.xPos + 20, buff.yPos + 20);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function paintPrediction(){
|
||||
var vec = createNormalizedVector(mouseStop, mouseStart);
|
||||
var prediction = {
|
||||
x: mouseStart.x,
|
||||
y: mouseStart.y,
|
||||
radius: 4,
|
||||
vec: vec,
|
||||
simulation: true,
|
||||
bounces: 2
|
||||
};
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.moveTo(prediction.x, prediction.y);
|
||||
var cnt = 0;
|
||||
while(prediction.bounces > 0 && cnt < config.balls.simulationDepth) {
|
||||
ballAct(prediction);
|
||||
ctx.lineTo(prediction.x, prediction.y);
|
||||
cnt++;
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
}
|
||||
|
||||
function paintIndicator(){
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle='black';
|
||||
ctx.fillStyle='black';
|
||||
|
||||
if(mouseDown){
|
||||
paintPrediction();
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'lightBlue';
|
||||
ctx.rect(0, config.size.height - 30, (balls.length / config.balls.balls) * config.size.width / 2, 10);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(toSpawn, 0, config.size.height - 20);
|
||||
ctx.fill();
|
||||
|
||||
if(reboundBalls > 0){
|
||||
ctx.fillStyle = 'yellow';
|
||||
ctx.rect(0, config.size.height - 50, reboundBalls * 10, 10);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(reboundBalls, 0, config.size.height - 40);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
function rectsAct(){
|
||||
rects.forEach(function(rect){
|
||||
rect.yPos += config.balls.verticalSize;
|
||||
if(rect.yPos > config.size.height - 50){
|
||||
gameOver = true;
|
||||
}
|
||||
});
|
||||
portals.forEach(function(portal, index, array){
|
||||
portal.source.yPos += config.balls.verticalSize;
|
||||
portal.target.yPos += config.balls.verticalSize;
|
||||
if(portal.source.yPos > config.size.height - 50){
|
||||
array.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
if(portal.target.yPos > config.size.height - 50){
|
||||
array.splice(index, 1);
|
||||
}
|
||||
});
|
||||
buffs.forEach(function(buff, index, array){
|
||||
buff.yPos += config.balls.verticalSize;
|
||||
if(buff.yPos > config.size.height - 50){
|
||||
array.splice(index, 1);
|
||||
}
|
||||
});
|
||||
config.balls.balls++;
|
||||
toSpawn = config.balls.balls;
|
||||
spawnStuff();
|
||||
updateCookieStorage();
|
||||
spawning = false;
|
||||
}
|
||||
|
||||
function updateCookieStorage(){
|
||||
var objectToStore = {
|
||||
rects: rects,
|
||||
balls: config.balls.balls,
|
||||
buffs: buffs,
|
||||
portals: portals
|
||||
};
|
||||
|
||||
Cookies.remove('balls');
|
||||
Cookies.set('balls', objectToStore);
|
||||
}
|
||||
|
||||
function paintBalls(){
|
||||
balls.forEach(paintBall)
|
||||
}
|
||||
|
||||
function paintRects(){
|
||||
rects.forEach(paintRect)
|
||||
}
|
||||
|
||||
function paintRect(rect){
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle="#ffffff";
|
||||
var col = getColor(rect.points, rect.maxPoints);
|
||||
if(col == undefined){
|
||||
ctx.fillStyle = 'white'
|
||||
} else {
|
||||
ctx.fillStyle = col.styleRGB;
|
||||
}
|
||||
ctx.rect(rect.xPos, rect.yPos, rect.width, rect.height);
|
||||
ctx.fill();
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(rect.points, rect.xPos + 20, rect.yPos + 20);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function getColor(value, maxValue){
|
||||
var percent = value / maxValue;
|
||||
return colors[(colors.length * percent - 1)<<0];
|
||||
}
|
||||
|
||||
function paintBall(ball){
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.arc(ball.x,ball.y,ball.radius,0,2*Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
function ballsAct(){
|
||||
balls.forEach(ballAct);
|
||||
newSpawnSet = false;
|
||||
if(rects.length == 0){
|
||||
objectsToDisplay[0].reset();
|
||||
return;
|
||||
}
|
||||
balls.forEach(function(ball, index, array){
|
||||
if(ball.done){
|
||||
array.splice(index, 1);
|
||||
}
|
||||
if(array.length == 0 && spawning){
|
||||
rectsAct();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
var placeHolder = {x: config.size.width / 2, y: config.size.height};
|
||||
|
||||
function drawPlaceHolder(){
|
||||
ctx.fillText('Any idea for a better animation?', placeHolder.x, placeHolder.y);
|
||||
ctx.fill();
|
||||
placeHolder.y -= 5;
|
||||
}
|
||||
|
||||
function ballAct(ball){
|
||||
ball.x = ball.x + ball.vec.x * config.balls.speed;
|
||||
ball.y = ball.y + ball.vec.y * config.balls.speed;
|
||||
|
||||
if ((ball.x + ball.radius) > config.size.width || (ball.x - ball.radius) < 0) {
|
||||
ball.vec.x *= -1;
|
||||
if(ball.bounces > 0){
|
||||
ball.bounces--;
|
||||
}
|
||||
}
|
||||
|
||||
if (ball.y - ball.radius < 0) {
|
||||
ball.vec.y *= -1;
|
||||
if(ball.bounces > 0){
|
||||
ball.bounces--;
|
||||
}
|
||||
}
|
||||
|
||||
if((ball.y + ball.radius) > config.size.height && !ball.simulation) {
|
||||
if(reboundBalls <= 0){
|
||||
if(!newSpawnSet){
|
||||
newStartPoint = {x: ball.x, y: config.size.height - 25};
|
||||
newSpawnSet = true;
|
||||
}
|
||||
ball.done = true;
|
||||
} else {
|
||||
reboundBalls--;
|
||||
ball.vec.y *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
var nextX = ball.x;
|
||||
var nextY = ball.y;
|
||||
|
||||
var ballBottomY = nextY + ball.radius;
|
||||
var ballLeftX = nextX - ball.radius;
|
||||
var ballTopY = nextY - ball.radius;
|
||||
var ballRightX = nextX + ball.radius;
|
||||
|
||||
var found = false;
|
||||
rects.forEach(function(rect, index, array){
|
||||
var topRightX = rect.xPos + rect.width;
|
||||
//var bottomRightX = rect.xPos + rect.width;
|
||||
//var bottomRightY = rect.yPos + rect.height;
|
||||
var bottomLeftY = rect.yPos + rect.height;
|
||||
|
||||
if(ballLeftX < topRightX && ballRightX > rect.xPos && ballBottomY > rect.yPos && ballTopY < bottomLeftY) {
|
||||
var centerX = rect.xPos + rect.width / 2;
|
||||
var centerY = rect.yPos + rect.height / 2;
|
||||
var rectCenterToBallCenter = createNormalizedVector(ball, {x: centerX, y: centerY});
|
||||
var normalLevel = createNormalizedVector({x: centerX + 10, y: centerY}, {x: centerX, y: centerY});
|
||||
var angle = angleBetweenTwoVectors(rectCenterToBallCenter, normalLevel);
|
||||
if(angle < fourtyFiveDegrees){
|
||||
ball.vec.x *= -1;
|
||||
} else if(angle < oneHundredThirtyFriveDegrees){
|
||||
ball.vec.y *= -1;
|
||||
} else if(angle < oneHundredEightyDegrees){
|
||||
ball.vec.x *= -1;
|
||||
}
|
||||
if(ball.bounces > 0){
|
||||
ball.bounces--;
|
||||
}
|
||||
|
||||
|
||||
nextX = ball.x + ball.vec.x * config.balls.speed;
|
||||
nextY = ball.y + ball.vec.y * config.balls.speed;
|
||||
|
||||
ballBottomY = nextY + ball.radius;
|
||||
ballLeftX = nextX - ball.radius;
|
||||
ballTopY = nextY - ball.radius;
|
||||
ballRightX = nextX + ball.radius;
|
||||
|
||||
if(ballLeftX < topRightX && ballRightX > rect.xPos && ballBottomY > rect.yPos && ballTopY < bottomLeftY) {
|
||||
var centerX2 = rect.xPos + rect.width / 2;
|
||||
var centerY2 = rect.yPos + rect.height / 2;
|
||||
var rectCenterToBallCenter2 = createNormalizedVector(ball, {x: centerX2, y: centerY2});
|
||||
var normalLevel2 = createNormalizedVector({x: centerX2 + 10, y: centerY2}, {x: centerX2, y: centerY2});
|
||||
var angle2 = angleBetweenTwoVectors(rectCenterToBallCenter2, normalLevel2);
|
||||
|
||||
if (angle2 < fourtyFiveDegrees) {
|
||||
ball.vec.y *= -1;
|
||||
} else if (angle2 < oneHundredThirtyFriveDegrees) {
|
||||
ball.vec.y *= -1;
|
||||
} else if (angle2 < oneHundredEightyDegrees) {
|
||||
ball.vec.x *= -1;
|
||||
}
|
||||
}
|
||||
if(!ball.simulation) {
|
||||
rect.points--;
|
||||
}
|
||||
found = true;
|
||||
if(rect.points <= 0 && !ball.simulation){
|
||||
array.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
found = false;
|
||||
|
||||
buffs.forEach(function(buff, index, array){
|
||||
if(found || buff.points == 0) return;
|
||||
var topRightX = buff.xPos + buff.width;
|
||||
//var bottomRightX = buff.xPos + buff.width;
|
||||
//var bottomRightY = buff.yPos + buff.height;
|
||||
var bottomLeftY = buff.yPos + buff.height;
|
||||
|
||||
if(ballLeftX < topRightX && ballRightX > buff.xPos && ballBottomY > buff.yPos && ballTopY < bottomLeftY && !ball.simulation) {
|
||||
buff.points--;
|
||||
found = true;
|
||||
buff.effect(ball);
|
||||
array.splice(index, 1);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
found = false;
|
||||
portals.forEach(function(portal, index, array){
|
||||
function portalCollision(portal, ball){
|
||||
if(found) return;
|
||||
var topRightX = portal.xPos + portal.width;
|
||||
//var bottomRightX = portal.xPos + portal.width;
|
||||
//var bottomRightY = portal.yPos + portal.height;
|
||||
var bottomLeftY = portal.yPos + portal.height;
|
||||
if(ballLeftX < topRightX && ballRightX > portal.xPos && ballBottomY > portal.yPos && ballTopY < bottomLeftY) {
|
||||
found = true;
|
||||
portal.effect(ball);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
portalCollision(portal.source, ball);
|
||||
if(!found){
|
||||
portalCollision(portal.target, ball)
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function spawnStuff(){
|
||||
if(Math.random() < config.balls.reboundBuffChance){
|
||||
var buff = {
|
||||
xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize,
|
||||
yPos: 1 * config.balls.verticalSize,
|
||||
height: config.balls.verticalSize - 5,
|
||||
width: config.balls.horizontalSize - 5,
|
||||
duration: (Math.random() * config.balls.balls + 1) << 0,
|
||||
effect: function(ball) {
|
||||
reboundBalls += buff.duration;
|
||||
}
|
||||
};
|
||||
buffs.push(buff);
|
||||
}
|
||||
|
||||
if(Math.random() < config.balls.portalChance)
|
||||
{
|
||||
var source = {
|
||||
xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize,
|
||||
yPos: ((Math.random() * config.balls.rows) << 0) * config.balls.verticalSize + config.balls.verticalSize / 2 - 7.5,
|
||||
height: 15,
|
||||
width: config.balls.verticalSize - 5,
|
||||
linkedWith: portalCounter
|
||||
};
|
||||
var target = {
|
||||
xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize,
|
||||
yPos: ((Math.random() * config.balls.rows) << 0) * config.balls.verticalSize + config.balls.verticalSize / 2 - 7.5,
|
||||
height: 15,
|
||||
width: config.balls.verticalSize - 5,
|
||||
linkedWith: portalCounter
|
||||
};
|
||||
|
||||
portalCounter++;
|
||||
var centerX = target.xPos + target.width / 2;
|
||||
var centerY = target.yPos + target.height / 2;
|
||||
var base = createNormalizedVector({x: centerX, y: centerY}, {x : centerX + 10, y: centerY});
|
||||
var lowerRight = createNormalizedVector({x: centerX, y: centerY}, {x: target.xPos + target.width, y: target.yPos + target.height});
|
||||
var lowerLeft = createNormalizedVector({x: centerX, y: centerY}, {x: target.xPos, y: target.yPos + target.height});
|
||||
target.angles = [angleBetweenTwoVectors(base, lowerRight), angleBetweenTwoVectors(base, lowerLeft)];
|
||||
source.angles = [angleBetweenTwoVectors(base, lowerRight), angleBetweenTwoVectors(base, lowerLeft)];
|
||||
|
||||
var blocked = false;
|
||||
if(target.yPos == source.yPos && target.xPos == source.xPos){
|
||||
blocked = true;
|
||||
}
|
||||
if(!blocked)
|
||||
blocked = blocked || overlaysRect(source);
|
||||
if(!blocked)
|
||||
blocked = blocked || overlaysBuff(source);
|
||||
if(!blocked)
|
||||
blocked = blocked || overlaysPortal(source);
|
||||
if(!blocked)
|
||||
blocked = blocked || overlaysRect(target);
|
||||
if(!blocked)
|
||||
blocked = blocked || overlaysBuff(target);
|
||||
if(!blocked)
|
||||
blocked = blocked || overlaysPortal(target);
|
||||
|
||||
// orange
|
||||
source.effect = function(ball) {
|
||||
collisionWithPortal(ball, source, target);
|
||||
};
|
||||
|
||||
//blue
|
||||
target.effect = function(ball) {
|
||||
collisionWithPortal(ball, target, source);
|
||||
};
|
||||
|
||||
var portal = {
|
||||
source: source,
|
||||
target: target
|
||||
};
|
||||
if(!blocked){
|
||||
portals.push(portal)
|
||||
}
|
||||
}
|
||||
|
||||
var rectAmount = ((Math.random() * 9) << 0) + 1;
|
||||
for(var i = 0; i < rectAmount; i++){
|
||||
var rect = {
|
||||
xPos: ((Math.random() * config.balls.rowElements) << 0) * config.balls.horizontalSize,
|
||||
yPos: 1 * config.balls.verticalSize,
|
||||
height: config.balls.verticalSize - 5,
|
||||
width: config.balls.horizontalSize - 5,
|
||||
points: config.balls.balls,
|
||||
maxPoints: config.balls.balls
|
||||
};
|
||||
var exists = false;
|
||||
exists = exists || overlaysRect(rect);
|
||||
exists = exists || overlaysBuff(rect);
|
||||
exists = exists || overlaysPortal(rect);
|
||||
|
||||
|
||||
if(!exists){
|
||||
rects.push(rect);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setEndPoint(event){
|
||||
if(mouseDown){
|
||||
mouseStop = getMousePos(canvas, event);
|
||||
}
|
||||
}
|
||||
|
||||
function overlaysRect(rect){
|
||||
var exists = false;
|
||||
rects.forEach(function(rectToTest){
|
||||
if(exists) return;
|
||||
if(rectToTest.xPos == rect.xPos && rectToTest.yPos == rect.yPos){
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
function overlaysBuff(rect){
|
||||
var exists = false;
|
||||
buffs.forEach(function(buffToTest){
|
||||
if(exists) return;
|
||||
if(buffToTest.xPos == rect.xPos && buffToTest.yPos == rect.yPos){
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
return exists;
|
||||
}
|
||||
|
||||
function overlaysPortal(rect){
|
||||
var exists = false;
|
||||
portals.forEach(function (portalToTest){
|
||||
if(exists) return;
|
||||
var sourceCenter = {
|
||||
x: portalToTest.source.xPos + portalToTest.source.width / 2,
|
||||
y: portalToTest.source.yPos + portalToTest.source.height / 2
|
||||
};
|
||||
if(sourceCenter.x > rect.xPos && sourceCenter.x < (rect.xPos + rect.width) && sourceCenter.y > rect.yPos && sourceCenter.y < (rect.yPos + rect.height)){
|
||||
exists = true;
|
||||
}
|
||||
var targetCenter = {
|
||||
x: portalToTest.target.xPos + portalToTest.target.width / 2,
|
||||
y: portalToTest.target.yPos + portalToTest.target.height / 2
|
||||
};
|
||||
if(targetCenter.x > rect.xPos && targetCenter.x < (rect.xPos + rect.width) && targetCenter.y > rect.yPos && targetCenter.y < (rect.yPos + rect.height)){
|
||||
exists = true;
|
||||
}
|
||||
});
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
function buildBuffGradient(buff){
|
||||
var yellowMagenta = ctx.createRadialGradient(buff.xPos + buff.width / 2, buff.yPos + buff.height / 2, 0, buff.xPos + buff.width / 2, buff.yPos + buff.height / 2, buff.width / 2);
|
||||
yellowMagenta.addColorStop(0 ,"yellow");
|
||||
yellowMagenta.addColorStop(1, "DarkMagenta");
|
||||
return yellowMagenta;
|
||||
}
|
||||
|
||||
function collisionWithPortal(ball, thisOne, partner){
|
||||
var centerX = thisOne.xPos + thisOne.width / 2;
|
||||
var centerY = thisOne.yPos + thisOne.height / 2;
|
||||
|
||||
var xOffset = ball.x - thisOne.xPos;
|
||||
var yOffset = ball.y - thisOne.yPos;
|
||||
var rectCenterToBallCenter = createNormalizedVector(ball, {x: centerX, y: centerY});
|
||||
var normalLevel = createNormalizedVector({x: centerX + 10, y: centerY}, {x: centerX, y: centerY});
|
||||
var angle = angleBetweenTwoVectors(rectCenterToBallCenter, normalLevel);
|
||||
if(angle < thisOne.angles[0]){
|
||||
ball.x = partner.xPos - ball.radius;
|
||||
ball.y = partner.yPos + yOffset;
|
||||
} else if(angle < thisOne.angles[1]){
|
||||
if(ball.vec.y > 0){
|
||||
ball.y = partner.yPos + partner.height + ball.radius;
|
||||
ball.x = partner.xPos + xOffset;
|
||||
} else {
|
||||
ball.y = partner.yPos - ball.radius;
|
||||
ball.x = partner.xPos + xOffset;
|
||||
}
|
||||
} else if(angle < oneHundredEightyDegrees){
|
||||
ball.x = partner.xPos + partner.width + ball.radius;
|
||||
ball.y = partner.yPos + yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
docReady(function () {
|
||||
newStartPoint = {x: config.size.width / 2, y: config.size.height - 25};
|
||||
toSpawn = config.balls.balls;
|
||||
canvas = document.getElementById('canvas')
|
||||
ctx = canvas.getContext("2d");
|
||||
canvas.width = config.size.width;
|
||||
canvas.height = config.size.height;
|
||||
canvas.addEventListener("mousedown", setPoint, false);
|
||||
canvas.addEventListener("mouseup", setPoint, false);
|
||||
canvas.addEventListener("mousemove", setEndPoint, false);
|
||||
// Set up touch events for mobile, etc
|
||||
canvas.addEventListener("touchstart", function (e) {
|
||||
if(restarting) return;
|
||||
mousePos = getTouchPos(canvas, e);
|
||||
var touch = e.touches[0];
|
||||
var mouseEvent = new MouseEvent("mousedown", {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY
|
||||
});
|
||||
canvas.dispatchEvent(mouseEvent);
|
||||
}, false);
|
||||
|
||||
canvas.addEventListener("touchend", function (e) {
|
||||
if(restarting) return;
|
||||
var mouseEvent = new MouseEvent("mouseup", {
|
||||
});
|
||||
canvas.dispatchEvent(mouseEvent);
|
||||
}, false);
|
||||
canvas.addEventListener("touchmove", function (e) {
|
||||
if(restarting) return;
|
||||
var touch = e.touches[0];
|
||||
|
||||
mousePos = getTouchPos(canvas, e);
|
||||
var mouseEvent = new MouseEvent("mousemove", {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY
|
||||
});
|
||||
canvas.dispatchEvent(mouseEvent);
|
||||
}, false);
|
||||
|
||||
// Get the position of a touch relative to the canvas
|
||||
function getTouchPos(canvasDom, touchEvent) {
|
||||
var rect = canvasDom.getBoundingClientRect();
|
||||
return {
|
||||
x: touchEvent.touches[0].clientX - rect.left,
|
||||
y: touchEvent.touches[0].clientY - rect.top
|
||||
};
|
||||
}
|
||||
var possibleBalls = Cookies.getJSON('balls');
|
||||
if(possibleBalls == undefined){
|
||||
spawnStuff();
|
||||
} else {
|
||||
buffs = possibleBalls.buffs;
|
||||
portals = possibleBalls.portals;
|
||||
rects = possibleBalls.rects;
|
||||
|
||||
// it doesnt store functions
|
||||
buffs.forEach(function(buff){
|
||||
buff.effect = function(ball) {
|
||||
reboundBalls += buff.duration;
|
||||
}
|
||||
});
|
||||
|
||||
portals.forEach(function(portal){
|
||||
portal.source.effect = function(ball) {
|
||||
collisionWithPortal(ball, portal.source, portal.target);
|
||||
};
|
||||
|
||||
//blue
|
||||
portal.target.effect = function(ball) {
|
||||
collisionWithPortal(ball, portal.target, portal.source);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
config.balls.balls = possibleBalls.balls;
|
||||
}
|
||||
requestAnimationFrame(updateCanvas);
|
||||
|
||||
setInterval(function(){
|
||||
if(doRestart > 0){
|
||||
doRestart--;
|
||||
}
|
||||
}, config.balls.resetTapsDecreaseInterval * 1000)
|
||||
});
|
||||
|
||||
|
||||
24
balls/src/js/plugins.js
Normal file
24
balls/src/js/plugins.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// Avoid `console` errors in browsers that lack a console.
|
||||
(function() {
|
||||
var method;
|
||||
var noop = function () {};
|
||||
var methods = [
|
||||
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
|
||||
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
|
||||
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
|
||||
'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
|
||||
];
|
||||
var length = methods.length;
|
||||
var console = (window.console = window.console || {});
|
||||
|
||||
while (length--) {
|
||||
method = methods[length];
|
||||
|
||||
// Only stub undefined methods.
|
||||
if (!console[method]) {
|
||||
console[method] = noop;
|
||||
}
|
||||
}
|
||||
}());
|
||||
|
||||
// Place any jQuery/helper plugins in here.
|
||||
9
balls/vite.config.js
Normal file
9
balls/vite.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
root: 'src',
|
||||
build: {
|
||||
outDir: '../../dist/balls'
|
||||
}
|
||||
})
|
||||
@@ -124,3 +124,195 @@ export function randomNumberButAtLeast(range, min) {
|
||||
export function getIndexForCoordinate(config, x, y) {
|
||||
return (y * config.size.width + x) * 4;
|
||||
}
|
||||
|
||||
export function createNormalizedVector(tip, shaft) {
|
||||
let vect = createVector(tip, shaft);
|
||||
|
||||
let dist = pointDistance(tip, shaft);
|
||||
vect.x /= dist;
|
||||
vect.y /= dist;
|
||||
return vect;
|
||||
}
|
||||
|
||||
// only normalized vectors
|
||||
export function angleBetweenTwoVectors(vectorA, vectorB){
|
||||
return Math.acos(dotProduct(vectorA, vectorB));
|
||||
}
|
||||
|
||||
export function dotProduct(vector1, vector2) {
|
||||
return vector1.x * vector2.x + vector1.y * vector2.y;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* JavaScript Cookie v2.2.0
|
||||
* https://github.com/js-cookie/js-cookie
|
||||
*
|
||||
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
|
||||
* Released under the MIT license
|
||||
*/
|
||||
/*!
|
||||
* JavaScript Cookie v2.2.0
|
||||
* https://github.com/js-cookie/js-cookie
|
||||
*
|
||||
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
|
||||
* Released under the MIT license
|
||||
*/
|
||||
;(function (factory) {
|
||||
var registeredInModuleLoader = false;
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(factory);
|
||||
registeredInModuleLoader = true;
|
||||
}
|
||||
if (typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
registeredInModuleLoader = true;
|
||||
}
|
||||
if (!registeredInModuleLoader) {
|
||||
var OldCookies = window.Cookies;
|
||||
var api = window.Cookies = factory();
|
||||
api.noConflict = function () {
|
||||
window.Cookies = OldCookies;
|
||||
return api;
|
||||
};
|
||||
}
|
||||
}(function () {
|
||||
function extend () {
|
||||
var i = 0;
|
||||
var result = {};
|
||||
for (; i < arguments.length; i++) {
|
||||
var attributes = arguments[ i ];
|
||||
for (var key in attributes) {
|
||||
result[key] = attributes[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function init (converter) {
|
||||
function api (key, value, attributes) {
|
||||
var result;
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write
|
||||
|
||||
if (arguments.length > 1) {
|
||||
attributes = extend({
|
||||
path: '/'
|
||||
}, api.defaults, attributes);
|
||||
|
||||
if (typeof attributes.expires === 'number') {
|
||||
var expires = new Date();
|
||||
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
|
||||
attributes.expires = expires;
|
||||
}
|
||||
|
||||
// We're using "expires" because "max-age" is not supported by IE
|
||||
attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';
|
||||
|
||||
try {
|
||||
result = JSON.stringify(value);
|
||||
if (/^[\{\[]/.test(result)) {
|
||||
value = result;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (!converter.write) {
|
||||
value = encodeURIComponent(String(value))
|
||||
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
|
||||
} else {
|
||||
value = converter.write(value, key);
|
||||
}
|
||||
|
||||
key = encodeURIComponent(String(key));
|
||||
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
|
||||
key = key.replace(/[\(\)]/g, escape);
|
||||
|
||||
var stringifiedAttributes = '';
|
||||
|
||||
for (var attributeName in attributes) {
|
||||
if (!attributes[attributeName]) {
|
||||
continue;
|
||||
}
|
||||
stringifiedAttributes += '; ' + attributeName;
|
||||
if (attributes[attributeName] === true) {
|
||||
continue;
|
||||
}
|
||||
stringifiedAttributes += '=' + attributes[attributeName];
|
||||
}
|
||||
return (document.cookie = key + '=' + value + stringifiedAttributes);
|
||||
}
|
||||
|
||||
// Read
|
||||
|
||||
if (!key) {
|
||||
result = {};
|
||||
}
|
||||
|
||||
// To prevent the for loop in the first place assign an empty array
|
||||
// in case there are no cookies at all. Also prevents odd result when
|
||||
// calling "get()"
|
||||
var cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||
var rdecode = /(%[0-9A-Z]{2})+/g;
|
||||
var i = 0;
|
||||
|
||||
for (; i < cookies.length; i++) {
|
||||
var parts = cookies[i].split('=');
|
||||
var cookie = parts.slice(1).join('=');
|
||||
|
||||
if (!this.json && cookie.charAt(0) === '"') {
|
||||
cookie = cookie.slice(1, -1);
|
||||
}
|
||||
|
||||
try {
|
||||
var name = parts[0].replace(rdecode, decodeURIComponent);
|
||||
cookie = converter.read ?
|
||||
converter.read(cookie, name) : converter(cookie, name) ||
|
||||
cookie.replace(rdecode, decodeURIComponent);
|
||||
|
||||
if (this.json) {
|
||||
try {
|
||||
cookie = JSON.parse(cookie);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (key === name) {
|
||||
result = cookie;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
result[name] = cookie;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
api.set = api;
|
||||
api.get = function (key) {
|
||||
return api.call(api, key);
|
||||
};
|
||||
api.getJSON = function () {
|
||||
return api.apply({
|
||||
json: true
|
||||
}, [].slice.call(arguments));
|
||||
};
|
||||
api.defaults = {};
|
||||
|
||||
api.remove = function (key, attributes) {
|
||||
api(key, '', extend(attributes, {
|
||||
expires: -1
|
||||
}));
|
||||
};
|
||||
|
||||
api.withConverter = init;
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
return init(function () {});
|
||||
}));
|
||||
|
||||
BIN
img/balls.png
Normal file
BIN
img/balls.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
@@ -12,5 +12,6 @@
|
||||
<a href="/orbits"><img src="img/orbits.png" alt="Solar system simulation" class="preview" title="Solar system simulation"></a> <br>
|
||||
<a href="/recBubbles"><img src="img/recBubbles.png" alt="Recursive bubbles" class="preview" title="Recursive bubbles"></a> <br>
|
||||
<a href="/fireWorks"><img src="img/fireWorks.png" alt="Fireworks" class="preview" title="Fireworks"></a>
|
||||
<a href="/balls"><img src="img/balls.png" alt="Balls" class="preview" title="Balls"></a>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user