import ddf.minim.*;
/**
* Game 14: "Ribbonoid"
* Use the ribbon to bounce the ball.
*
Go play more games at NMcCoy.net!
* 
This work by Nathan McCoy is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.
*/
boolean[] keys = new boolean[256];
boolean title;
boolean paused;
boolean muted;
int drawing_ms_last;
int physics_ms_last;
int CORE_FPS = 540;
int DRAWING_FPS = 60;
int PHYSICS_FPS = 180;
int DRAW_MS = 1000 / DRAWING_FPS;
int PHYS_MS = 1000 / PHYSICS_FPS;
Minim minim;
float RIBBON_LENGTH_START = 250;
float MIN_RIBBON_STEP = 2;
ArrayList ribbon_points;
ArrayList fire_list;
PVector ball_pos;
PVector ball_vel;
float GRAVITY = 0.005;
ArrayList boxes;
float ribbon_length;
int level;
int lives;
int START_LIVES = 4;
float FIRE_SPEED = 0.3;
float FIRE_ACCEL = 0.05;
int draw_frame;
PFont font;
int LEVEL_INTRODUCE_FIRE = 1;
int LEVEL_CEILING_FIRE = 2;
int LEVEL_MORE_BLOCKS = 3;
int LEVEL_BLOCK_FIRE = 4;
int LEVEL_EVEN_MORE_BLOCKS = 5;
int LEVEL_INVISIBLE_RIBBON = 6;
int LEVEL_VICTORY = 7;
AudioSample hitRibbonSound;
AudioSample hitWallSound;
AudioSample hitCeilSound;
AudioSample block1Sound;
AudioSample block2Sound;
AudioSample block3Sound;
AudioSample block4Sound;
AudioSample fireSound;
AudioSample[] blockSounds;
ParticleList box_particles;
class BoxParticle extends Particle
{
color col;
BoxParticle(PVector p, color c)
{
super(p, new PVector(0, 0), 180);
col = c;
}
void draw()
{
rectMode(CENTER);
noFill();
stroke(col, (255.0*life)/maxlife);
strokeWeight(1);
rect(pos.x, pos.y, maxlife-life, maxlife-life);
}
}
class BurnParticle extends Particle
{
BurnParticle(PVector p)
{
super(p, new PVector(0, 0), 180);
}
void draw()
{
rectMode(CENTER);
noFill();
stroke(255, (255.0*life)/maxlife, 0, (255.0*life)/maxlife);
strokeWeight(3);
ellipse(pos.x, pos.y, (maxlife-life)*(2), (maxlife-life)*(2));
}
}
void setup()
{
size(720, 480, P2D);
frameRate(600);
minim = new Minim(this);
hitRibbonSound = minim.loadSample("ribbonhit.wav", 1024);
hitWallSound = minim.loadSample("wallhit.wav", 1024);
hitCeilSound = minim.loadSample("ceilhit.wav", 1024);
block1Sound = minim.loadSample("block1.wav", 1024);
block2Sound = minim.loadSample("block2.wav", 1024);
block3Sound = minim.loadSample("block3.wav", 1024);
block4Sound = minim.loadSample("block4.wav", 1024);
fireSound = minim.loadSample("firehit.wav", 1024);
blockSounds = new AudioSample[]{block1Sound, block2Sound, block3Sound, block4Sound};
box_particles = new ParticleList();
reset(0);
font = loadFont("PressStartK-64.vlw");
}
void stop()
{
hitRibbonSound.close();
hitWallSound.close();
hitCeilSound.close();
block1Sound.close();
block2Sound.close();
block3Sound.close();
block4Sound.close();
fireSound.close();
minim.stop();
super.stop();
}
class Box
{
PVector topleft;
PVector bottomright;
color c;
Box(PVector tl, PVector br)
{
topleft = new PVector(tl.x, tl.y);
bottomright = new PVector(br.x, br.y);
c = color(random(255), random(255), random(255));
}
boolean contains(PVector p)
{
return (p.x > topleft.x && p.x < bottomright.x && p.y > topleft.y && p.y < bottomright.y);
}
void draw()
{
fill(colorScale(c, 0.75));
stroke(colorAdd(c, color(96)));
strokeWeight(1);
rectMode(CORNERS);
rect(topleft.x, topleft.y, bottomright.x, bottomright.y);
}
}
void reset(int at_level)
{
lives = START_LIVES;
level = at_level;
boxes = new ArrayList();
int COLS = 8;
int ROWS = 8;
if(level >= LEVEL_MORE_BLOCKS) {COLS = 10;}
if(level >= LEVEL_EVEN_MORE_BLOCKS) {ROWS = 10;}
float MARG = 3;
ribbon_points = new ArrayList();
ribbon_points.add(new PVector(mouseX, mouseY));
if(level < LEVEL_VICTORY)
{
for(int c = 0; c < COLS; c++)
for(int r = 0; r < ROWS; r++)
{
float x1 = c*(width/COLS)+MARG;
float x2 = (c+1)*(width/COLS)-MARG;
float y1 = r*(0.5*height/ROWS)+MARG;
float y2 = (r+1)*(0.5*height/ROWS)-MARG;
boxes.add(new Box(new PVector(x1, y1), new PVector(x2, y2)));
}
}
serve();
ball_pos = new PVector(width/2, height*2);
ball_vel = new PVector(0, 1);
}
void serve()
{
ball_pos = new PVector(width/2, 0);
ball_vel = new PVector(0, 1);
fire_list = new ArrayList();
ribbon_length = RIBBON_LENGTH_START;
if(level >= LEVEL_VICTORY) ribbon_length = RIBBON_LENGTH_START * 3;
lives--;
}
void physicsStep()
{
box_particles.tick();
boolean cut = false;
if(level >= LEVEL_INTRODUCE_FIRE && level != LEVEL_BLOCK_FIRE && level < LEVEL_VICTORY) if(random(800) < level+1 && ball_pos.y < height) fire_list.add(new PVector(random(width), 0));
Iterator fi = fire_list.iterator();
while(fi.hasNext())
{
PVector f = (PVector)fi.next();
f.y += FIRE_SPEED + level*FIRE_ACCEL;
if(f.y > height) fi.remove();
else
{
float length_sum = 0;
int i;
for(i = 1; i < ribbon_points.size(); i++)
{
PVector p1 = (PVector) ribbon_points.get(i - 1);
PVector p2 = (PVector) ribbon_points.get(i);
float d = PVector.dist(p1, p2);
if(intersects(f, new PVector(f.x, f.y-(FIRE_SPEED+ level*FIRE_ACCEL)), p1, p2))
{
length_sum += d * intersectionFloat(f, new PVector(f.x, f.y-FIRE_SPEED), p1, p2);
fi.remove();
box_particles.add(new BurnParticle(f));
cut = true;
break;
}
else length_sum += d;
}
if(cut) {ribbon_length = min(ribbon_length, length_sum);
}
}
}
if(cut && !muted)fireSound.trigger();
if(PVector.dist((PVector) ribbon_points.get(0), new PVector(mouseX, mouseY)) > MIN_RIBBON_STEP)
ribbon_points.add(0, new PVector(mouseX, mouseY));
{
float lengthcount = ribbon_length;
int i;
for(i = 1; i < ribbon_points.size(); i++)
{
PVector p1 = (PVector) ribbon_points.get(i - 1);
PVector p2 = (PVector) ribbon_points.get(i);
float d = PVector.dist(p1, p2);
if(lengthcount > d) lengthcount -= d;
else
{
PVector to = PVector.sub(p2, p1);
to.normalize();
to.mult(lengthcount);
PVector np2 = PVector.add(p1, to);
p2.x = np2.x;
p2.y = np2.y;
break;
}
}
i++;
while(i < ribbon_points.size()) ribbon_points.remove(i);
}
ball_vel.y += GRAVITY;
if(ball_pos.y > height) {ball_vel.x = 0;}
PVector ball_future = PVector.add(ball_pos, ball_vel);
for(int i = 1; i < ribbon_points.size(); i++)
{
PVector p1 = (PVector) ribbon_points.get(i - 1);
PVector p2 = (PVector) ribbon_points.get(i);
if(intersects(p1, p2, ball_pos, ball_future))
{
ball_vel = reboundVel(p1, p2, ball_pos, ball_future);
ball_future = reboundPoint(p1, p2, ball_pos, ball_future);
// print("collided");
//paused = true;
if(!muted) hitRibbonSound.trigger();
if(level >= LEVEL_VICTORY)
{
int ind = (int) random(4);
if(!muted) blockSounds[ind].trigger();
box_particles.add(new BoxParticle(ball_pos, color(128+random(127), 128+random(127), 128+random(127))));
}
break;
}
}
ball_pos = ball_future;
if(ball_pos.x < 0) {ball_pos.x = -ball_pos.x; ball_vel.x = -ball_vel.x; if(!muted) hitWallSound.trigger();}
if(ball_pos.x > width) {ball_pos.x = width - (ball_pos.x - width); ball_vel.x = -ball_vel.x; if(!muted) hitWallSound.trigger();}
if(ball_pos.y < 0) {ball_pos.y = -ball_pos.y; ball_vel.y = -ball_vel.y; if(!muted) hitCeilSound.trigger();
if(level >= LEVEL_CEILING_FIRE && level < LEVEL_VICTORY) fire_list.add(new PVector(ball_pos.x, 0));}
Iterator bi = boxes.iterator();
while(bi.hasNext())
{
Box b = (Box) bi.next();
if(b.contains(ball_pos))
{
bi.remove();
int ind = (int) random(4);
if(!muted) blockSounds[ind].trigger();
if(level >= LEVEL_BLOCK_FIRE) fire_list.add(new PVector(ball_pos.x, ball_pos.y));
box_particles.add(new BoxParticle(ball_pos, colorAdd(b.c, color(96))));
}
}
if(ball_pos.y > height && mousePressed)
{
if(lives > 0)
serve();
else reset(0);
}
if(boxes.isEmpty() && level < LEVEL_VICTORY) reset(level+1);
}
void drawingStep()
{
if(!paused)draw_frame++;
background(0);
fill(48);
textFont(font, 64);
textAlign(CENTER, TOP);
if(level < LEVEL_VICTORY) text("Level "+(1+level), width/2, height*0.75);
else text("Victory", width/2, height*0.75);
textFont(font, 32);
textAlign(CENTER, BOTTOM);
text("Lives "+lives, width/2, height*0.75);
Iterator bi = boxes.iterator();
while(bi.hasNext())
{
Box b = (Box) bi.next();
b.draw();
}
stroke(255);
strokeWeight(2);
noFill();
smooth();
if(level >= LEVEL_INVISIBLE_RIBBON && level < LEVEL_VICTORY)
{
int i = ribbon_points.size() - 1;
PVector p = (PVector) ribbon_points.get(i);
ellipse(p.x, p.y, 3, 3);
}
else
{
beginShape();
for(int i = 0; i < ribbon_points.size(); i++)
{
PVector p = (PVector) ribbon_points.get(i);
vertex(p.x, p.y);
}
endShape();
}
noStroke();
fill(0, 255-((5+draw_frame)%10)*25, 255);
ellipse(ball_pos.x, ball_pos.y, 11, 11);
fill(255);
ellipse(ball_pos.x, ball_pos.y, 7, 7);
noSmooth();
box_particles.draw();
Iterator fi = fire_list.iterator();
while(fi.hasNext())
{
PVector f = (PVector)fi.next();
fill(255, 255-(draw_frame%10)*25, 0);
noStroke();
ellipse(f.x, f.y, 9, 9);
fill(255, 255, 255);
ellipse(f.x, f.y, 5, 5);
}
// ellipse(ball_pos.x + ball_vel.x, ball_pos.y + ball_vel.y, 7, 7);
fill(128);
textFont(font, 32);
textAlign(CENTER, BOTTOM);
if(ball_pos.y > height)
{
if(lives <= 0) text("Game Over", width/2, height-16);
else text("Click To Serve", width/2, height-16);
}
}
void draw()
{
while(physics_ms_last + PHYS_MS < millis())
{
if(!paused)
{
physicsStep();
}
physics_ms_last += PHYS_MS;
}
if(drawing_ms_last + DRAW_MS < millis())
{
drawingStep();
drawing_ms_last = millis();
}
}
void keyPressed()
{
if(!keys[keyCode])
{
keys[keyCode] = true;
down(keyCode);
}
}
void keyReleased()
{
if(keys[keyCode])
{
keys[keyCode] = false;
up(keyCode);
}
}
void down(int theKey)
{
println(theKey + " down");
if(theKey == 'P') paused = !paused;
if(theKey == 'M') muted = !muted;
if(theKey == '=' && !paused) reset(level+1);
}
void up(int theKey)
{
println(theKey + " up");
}