import ddf.minim.*; import ddf.minim.signals.*; import ddf.minim.analysis.*; import ddf.minim.effects.*; import javax.sound.midi.*; //import processing.opengl.*; final int imageScale = 4; final int leftBorder = 48, rightBorder = 48; final int arenaWidth = 400 + leftBorder + rightBorder; // arenaWidth includes the border. final int arenaHeight = 700; final float stringHeight = arenaHeight - 200.5; final float mouseMaxY = arenaHeight - 50; final float stringLength = arenaWidth * 0.75, stringSpringiness0 = 0.04, stringSpringiness1 = 0.04; final int stringNSegments = 6; final float pi = 3.141593; final float gravity = 0.2; final float heartRepulsion = 2; PImage bgdImage; PImage heartImage; Animation coupleAnimation, cryAnimation, chomperAnimation, loveAnimation, mergeAnimation, bombAnimation, spikyAnimation, bumperAnimation, cannonAnimation; Minim minim; AudioSample wallhit, wallhitsoft, stringhit, bumperhit, monsterhit, couplehit, explosion, chirp; CapsFont font, invfont, fontBlack, fontGray; GuiButton levelSelect; String lossReason = ""; MouseStringable theMouseStringable = new MouseStringable(); ArrayList theHearts, theBouncies, theEntities, theGuis, theBombs; Bowstring theString = new Bowstring(leftBorder - 0.5, stringHeight, arenaWidth - rightBorder + 0.5, stringHeight, stringLength, stringSpringiness0, stringSpringiness1); int nLiveHearts = 0; int t = 0; int backgroundColor = color(255,255,255); int coupleCount = 0; int currentLevel; boolean playingTheLevel; boolean wasMousePressed; int wasMouseButton; GuiButton closeLevel; boolean hasWon[]; Sequence titleSong, failSong, winSong; Sequence happyLevelSongs[]; String happyLevelSongNames[] = { "low cost map.MID", "JOY01.MID", "LOT_BON.MID" }; int lastHappySong = 0; void setup() { // size(arenaWidth, arenaHeight); size(496, 700); frameRate(60); titleSong = LoadSequence("29TAPISi.MID"); failSong = LoadSequence("Chateau-2.MID"); //levelSong = LoadSequence("low cost map.MID"); winSong = LoadSequence("TRAIN.MID"); happyLevelSongs = new Sequence[happyLevelSongNames.length]; for (int i = 0; i < happyLevelSongNames.length; i++) { happyLevelSongs[i] = LoadSequence(happyLevelSongNames[i]); } minim = new Minim(this); wallhit = minim.loadSample("wallhit.wav", 2048); wallhitsoft = minim.loadSample("wallhit.wav", 2048); // need to get a consistent-but-softer sound? bumperhit = minim.loadSample("nicebouncy.wav", 2048); monsterhit = minim.loadSample("loseheart.wav", 2048); couplehit = minim.loadSample("goodhit.wav", 2048); chirp = minim.loadSample("chirp8.wav", 2048); explosion = minim.loadSample("explode_wobbly.wav", 2048); heartImage = loadImage("heart.gif"); coupleAnimation = new Animation("couple.gif", "couple.csv"); coupleAnimation.init(); cryAnimation = new Animation("cry.gif", "cry.csv"); cryAnimation.init(); chomperAnimation = new Animation("chomper.gif", "chomper.csv"); chomperAnimation.init(); loveAnimation = new Animation("love.gif", "love.csv"); loveAnimation.init(); mergeAnimation = new Animation("heartform.gif", "heartform.csv"); mergeAnimation.init(); bombAnimation = new Animation("bomb.gif", "bomb.csv"); bombAnimation.init(); spikyAnimation = new Animation("pulsating.gif", "pulsating.csv"); spikyAnimation.init(); bumperAnimation = new Animation("bumper2.gif", "bumper.csv"); bumperAnimation.init(); cannonAnimation = new Animation("cannon.gif", "cannon.csv"); cannonAnimation.init(); closeLevel = new GuiButton("MENU", width-46, 5, 2); hasWon = new boolean[theStages.length]; bgdImage = loadImage("woman1.png"); font = new CapsFont("alphabet.gif"); fontBlack = new CapsFont(font, color(255,0,0), color(0,255,255)); fontGray = new CapsFont(font, color(255,0,0), color(200,150,150)); invfont = new CapsFont(font, color(255,0,0), color(250,150,150)); theEntities = new ArrayList(); theBouncies = new ArrayList(); theHearts = new ArrayList(); theGuis = new ArrayList(); theBombs = new ArrayList(); theString.stringables.add(theMouseStringable); theEntities.add(theString); theEntities.add(theMouseStringable); initGameStart(); } class GuiButton { String msg; boolean hot, down; float x, y; int s; boolean old; int id; GuiButton(String m, float x, float y, int s) { this.x = x; this.y = y; this.s = s; this.msg = m; this.id = -1; } GuiButton(String m, float x, float y, int s, int id, boolean old) { this.x = x; this.y = y; this.s = s; this.msg = m; this.id = id; this.old = old; } boolean isMouseOver() { return (mouseX-x>=-s && mouseX-x<=msg.length()*font.w*s+s && mouseY-y>=-s && mouseY-y<=font.h*s+s); } // returns true if button was clicked boolean Update() { boolean mouseOver = isMouseOver(); if (hot && down) { if (!(mousePressed)) { if (mouseOver) { down = false; return true; } else { down = false; hot = mouseOver; } } } else if (!down) { hot = mouseOver && !(wasMousePressed); if (hot) { if (mousePressed) down = true; } } return false; } void Draw() { if (!hot && !down) { if (!old) font.Draw(x,y,msg,s); else fontGray.Draw(x,y,msg,s); } else if ((hot && !down)) { invfont.Draw(x,y,msg,s); } else { if (isMouseOver()) invfont.Draw(x+s,y+s,msg,s); else { if (!old) font.Draw(x+s,y+s,msg,s); else fontGray.Draw(x+s,y+s,msg,s); } } } } void nukeLevel() { // from orbit theHearts.clear(); theBouncies.clear(); theEntities.clear(); theGuis.clear(); theBombs.clear(); theString.stringables.clear(); theString.strung.clear(); nLiveHearts = 0; coupleCount = -1; t = 0; backgroundColor = color(255,255,255); theString.stringables.add(theMouseStringable); theEntities.add(theString); theEntities.add(theMouseStringable); } // just a layer of indirection to make it easier to change how audio is played void playSound(AudioSample as) { as.trigger(); } void stop() { if (playing != null) { playing.stop(); playing.close(); } // always close minim audio classes wallhit.close(); wallhitsoft.close(); //stringhit.close(); bumperhit.close(); monsterhit.close(); couplehit.close(); chirp.close(); // always stop minim minim.stop(); super.stop(); } void initLevel() { lastHappySong = (lastHappySong + 1) % happyLevelSongs.length; play(happyLevelSongs[lastHappySong]); nukeLevel(); playingTheLevel = true; coupleCount = 0; theStages[currentLevel].init(); for (int i = 0; i < theEntities.size(); ++ i) { ((Entity)theEntities.get(i)).levelStart(); } } void initGameStart() { play(titleSong); nukeLevel(); playingTheLevel = false; currentLevel = -1; if (!font.inited) font.init(); theGuis.add(new GuiButton("start game",70 + leftBorder,350,4)); theGuis.add(new GuiButton("level select",55 + leftBorder,400,4)); (new LevelMessage("Love", 130+leftBorder,40,6)).addToLevel(); (new LevelMessage("trampoline", 200+leftBorder,85,6)).addToLevel(); println(leftBorder); (new LevelMessage("a game by", 200+leftBorder,160,2)).addToLevel(); (new LevelMessage("james cook and jimmy andrews", 200+leftBorder,180,2)).addToLevel(); (new LevelMessage("cc licensed music by Morusque", 200+leftBorder,250,2)).addToLevel(); (new LevelMessage("http://ccmixter.org/files/Nurykabe/10176", 200+leftBorder,270,1)).addToLevel(); (new Heart(300, 200)).addToLevel(); } void initGameOver() { play(failSong); nukeLevel(); playingTheLevel = false; (new LevelMessage(lossReason, 200 + leftBorder, 80, 2)).addToLevel(); lossReason = ""; theGuis.add(new GuiButton("retry",110 + leftBorder,180,6)); if (currentLevel == 0 || currentLevel + 1 < theStages.length && (hasWon[currentLevel] || hasWon[currentLevel - 1])) theGuis.add(new GuiButton("skip level",85 + leftBorder,280,4)); theGuis.add(new GuiButton("level select",55 + leftBorder,360,4)); backgroundColor = color(100,100,250); (new Bomb(300, 200)).addToLevel(); } void initLevelWin() { play(winSong); nukeLevel(); playingTheLevel = false; (new LevelMessage("Nice job!", 200 + leftBorder, 80, 4)).addToLevel(); theGuis.add(new GuiButton("next level",25 + leftBorder,180,6)); theGuis.add(new GuiButton("level select",55 + leftBorder,280,4)); (new Heart(300, 200)).addToLevel(); backgroundColor = color(100,100,250); } void initLevelSelect() { play(titleSong); nukeLevel(); playingTheLevel = false; (new LevelMessage("Select level:", 200+leftBorder, 30, 5)).addToLevel(); int sinceLastHasWon = 0; for (int i = 0; i < theStages.length; i++) { // only allow seeing ahead so far ... if (hasWon[i]) sinceLastHasWon = 0; else sinceLastHasWon++; if (sinceLastHasWon > 2) break; theGuis.add(new GuiButton(i + ": " + theStages[i].name(), leftBorder + 10, 80+25*i,3,i, hasWon[i])); } backgroundColor = color(100,100,250); } void initGameWin() { play(winSong); nukeLevel(); playingTheLevel = false; currentLevel = -1; theGuis.add(new GuiButton("you win!", 65 + leftBorder, 200, 6)); backgroundColor = color(255,200,200); (new Heart(10, 10)).addToLevel(); (new Heart(150, 400)).addToLevel(); (new Heart(300, 200)).addToLevel(); (new Heart(120, 10)).addToLevel(); (new Heart(130, 400)).addToLevel(); (new Heart(310, 200)).addToLevel(); //(new Heart(105, 10)).addToLevel(); (new Heart(250, 400)).addToLevel(); (new Heart(260, 200)).addToLevel(); } // Draws something that looks a bit like a pixellated line. The first block will be vertically centred at y0, and the last block will be vertically centred at y1. void blockyLine(float x0, float y0, float x1, float y1) { if (x0 > x1) { blockyLine(x1, y1, x0, y0); return; } final int blockWidth = 8; final int blockHeight = 4; final int x0i = (int)x0 - (int)x0 % blockWidth; final int x1i = (int)x1 - (int)x1 % blockWidth; for (int x = x0i; x <= x1i; x += blockWidth) { float t = (float)(x - x0i) / (x1i - x0i); float y = (1 - t) * y0 + t * y1; rect(x, y - blockHeight / 2, blockWidth, blockHeight); } } float pwr(float b, float e) { return exp(e * log(b)); } void draw() { if (playingTheLevel) { if (theStages[currentLevel].lost()) { initGameOver(); } if (theStages[currentLevel].won() || (keyPressed && key == 'l')) { hasWon[currentLevel] = true; ++ currentLevel; if (currentLevel == theStages.length) initGameWin(); else initLevelWin(); //initLevel(); } } // Begin Physics // Phase 0: update positions. for (int i = 0; i < theEntities.size(); ++ i) { Entity e = ((Entity)(theEntities.get(i))); e.doPhysics0(); if (i >= theEntities.size() || e != ((Entity)(theEntities.get(i)))) i--; } // Phase 1: update velocities. for (int i = 0; i < theEntities.size(); ++ i) { Entity e = ((Entity)(theEntities.get(i))); e.doPhysics1(); if (i >= theEntities.size() || e != ((Entity)(theEntities.get(i)))) i--; } // Collisions. for (int i = 0; i < theBouncies.size(); ++ i) { Bouncy a = (Bouncy)theBouncies.get(i); for (int j = i + 1; j < theBouncies.size(); ++ j) { Bouncy b = (Bouncy)theBouncies.get(j); if (dist(a.x, a.y, b.x, b.y) < a.collisionRadius + b.collisionRadius) a.collideWith(b); } } // End Physics // Begin Drawing background(255, 255, 255); // Simple border. //image(bgdImage, 0,0,bgdImage.width*8,bgdImage.height*8); // The Heart Trails for (int i = 0; i < Heart.trailLength - 2; i ++) { int j = (t/Heart.framesPerTrailPoint + 1 + i) % Heart.trailLength; int k = (j + 1) % Heart.trailLength; float age = (Heart.trailLength - i) / (float)Heart.trailLength; stroke(255, 200 + 55 * age, 200 + 55 * age); fill(255, 200 + 55 * age, 200 + 55 * age); for (int l = 0; l < theHearts.size(); ++ l) { Heart h = (Heart)theHearts.get(l); if (!h.trailOn[j]) continue; ellipse(h.trailX[j], h.trailY[j], 2 * Heart.trailRadius, 2 * Heart.trailRadius); if (!h.trailOn[k]) continue; strokeWeight(2 * Heart.trailRadius + 2); line(h.trailX[j], h.trailY[j], h.trailX[k], h.trailY[k]); strokeWeight(1); } } // Now, the entities! for (int i = 0; i < theEntities.size(); ++ i) ((Entity)(theEntities.get(i))).draw(); for (int i = 0; i < theGuis.size(); ++i) { GuiButton g = ((GuiButton)(theGuis.get(i))); if (g.Update()) { if (g.msg.equals("start game")) { currentLevel = 0; initLevel(); return; } else if (g.msg.equals("retry")) { initLevel(); return; } else if (g.msg.equals("you win!")) { initGameStart(); return; } else if (g.msg.equals("next level")) { initLevel(); return; } else if (g.msg.equals("level select")) { initLevelSelect(); return; } else if (g.msg.equals("skip level")) { ++ currentLevel; initLevel(); return; } else if (g.id > -1) { currentLevel = g.id; initLevel(); return; } } ((GuiButton)(theGuis.get(i))).Draw(); } if (coupleCount > 0) font.Draw(10 + leftBorder,10,"Singles left: " + (coupleCount*2), 2); stroke(255, 200, 200); fill(255, 200, 200); rect(0, 0, leftBorder, arenaHeight); rect(arenaWidth - rightBorder, 0, rightBorder, arenaHeight); // font.Draw(10,50,"Framerate: " + int(frameRate), 2); if (playingTheLevel && closeLevel.Update()) { initLevelSelect(); return; } if (playingTheLevel) closeLevel.Draw(); // End Drawing ++ t; wasMousePressed = mousePressed; wasMouseButton = mouseButton; }