curvepong
Pong with a twist...
Try hitting the ball with the paddle in motion!
Keyboard
- Use
wandsto move the paddle up and down. - For player 2, use
iandk. Player 2 will be controlled by the computer if no input is detected. - Press
enterto reset the game.
Mouse or Touch
- Use your finger or your mouse to move the paddle up and down.
- Use another finger on the right side of the screen to control player 2. Player 2 will be controlled by the computer if no input is detected.
- Tap with three fingers to reset the game.
// Paddle properties
// width
var paddle_w = 10;
// height
var paddle_h_ratio = 0.2;
// horizontal position (how close paddle is to the goal)
var paddle_x_ratio = 0.0625;
var paddle_speed_ratio = 0.02;
// Ball properties
var ball_r = 5; // radius
var ball_vx_start_ratio = 0.4; // starting velocity
// Physics properties
// How much of paddle velocity is inherited by ball upon collision
var friction = 1;
// How much of paddle velocity is converted to spin
var spin_factor = 0.5;
// When ball hits a wall, velocity is increased depending on the amount of spin
var spin_boost_wall = 5;
// Spin is multiplied by this value when the ball hits a paddle
var spin_decay_paddle = 0.75;
// Spin is multiplied by this value when the ball hits a wall
var spin_decay_wall = 0.5;
// Set less than 1 to make spin decrease over time
var spin_decay_time = 1;
// How well the AI controls the paddle. Higher number means more snappy control.
var ai_control_factor = 0.1;
// Render properties
var w_max = 800;
var aspect = 1.6;
var fps = 60;
var bg_color = "#000000";
var fg_color = "#FFFFFF";
var score_text_size = 32;
var score_text_pos_x_rel = 0.5;
var score_text_pos_y_rel = 0.1;
var rally_text_size = 32;
var rally_text_pos_x_rel = 0.5;
var rally_text_pos_y_rel = 0.9;
var canvasDiv;
class Game {
constructor(w) {
this.resize(w);
}
resize(w) {
var h = w / aspect;
if (h > displayHeight * 0.75) {
h = displayHeight * 0.75;
w = h * aspect;
}
resizeCanvas(w, h);
//Player 1 parameters
this.x1 = paddle_x_ratio * w;
this.y1 = h / 2;
this.v1 = 0;
//Player 2 parameters
this.x2 = w - paddle_x_ratio * w;
this.y2 = h / 2;
this.v2 = 0;
//Ball parameters
this.bx = w / 2;
this.by = h / 2;
this.bvx = 0;
this.bvy = 0;
this.spin = 0;
// Paddle parameters
this.paddle_h = h * paddle_h_ratio;
//Game parameters
this.score1 = 0;
this.score2 = 0;
this.rally_count = 0;
//Render parameters
this.w = w;
this.h = h;
}
reset() {
this.score1 = 0;
this.score2 = 0;
this.rally_count = 0;
this.start();
}
start() {
// Place ball in center
this.bx = this.w / 2;
this.by = this.h / 2;
// Launch ball in random direction at start
this.bvx = this.w * ball_vx_start_ratio * (1 - 2 * Math.round(random(0, 1)));
this.bvy = this.bvx * (random() * 2 - 1);
// Initialize state variables
this.rally_count = 0;
this.spin = 0;
}
update(v1, v2, dt) {
//Update ball
this.bx += this.bvx * dt;
this.by += this.bvy * dt;
if (this.bvx > 0) {
this.bvy -= this.spin * spin_factor;
} else {
this.bvy += this.spin * spin_factor;
}
this.spin *= spin_decay_time;
//Update paddle velocities
this.v1 = v1;
this.v2 = v2;
//Update paddle positions
this.y1 += this.v1;
this.y2 += this.v2;
if (this.y1 + this.paddle_h / 2 < 10) {
this.y1 = -this.paddle_h / 2 + 10;
this.v1 = 0;
} else if (this.y1 - this.paddle_h / 2 > this.h - 10) {
this.y1 = this.h + this.paddle_h / 2 - 10;
this.v1 = 0;
}
if (this.y2 + this.paddle_h / 2 < 10) {
this.y2 = -this.paddle_h / 2 + 10;
this.v2 = 0;
} else if (this.y2 - this.paddle_h / 2 > this.h - 10) {
this.y2 = this.h + this.paddle_h / 2 - 10;
this.v2 = 0;
}
//Top and bottom collision
if ((this.by - ball_r < 0 && this.bvy < 0) || (this.by + ball_r > this.h && this.bvy > 0)) {
this.bvy *= -1;
this.bvy += this.spin;
if (this.bvx > 0) {
this.bvx += Math.abs(this.spin) * spin_boost_wall;
} else {
this.bvx -= Math.abs(this.spin) * spin_boost_wall;
}
this.spin *= spin_decay_wall;
}
// Left collision
if (this.bx - ball_r < paddle_x_ratio * this.w + paddle_w / 2 && this.bx > paddle_x_ratio * this.w) {
if (this.by + ball_r >= this.y1 - this.paddle_h / 2 && this.by - ball_r <= this.y1 + this.paddle_h / 2 && this.bvx < 0) {
this.processCollide(true);
}
}
// Right collision
else if (this.bx + ball_r > (1 - paddle_x_ratio) * this.w - paddle_w / 2 && this.bx < this.w - paddle_x_ratio * this.w) {
if (this.by + ball_r >= this.y2 - this.paddle_h / 2 && this.by - ball_r <= this.y2 + this.paddle_h / 2 && this.bvx > 0) {
this.processCollide(false);
}
}
// Check scoring
if (this.bx < 0) {
this.score2 += 1;
this.start();
} else if (this.bx > this.w) {
this.score1 += 1;
this.start();
}
rect(this.bx - ball_r, this.by - ball_r, ball_r * 2, ball_r * 2);
rect(this.x1 - paddle_w / 2, this.y1 - this.paddle_h / 2, paddle_w, this.paddle_h);
rect(this.x2 - paddle_w / 2, this.y2 - this.paddle_h / 2, paddle_w, this.paddle_h);
textAlign(CENTER);
textSize(score_text_size);
text(this.score1 + " : " + this.score2, this.w * score_text_pos_x_rel, this.h * score_text_pos_y_rel);
textSize(rally_text_size);
text(this.rally_count, this.w * rally_text_pos_x_rel, this.h * rally_text_pos_y_rel);
}
processCollide(is_p1) {
this.bvx *= -1;
this.spin *= spin_decay_paddle;
if (is_p1) {
this.spin -= this.v1;
this.bvy += this.v1 * friction;
} else {
this.spin -= this.v2;
this.bvy += this.v2 * friction;
}
this.rally_count += 1;
}
}
var game;
function setup() {
canvasDiv = document.getElementById("sketch-holder");
w = canvasDiv.offsetWidth;
frameRate(fps);
fill(fg_color);
stroke(fg_color);
background(bg_color);
game = new Game(w);
game.start();
createCanvas(game.w, game.h).parent("sketch-holder");
}
// Rendering and Inputs
var c;
var vel1 = 0;
var vel2 = 0;
var p1u = false; // player one up key down
var p1d = false; // player one down key down
var p2u = false; // player two up key down
var p2d = false; // player two down key down
// whether to control player 2 with AI.
// Automatically disables if player two inputs are pressed.
var do_ai = true;
// Whether to use touch inputs.
// Automatically disables keyboard controls when touchscreen activated.
var do_touch = false;
var do_reset = false;
var z = 0;
function draw() {
if (do_reset) {
game.reset();
do_reset = false;
}
// Color background based on spin
var spin_color_factor = Math.min(Math.abs(game.spin) / 50, 1);
if (game.spin > 0) {
c = [spin_color_factor * 255, 0, 0, 50];
} else {
c = [0, 0, spin_color_factor * 255, 50];
}
background(c);
// Process inputs
vel1 = 0;
vel2 = 0;
if (keyIsDown(87)) {
vel1 = -paddle_speed_ratio * game.h;
} else if (keyIsDown(83)) {
vel1 = paddle_speed_ratio * game.h;
}
if (keyIsDown(73)) {
vel2 = -paddle_speed_ratio * game.h;
do_ai = false;
} else if (keyIsDown(75)) {
vel2 = paddle_speed_ratio * game.h;
do_ai = false;
}
if (touches.length > 2) {
game.reset();
}
for (let touch of touches) {
if (touch.x > 0 && touch.x < game.w && touch.y > 0 && touch.y < game.h) {
if (touch.x < game.w / 2) {
// Left side
vel1 += (touch.y - game.y1) * 0.5;
} else {
// Right side
do_ai = false;
vel2 += (touch.y - game.y2) * 0.5;
}
}
}
if (mouseIsPressed) {
if (mouseX > 0 && mouseX < game.w && mouseY > 0 && mouseY < game.h) vel1 += (mouseY - game.y1) * 0.5;
}
if (do_ai) {
vel2 += (((game.by - game.y2) * game.h) / 500) * ai_control_factor;
}
game.update(vel1, vel2, 1 / fps);
}
function keyPressed() {
if (keyCode == 13) {
// enter
do_reset = true;
}
}
function keyReleased() {}
function windowResized() {
var width = canvasDiv.offsetWidth;
game.resize(width);
game.reset();
}