// images
PImage startImg, bgImg, readyImg, goImg, earthImg, saturnImg, winImg, loseImg, tieImg;
PFont kontra;
// obj initialization
int numBalls = 2;
float radius = 6;
Ball[] balls = new Ball[numBalls];
int numPaddles = 2;
Paddle[] paddles = new Paddle[numPaddles];
Position[] origins = new Position[numPaddles];
float paddleW = HALF_PI/3, r1 = 100, r2 = 80;
int maxExplosions = 1000;
int explosionCount = 0;
Explosion[] explosions = new Explosion[maxExplosions];
PImage[] explosionFrames = new PImage[9];
// control vars
boolean start = true;
boolean play = false;
boolean pause = true;
boolean done = false;
boolean win = false;
int level = 0;
int time, timer, wait = 2500;
int gameLength = 10000;
int timerBarWidth = 5;
void setup()
{
size(600,300);
smooth();
//framerate(60);
kontra = loadFont("Kontrapunkt-Light-48.vlw");
textFont(kontra, 16);
startImg = loadImage("start.jpg");
bgImg = loadImage("bg.jpg");
readyImg = loadImage("ready.gif");
goImg = loadImage("go.gif");
earthImg = loadImage("earth.gif");
saturnImg = loadImage("saturn.gif");
winImg = loadImage("youwin.gif");
loseImg = loadImage("youlose.gif");
tieImg = loadImage("tiegame.gif");
for (int i = 0; i < 9; i++) {
explosionFrames[i] = loadImage("explode_0"+i+".gif");
}
origins[0] = new Position(width/5, height/2);
origins[1] = new Position(4*width/5, height/2);
paddles[0] = new Paddle(origins[0].x, origins[0].y, paddleW, r1, r2);
paddles[1] = new Paddle(origins[1].x, origins[1].y, paddleW, r1, r2);
paddles[1].computer_controlled(true);
for (int i = 0; i < numBalls; i++ )
balls[i] = new Ball(radius, i);
}
void draw()
{
if (start) {
image(startImg, 0, 0);
} else if (play) {
event_game();
} else if (done) {
show_winner();
}
}
void event_game()
{
draw_bg();
draw_planets();
draw_explosions();
for (int i = 0; i < numPaddles; i++) {
paddles[i].draw();
paddles[i].test();
}
if (pause) {
timer = (timer == 0) ? millis() : timer+1;
time = millis();
if (time - timer >= wait) {
pause = false;
timer = 0;
}
if (time - timer <= wait-500) {
image(readyImg, width/2-readyImg.width/2, height/2-readyImg.height/2);
} else {
image(goImg, width/2-goImg.width/2, height/2-goImg.height/2);
}
for (int i = 0; i < numBalls; i++)
balls[i].draw_ball();
} else {
draw_timer();
draw_score();
for (int i = 0; i < numBalls; i++) {
balls[i].draw();
}
}
}
void draw_bg()
{
background(bgImg);
}
void draw_planets()
{
noFill();
stroke(0,255,0,127);
for (int i = 1; i < 12; i++) {
stroke(0,255,0,255-lerp(0,255,(float)i/12));
ellipse(origins[0].x, origins[0].y, sq(i)+2*i, sq(i)+2*i);
ellipse(origins[1].x, origins[1].y, sq(i)+2*i, sq(i)+2*i);
}
image(earthImg, origins[0].x-earthImg.width/2, origins[0].y-earthImg.height/2);
image(saturnImg, origins[1].x-saturnImg.width/2, origins[1].y-saturnImg.height/2);
}
void draw_explosions()
{
for (int i = 0; i < explosionCount; i++) {
explosions[i].draw();
}
}
void draw_timer()
{
fill(0,255,0);
stroke(0,255,0);
timer = (timer == 0) ? millis() : timer+1;
time = millis();
float diff = time - timer;
float totalBars = (width/2)/(timerBarWidth+1);
float currentBars = int(diff*totalBars/gameLength);
for (int i = 0; i < currentBars; i++) {
rect(i*(timerBarWidth+1), height-20, 3, 18);
rect(width-timerBarWidth-i*(timerBarWidth+1), height-20, 3, 18);
}
if (diff >= gameLength) {
play = false;
done = true;
}
}
void draw_score()
{
textMode(ALIGN_LEFT);
text("YOU: "+paddles[1].score, 10, 18);
textMode(ALIGN_RIGHT);
text("THEM: "+paddles[0].score, width-10, 18);
}
void show_winner()
{
draw_bg();
push(); translate(width/2, height/2);
if (paddles[0].score == paddles[1].score) {
win = false;
image(tieImg, -tieImg.width/2, -tieImg.height/2);
} else if (paddles[0].score > paddles[1].score) {
win = false;
image(loseImg, -loseImg.width/2, -loseImg.height/2);
} else {
win = true;
image(winImg, -winImg.width/2, -winImg.height/2);
}
pop();
textMode(ALIGN_CENTER);
text("YOU: "+paddles[1].score+" THEM: "+paddles[0].score, width/2, 200);
text("Click to play again", width/2, 220);
}
void mousePressed()
{
if (start) {
start = false;
play = true;
} else if (done) {
done = false;
play = true;
reset();
}
}
void reset()
{
pause = true;
timer = 0;
for (int i = 0; i < numPaddles; i++)
paddles[i].score = 0;
for (int i = 0; i < numBalls; i++)
balls[i].reset();
explosionCount = 0;
if (win) level++;
}
/***********************************************/
/*** PADDLE CLASS
/***********************************************/
class Paddle
{
Position anchor;
float a, w, r1, r2;
Position[] p = new Position[6];
float speed = HALF_PI/10;
boolean user = true;
int score = 0;
float friction = .06;
Paddle(float $x, float $y, float $w, float $r1, float $r2)
{
w = $w; r1 = $r1; r2 = $r2;
a = 0;
anchor = new Position($x,$y);
for (int i = 0; i < p.length; i++)
p[i] = new Position();
}
void computer_controlled(boolean t)
{
user = !t;
friction -= level*.01;
}
void draw()
{
if (user) {
rotate();
} else {
autorotate();
}
draw_shape();
}
void draw_shape()
{
fill(0,255,0,127);
stroke(0,255,0);
p[0].set(anchor.x + cos(a-w/2)*r1, anchor.y + sin(a-w/2)*r1);
p[1].set(anchor.x + cos(a)*r1, anchor.y + sin(a)*r1);
p[2].set(anchor.x + cos(a+w/2)*r1, anchor.y + sin(a+w/2)*r1);
p[3].set(anchor.x + cos(a+w/2)*r2, anchor.y + sin(a+w/2)*r2);
p[4].set(anchor.x + cos(a)*r2, anchor.y + sin(a)*r2);
p[5].set(anchor.x + cos(a-w/2)*r2, anchor.y + sin(a-w/2)*r2);
beginShape(POLYGON);
vertex(p[0].x, p[0].y);
vertex(p[1].x, p[1].y);
vertex(p[2].x, p[2].y);
vertex(p[3].x, p[3].y);
vertex(p[4].x, p[4].y);
vertex(p[5].x, p[5].y);
endShape();
}
void autorotate()
{
float na, d = 100000, nd, ny = 0, nx = 0;
for (int i = 0; i < numBalls; i++) {
nd = dist(balls[i].p.x, balls[i].p.y, anchor.x, anchor.y);
if (nd < d) {
d = nd;
nx = balls[i].p.x;
ny = balls[i].p.y;
}
}
na = atan2(anchor.y - ny, anchor.x - nx)+PI;
a = friction(a, na, friction);
}
void rotate()
{
a = atan2(anchor.y - mouseY, anchor.x - mouseX)+PI;
}
float friction(float o, float n, float f)
{
if (o > 3*HALF_PI && o < TWO_PI && n < HALF_PI && n > 0) {
n += TWO_PI;
}
return o+(n-o)*f;
}
boolean hit(Position oldp, Position newp, float rad)
{
float d = dist(anchor, newp);
boolean inside = (d <= r1 && d >= r2);
float th = atan2(anchor.y - newp.y, anchor.x - newp.x)+PI;
boolean btw = (th >= a - w/2 && th <= a + w/2);
return (btw && inside) ? true : false;
}
void monitor()
{
fill(0,255,0);
textMode(ALIGN_LEFT);
float th = atan2(anchor.y - balls[0].p.y, anchor.x - balls[0].p.x)+PI;
boolean btw = (th > a - w/2 && th < a + w/2);
float d = dist(anchor, balls[0].p);
text(str(btw), mouseX,mouseY+20);
boolean inside = (d <= r1 && d >= r2);
text(str(inside), mouseX,mouseY);
}
void test()
{
for (int i = 0; i < numBalls; i++) {
if (balls[i].p.closeTo(anchor, 7)) {
explosions[explosionCount] = new Explosion(anchor.x, anchor.y, explosionFrames);
explosionCount++;
score++;
}
}
}
}
/***********************************************/
/*** BALL CLASS
/***********************************************/
class Ball
{
Position p,op;
Position[] h = new Position[numPaddles];
Vector v;
Vector[] g = new Vector[numPaddles];
float r, id, M = 5000;
Ball(float $r, float $id)
{
r = $r; id = $id;
p = new Position(width/2+random(-5,5),(id+1)*height/(numBalls+1));
op = new Position(p.x, p.y);
for (int i = 0; i < numPaddles; i++)
h[i] = new Position(origins[i].x, origins[i].y);
for (int i = 0; i < numPaddles; i++)
g[i] = new Vector();
v = new Vector(0,0);
}
void drop(float $x, float $y)
{
p.set($x,$y);
v.set(0,0);
}
void draw()
{
float m,a;
for (int i = 0; i < origins.length; i++) {
m = dist(p,h[i]);
m = M/(sq(m) + 4*m + 2);
a = atan2(p,h[i]);
g[i].set(m,a);
v.add(g[i]);
}
v.m = constrain(v.m, 0, 100);
op.set(p.x, p.y);
p.frictionAdd(v, .15);
bounce();
draw_ball();
}
void draw_ball()
{
fill(0,255,0);
stroke(0,255,0);
ellipse(p.x, p.y, r*2, r*2);
}
void bounce()
{
float th;
float add;
for (int i = 0; i < numBalls; i++) {
if (i != id && balls[i].hit(p,r)) {
th = atan2(balls[i].p.y - p.y, balls[i].p.x - p.x);
reflectOver(th);
ballBoundaries(balls[i]);
//v.scale(.8);
}
}
for (int i = 0; i < numPaddles; i++) {
if (paddles[i].hit(op,p,r)) {
th = atan2(paddles[i].anchor.y - p.y, paddles[i].anchor.x - p.x)+PI;
th = (th > HALF_PI && th <= 3*HALF_PI) ? th+PI : th;
reflectOver(th);
v.scale(1.3);
paddleBoundaries(paddles[i]);
}
}
if (hit_horiz_wall()) {
reflectY();
v.scale(.8);
boundaries();
} else if (hit_vert_wall()) {
reflectX();
v.scale(.8);
boundaries();
}
}
boolean hit(Position b, float br)
{
return (dist(p.x, p.y, b.x, b.y) <= r+br) ? true : false;
}
boolean hit_vert_wall()
{
return (p.x-r <= 0 || p.x+r >= width) ? true : false;
}
boolean hit_horiz_wall()
{
return (p.y-r <= 0 || p.y+r >= height) ? true: false;
}
void reflectX()
{
float x = cos(v.a)*v.m;
float y = sin(v.a)*v.m;
v.a = atan2(y,-x);
}
void reflectY()
{
float x = cos(v.a)*v.m;
float y = sin(v.a)*v.m;
v.a = atan2(-y, x);
}
void reflectOver(float a)
{
float x = cos(v.a)*v.m;
float y = sin(v.a)*v.m;
v.a = atan2(y, -x) + a;
}
void boundaries()
{
p.x = (p.x-r <= 0) ? 0+r : p.x;
p.x = (p.x+r >= width) ? width-r : p.x;
p.y = (p.y-r <= 0) ? 0+r : p.y;
p.y = (p.y+r >= height) ? height-r : p.y;
}
void paddleBoundaries(Paddle paddle)
{
float th = atan2(paddle.anchor.y - p.y, paddle.anchor.x - p.x)+PI;
float d1 = dist(op.x, op.y, paddle.anchor.x, paddle.anchor.y);
if (d1-r >= paddle.r1) {
p.x = cos(th)*paddle.r1 + paddle.anchor.x + r+0.1;
p.y = sin(th)*paddle.r1 + paddle.anchor.y + r+.1;
} else if (d1+r <= paddle.r2) {
p.x = cos(th)*paddle.r2 + paddle.anchor.x + r+.1;
p.y = sin(th)*paddle.r2 + paddle.anchor.y + r+.1;
}
}
void ballBoundaries(Ball ball)
{
float th = atan2(ball.p.y - p.y, ball.p.x - p.x)+PI;
float d1 = dist(p.x+r, p.y+r, ball.p.x, ball.p.y);
p.x += cos(th)*ball.r + cos(th)*r;
p.y += sin(th)*ball.r + sin(th)*r;
}
void test()
{
for (int i = 0; i < origins.length; i++) {
if (p.closeTo(h[i], r)) {
done = true;
}
}
}
void reset() {
v.set(0,0);
p.set(width/2+random(-5,5), (id+1)*height/(numBalls+1));
}
}
/***********************************************/
/*** EXPLOSION CLASS
/***********************************************/
class Explosion
{
float x,y;
PImage[] f;
int c = 0, delay = 5;
Explosion(float $x, float $y, PImage[] $frames)
{
x = $x; y = $y; f = $frames;
}
void draw()
{
if (c < f.length*delay) {
int i = int(c/delay);
alpha(f[i], f[i]);
image(f[i], x-f[i].width/2, y-f[i].height/2);
}
c++;
}
}
/***********************************************/
/*** POSITION CLASS
/***********************************************/
class Position
{
float x,y;
Position() {}
Position(float $x, float $y)
{
x = $x; y = $y;
}
Position(Position p)
{
x = p.x; y = p.y;
}
void set(float $x, float $y)
{
x = $x; y = $y;
}
void add(Vector v)
{
x += cos(v.a)*v.m;
y += sin(v.a)*v.m;
}
boolean eq(Position p)
{
return (x == p.x && y == p.y) ? true : false;
}
boolean closeTo(Position p, float d)
{
return (dist(x,y,p.x,p.y) <= d) ? true : false;
}
void friction(Position n, float f)
{
x += (n.x-x)*f;
y += (n.y-y)*f;
}
void frictionAdd(Vector v, float f)
{
float nx = x + cos(v.a)*v.m;
float ny = y + sin(v.a)*v.m;
x += (nx-x)*f;
y += (ny-y)*f;
}
}
/***********************************************/
/*** VECTOR CLASS
/***********************************************/
class Vector
{
float m,a;
Vector() {}
Vector (float $m, float $a)
{
m = $m; a = $a;
}
void set(float $m, float $a)
{
m = $m; a = $a;
}
void add(Vector v)
{
float x = cos(a)*m + cos(v.a)*v.m;
float y = sin(a)*m + sin(v.a)*v.m;
m = dist(0,0,x,y);
a = atan2(y,x);
}
void negate()
{
a += HALF_PI;
}
void scale(float s)
{
m *= s;
}
}
// MISC FUNCTIONS
float dist(Position $1, Position $2)
{
return dist($1.x, $1.y, $2.x, $2.y);
}
float atan2(Position $1, Position $2)
{
return atan2($2.y - $1.y, $2.x - $1.x);
}