import ddf.minim.*;
/**
* Game 11: "Waterfall Bomb"
* Avoid rocks and bombs. Use rocks as shelter from explosions.
* P to pause, M to mute.
*
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;
boolean game_over;
int drawing_ms_last;
int physics_ms_last;
int CORE_FPS = 300;
int DRAWING_FPS = 60;
int PHYSICS_FPS = 180;
float DRAW_MS = 1000.0 / DRAWING_FPS;
float PHYS_MS = 1000.0 / PHYSICS_FPS;
int physframe;
int drawframe;
Minim minim;
ParticleList particles;
float WATER_ACCEL = 0.03;
int WATER_LIFE = 200;
float WATER_SW = 30;
float WATER_EW = 10;
float ROCK_RADIUS = 30;
float ROCK_ADD_RADIUS = 20;
float BOMB_RADIUS = 15;
float MARGIN = 50;
int EXPLOSIONS_PER_BLAST = 50;
float EXPL_SPEED = 2;
float EXPL_SIZE = 10;
float EXPL_GROWTH = 0.2;
int BOMB_START_FRAMES = 500;
int BOMB_INCREASE_RATE = 5;
int BOMB_MAX_RATE = 150;
int ROCK_FRAMES = 50;
int LIFE_FRAMES = 4000;
int STUN_TIME = PHYSICS_FPS * 3;
int START_LIVES = 5;
float GRAVITY = 0.0015;
float INITIAL_VEL = 0.5;
ArrayList falling_stuff;
ParticleList explosions;
PVector player;
int score;
int lives;
float drop_offset;
int stun_timer;
int extra_lives;
int bomb_counter;
int rock_counter;
int life_counter;
AudioSample bombSound;
AudioSample hurtSound;
AudioSample lifeSound;
AudioPlayer bgm;
PFont font;
void setup()
{
size(720, 480, P2D);
frameRate(600);
minim = new Minim(this);
bombSound = minim.loadSample("explo1.wav", 1024);
bombSound.setGain(-10);
hurtSound = minim.loadSample("hurt.wav", 1024);
hurtSound.setGain(-10);
lifeSound = minim.loadSample("1up.wav", 1024);
lifeSound.setGain(-10);
bgm = minim.loadFile("dripblop.mp3", 2048);
bgm.loop();
reset();
cursor(CROSS);
font = loadFont("ARBERKLEY-48.vlw");
title = true;
game_over = true;
particles = new ParticleList();
}
void stop()
{
bombSound.close();
hurtSound.close();
lifeSound.close();
bgm.close();
minim.stop();
super.stop();
}
void reset()
{
title = false;
score = life_counter = bomb_counter = rock_counter = 0;
game_over = false;
stun_timer = STUN_TIME;
lives = START_LIVES;
player = new PVector(mouseX, mouseY);
falling_stuff = new ArrayList();
explosions = new ParticleList();
}
void loseLife()
{
lives--;
stun_timer = STUN_TIME;
if(!muted) hurtSound.trigger();
if(lives == 0) game_over = true;
}
void drawPlayer()
{
ellipseMode(RADIUS);
strokeWeight(2);
fill(255 - (drawframe % 5) * 64, 255, 0);
stroke(0, 255, 255 - ((drawframe + 2) % 5) * 64);
if(stun_timer == 0)
{
ellipse(player.x, player.y, 9, 9);
}
else
{
if((drawframe / 4) % 2 == 0) ellipse(player.x, player.y, 9, 9);
noFill();
ellipse(player.x, player.y, stun_timer * 2, stun_timer * 2);
}
}
void drawHud()
{
fill(0);
textFont(font, 24);
textAlign(LEFT, TOP);
if(!title) text("Score: "+score + "\nLives: " + lives, 8, 8);
textFont(font, 48);
textAlign(CENTER, CENTER);
if(game_over)
{
if(title)text("Waterfall Bomb\nclick to play", width/2, height/2);
else
text("Game Over", width/2, height/2);
}
else if(paused)
{
text("Paused\nclick dot to resume", width/2, height/2);
}
}
void mousePressed()
{
if(game_over) reset();
else if(paused)
{
if(PVector.dist(new PVector(mouseX, mouseY), player) < 10) paused = false;
}
}
class Explosion extends Particle
{
float radius;
Explosion(PVector p, int num)
{
super(p, new PVector(cos(TWO_PI / EXPLOSIONS_PER_BLAST * num) * EXPL_SPEED, sin(TWO_PI / EXPLOSIONS_PER_BLAST * num) * EXPL_SPEED), 0);
radius = EXPL_SIZE;
// float angle = TWO_PI / EXPLOSIONS_PER_BLAST * num;
}
void tick()
{
radius += EXPL_GROWTH;
super.tick();
}
void draw()
{
fill(300+life/2, 255 + life, 255 + life*2);
noStroke();
ellipseMode(RADIUS);
ellipse(pos.x, pos.y, radius, radius);
}
}
abstract class FallingThing
{
PVector pos;
PVector vel;
float radius;
abstract void draw();
abstract void die();
}
class Rock extends FallingThing
{
Rock(PVector p)
{
pos = p; vel = new PVector(0, INITIAL_VEL); radius = ROCK_RADIUS + random(ROCK_ADD_RADIUS);
}
void draw()
{
ellipseMode(RADIUS);
fill(192, 128, 64);
stroke(128, 64, 0);
strokeWeight(2);
ellipse(pos.x, pos.y, radius, radius);
}
void die()
{
}
}
class ExtraLife extends FallingThing
{
ExtraLife(PVector p)
{
pos = p; vel = new PVector(0, INITIAL_VEL); radius = 15;
}
void draw()
{
ellipseMode(RADIUS);
fill(255 - (drawframe % 5) * 64, 255, 0);
stroke(0, 255, 255 - ((drawframe + 2) % 5) * 64);
strokeWeight(2);
ellipse(pos.x, pos.y, radius, radius);
noFill();
ellipse(pos.x, pos.y, radius * 2 - radius * sin(drawframe * 0.3), radius* 2 + radius * cos(drawframe * 0.3));
}
void die()
{
}
}
class Bomb extends FallingThing
{
Bomb(PVector p)
{
pos = p; vel = new PVector(0, INITIAL_VEL); radius = BOMB_RADIUS;
}
void draw()
{
ellipseMode(RADIUS);
fill(64, 64, 64);
stroke(255 - 15*(drawframe%16), 0, 0);
strokeWeight(2);
ellipse(pos.x, pos.y, radius, radius);
}
void die()
{
if(!game_over) score++;
if(!muted) bombSound.trigger();
for(int i = 0; i < EXPLOSIONS_PER_BLAST; i++)
{
explosions.add(new Explosion(pos, i));
}
}
}
void physicsStep()
{
player.x = mouseX;
if(player.x < 0) player.x = 0;
if(player.x > width) player.x = width;
player.y = mouseY;
if(player.y < 0) player.y = 0;
if(player.y > height) player.y = height;
if(physframe % 1 == 0) particles.add(new WaterDrop(new PVector(random(width), 0)));
if(!game_over)
{
bomb_counter++;
rock_counter++;
life_counter++;
}
stun_timer = max(stun_timer - 1, 0);
if(bomb_counter > max(BOMB_START_FRAMES - BOMB_INCREASE_RATE * score, BOMB_MAX_RATE))
{
drop_offset += width/4 + random(width/2);
while(drop_offset > width) drop_offset -= width;
falling_stuff.add(new Bomb(new PVector(drop_offset, -MARGIN)));
bomb_counter = 0;
}
if(rock_counter > ROCK_FRAMES)
{
drop_offset += width/4 + random(width/2);
while(drop_offset > width) drop_offset -= width;
falling_stuff.add(new Rock(new PVector(drop_offset, -MARGIN)));
rock_counter = 0;
}
if(life_counter > LIFE_FRAMES)
{
drop_offset += width/4 + random(width/2);
while(drop_offset > width) drop_offset -= width;
falling_stuff.add(new ExtraLife(new PVector(drop_offset, -MARGIN)));
life_counter = 0;
}
explosions.tick();
Iterator exi = explosions.iterator();
ArrayList blasts = new ArrayList();
while(exi.hasNext())
{
Explosion ex = (Explosion) exi.next();
if(ex.pos.x < -MARGIN || ex.pos.x > width+MARGIN || ex.pos.y < -MARGIN || ex.pos.y > height+MARGIN) exi.remove();
else
{
Iterator fsi = falling_stuff.iterator();
while(fsi.hasNext())
{
FallingThing ft = (FallingThing)fsi.next();
if(inRadius(ft.pos, ex.pos, ft.radius + ex.radius))
{
if(ft instanceof Bomb)
{
blasts.add(ft);
fsi.remove();
}
else
{
float distance = PVector.dist(ft.pos, ex.pos);
ex.radius = distance - ft.radius;
}
}
}
if(ex.radius < 0) exi.remove();
else if(inRadius(player, ex.pos, ex.radius) && !game_over)
{
if(stun_timer == 0) loseLife();
}
}
}
for(int i = 0; i < blasts.size(); i++)
{
Bomb b = (Bomb) blasts.get(i);
b.die();
}
particles.tick();
Iterator fsi = falling_stuff.iterator();
while(fsi.hasNext())
{
FallingThing ft = (FallingThing)fsi.next();
ft.pos.add(ft.vel);
ft.vel.y += GRAVITY;
if(ft.pos.y > height + MARGIN || (ft instanceof Bomb && ft.pos.y > height))
{
ft.die();
fsi.remove();
}
else if(inRadius(player, ft.pos, ft.radius) && !game_over)
{
if(ft instanceof ExtraLife)
{
lives++;
if(!muted) lifeSound.trigger();
fsi.remove();
}
else if(stun_timer == 0) loseLife();
}
}
physframe++;
}
void drawingStep()
{
background(0, 128, 255);
particles.draw();
explosions.draw();
Iterator fsi = falling_stuff.iterator();
while(fsi.hasNext())
{
FallingThing ft = (FallingThing)fsi.next();
ft.draw();
}
if(!game_over) drawPlayer();
drawHud();
drawframe++;
}
class WaterDrop extends Particle
{
WaterDrop(PVector p)
{
super(p, new PVector(0, 0), new PVector(0, WATER_ACCEL), WATER_LIFE);
}
void draw()
{
//strokeWeight(1);
ellipseMode(CORNERS);
rectMode(CORNERS);
float lp = ((float)life)/maxlife;
fill(lp*255, lp*128+127, 255);
noStroke();
rect((int)pos.x - WATER_SW*lp - WATER_EW, 0, (int)pos.x + WATER_SW*lp + WATER_EW, pos.y);
}
}
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' && !game_over) paused = true;
if(theKey == 'M')
{
muted = !muted;
if(muted) bgm.mute();
else bgm.unmute();
}
}
void up(int theKey)
{
println(theKey + " up");
}