xxxxxxxxxx
540
//An excercise in functional programming
const getPaddle = (pos) => {
return (
{
w: 10,
h: 100,
pos: {
x: pos.x,
y: pos.y
} ,
old_pos: {
x: 0,
y: 0
},
velocity: {x:0, y:0},
acceleration: 7000,
score: 0
}
)
} ;
const getBall = (pos) => {
return (
{
radius: 10,
velocity: 800,
direction: createVector(1,0),
pos: {
x: pos.x,
y: pos.y
},
side: ''
}
)
};
let paddles = [];
let ball;
const getRandomDirection = () => {
let v = createVector(random(-1,1), random(-0.3,0.3));
v.normalize();
return v;
}
const solvePosition = (pos, vel, acc, dt) => {
return pos + vel * dt + ((acc * (dt*dt)) / 2);
}
const moveUp = (paddle, dt) => {
let newPaddle = paddle;
let acc = -newPaddle.acceleration;
return moveY(newPaddle, dt, acc);
}
const moveDown = (paddle, dt) => {
let newPaddle = paddle;
let acc = newPaddle.acceleration;
return moveY(newPaddle, dt, acc);
}
const moveY = (obj, dt, acc) => {
let newObj = obj;
newObj.old_pos.y = newObj.pos.y;
acc -= newObj.velocity.y * 10;
newObj.velocity.y = newObj.velocity.y + acc * dt;
let vel = newObj.velocity.y;
let pos = newObj.pos.y;
newObj.pos.y = solvePosition(pos, vel, acc, dt);
return newObj;
}
const moveX = (obj, dt, acc) => {
let newObj = obj;
acc -= newObj.velocity.x * 10;
newObj.velocity.x = newObj.velocity.x + acc * dt;
let vel = newObj.velocity.x;
let pos = newObj.pos.x;
newObj.pos.x = solvePosition(pos, vel, acc, dt);
return newObj;
}
const movePaddles = (paddles, dt) => {
let newPaddles = paddles;
return newPaddles.map((paddle) => {
return moveY(paddle, dt, 0);
});
}
const resetBall = (dt) => {
let ball = getBall({x:width/2, y:height/2});
ball = moveBall(getRandomDirection(), ball, dt);
return ball;
}
const resetBallLeft = (dt) => {
let ball = getBall({x:0.75*width, y:height/2});
ball = moveBall(createVector(-1, random(0,1)), ball, dt);
return ball;
}
const resetBallRight = (dt) => {
let ball = getBall({x:0.25*width, y:height/2});
ball = moveBall(createVector(1, random(0,1)), ball, dt);
return ball;
}
const moveBall = (direction, ball, dt) => {
let newBall = ball;
newBall.direction = direction;
newBall.pos.x = newBall.pos.x + newBall.velocity * direction.x * dt;
newBall.pos.y = newBall.pos.y + newBall.velocity * direction.y * dt;
return newBall;
}
const checkBallWalls = (ball, audio) => {
let newBall = ball;
let newAudio = audio;
if ( (newBall.pos.y - 0.5 * newBall.radius) <= 0 ) {
newBall.direction.y *= -1;
//nudge
newBall.pos.y = newBall.radius;
}
if ( (newBall.pos.y + 0.5 * newBall.radius) > height ) {
newBall.direction.y *= -1;
newBall.pos.y = height - newBall.radius;
}
const sidedBall = (ball) => {
let newBall = ball;
let range = 100;
if (newBall.pos.x < -range) {
banewBallll = leftBall(ball);
newAudio = playToneScore(audio, 24);
}
else if (newBall.pos.x > width + range) {
newBall = rightBall(ball);
newAudio = playToneScore(audio, 20);
}
return newBall;
}
newBall = sidedBall(newBall);
return { newBall, newAudio } ;
}
const updateScore = (paddles, ball, dt) => {
let newBall = ball;
let newPaddles = paddles;
if (newBall.side == 'left') {
newPaddles[1].score += 10;
newBall = resetBallRight(dt);
}
if (newBall.side == 'right') {
newPaddles[0].score += 10;
newBall = resetBallLeft(dt);
}
return {newPaddles, newBall};
};
const handleInput = (paddles, dt) => {
let newPaddles = paddles;
if (keyIsDown(UP_ARROW)){
newPaddles[1] = moveUp(paddles[1], dt);
}
if (keyIsDown(DOWN_ARROW)){
newPaddles[1] = moveDown(paddles[1], dt);
}
if (keyIsDown(65)){
newPaddles[0] = moveUp(paddles[0], dt);
}
if (keyIsDown(90)){
newPaddles[0] = moveDown(paddles[0], dt);
}
return newPaddles;
}
const checkPaddlesOOB = (paddles) => {
const newPaddles = paddles.map((paddle) => {
let newPaddle = paddle;
if ( (newPaddle.pos.y - 0.5 * newPaddle.h) <= 0) {
newPaddle.pos.y = 0.5 * newPaddle.h;
newPaddle.old_pos.y = 0.5 * newPaddle.h;
} else if ((newPaddle.pos.y + 0.5 * newPaddle.h) > height){
newPaddle.pos.y = height - 0.5 * newPaddle.h;
newPaddle.old_pos.y = height - 0.5 * newPaddle.h;
}
return newPaddle;
})
return newPaddles;
}
const overlap = (paddle, ball) => {
let test = false;
let newBall = ball;
let newPaddle = paddle;
if ( newBall.pos.x - (0.5*newBall.radius) < newPaddle.pos.x + (0.5*newPaddle.w) &&
newBall.pos.x + (0.5*newBall.radius) > newPaddle.pos.x - (0.5*newPaddle.w) )
{
if ( newBall.pos.y + (0.5*newBall.radius) > newPaddle.pos.y - (0.5*newPaddle.h) &&
newBall.pos.y - (0.5*newBall.radius) < newPaddle.pos.y + (0.5*newPaddle.h) )
{
test = true;
}
}
return test;
}
const solveBallPaddleCollision = (ball, paddle, dt) => {
let haveCollided = false;
let newBall = ball,
newPaddle = paddle;
let origDir_x = newBall.direction.x;
let origDir_y = newBall.direction.y;
let count = 0;
while( overlap(newPaddle, newBall ) ) {
let dir = createVector(origDir_x * -1, origDir_y *-1);
dir.normalize();
let newPos = newBall.pos;
newPos.x = newPos.x + ((newBall.velocity + 100) * dir.x * dt);
newPos.y = newPos.y + ((newBall.velocity + 100) * dir.y * dt);
newBall.pos = newPos;
haveCollided = true;
}
return {newBall, haveCollided};
}
const checkBallPaddlesCollision = (paddles, ball, audio, dt) => {
let newBall = ball;
const getBounceDirection = (paddle, ball) => {
// map output dir to where paddle is hitExternal
let ball_y = ball.pos.y;
let padd_y = paddle.pos.y;
let lim = 0.5 * paddle.h;
let paddleLerp = map(ball_y, padd_y - lim, padd_y + lim, -1,1);
let v = p5.Vector.fromAngle(0.25*PI * paddleLerp);
v.x = paddle.pos.x > 0.5 * width ? v.x *-1 : v.x;
return v;
}
paddles.map((paddle) => {
let result = solveBallPaddleCollision(newBall, paddle, dt);
newBall = result.newBall;
if (result.haveCollided){
let newDir = getBounceDirection(paddle, newBall);
newBall = moveBall(newDir, newBall, dt);
let val = Math.abs(newDir.y);
let freq = map(val, 0, 1, 60, 160);
audio = playTonePaddle(audio, freq);
}
});
return newBall;
}
// Web AUDIO api
var audioContext = null;
var gainNode;
var osc;
//AUDIO
const getOrCreateContext = (context) => {
if (!context) {
context = new AudioContext();
}
return context;
};
let audioObjects = {
context: audioContext,
gainNode: gainNode,
osc: osc
};
const initAudio = (audioObjects) => {
let {context, gainNode, osc} = audioObjects;
context = getOrCreateContext(context);
gainNode = context.createGain();
osc = context.createOscillator();
let f = 80;
osc.frequency.setTargetAtTime(f, context.currentTime, 0.05);
osc.type = "square";
osc.start();
gainNode.gain.value = 0;
//gainNode.gain.setTargetAtTime(0.02, context.currentTime, 0.05);
osc.connect(gainNode);
gainNode.connect(context.destination);
return {context, gainNode, osc};
}
const playTonePaddle = (audio, freq) => {
let {gainNode, context, osc} = audio;
//osc.frequency.setValueAtTime( freq, context.currentTime);
osc = pitchEnvelope(osc, context, freq, 0.005, 0.01, 800);
gainNode = envelope(gainNode, context, 0.005, 0.1);
audio.gainNode = gainNode;
audio.osc = osc;
return audio;
}
const playToneScore = (audio, freq) => {
let {gainNode, context, osc} = audio;
//osc.frequency.setValueAtTime( freq, context.currentTime);
osc = pitchEnvelope(osc, context, freq, 0.005, 0.07, 0);
gainNode = envelope(gainNode, context, 0.05, 0.5);
audio.gainNode = gainNode;
audio.osc = osc;
return audio;
}
const envelope = (gainNode, context, a, d) => {
let now = context.currentTime;
let s = 0;
//gainNode.gain.cancelScheduledValues(0);
gainNode.gain.setValueAtTime(0, now);
gainNode.gain.linearRampToValueAtTime(0.15, now + a);
gainNode.gain.linearRampToValueAtTime(s, now + a + d);
return gainNode;
};
const pitchEnvelope = (osc, context, freq, a, d, pointyness) => {
let now = context.currentTime;
let f = freq;
osc.frequency.cancelScheduledValues(0);
osc.frequency.setValueAtTime(f, now);
osc.frequency.linearRampToValueAtTime(f+pointyness, now + a);
osc.frequency.linearRampToValueAtTime(f, now + a + d);
return osc;
}
function setup() {
createCanvas(800, 600, P2D);
paddles.push(
getPaddle({x:40, y:height/2}),
getPaddle({x:width-40, y:height/2})
);
let dt = deltaTime / 1000;
ball = resetBall(dt);
audioObjects = initAudio(audioObjects);
}
const leftBall = (ball) => {
let b = ball;
b.side = 'left';
return b;
}
const rightBall = (ball) => {
let b = ball;
b.side = 'right';
return b;
}
const getDeltaPos = (paddle) => {
return Math.abs(paddle.pos.y - paddle.old_pos.y);
}
const getDeltaTimeForBall = (paddles) => {
let result = paddles.map((paddle) => {
return getDeltaPos(paddle);
});
let total = result[0] + result[1];
return total / 800;
}
const simulate = (paddles, ball, audio, numSteps, n, deltaT) => {
let newPaddles = paddles,
newBall = ball,
newAudio = audio;
if (n < 1)
{
return {newPaddles, newBall, newAudio};
}
const copy_object = (src) => {
return Object.assign({}, src);
}
let dt = deltaT / numSteps;
let objs = updateScore(newPaddles, newBall, dt);
newBall = objs.newBall;
newPaddles = objs.newPaddles;
newPaddles = movePaddles(newPaddles, dt);
newPaddles = checkPaddlesOOB(newPaddles);
let res = checkBallWalls(newBall, newAudio);
newBall = res.newBall;
newAudio = res.newAudio;
let ballDT = getDeltaTimeForBall(newPaddles);
newBall = moveBall(newBall.direction, newBall, ballDT);
newBall = checkBallPaddlesCollision(newPaddles, newBall, newAudio, dt);
return simulate(newPaddles,newBall,newAudio, numSteps, n - 1, deltaT);
}
const updateGame = (objs, dt) => {
let {paddles, ball, audioObjects} = objs;
paddles = handleInput(paddles, dt);
let steps = 4;
let result = simulate(paddles, ball, audioObjects, steps, steps, dt);
ball = result.newBall;
audioObjects = result.newAudio;
paddles = result.newPaddles;
return {paddles, ball, audioObjects};
}
const renderGame = (objs) => {
const {paddles, ball} = objs;
background(22);
rectMode(CENTER);
noStroke();
fill(255);
paddles.map((paddle) => {
rect(paddle.pos.x, paddle.pos.y, paddle.w, paddle.h);
textSize(20);
text(str(paddle.score), paddle.pos.x - 0.5*paddle.w, 40);
});
//draw ball
rect(ball.pos.x, ball.pos.y, ball.radius, ball.radius);
// middle line
rectMode(CORNER);
rect(0.5*width, 0, 6, height)
}
function draw() {
let dt = deltaTime / 1000;
let res = updateGame({paddles, ball, audioObjects}, dt);
ball = res.ball;
paddles = res.paddles;
audioObjects = res.audioObjects;
renderGame({paddles, ball});
}