{
  "name": "Spielwerkstatt: nach Vertiefter Objektorientierung",
  "settings": {
    "language": "Java",
    "libraries": [
      "scratch",
      "nrw"
    ]
  },
  "modules": [
    {
      "name": "Welt.java",
      "text": "/// Deine Spielwelt. Hier stellst du alles auf, was im Spiel vorkommt.\npublic class Welt extends Spielwelt {\n\n   /// So viele Münzen hat der Spieler schon eingesammelt.\n   private int punkte = 0;\n   /// So viele Münzen liegen insgesamt in der Welt.\n   private int anzahlMuenzen = 0;\n   /// So oft darf ein Gegner den Spieler noch erwischen.\n   private int leben = 3;\n   /// Hier steht der Spieler am Anfang und nach jedem Treffer.\n   private double startX;\n   private double startY;\n   /// So viele Sekunden bleiben noch.\n   private double zeit = 30;\n   /// Solange das true ist, läuft das Spiel noch.\n   private boolean laeuft = true;\n   /// Die Anzeige oben links.\n   private Text anzeige;\n   /// Die besten Zeiten in Sekunden, aufsteigend sortiert. Nur die ersten anzahlBestzeiten Stellen zählen.\n   private double[] bestzeiten = new double[5];\n   private int anzahlBestzeiten = 0;\n   /// Die Anzeige der Bestenliste.\n   private Text bestenliste;\n   /// Die Spielfigur. Sie ist eine Variable der Welt, damit run() sie anhalten kann.\n   private Spieler held;\n\n   /// Der Plan der Welt: jede Zeichenkette eine Zeile, jedes Zeichen ein Feld von 48 × 48 Pixeln.\n   /// `#` Stein, `B` Baum, `T` Tanne, `F` Fels, `M` Münze, `H` Herz, `X` Falle,\n   /// `G` Schleim, `V` Fledermaus, `S` Start, `.` Wiese\n   private String[] plan = {\n      \"################\",\n      \"#S.....M.......#\",\n      \"#..B.......F...#\",\n      \"#......M.....V.#\",\n      \"#.M.....T....M.#\",\n      \"#..X......G....#\",\n      \"#..M..B....M.H.#\",\n      \"#..G......M....#\",\n      \"################\"\n   };\n\n   public Welt() {\n      this.addSound(\"muenze\", \"assets/audio/sounds/bonus/coin.ogg\");\n      held = new Spieler(\"boy\");\n\n      for (int z = 0; z < plan.length; z++) {\n         for (int s = 0; s < plan[z].length(); s++) {\n            char zeichen = plan[z].charAt(s);\n            double x = this.zuX(s);\n            double y = this.zuY(z);\n            if (zeichen == '#') {\n               this.setze(new Stein(), x, y);\n            } else if (zeichen == 'B') {\n               this.setze(new Pflanze(0, 0), x, y);\n            } else if (zeichen == 'T') {\n               this.setze(new Pflanze(32, 0), x, y);\n            } else if (zeichen == 'F') {\n               this.setze(new Pflanze(256, 128), x, y);\n            } else if (zeichen == 'X') {\n               this.setze(new Falle(), x, y);\n            } else if (zeichen == 'G') {\n               this.setze(new Schleim(), x, y);\n            } else if (zeichen == 'V') {\n               this.setze(new Fledermaus(), x, y);\n            } else if (zeichen == 'S') {\n               startX = x;\n               startY = y;\n               held.setPosition(x, y);\n            }\n         }\n      }\n\n      this.verteileGegenstaende();\n\n      anzeige = new Text(\"\", 0, 200, 700);\n      anzeige.setTextSize(20);\n      anzeige.setTextColor(255, 255, 255);\n      this.add(anzeige);\n\n      bestenliste = new Text(\"\", 290, 140, 160);\n      bestenliste.setTextSize(16);\n      bestenliste.setTextColor(255, 255, 255);\n      this.add(bestenliste);\n\n      this.add(held);\n   }\n\n   /// Legt auf jedes M im Plan eine Münze und auf jedes H ein Herz und zählt die Münzen.\n   private void verteileGegenstaende() {\n      anzahlMuenzen = 0;\n      for (int z = 0; z < plan.length; z++) {\n         for (int s = 0; s < plan[z].length(); s++) {\n            char zeichen = plan[z].charAt(s);\n            if (zeichen == 'M') {\n               this.setze(new Muenze(), this.zuX(s), this.zuY(z));\n               anzahlMuenzen = anzahlMuenzen + 1;\n            } else if (zeichen == 'H') {\n               this.setze(new Herz(), this.zuX(s), this.zuY(z));\n            }\n         }\n      }\n   }\n\n   /// Beginnt eine neue Runde: alle Münzen zurück, Zeit und Leben voll, Spieler an den Start.\n   private void neueRunde() {\n      this.remove(Muenze.class);\n      this.remove(Herz.class);\n      this.verteileGegenstaende();\n      punkte = 0;\n      leben = 3;\n      zeit = 30;\n      laeuft = true;\n      held.setPosition(startX, startY);\n      held.setTempo(3);\n   }\n\n   /// Sortiert pSekunden in die Bestenliste ein, wie beim Sortieren durch Einfügen:\n   /// Alle langsameren Zeiten rücken eine Stelle nach hinten. Ist die Liste voll, fällt die letzte heraus.\n   private void trageEin(double pSekunden) {\n      int i = anzahlBestzeiten;\n      if (i == bestzeiten.length) {\n         i = i - 1;\n         if (bestzeiten[i] <= pSekunden) {\n            return;   // langsamer als alle in der vollen Liste\n         }\n      } else {\n         anzahlBestzeiten = anzahlBestzeiten + 1;\n      }\n      while (i > 0 && bestzeiten[i - 1] > pSekunden) {\n         bestzeiten[i] = bestzeiten[i - 1];\n         i = i - 1;\n      }\n      bestzeiten[i] = pSekunden;\n   }\n\n   /// Zeigt die Bestenliste an, eine Zeit je Zeile.\n   private void zeigeBestenliste() {\n      String text = \"Bestzeiten\";\n      for (int i = 0; i < anzahlBestzeiten; i++) {\n         text = text + \"\\n\" + (i + 1) + \". \" + (int) bestzeiten[i] + \" s\";\n      }\n      bestenliste.showText(text);\n   }\n\n   /// Mit der Leertaste beginnt nach dem Ende eine neue Runde.\n   public void whenKeyPressed(KeyCode pTaste) {\n      if (pTaste == KeyCode.SPACE && !laeuft) {\n         this.neueRunde();\n      }\n   }\n\n   /// Die x-Koordinate der Mitte von Spalte pSpalte. Spalte 0 liegt ganz links.\n   private double zuX(int pSpalte) {\n      return -360 + pSpalte * 48;\n   }\n\n   /// Die y-Koordinate der Mitte von Zeile pZeile. Zeile 0 liegt ganz oben.\n   private double zuY(int pZeile) {\n      return 192 - pZeile * 48;\n   }\n\n   /// Stellt pFigur an die Stelle (pX, pY) auf die Bühne.\n   private void setze(Sprite pFigur, double pX, double pY) {\n      pFigur.setPosition(pX, pY);\n      this.add(pFigur);\n   }\n\n   /// Liefert true, wenn alle Münzen eingesammelt sind.\n   private boolean istGewonnen() {\n      return punkte == anzahlMuenzen;\n   }\n\n   /// Hält das Spiel an und zeigt pMeldung an.\n   private void beende(String pMeldung) {\n      laeuft = false;\n      held.setTempo(0);\n      anzeige.showText(pMeldung);\n   }\n\n   /// Eine Münze meldet, dass sie eingesammelt wurde.\n   public void muenzeEingesammelt() {\n      punkte = punkte + 1;\n      this.playSound(\"muenze\");\n   }\n\n   /// Ein Herz gibt ein Leben zurück, höchstens bis fünf.\n   public void heile() {\n      if (leben < 5) {\n         leben = leben + 1;\n      }\n   }\n\n   /// Ein Gegner oder eine Falle hat den Spieler erwischt: ein Leben weniger, zurück zum Start.\n   public void verletze() {\n      if (laeuft) {\n         leben = leben - 1;\n         held.setPosition(startX, startY);\n         if (leben == 0) {\n            this.beende(\"Kein Leben mehr! \" + punkte + \" von \" + anzahlMuenzen + \" Münzen. Leertaste: neue Runde\");\n         }\n      }\n   }\n\n   /// Wird immer wieder ausgeführt, etwa 60-mal in der Sekunde.\n   public void run() {\n      if (laeuft) {\n         zeit = zeit - 1.0 / 60;\n\n         if (this.istGewonnen()) {\n            this.trageEin(30 - zeit);\n            this.zeigeBestenliste();\n            this.beende(\"Gewonnen in \" + (int) (30 - zeit) + \" Sekunden! Leertaste: neue Runde\");\n         } else if (zeit <= 0) {\n            this.beende(\"Die Zeit ist um! \" + punkte + \" von \" + anzahlMuenzen + \" Münzen. Leertaste: neue Runde\");\n         } else {\n            anzeige.showText(\"Münzen: \" + punkte + \" von \" + anzahlMuenzen + \"   Leben: \" + leben + \"   Zeit: \" + (int) zeit);\n         }\n      }\n   }\n}\n"
    },
    {
      "name": "Main.java",
      "text": "/// Startet das Spiel.\nvoid main() {\n   // Pixelgrafik bleibt beim Vergrößern scharf.\n   Window.useTextureSampling(TextureSampling.POINT);\n   Window fenster = new Window(768, 432);\n   fenster.setStage(new Welt());\n}\n"
    },
    {
      "name": "Falle.java",
      "text": "/// Eine Stachelfalle. Sie bleibt liegen und kostet jedes Mal ein Leben.\npublic class Falle extends Gegenstand {\n\n   public Falle() {\n      this.addCostume(\"falle\", \"assets/backgrounds/tilesets/tileset-dungeon.png\", 64, 16, 16, 16);\n   }\n\n   public void beruehrtVon(Welt pWelt) {\n      pWelt.verletze();\n   }\n}\n"
    },
    {
      "name": "Fledermaus.java",
      "text": "/// Eine Fledermaus fliegt senkrecht auf und ab und kehrt an Hindernissen um.\npublic class Fledermaus extends Gegner {\n\n   private int tempo = 3;\n\n   public Fledermaus() {\n      super(\"assets/actor/monster/blue-bat/sprite-sheet.png\");\n   }\n\n   protected void bewege() {\n      this.changeY(tempo);\n      if (this.getTouchingSprite(Hindernis.class) != null) {\n         this.changeY(-tempo);\n         tempo = -tempo;\n      }\n   }\n}\n"
    },
    {
      "name": "Gegenstand.java",
      "text": "/// Etwas, das auf dem Boden liegt und etwas tut, wenn der Spieler es berührt.\n/// Was genau, legt jede Unterklasse selbst fest.\npublic abstract class Gegenstand extends AnimatedSprite {\n\n   public Gegenstand() {\n      this.setSize(300);\n   }\n\n   /// Wird aufgerufen, wenn der Spieler diesen Gegenstand berührt.\n   public abstract void beruehrtVon(Welt pWelt);\n}\n"
    },
    {
      "name": "Gegner.java",
      "text": "/// Ein Monster, das sich von selbst bewegt. Wie, legt jede Unterklasse in bewege() fest.\npublic abstract class Gegner extends AnimatedSprite {\n\n   /// pBild ist ein Bild aus assets/actor/monster/ im Aufbau der Figuren: eine Spalte je Blickrichtung.\n   public Gegner(String pBild) {\n      this.addAnimation(\"laufen\", pBild, 4, 16, 16, 0, true);\n      this.setAnimationInterval(200);\n      this.setSize(250);\n      // Nur die Füße zählen: So kann die Figur vor einem Baum stehen, ohne an ihm hängen zu bleiben.\n      this.setHitbox(4, 10, 12, 10, 12, 16, 4, 16);\n   }\n\n   public void run() {\n      this.playAnimation(\"laufen\");\n      this.bewege();\n   }\n\n   /// Bewegt den Gegner um einen Schritt.\n   protected abstract void bewege();\n}\n"
    },
    {
      "name": "Herz.java",
      "text": "/// Ein Herz gibt ein Leben zurück und verschwindet.\npublic class Herz extends Gegenstand {\n\n   public Herz() {\n      this.addCostume(\"herz\", \"assets/items/potion/heart.png\");\n   }\n\n   public void beruehrtVon(Welt pWelt) {\n      this.remove();\n      pWelt.heile();\n   }\n}\n"
    },
    {
      "name": "Hindernis.java",
      "text": "/// Fertig für dich: eine Figur, durch die der Spieler nicht hindurchlaufen kann.\n/// Ein Hindernis ist ein Sprite wie jedes andere. Sein Kostüm gibst du ihm selbst.\npublic class Hindernis extends Sprite {\n\n   public Hindernis() {\n      // Die Bilder sind winzig. Dreifach vergrößert passen sie zum Spieler.\n      this.setSize(300);\n   }\n\n   /// Wie addCostume von Sprite. Zusätzlich hält nur die untere Hälfte des Bildes auf,\n   /// also der Stamm eines Baums oder der Fuß eines Felsens. Hinter dem Rest kann man vorbeigehen.\n   public void addCostume(String pName, String pBild, int pX, int pY, int pBreite, int pHoehe) {\n      super.addCostume(pName, pBild, pX, pY, pBreite, pHoehe);\n      this.setHitbox(0, pHoehe / 2.0, pBreite, pHoehe / 2.0, pBreite, pHoehe, 0, pHoehe);\n   }\n}\n"
    },
    {
      "name": "Muenze.java",
      "text": "/// Eine Münze, die sich dreht, bis der Spieler sie einsammelt.\npublic class Muenze extends Gegenstand {\n\n   public Muenze() {\n      // Vier Bilder zu je 10 × 10 Pixeln nebeneinander.\n      this.addAnimation(\"drehen\", \"assets/items/treasure/coin2.png\", 4, 10, 10);\n      this.setAnimationInterval(150);\n   }\n\n   public void run() {\n      this.playAnimation(\"drehen\");\n   }\n\n   public void beruehrtVon(Welt pWelt) {\n      this.remove();\n      pWelt.muenzeEingesammelt();\n   }\n}\n"
    },
    {
      "name": "Pflanze.java",
      "text": "/// Eine große Pflanze oder ein Felsen, 32 × 32 Pixel aus dem Natur-Kachelbild.\n/// pBildX und pBildY geben die linke obere Ecke im Kachelbild an.\npublic class Pflanze extends Hindernis {\n\n   public Pflanze(int pBildX, int pBildY) {\n      this.addCostume(\"pflanze\", \"assets/backgrounds/tilesets/tileset-nature.png\", pBildX, pBildY, 32, 32);\n   }\n}\n"
    },
    {
      "name": "Schleim.java",
      "text": "/// Ein Schleim kriecht waagerecht hin und her und kehrt an Hindernissen um.\npublic class Schleim extends Gegner {\n\n   private int tempo = 2;\n\n   public Schleim() {\n      super(\"assets/actor/monster/slime/slime.png\");\n   }\n\n   protected void bewege() {\n      this.changeX(tempo);\n      if (this.getTouchingSprite(Hindernis.class) != null) {\n         this.changeX(-tempo);\n         tempo = -tempo;\n      }\n   }\n}\n"
    },
    {
      "name": "Spieler.java",
      "text": "/// Die Figur, die du mit den Pfeiltasten steuerst.\n/// Sie bleibt an Hindernissen hängen, berührt Gegenstände und meldet der Welt, wenn ein Gegner sie erwischt.\npublic class Spieler extends AnimatedSprite {\n\n   /// So viele Pixel geht der Spieler in jedem Bild.\n   private int tempo = 3;\n\n   /// Erzeugt einen Spieler. pFigur ist ein Ordner in assets/actor/character/, zum Beispiel \"boy\".\n   public Spieler(String pFigur) {\n      String bild = \"assets/actor/character/\" + pFigur + \"/sprite-sheet.png\";\n      // Im Bild steht jede Blickrichtung in einer eigenen Spalte,\n      // die vier Schritte untereinander.\n      this.addAnimation(\"unten\", bild, 4, 16, 16, 0, true);\n      this.addAnimation(\"oben\", bild, 4, 16, 16, 1, true);\n      this.addAnimation(\"links\", bild, 4, 16, 16, 2, true);\n      this.addAnimation(\"rechts\", bild, 4, 16, 16, 3, true);\n      this.setAnimationInterval(150);\n      this.setSize(250);\n      // Nur die Füße zählen: So kann die Figur vor einem Baum stehen, ohne an ihm hängen zu bleiben.\n      this.setHitbox(4, 10, 12, 10, 12, 16, 4, 16);\n   }\n\n   public void run() {\n      double altX = this.getX();\n      double altY = this.getY();\n\n      if (this.isKeyPressed(KeyCode.RIGHT)) {\n         this.changeX(tempo);\n         this.playAnimation(\"rechts\");\n      } else if (this.isKeyPressed(KeyCode.LEFT)) {\n         this.changeX(-tempo);\n         this.playAnimation(\"links\");\n      } else if (this.isKeyPressed(KeyCode.UP)) {\n         this.changeY(tempo);\n         this.playAnimation(\"oben\");\n      } else if (this.isKeyPressed(KeyCode.DOWN)) {\n         this.changeY(-tempo);\n         this.playAnimation(\"unten\");\n      }\n\n      // In ein Hindernis hineingelaufen? Dann zurück auf den alten Platz.\n      if (this.getTouchingSprite(Hindernis.class) != null) {\n         this.setPosition(altX, altY);\n      }\n\n      Welt welt = (Welt) this.getStage();\n\n      // Was der Gegenstand tut, entscheidet der Gegenstand selbst.\n      Gegenstand g = this.getTouchingSprite(Gegenstand.class);\n      if (g != null) {\n         g.beruehrtVon(welt);\n      }\n\n      if (this.getTouchingSprite(Gegner.class) != null) {\n         welt.verletze();\n      }\n   }\n\n   /// Legt fest, wie viele Pixel der Spieler in jedem Bild geht. Mit 0 bleibt er stehen.\n   public void setTempo(int pTempo) {\n      tempo = pTempo;\n   }\n}\n"
    },
    {
      "name": "Spielwelt.java",
      "text": "/// Fertig für dich: die Wiese als Hintergrund und die Reihenfolge, in der gezeichnet wird.\npublic class Spielwelt extends Stage {\n\n   /// So viele Pixel ist eine Kachel auf der Bühne breit und hoch.\n   public static final int KACHEL = 48;\n\n   public Spielwelt() {\n      // Die Wiese ist ein einziges Bild, so groß wie die Bühne.\n      this.addBackdrop(\"wiese\", \"assets/backgrounds/wiese.png\");\n\n      // Wer weiter unten steht, ist näher am Betrachter: Er wird später gezeichnet\n      // und verdeckt, was hinter ihm steht. So läuft man hinter Bäumen vorbei.\n      this.getSorting().byY();\n   }\n}\n"
    },
    {
      "name": "Stein.java",
      "text": "/// Ein Stein für Mauern, 16 × 16 Pixel aus dem Dungeon-Kachelbild.\npublic class Stein extends Hindernis {\n\n   public Stein() {\n      this.addCostume(\"stein\", \"assets/backgrounds/tilesets/tileset-dungeon.png\", 128, 48, 16, 16);\n   }\n}\n"
    }
  ]
}
