balls: adding initial version of balls

This commit is contained in:
Sheldan
2025-08-08 18:56:46 +02:00
parent 68d7096ca8
commit ea49c1c41a
9 changed files with 2419 additions and 1 deletions

1293
balls/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
balls/package.json Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,9 @@
import { defineConfig } from 'vite'
export default defineConfig({
base: './',
root: 'src',
build: {
outDir: '../../dist/balls'
}
})

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -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>