import tester.*; import javalib.funworld.*; import javalib.colors.*; import javalib.worldimages.*; import java.util.*; import java.awt.Color; // the basic data used by the ocean world game interface OceanWorldConstants{ // a random number generator public Random rand = new Random(); // the width of the world public int WIDTH = 400; // the height of the world public int HEIGHT = 400; // the color of the ocean public Color oceanColor = new Color(50, 150, 255); // the background image of the ocean public WorldImage oceanImage = new RectangleImage(new Posn(WIDTH/2, HEIGHT/2), WIDTH, HEIGHT, oceanColor); } //To represent a location (x,y) in graphics coordinates //-- adding methods to the library Posn class class CartPt extends Posn implements OceanWorldConstants{ // teh standard constructor - invokes the one in the super class CartPt(int x, int y) { super(x, y); } /** TEMPLATE Fields: ... this.x ... -- int ... this.y ... -- int Methods: ... this.moveLeft(int) ... -- CartPt ... this.moveLeftRandom(int) ... -- CartPt ... this.distTo(CartPt) ... -- double */ // Move this CartPt left i units -- or back to the right, if out of bounds CartPt moveLeft(int i) { if (this.x - i < 0) return new CartPt(WIDTH, this.y); else return new CartPt(this.x-i, this.y); } // Move this CartPt left i units -- or back to the right, if out of bounds // also move randomly up or down -2 to 2 pixel CartPt moveLeftRandom(int i) { if (this.x - i < 0) return new CartPt(WIDTH, this.y); else // our tests did not catch that rand.nextInt(2) did not provide all desired values return new CartPt(this.x-i, this.y + rand.nextInt(5) - 2); } // Compute the distance from this point to the given one double distTo(CartPt that){ return Math.sqrt((this.x - that.x) * (this.x - that.x) + (this.y - that.y) * (this.y - that.y)); } } // A Shark swims in the ocean, looking for fish to eat class Shark implements OceanWorldConstants{ int y; // the horizontal coordinate int health; // the hunger level, when zero, the shark dies // the standard complete constructor Shark(int y, int health) { this.y = y; this.health = health; } /** TEMPLATE Fields: ... this.y ... -- int ... this.health ... -- int Methods: ... this.move(int) ... -- CartPt ... this.sharkImage() ... -- WorldImage ... this.starve() ... -- Shark ... this.isDead() ... -- boolean ... this.eat(int) ... -- Shark */ // produce the position of this shark CartPt sharkPos(){ return new CartPt(20, this.y); } // move the shark up or down, on up-down key press Shark move(String ke){ if (ke.equals("up")) return new Shark(this.y - 3, this.health); else if (ke.equals("down")) return new Shark(this.y + 3, this.health); else return this; } // produce the image of this shark at its position WorldImage sharkImage(){ return new FromFileImage(this.sharkPos(), "small-shark.png"); } // produce a shark hungrier than before Shark starve(){ return new Shark(this.y, this.health - 1); } // is this shark dead? boolean isDead(){ return this.health <= 0; } // produce a fatter shark after he ate the fish of the given size Shark eat(int size){ return new Shark(this.y, this.health + size); } } // A Fish swims in the ocean - providing nourishment for the shark class Fish implements OceanWorldConstants{ CartPt p; // the location of this fish int size; // the size of this fish String name; // the standard complete constructor Fish(CartPt p, int size, String name) { this.p = p; this.size = size; this.name = name; } /** TEMPLATE Fields: ... this.p ... -- CartPt ... this.size ... -- int Methods: ... this.start(int, int) ... -- Fish ... this.reStart() ... -- Fish ... this.swim() ... -- Fish ... this.closeByHuh(Shark) ... -- boolean ... this.fishImage() ... -- WorldImage */ // produce a fish at the right side of the ocean Fish start(int y, int size){ return new Fish(new CartPt(WIDTH, y), size, this.name); } // produce a fish at the right side of the ocean // at the same y coordinate and of the same size as this one Fish reStart(){ return this.start(this.p.y, this.size); } // fish swims from right to left towards the waiting shark Fish swim() { return new Fish(this.p.moveLeftRandom(5), this.size, this.name); } // Is this fish close to the given shark? boolean closeByHuh(Shark that) { return this.p.distTo(that.sharkPos()) < 30; } // produce the image of this fish at its position WorldImage fishImage(){ return new FromFileImage(this.p, this.name); } } // A School of Fish is one of: // - new EmptySchool() // - new ConsSchool(Fish, School) // A school of (an arbitrary number of) fish. interface School extends OceanWorldConstants{ // produce the image of all fish in this school public WorldImage fishesImage(); // let all fish in this school swim ahead on tick public School swim(); // Is any fish in this school close to the given shark? public boolean closeByHuh(Shark that); // produce a shark that has eaten the first closest fish (is there is such) public Shark feedShark(Shark mac); // replace the fish that the shark has eaten public School removeEaten(Shark mac); } // A school with no fish class EmptySchool implements School, OceanWorldConstants { EmptySchool() {} // produce an empty blue dot in the sea of blue public WorldImage fishesImage(){ return new CircleImage(new Posn(0, 0), 1, oceanColor); } // let all fish in this school swim ahead on tick public School swim(){ return this; } // Is any fish in this school close to the given shark? public boolean closeByHuh(Shark that){ return false; } // produce a shark that has eaten the first closes fish (is there is such) public Shark feedShark(Shark mac){ return mac; } // replace the fish that the shark has eaten public School removeEaten(Shark mac){ return this; } } // A school with at least one fish class ConsSchool implements School, OceanWorldConstants { Fish first; School rest; // the standard complete constructor ConsSchool (Fish first, School rest) { this.first = first; this.rest = rest; } /** TEMPLATE Fields: ... this.first... -- Fish ... this.rest ... -- School Methods: ... this.swim() ... -- Fish ... this.closeByHuh(Shark) ... -- boolean ... this.feed(Shark) ... -- School ... this.removeEaten(Shark) ... -- School ... this.fishesImage() ... -- WorldImage Methods for fields: ... this.first.start(int, int) ... -- Fish ... this.first.reStart() ... -- Fish ... this.first.swim() ... -- Fish ... this.first.closeByHuh(Shark) ... -- boolean ... this.first.fishImage() ... -- WorldImage ... this.rest.swim() ... -- Fish ... this.rest.closeByHuh(Shark) ... -- boolean ... this.rest.feed(Shark) ... -- School ... this.rest.removeEaten(Shark) ... -- School ... this.rest.fishesImage() ... -- WorldImage */ // produce the image of this school of fish public WorldImage fishesImage(){ return new OverlayImages(this.first.fishImage(), this.rest.fishesImage()); } // let all fish in this school swim ahead on tick public School swim(){ return new ConsSchool(this.first.swim(), this.rest.swim()); } // Is any fish in this school close to the given shark? public boolean closeByHuh(Shark that){ return this.first.closeByHuh(that) || this.rest.closeByHuh(that); } // produce a shark that has eaten the first closes fish (is there is such) public Shark feedShark(Shark mac){ if (this.first.closeByHuh(mac)) return mac.eat(this.first.size); else return this.rest.feedShark(mac); } // replace the fish that the shark has eaten public School removeEaten(Shark mac){ if (this.first.closeByHuh(mac)) return new ConsSchool(this.first.reStart(), this.rest); else return new ConsSchool(this.first, this.rest.removeEaten(mac)); } } //An Ocean has a shark and a fish swimming in it class Ocean extends World implements OceanWorldConstants{ Shark shark; // the hungry shark School fish; // the fish the shark will eat (maybe) Ocean(Shark shark, School fish) { this.shark = shark; this.fish = fish; } /** TEMPLATE Fields: ... this.shark ... -- Shark ... this.fish ... -- School Methods: ... this.onTick() ... -- Ocean ... this.onKeyEvent(String) ... -- Ocean ... this.makeImage() ... -- WorldImage ... this.worldEnds() ... -- WorldEnd */ // the fish swim from right to left on each tick public World onTick(){ if (this.fish.closeByHuh(this.shark)){ return new Ocean(this.fish.feedShark(this.shark), this.fish.removeEaten(this.shark)); } else return new Ocean(this.shark.starve(), this.fish.swim()); } // the shark moves up or down as directed by the arrow key public World onKeyEvent(String ke){ return new Ocean(this.shark.move(ke), this.fish); } // produce the image of the fish and shark swimming in the sea of blue public WorldImage makeImage(){ return new RectangleImage(new Posn(200, 200), 400, 400, new Color(50, 150, 255)). overlayImages(this.shark.sharkImage(), this.fish.fishesImage()); } // the world ends when the shark starves to death public WorldEnd worldEnds(){ if (this.shark.isDead()) return new WorldEnd(true, new OverlayImages(this.makeImage(), new TextImage(new Posn(100, 50), "The shark died", new Red()))); else return new WorldEnd(false, this.makeImage()); } } // Examples for the ocean game public class ExamplesOceanWorldFun implements OceanWorldConstants{ ExamplesOceanWorldFun() {} public static ExamplesOceanWorldFun examplesInstance = new ExamplesOceanWorldFun(); CartPt p1 = new CartPt(WIDTH, HEIGHT/2); CartPt p2 = new CartPt(WIDTH, HEIGHT/2 - 50); CartPt p3 = new CartPt(WIDTH, HEIGHT/2 + 50); CartPt p4 = new CartPt(WIDTH, HEIGHT/2 + 100); CartPt pEnd = new CartPt(3, HEIGHT/2); CartPt pStart = new CartPt(WIDTH, HEIGHT/2); Fish f = new Fish(p1, 50, "small-red-fish.png"); Fish f1 = new Fish(p1, 50, "small-red-fish.png"); Fish f2 = new Fish(p2, 50, "small-green-fish.png"); Fish f3 = new Fish(p3, 50, "small-blue-fish.png"); Fish f4 = new Fish(p4, 50, "small-yellow-fish.png"); Fish fCloseBy = new Fish(new CartPt(3, HEIGHT/2 - 3), 20, this.f.name); School noFish = new EmptySchool(); School allFish = new ConsSchool(this.f1, new ConsSchool(this.f2, new ConsSchool(this.f3, new ConsSchool(this.f4, this.noFish)))); School feedSchool = new ConsSchool(this.f1, new ConsSchool(this.fCloseBy, new ConsSchool(this.f4, this.noFish))); School eatenSchool = new ConsSchool(this.f1, new ConsSchool(new Fish(new CartPt(WIDTH, HEIGHT/2 - 3), 20, this.f.name), new ConsSchool(this.f4, this.noFish))); Shark s = new Shark(HEIGHT/2, 200); Ocean ocean = new Ocean(this.s, this.allFish); // test all methods in the class Shark public boolean testSharkMethods(Tester t){ return t.checkExpect(this.s.sharkPos(), new CartPt(20, HEIGHT/2)) && t.checkExpect(this.s.move("up"), new Shark(HEIGHT/2 - 3, 200)) && t.checkExpect(this.s.move("down"), new Shark(HEIGHT/2 + 3, 200)) && t.checkExpect(this.s.move("left"), new Shark(HEIGHT/2, 200)) && t.checkExpect(this.s.starve(), new Shark(HEIGHT/2, 199)) && t.checkExpect(this.s.isDead(), false) && t.checkExpect(new Shark(30, 0).isDead(), true) && t.checkExpect(this.s.eat(20), new Shark(HEIGHT/2, 220)); } // test all methods in the class Fish public boolean testFishMethods(Tester t){ return t.checkExpect(this.f.start(30, 50), new Fish(new CartPt(WIDTH, 30), 50, this.f.name)) && //t.checkExpect(this.f.swim(), new Fish(new CartPt(195, 100), 50)) && t.checkExpect(new Fish(new CartPt(2, HEIGHT/2), 50, this.f.name).swim(), new Fish(new CartPt(WIDTH, HEIGHT/2), 50, this.f.name)) && t.checkExpect(this.f.closeByHuh(this.s), false) && t.checkExpect(new Fish(new CartPt(19, 195), 30, this.f.name).closeByHuh(this.s), true); } // test all methods in the class CartPt public boolean testCartPtMethods(Tester t) { return t.checkExpect(this.p1.moveLeft(5), new CartPt(395,HEIGHT/2)) && t.checkExpect(this.pEnd.moveLeft(5), this.pStart) && t.checkExpect(this.p1.distTo(this.pEnd), 397.0); } // test that the expected value is one of several possible values public void testRandomMethods(Tester t){ for (int i = 0; i < 100; i++){ t.checkRange((new CartPt(10, 20).moveLeftRandom(5)).y, 18, 23); } for (int i = 0; i < 100; i++){ t.checkOneOf(new Fish(new CartPt(10, 20), 25, f.name).swim(), new Fish(new CartPt(5, 18), 25, f.name), new Fish(new CartPt(5, 19), 25, f.name), new Fish(new CartPt(5, 20), 25, f.name), new Fish(new CartPt(5, 21), 25, f.name), new Fish(new CartPt(5, 22), 25, f.name)); } } // test the methods for the classes that implement the School interface public void testSchoolMethods(Tester t){ t.checkExpect(this.noFish.closeByHuh(this.s), false); t.checkExpect(this.allFish.closeByHuh(this.s), false); t.checkExpect(this.feedSchool.closeByHuh(this.s), true); t.checkExpect(this.noFish.feedShark(this.s), this.s); t.checkExpect(this.allFish.feedShark(this.s), this.s); t.checkExpect(this.feedSchool.feedShark(this.s), new Shark(HEIGHT/2, 220)); t.checkExpect(this.noFish.removeEaten(this.s), this.noFish); t.checkExpect(this.allFish.removeEaten(this.s), this.allFish); t.checkExpect(this.feedSchool.removeEaten(this.s), this.eatenSchool); } // test Ocean method onKeyEvent public void testOceanOnKeyEvent(Tester t){ t.checkExpect(this.ocean.onKeyEvent("x"), this.ocean); t.checkExpect(this.ocean.onKeyEvent("up"), new Ocean(new Shark(HEIGHT/2 - 3, 200), this.allFish)); t.checkExpect(this.ocean.onKeyEvent("down"), new Ocean(new Shark(HEIGHT/2 + 3, 200), this.allFish)); } // test Ocean method onTick public void testOceanOnTick(Tester t){ t.checkExpect(this.ocean.onTick(), new Ocean(new Shark(HEIGHT/2, 199), new ConsSchool(new Fish(new CartPt(WIDTH - 3, HEIGHT/2), 50, "small-red-fish.png"), new ConsSchool(new Fish(new CartPt(WIDTH - 3, HEIGHT/2 - 50), 50, "small-red-fish.png"), new ConsSchool(new Fish(new CartPt(WIDTH - 3, HEIGHT/2 + 50), 50, "small-red-fish.png"), new ConsSchool(new Fish(new CartPt(WIDTH - 3, HEIGHT/2 + 100), 50, "small-red-fish.png"), this.noFish)))))); } // test Ocean method onTick, worldEnds public void testOceanWorldEnds(Tester t){ t.checkExpect(this.ocean.worldEnds(), new WorldEnd(false, this.ocean.makeImage())); t.checkExpect((new Ocean(new Shark(HEIGHT/2, 0), this.allFish)).worldEnds(), new WorldEnd(true, new OverlayImages((new Ocean(new Shark(HEIGHT/2, 0), this.allFish)).makeImage(), new TextImage(new Posn(100, 50), "The shark died", new Red())))); } // run the game public void testRun(Tester t){ this.ocean.bigBang(400, 400, 0.07); } public static void main(String[] argv){ ExamplesOceanWorldFun e = new ExamplesOceanWorldFun(); Tester.runReport(e, false, false); e.ocean.bigBang(400, 400, 0.07); } }