import ddf.minim.*; /** * Game 14: "Ribbonoid"
* Use the ribbon to bounce the ball.
*
Go play more games at NMcCoy.net!
* Creative Commons License
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"); }