/* --- THE OCEAN WORLD ----------------------------------------------- Fish swim across the screen from right to left. There may be one fish, or a whole school of fish, or none at a time. A hungry shark swims in place at the left side, moving only up and down, controlled by the \scheme{"up"} and \scheme{"down"} keys. It tries to catch and eat the fish. It gets bigger with each fish it eats, it keeps getting hungrier and hungrier as the time goes on between the catches. It dies of starvation, if not fed in time. --------------------------------------------------------------------*/ import colors.*; import draw.*; import geometry.*; import java.util.Random; /* +----------+ | Shark | +----------+ | Posn loc | | int life | +----------+ */ // to represent a shark in an ocean world class Shark { Posn loc; int life; Shark(Posn loc, int life) { this.loc = loc; this.life = life; } // produce a new shark from this shark moved in response to the key press Shark onKeyEvent(String ke){ if (ke.equals("up")){ return new Shark(new Posn(this.loc.x, this.loc.y - 3), this.life); } else {if (ke.equals("down")){ return new Shark(new Posn(this.loc.x, this.loc.y + 3), this.life); } else{ return this; }} } // draw this shark on the given Canvas boolean draw(Canvas c){ return c.drawDisk(this.loc, this.life / 10 + 4, new Black()) && c.drawRect(new Posn(this.loc.x + this.life / 10 + 1, this.loc.y - 3), 10, 6, new Black()) && c.drawLine(new Posn(this.loc.x + this.life / 10 + 1, this.loc.y - 1), new Posn(this.loc.x + this.life / 10 + 10, this.loc.y - 1), new Yellow()) && c.drawLine(new Posn(this.loc.x + this.life / 10 + 1, this.loc.y), new Posn(this.loc.x + this.life / 10 + 10, this.loc.y), new Yellow()) && this.drawEye(c); } // draw shark's eye - if he is big enough boolean drawEye(Canvas c){ if (this.life > 20){ return c.drawDisk(new Posn(this.loc.x + this.life / 10 - 3, this.loc.y - 2), 2, new White()); } else{ return true; } } // produce a shark hungrier by a minute than this one Shark onTick(){ if (this.life > 0){ return new Shark(this.loc, this.life - 1); } else { return this; } } // produce a shark after this one ate one fish // for now every fish tastes the same // later, fish nutrition value may be determined by the size or the color Shark getFatter(){ return new Shark(this.loc, this.life + 30); } // is this shark dead? boolean isDead(){ return this.life <= 0; } } /* +-----------------+ | Fish | +-----------------+ | Posn loc | | boolean escaped | +-----------------+ */ // to represent a fish in an ocean world class Fish { Posn loc; boolean hasEscaped; Fish(Posn loc, boolean hasEscaped) { this.loc = loc; this.hasEscaped = hasEscaped; } // produce from this fish the one that swam for a minute more Fish onTick(){ if (hasEscaped){ return this.makeFish(200, 200); } else {if (this.loc.x < 0){ return new Fish(this.loc, true); } else { return new Fish(new Posn(this.loc.x - 3, this.loc.y + new Random().nextInt() % 7 - 3), false); }} } // has this fish escaped? boolean escaped(){ return (this.loc.x < 0) || this.hasEscaped; } // is this fish a food for the given shark? boolean isFood(Shark shark){ return Math.sqrt((this.loc.x - shark.loc.x) * (this.loc.x - shark.loc.x) + (this.loc.y - shark.loc.y) * (this.loc.y - shark.loc.y)) < 25; } // draw this fish on the given Canvas boolean draw(Canvas c){ return c.drawDisk(this.loc, 10, new Red()) && c.drawDisk(new Posn(this.loc.x - 3, this.loc.y), 2, new White()) && c.drawRect(new Posn(this.loc.x + 8, this.loc.y - 4), 4, 9, new Red()); } // produce a new fish at the right quarter of the canvas at random height Fish makeFish(int WIDTH, int HEIGHT){ return new Fish(new Posn(WIDTH - WIDTH / 4 + this.randomLimited(WIDTH / 4), this.randomLimited(HEIGHT)), false); } // produce a random initial coordinate of the fish int randomLimited(int LIMIT){ return new Random().nextInt() % LIMIT; } // replace this fish with a new one Fish feedShark(Shark shark){ return this.makeFish(200, 200); } } /* +---------+ | ILoFish |<--------------+ +---------+ | +---------+ | | | / \ | --- | | | ------------------- | | | | +----------+ +--------------+ | | MtLoFish | | ConsLoFish | | +----------+ +--------------+ | +----------+ | Fish first | | | ILoFish rest |-+ | +--------------+ | | | | +--+ */ // to represent a list of Fish interface ILoFish { // produce from this fish the one that swam for a minute more ILoFish onTick(); // is there a fish in this school that can be eaten by the given shark? boolean isFood(Shark shark); // produce a school of fish with one eaten and replaced by a new random one ILoFish feedShark(Shark shark); // draw this fish on the given Canvas boolean draw(Canvas c); } // to represent an empty list of Fish class MtLoFish implements ILoFish { MtLoFish() { } // produce from this fish the one that swam for a minute more ILoFish onTick(){ return this; } // is there a fish in this school that can be eaten by the given shark? boolean isFood(Shark shark){ return false; } // produce a school of fish with one eaten and replaced by a new random one ILoFish feedShark(Shark shark){ return this; } // draw this fish on the given Canvas boolean draw(Canvas c){ return true; } } // to represent a nonempty list of Fish class ConsLoFish implements ILoFish { Fish first; ILoFish rest; ConsLoFish(Fish first, ILoFish rest) { this.first = first; this.rest = rest; } // produce from this fish the one that swam for a minute more ILoFish onTick(){ return new ConsLoFish(this.first.onTick(), this.rest.onTick()); } // is there a fish in this school that can be eaten by the given shark? boolean isFood(Shark shark){ return this.first.isFood(shark) || this.rest.isFood(shark); } // produce a school of fish with one eaten and replaced by a new random one ILoFish feedShark(Shark shark){ if (this.first.isFood(shark)){ return new ConsLoFish(this.first.makeFish(200, 200), this.rest); } else { return new ConsLoFish(this.first, this.rest.feedShark(shark)); } } // draw this fish on the given Canvas boolean draw(Canvas c){ return this.first.draw(c) && this.rest.draw(c); } } /* +--------------+ | OceanWorld | +--------------+ | Shark shark | | ILoFish fish | +--------------+ */ // to represent an ocean world class OceanWorld extends World{ Shark shark; ILoFish fish; int WIDTH = 200; int HEIGHT = 200; OceanWorld(Shark shark, ILoFish fish) { this.shark = shark; this.fish = fish; } // start the world and the timer boolean go() { return this.bigBang(200, 200, 0.05); } // produce a new OceanWorld after one minute elapsed: // move the fish, starve the shark, check if the fish is eaten or has escaped World onTick(){ // if the shark found fish, fed the shark, replace the fish with a new one if (this.fish.isFood(this.shark)){ return new OceanWorld(this.shark.getFatter(), this.fish.feedShark(shark)); } // if the shark starved to death, end the world else {if(this.shark.isDead()) { return this.endOfWorld("The shark starved to death"); } // no special events, just move the fish and starve the shark else { return new OceanWorld(this.shark.onTick(), this.fish.onTick()); }} } // produce a new OceanWorld in response to the given key press World onKeyEvent(String ke){ return new OceanWorld(this.shark.onKeyEvent(ke), this.fish); } // draw this world boolean draw(){ return this.theCanvas.drawRect(new Posn(0, 0), this.WIDTH, this.HEIGHT, new Blue()) && this.fish.draw(this.theCanvas) && this.shark.draw(this.theCanvas); } } // Examples of the classes Fish, Shark, OceanWorld and tests for all methods class Examples{ Examples(){} Shark mac = new Shark(new Posn(10, 100), 200); Shark mac2 = new Shark(new Posn(10, 100), 100); Shark mac3 = new Shark(new Posn(10, 100), -1); Fish fishy = new Fish(new Posn(190, 100), false); Fish yummy = new Fish(new Posn(15, 98), false); Fish gone = new Fish(new Posn(15, 98), true); Fish gone2 = new Fish(new Posn(-2, 98), false); Fish fishy1 = this.fishy.makeFish(200, 200); Fish fishy2 = this.fishy.makeFish(200, 200); Fish fishy3 = this.fishy.makeFish(200, 200); Fish fishy4 = this.fishy.makeFish(200, 200); Fish fishy5 = this.fishy.makeFish(200, 200); ILoFish nofish = new MtLoFish(); ILoFish fishies = new ConsLoFish(fishy1, new ConsLoFish(fishy2, new ConsLoFish(fishy3, new ConsLoFish(fishy4, new ConsLoFish(fishy5, new MtLoFish()))))); ILoFish manyfish = new ConsLoFish(fishy, new ConsLoFish(yummy, new MtLoFish())); OceanWorld sfw = new OceanWorld(this.mac, this.fishies); Canvas c = new Canvas(200, 200); boolean go(){ return sfw.bigBang(200, 200, 0.1); } // test the method onKeyEvent in the class Shark boolean testSharkOnKeyEvent(){ return (check this.mac.onKeyEvent("left") expect this.mac) && (check this.mac.onKeyEvent("up") expect new Shark(new Posn(10, 97), 200)) && (check this.mac.onKeyEvent("down") expect new Shark(new Posn(10, 103), 200)); } // a visual test for drawing a Shark boolean drawShark(){ return c.show() && this.mac.draw(c); } // test the method onTick in the class Shark boolean testSharkOnTick(){ return (check this.mac.onTick() expect new Shark(new Posn(10, 100), 199)) && (check (new Shark(new Posn(10, 100), 0)).onTick() expect new Shark(new Posn(10, 100), 0)); } // test the method getFatter in the class Shark boolean testGetFatter(){ return (check mac.getFatter() expect new Shark(new Posn(10, 100), 230)) && (check mac2.getFatter() expect new Shark(new Posn(10, 100), 130)); } // test the method isDead in the class Shark boolean testisDead(){ return (check mac.isDead() expect false) && (check mac3.isDead() expect true); } // test the method onTick in the class Fish // results are correct if the random move is replaced with exactly 5 boolean testFishOnTick(){ return (check this.fishy.onTick() expect new Fish(new Posn(187, 105), false)) && (check (new Fish(new Posn(-2, 100), true)).onTick() expect new Fish(new Posn(-2, 100), true)) && (check (new Fish(new Posn(-2, 100), false)).onTick() expect new Fish(new Posn(-2, 100), true)); } // test the method escaped in the class Fish boolean testFishEscaped(){ return (check this.fishy.escaped() expect false) && (check (new Fish(new Posn(-2, 100), true)).escaped() expect true) && (check (new Fish(new Posn(-2, 100), false)).escaped() expect true); } // test the method isFood in the class Fish boolean testFishIsFood(){ return (check this.fishy.isFood(this.mac) expect false) && (check this.yummy.isFood(new Shark(new Posn(10, 100), 20)) expect true); } // a visual test for drawing a Fish boolean drawFish(){ return c.show() && this.fishy.draw(c); } // test for the method randomLimited in the class Fish boolean testRandomLimited(){ return check (this.fishy.randomLimited(5) < 6 && this.fishy.randomLimited(5) > 0) expect true; } // test the method makeFish in the class Fish boolean testMakeFish(){ return (check this.fishy.makeFish(200, 200).hasEscaped expect false) && (check this.fishy.makeFish(200, 200).loc.y < 200 expect true) && (check this.fishy.makeFish(200, 200).loc.y > 0 expect true) && (check this.fishy.makeFish(200, 200).loc.x < 200 expect true) && (check this.fishy.makeFish(200, 200).loc.x > 150 expect true); } ILoFish manyfishNext = new ConsLoFish(fishy.onTick(), new ConsLoFish(yummy.onTick(), new MtLoFish())); ILoFish manyfishEaten = new ConsLoFish(fishy, new ConsLoFish(yummy.feedShark(this.mac), new MtLoFish())); // test the method onTick in the classes that represent a school of fish boolean testILoFishOnTick(){ return (check this.nofish.onTick() expect this.nofish) && (check this.manyfish.onTick() expect this.manyfishNext); } // test the method isFood in the classes that represent a school of fish boolean testILoFishIsFood(){ return (check this.nofish.isFood(this.mac) expect false) && (check this.manyfish.isFood(this.mac) expect true) && (check this.fishies.isFood(this.mac) expect false); } // test the method feedShark in the classes that represent a school of fish boolean testILoFishFeedShark(){ return (check this.nofish.feedShark(this.mac) expect this.nofish) && (check this.manyfish.feedShark(this.mac) expect this.manyfishEaten) && (check this.fishies.feedShark(this.mac) expect this.fishies); } // a visual test for drawing a school of fish boolean drawFishies(){ return c.show() && this.manyfish.draw(c); } // test the method onKeyEvent in the class OceanWorld boolean testOceanOnKeyEvent(){ return (check this.sfw.onKeyEvent("left") expect new OceanWorld(this.mac, this.fishies)) && (check this.sfw.onKeyEvent("up") expect new OceanWorld(new Shark(new Posn(10, 97), 200), this.fishies)) && (check this.sfw.onKeyEvent("down") expect new OceanWorld(new Shark(new Posn(10, 103), 200), this.fishies)); } OceanWorld sfwEnd = new OceanWorld(this.mac3, this.fishies); OceanWorld sfwReg = new OceanWorld(this.mac, this.fishies); OceanWorld sfwYum = new OceanWorld(this.mac, this.manyfish); // test the method onTick in the class OceanWorld boolean testOceanOnTick(){ return // world goes on (check this.sfwReg.onTick() expect new OceanWorld(this.mac.onTick(), this.fishies.onTick())) && // fish is eaten, shark is fatter, new fish appears (check this.sfwYum.onTick() expect new OceanWorld(this.mac.getFatter(), this.manyfishEaten)); // world ends - the shark starves to death --- fails due to randomness // cannot invoke method in the teachpack - no test is possible //(check this.sfwEnd.onTick() // expect this.sfw.endWorld("The shark has starved to death")); } /* // a visual test for drawing the OceanWorld boolean drawOcean(){ return this.sfw.draw(); } */ /* To run the world, run the program, then type in the following two line in the Interactions window: Examples e = new Examples(); e.go(); */ }